Merge "Make per-display focus optional (1/2)"
diff --git a/Android.bp b/Android.bp
index fb018a5..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",
@@ -535,6 +537,7 @@
         "telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl",
         "telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl",
         "telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl",
+        "telephony/java/android/telephony/ims/aidl/IRcs.aidl",
         "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
         "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl",
         "telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl",
@@ -611,7 +614,6 @@
         "telephony/java/com/android/internal/telephony/euicc/ISetDefaultSmdpAddressCallback.aidl",
         "telephony/java/com/android/internal/telephony/euicc/ISetNicknameCallback.aidl",
         "telephony/java/com/android/internal/telephony/euicc/ISwitchToProfileCallback.aidl",
-        "telephony/java/com/android/internal/telephony/rcs/IRcs.aidl",
         "wifi/java/android/net/wifi/INetworkRequestMatchCallback.aidl",
         "wifi/java/android/net/wifi/INetworkRequestUserSelectionCallback.aidl",
         "wifi/java/android/net/wifi/ISoftApCallback.aidl",
@@ -691,7 +693,6 @@
             "frameworks/av/media/libaudioclient/aidl",
             "frameworks/native/aidl/gui",
             "system/core/storaged/binder",
-            "system/netd/server/binder",
             "system/vold/binder",
             "system/bt/binder",
             "system/security/keystore/binder",
@@ -1191,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 " +
@@ -1607,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 c893840..c8283d0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -57,6 +57,7 @@
     field public static final java.lang.String BROADCAST_SMS = "android.permission.BROADCAST_SMS";
     field public static final java.lang.String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY";
     field public static final java.lang.String BROADCAST_WAP_PUSH = "android.permission.BROADCAST_WAP_PUSH";
+    field public static final java.lang.String CALL_COMPANION_APP = "android.permission.CALL_COMPANION_APP";
     field public static final java.lang.String CALL_PHONE = "android.permission.CALL_PHONE";
     field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED";
     field public static final java.lang.String CAMERA = "android.permission.CAMERA";
@@ -78,6 +79,7 @@
     field public static final java.lang.String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
     field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
     field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
+    field public static final java.lang.String GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY = "android.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY";
     field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
     field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
     field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
@@ -415,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
@@ -702,6 +704,7 @@
     field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
     field public static final int hardwareAccelerated = 16843475; // 0x10102d3
     field public static final int hasCode = 16842764; // 0x101000c
+    field public static final int hasFragileUserData = 16844192; // 0x10105a0
     field public static final deprecated int headerAmPmTextAppearance = 16843936; // 0x10104a0
     field public static final int headerBackground = 16843055; // 0x101012f
     field public static final deprecated int headerDayOfMonthTextAppearance = 16843927; // 0x1010497
@@ -715,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
@@ -723,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
@@ -784,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
@@ -799,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
@@ -937,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
@@ -1043,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
@@ -1147,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
@@ -1283,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
@@ -1521,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
@@ -1891,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
@@ -2602,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
@@ -5948,6 +5952,15 @@
     field public static final int STYLE_SPINNER = 0; // 0x0
   }
 
+  public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+    ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction);
+    method public int describeContents();
+    method public android.app.RemoteAction getUserAction();
+    method public java.lang.CharSequence getUserMessage();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
+  }
+
   public final class RemoteAction implements android.os.Parcelable {
     ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
     method public android.app.RemoteAction clone();
@@ -5973,6 +5986,7 @@
     method public java.util.Set<java.lang.String> getAllowedDataTypes();
     method public java.lang.CharSequence[] getChoices();
     method public static java.util.Map<java.lang.String, android.net.Uri> getDataResultsFromIntent(android.content.Intent, java.lang.String);
+    method public int getEditChoicesBeforeSending();
     method public android.os.Bundle getExtras();
     method public java.lang.CharSequence getLabel();
     method public java.lang.String getResultKey();
@@ -5982,6 +5996,9 @@
     method public static void setResultsSource(android.content.Intent, int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.RemoteInput> CREATOR;
+    field public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; // 0x0
+    field public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; // 0x1
+    field public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; // 0x2
     field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
     field public static final java.lang.String RESULTS_CLIP_LABEL = "android.remoteinput.results";
     field public static final int SOURCE_CHOICE = 1; // 0x1
@@ -5996,6 +6013,7 @@
     method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean);
     method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean);
     method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]);
+    method public android.app.RemoteInput.Builder setEditChoicesBeforeSending(int);
     method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence);
   }
 
@@ -6433,6 +6451,13 @@
     field public static final android.os.Parcelable.Creator<android.app.admin.ConnectEvent> CREATOR;
   }
 
+  public class DelegatedAdminReceiver extends android.content.BroadcastReceiver {
+    ctor public DelegatedAdminReceiver();
+    method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, int, android.net.Uri, java.lang.String);
+    method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int);
+    method public void onReceive(android.content.Context, android.content.Intent);
+  }
+
   public final class DeviceAdminInfo implements android.os.Parcelable {
     ctor public DeviceAdminInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method public int describeContents();
@@ -6495,11 +6520,13 @@
     method public void onUserStarted(android.content.Context, android.content.Intent, android.os.UserHandle);
     method public void onUserStopped(android.content.Context, android.content.Intent, android.os.UserHandle);
     method public void onUserSwitched(android.content.Context, android.content.Intent, android.os.UserHandle);
+    field public static final java.lang.String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED";
     field public static final java.lang.String ACTION_LOCK_TASK_ENTERING = "android.app.action.LOCK_TASK_ENTERING";
     field public static final java.lang.String ACTION_LOCK_TASK_EXITING = "android.app.action.LOCK_TASK_EXITING";
+    field public static final java.lang.String ACTION_NETWORK_LOGS_AVAILABLE = "android.app.action.NETWORK_LOGS_AVAILABLE";
     field public static final java.lang.String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED";
     field public static final java.lang.String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING";
     field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED";
@@ -6573,6 +6600,7 @@
     method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
     method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(android.content.ComponentName);
     method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
+    method public int getPasswordComplexity();
     method public long getPasswordExpiration(android.content.ComponentName);
     method public long getPasswordExpirationTimeout(android.content.ComponentName);
     method public int getPasswordHistoryLength(android.content.ComponentName);
@@ -6744,10 +6772,13 @@
     field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
     field public static final java.lang.String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
     field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+    field public static final java.lang.String DELEGATION_CERT_SELECTION = "delegation-cert-selection";
     field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
     field public static final java.lang.String DELEGATION_INSTALL_EXISTING_PACKAGE = "delegation-install-existing-package";
     field public static final java.lang.String DELEGATION_KEEP_UNINSTALLED_PACKAGES = "delegation-keep-uninstalled-packages";
+    field public static final java.lang.String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
     field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+    field public static final java.lang.String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation";
     field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
     field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
     field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
@@ -6826,6 +6857,10 @@
     field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
     field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2
     field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
+    field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000
+    field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000
+    field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000
+    field public static final int PASSWORD_COMPLEXITY_NONE = 0; // 0x0
     field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000
     field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000
     field public static final int PASSWORD_QUALITY_BIOMETRIC_WEAK = 32768; // 0x8000
@@ -7614,13 +7649,16 @@
     method public java.lang.String getPackageName();
     method public java.lang.String getShortcutId();
     method public long getTimeStamp();
+    field public static final int ACTIVITY_PAUSED = 2; // 0x2
+    field public static final int ACTIVITY_RESUMED = 1; // 0x1
+    field public static final int ACTIVITY_STOPPED = 23; // 0x17
     field public static final int CONFIGURATION_CHANGE = 5; // 0x5
     field public static final int FOREGROUND_SERVICE_START = 19; // 0x13
     field public static final int FOREGROUND_SERVICE_STOP = 20; // 0x14
     field public static final int KEYGUARD_HIDDEN = 18; // 0x12
     field public static final int KEYGUARD_SHOWN = 17; // 0x11
-    field public static final int MOVE_TO_BACKGROUND = 2; // 0x2
-    field public static final int MOVE_TO_FOREGROUND = 1; // 0x1
+    field public static final deprecated int MOVE_TO_BACKGROUND = 2; // 0x2
+    field public static final deprecated int MOVE_TO_FOREGROUND = 1; // 0x1
     field public static final int NONE = 0; // 0x0
     field public static final int SCREEN_INTERACTIVE = 15; // 0xf
     field public static final int SCREEN_NON_INTERACTIVE = 16; // 0x10
@@ -7637,9 +7675,11 @@
     method public long getLastTimeForegroundServiceUsed();
     method public long getLastTimeStamp();
     method public long getLastTimeUsed();
+    method public long getLastTimeVisible();
     method public java.lang.String getPackageName();
     method public long getTotalTimeForegroundServiceUsed();
     method public long getTotalTimeInForeground();
+    method public long getTotalTimeVisible();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.usage.UsageStats> CREATOR;
   }
@@ -11188,6 +11228,15 @@
     field public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1024; // 0x400
   }
 
+  public final class ModuleInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getName();
+    method public java.lang.String getPackageName();
+    method public boolean isHidden();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.content.pm.ModuleInfo> CREATOR;
+  }
+
   public class PackageInfo implements android.os.Parcelable {
     ctor public PackageInfo();
     method public int describeContents();
@@ -11235,6 +11284,7 @@
     method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllSessions();
     method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getMySessions();
     method public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int);
+    method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getStagedSessions();
     method public android.content.pm.PackageInstaller.Session openSession(int) throws java.io.IOException;
     method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
     method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler);
@@ -11273,6 +11323,7 @@
     method public java.lang.String[] getNames() throws java.io.IOException;
     method public int getParentSessionId();
     method public boolean isMultiPackage();
+    method public boolean isStaged();
     method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException;
     method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
     method public void removeChildSessionId(int);
@@ -11311,6 +11362,7 @@
     method public boolean isActive();
     method public boolean isMultiPackage();
     method public boolean isSealed();
+    method public boolean isStaged();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR;
     field public static final int INVALID_ID = -1; // 0xffffffff
@@ -11329,6 +11381,7 @@
     method public void setOriginatingUri(android.net.Uri);
     method public void setReferrerUri(android.net.Uri);
     method public void setSize(long);
+    method public void setStaged();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionParams> CREATOR;
     field public static final int MODE_FULL_INSTALL = 1; // 0x1
@@ -11400,6 +11453,7 @@
     method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
     method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
     method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
+    method public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int);
     method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
     method public abstract java.lang.String getInstallerPackageName(java.lang.String);
     method public abstract byte[] getInstantAppCookie();
@@ -11407,6 +11461,7 @@
     method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String);
     method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
+    method public android.content.pm.ModuleInfo getModuleInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract java.lang.String getNameForUid(int);
     method public android.content.pm.PackageInfo getPackageArchiveInfo(java.lang.String, int);
     method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -13518,6 +13573,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);
   }
@@ -13580,7 +13675,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);
@@ -14202,6 +14298,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);
@@ -14256,7 +14353,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();
@@ -14276,6 +14373,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);
@@ -14309,7 +14407,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
@@ -14591,7 +14689,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);
   }
 
@@ -15147,7 +15245,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);
@@ -15724,6 +15822,7 @@
 
   public static class MeasuredText.Builder {
     ctor public MeasuredText.Builder(char[]);
+    ctor public MeasuredText.Builder(android.graphics.text.MeasuredText);
     method public android.graphics.text.MeasuredText.Builder appendReplacementRun(android.graphics.Paint, int, float);
     method public android.graphics.text.MeasuredText.Builder appendStyleRun(android.graphics.Paint, int, boolean);
     method public android.graphics.text.MeasuredText build();
@@ -16485,6 +16584,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;
@@ -17055,6 +17155,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);
@@ -22270,7 +22382,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);
@@ -22344,7 +22456,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);
@@ -22823,9 +22935,10 @@
     ctor public SettingInjectorService(java.lang.String);
     method public final android.os.IBinder onBind(android.content.Intent);
     method protected abstract boolean onGetEnabled();
-    method protected abstract deprecated java.lang.String onGetSummary();
+    method protected abstract java.lang.String onGetSummary();
     method public final void onStart(android.content.Intent, int);
     method public final int onStartCommand(android.content.Intent, int, int);
+    method public static final void refreshSettings(android.content.Context);
     field public static final java.lang.String ACTION_INJECTED_SETTING_CHANGED = "android.location.InjectedSettingChanged";
     field public static final java.lang.String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService";
     field public static final java.lang.String ATTRIBUTES_NAME = "injected-location-setting";
@@ -23957,6 +24070,7 @@
     method public void releaseOutputBuffer(int, boolean);
     method public void releaseOutputBuffer(int, long);
     method public void reset();
+    method public void setAudioPresentation(android.media.AudioPresentation);
     method public void setCallback(android.media.MediaCodec.Callback, android.os.Handler);
     method public void setCallback(android.media.MediaCodec.Callback);
     method public void setInputSurface(android.view.Surface);
@@ -24947,6 +25061,7 @@
     field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
     field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
     field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
+    field public static final int MUXER_OUTPUT_OGG = 4; // 0x4
     field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
   }
 
@@ -25402,6 +25517,7 @@
     field public static final int AMR_WB = 2; // 0x2
     field public static final int DEFAULT = 0; // 0x0
     field public static final int HE_AAC = 4; // 0x4
+    field public static final int OPUS = 7; // 0x7
     field public static final int VORBIS = 6; // 0x6
   }
 
@@ -25452,6 +25568,7 @@
     field public static final int DEFAULT = 0; // 0x0
     field public static final int MPEG_2_TS = 8; // 0x8
     field public static final int MPEG_4 = 2; // 0x2
+    field public static final int OGG = 11; // 0xb
     field public static final deprecated int RAW_AMR = 3; // 0x3
     field public static final int THREE_GPP = 1; // 0x1
     field public static final int WEBM = 9; // 0x9
@@ -26217,9 +26334,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;
@@ -29557,8 +29677,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[]);
   }
 
@@ -29643,6 +29763,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[]);
@@ -35930,9 +36065,11 @@
   }
 
   public final class CalendarContract {
+    method public static boolean startViewCalendarEventInManagedProfile(android.content.Context, long, long, long, boolean, int);
     field public static final java.lang.String ACCOUNT_TYPE_LOCAL = "LOCAL";
     field public static final java.lang.String ACTION_EVENT_REMINDER = "android.intent.action.EVENT_REMINDER";
     field public static final java.lang.String ACTION_HANDLE_CUSTOM_EVENT = "android.provider.calendar.action.HANDLE_CUSTOM_EVENT";
+    field public static final java.lang.String ACTION_VIEW_WORK_CALENDAR_EVENT = "android.provider.calendar.action.VIEW_WORK_CALENDAR_EVENT";
     field public static final java.lang.String AUTHORITY = "com.android.calendar";
     field public static final java.lang.String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
     field public static final android.net.Uri CONTENT_URI;
@@ -35940,6 +36077,7 @@
     field public static final java.lang.String EXTRA_EVENT_ALL_DAY = "allDay";
     field public static final java.lang.String EXTRA_EVENT_BEGIN_TIME = "beginTime";
     field public static final java.lang.String EXTRA_EVENT_END_TIME = "endTime";
+    field public static final java.lang.String EXTRA_EVENT_ID = "id";
   }
 
   public static final class CalendarContract.Attendees implements android.provider.BaseColumns android.provider.CalendarContract.AttendeesColumns android.provider.CalendarContract.EventsColumns {
@@ -36246,6 +36384,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;
@@ -37427,25 +37571,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";
@@ -38470,10 +38626,13 @@
   }
 
   public static final class Telephony.CarrierId implements android.provider.BaseColumns {
+    method public static android.net.Uri getPreciseCarrierIdUriForSubscriptionId(int);
     method public static android.net.Uri getUriForSubscriptionId(int);
     field public static final java.lang.String CARRIER_ID = "carrier_id";
     field public static final java.lang.String CARRIER_NAME = "carrier_name";
     field public static final android.net.Uri CONTENT_URI;
+    field public static final java.lang.String PRECISE_CARRIER_ID = "precise_carrier_id";
+    field public static final java.lang.String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name";
   }
 
   public static final class Telephony.Carriers implements android.provider.BaseColumns {
@@ -40619,13 +40778,16 @@
 
   public class CarrierIdentifier implements android.os.Parcelable {
     ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
+    ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, int, int);
     ctor public CarrierIdentifier(byte[], java.lang.String, java.lang.String);
     method public int describeContents();
+    method public int getCarrierId();
     method public java.lang.String getGid1();
     method public java.lang.String getGid2();
     method public java.lang.String getImsi();
     method public java.lang.String getMcc();
     method public java.lang.String getMnc();
+    method public int getPreciseCarrierId();
     method public java.lang.String getSpn();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.carrier.CarrierIdentifier> CREATOR;
@@ -40937,12 +41099,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();
@@ -42078,7 +42240,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;
@@ -42385,6 +42549,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();
@@ -42463,6 +42628,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();
@@ -42478,6 +42671,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";
   }
@@ -43072,7 +43266,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);
@@ -43081,7 +43274,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);
@@ -43110,6 +43302,7 @@
     field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS";
     field public static final java.lang.String EXTRA_INCOMING_VIDEO_STATE = "android.telecom.extra.INCOMING_VIDEO_STATE";
     field public static final java.lang.String EXTRA_IS_DEFAULT_CALL_SCREENING_APP = "android.telecom.extra.IS_DEFAULT_CALL_SCREENING_APP";
+    field public static final java.lang.String EXTRA_IS_ENABLED = "android.telecom.extra.IS_ENABLED";
     field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telecom.extra.NOTIFICATION_COUNT";
     field public static final java.lang.String EXTRA_NOTIFICATION_PHONE_NUMBER = "android.telecom.extra.NOTIFICATION_PHONE_NUMBER";
     field public static final java.lang.String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS";
@@ -43121,6 +43314,7 @@
     field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
     field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
     field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
+    field public static final java.lang.String METADATA_IN_CALL_SERVICE_CAR_MODE_UI = "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -43310,6 +43504,7 @@
     field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array";
     field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool";
     field public static final java.lang.String KEY_CARRIER_CALL_SCREENING_APP_STRING = "call_screening_app";
+    field public static final java.lang.String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string";
     field public static final java.lang.String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
     field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int";
     field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int";
@@ -44115,6 +44310,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);
@@ -44179,6 +44376,7 @@
     method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
     method public int getCallState();
     method public android.os.PersistableBundle getCarrierConfig();
+    method public int getCarrierIdFromSimMccMnc();
     method public deprecated android.telephony.CellLocation getCellLocation();
     method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList();
     method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(int);
@@ -44216,6 +44414,8 @@
     method public java.lang.String getSimCountryIso();
     method public java.lang.String getSimOperator();
     method public java.lang.String getSimOperatorName();
+    method public int getSimPreciseCarrierId();
+    method public java.lang.CharSequence getSimPreciseCarrierIdName();
     method public java.lang.String getSimSerialNumber();
     method public int getSimState();
     method public int getSimState(int);
@@ -44272,6 +44472,7 @@
     field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
     field public static final java.lang.String ACTION_SMS_APP_SERVICE = "android.telephony.action.SMS_APP_SERVICE";
     field public static final java.lang.String ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED";
+    field public static final java.lang.String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED";
     field public static final int APPTYPE_CSIM = 4; // 0x4
     field public static final int APPTYPE_ISIM = 5; // 0x5
     field public static final int APPTYPE_RUIM = 3; // 0x3
@@ -44304,6 +44505,8 @@
     field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
     field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
     field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telephony.extra.PHONE_ACCOUNT_HANDLE";
+    field public static final java.lang.String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID";
+    field public static final java.lang.String EXTRA_PRECISE_CARRIER_NAME = "android.telephony.extra.PRECISE_CARRIER_NAME";
     field public static final java.lang.String EXTRA_STATE = "state";
     field public static final java.lang.String EXTRA_STATE_IDLE;
     field public static final java.lang.String EXTRA_STATE_OFFHOOK;
@@ -45266,6 +45469,7 @@
 
   public static class PrecomputedText.Params.Builder {
     ctor public PrecomputedText.Params.Builder(android.text.TextPaint);
+    ctor public PrecomputedText.Params.Builder(android.text.PrecomputedText.Params);
     method public android.text.PrecomputedText.Params build();
     method public android.text.PrecomputedText.Params.Builder setBreakStrategy(int);
     method public android.text.PrecomputedText.Params.Builder setHyphenationFrequency(int);
@@ -48825,7 +49029,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();
@@ -52454,9 +52660,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";
@@ -52497,19 +52704,19 @@
     method public int describeContents();
     method public android.app.Person getAuthor();
     method public android.os.Bundle getExtras();
+    method public java.time.ZonedDateTime getReferenceTime();
     method public java.lang.CharSequence getText();
-    method public java.time.ZonedDateTime getTime();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR;
     field public static final android.app.Person PERSON_USER_LOCAL;
+    field public static final android.app.Person PERSON_USER_REMOTE;
   }
 
   public static final class ConversationActions.Message.Builder {
-    ctor public ConversationActions.Message.Builder();
+    ctor public ConversationActions.Message.Builder(android.app.Person);
     method public android.view.textclassifier.ConversationActions.Message build();
-    method public android.view.textclassifier.ConversationActions.Message.Builder setAuthor(android.app.Person);
-    method public android.view.textclassifier.ConversationActions.Message.Builder setComposeTime(java.time.ZonedDateTime);
     method public android.view.textclassifier.ConversationActions.Message.Builder setExtras(android.os.Bundle);
+    method public android.view.textclassifier.ConversationActions.Message.Builder setReferenceTime(java.time.ZonedDateTime);
     method public android.view.textclassifier.ConversationActions.Message.Builder setText(java.lang.CharSequence);
   }
 
@@ -52517,6 +52724,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();
@@ -52527,6 +52735,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);
@@ -52689,9 +52898,11 @@
     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);
+    field public static final java.lang.String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
     field public static final java.lang.String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
     field public static final java.lang.String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable";
     field public static final android.view.textclassifier.TextClassifier NO_OP;
@@ -52709,6 +52920,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";
@@ -52726,6 +52938,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);
@@ -56101,6 +56375,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 6db4ec7..d15e826 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4,7 +4,6 @@
     field public static final java.lang.String ACCESS_AMBIENT_LIGHT_STATS = "android.permission.ACCESS_AMBIENT_LIGHT_STATS";
     field public static final java.lang.String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO";
     field public static final java.lang.String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM";
-    field public static final java.lang.String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
     field public static final java.lang.String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
     field public static final deprecated java.lang.String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
     field public static final java.lang.String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
@@ -14,14 +13,11 @@
     field public static final java.lang.String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
     field public static final java.lang.String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
     field public static final java.lang.String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
-    field public static final java.lang.String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
     field public static final java.lang.String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final java.lang.String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
     field public static final java.lang.String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
     field public static final java.lang.String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
     field public static final java.lang.String BACKUP = "android.permission.BACKUP";
-    field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS";
-    field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
     field public static final java.lang.String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
     field public static final deprecated java.lang.String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
@@ -32,7 +28,6 @@
     field public static final java.lang.String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
     field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
     field public static final java.lang.String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
-    field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
     field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
     field public static final java.lang.String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE";
@@ -42,18 +37,13 @@
     field public static final java.lang.String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
     field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
-    field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
     field public static final java.lang.String BRICK = "android.permission.BRICK";
     field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final deprecated java.lang.String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
-    field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED";
     field public static final java.lang.String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
     field public static final java.lang.String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
-    field public static final java.lang.String CAPTURE_AUDIO_OUTPUT = "android.permission.CAPTURE_AUDIO_OUTPUT";
     field public static final java.lang.String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
     field public static final java.lang.String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
-    field public static final java.lang.String CHANGE_COMPONENT_ENABLED_STATE = "android.permission.CHANGE_COMPONENT_ENABLED_STATE";
-    field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
     field public static final java.lang.String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
     field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
     field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
@@ -63,22 +53,15 @@
     field public static final java.lang.String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION";
     field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
     field public static final java.lang.String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS";
-    field public static final java.lang.String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
     field public static final java.lang.String CONTROL_VPN = "android.permission.CONTROL_VPN";
     field public static final java.lang.String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
-    field public static final java.lang.String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
-    field public static final java.lang.String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
     field public static final java.lang.String DEVICE_POWER = "android.permission.DEVICE_POWER";
-    field public static final java.lang.String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
     field public static final java.lang.String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
-    field public static final java.lang.String DUMP = "android.permission.DUMP";
     field public static final java.lang.String FORCE_BACK = "android.permission.FORCE_BACK";
     field public static final java.lang.String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
-    field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
     field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
     field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
     field public static final java.lang.String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO";
-    field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
     field public static final java.lang.String GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS = "android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS";
     field public static final java.lang.String GRANT_RUNTIME_PERMISSIONS = "android.permission.GRANT_RUNTIME_PERMISSIONS";
     field public static final java.lang.String HARDWARE_TEST = "android.permission.HARDWARE_TEST";
@@ -86,8 +69,6 @@
     field public static final java.lang.String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
     field public static final java.lang.String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
     field public static final java.lang.String INSTALL_GRANT_RUNTIME_PERMISSIONS = "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS";
-    field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER";
-    field public static final java.lang.String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES";
     field public static final java.lang.String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES";
     field public static final java.lang.String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES";
     field public static final java.lang.String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT";
@@ -98,7 +79,6 @@
     field public static final java.lang.String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP";
     field public static final java.lang.String KILL_UID = "android.permission.KILL_UID";
     field public static final java.lang.String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
-    field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
     field public static final java.lang.String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
     field public static final java.lang.String LOOP_RADIO = "android.permission.LOOP_RADIO";
     field public static final java.lang.String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
@@ -113,23 +93,19 @@
     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";
     field public static final java.lang.String MANAGE_USERS = "android.permission.MANAGE_USERS";
     field public static final java.lang.String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
-    field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
-    field public static final java.lang.String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
     field public static final java.lang.String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS";
     field public static final java.lang.String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING";
     field public static final java.lang.String MODIFY_CELL_BROADCASTS = "android.permission.MODIFY_CELL_BROADCASTS";
     field public static final java.lang.String MODIFY_DAY_NIGHT_MODE = "android.permission.MODIFY_DAY_NIGHT_MODE";
     field public static final deprecated java.lang.String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
     field public static final java.lang.String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
-    field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
     field public static final java.lang.String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
-    field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
-    field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
     field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
     field public static final java.lang.String NETWORK_MANAGED_PROVISIONING = "android.permission.NETWORK_MANAGED_PROVISIONING";
     field public static final java.lang.String NETWORK_SETUP_WIZARD = "android.permission.NETWORK_SETUP_WIZARD";
@@ -138,7 +114,6 @@
     field public static final java.lang.String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE";
     field public static final java.lang.String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS";
     field public static final java.lang.String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG";
-    field public static final java.lang.String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
     field public static final java.lang.String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT";
     field public static final java.lang.String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
     field public static final java.lang.String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING";
@@ -151,7 +126,6 @@
     field public static final java.lang.String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
     field public static final java.lang.String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
     field public static final java.lang.String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
-    field public static final java.lang.String READ_LOGS = "android.permission.READ_LOGS";
     field public static final java.lang.String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY";
     field public static final java.lang.String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
     field public static final java.lang.String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
@@ -163,7 +137,6 @@
     field public static final java.lang.String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
     field public static final java.lang.String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
     field public static final java.lang.String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS";
-    field public static final java.lang.String REBOOT = "android.permission.REBOOT";
     field public static final java.lang.String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
     field public static final java.lang.String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
     field public static final java.lang.String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
@@ -181,27 +154,19 @@
     field public static final java.lang.String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
     field public static final java.lang.String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
     field public static final java.lang.String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
-    field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final java.lang.String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
     field public static final java.lang.String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
     field public static final java.lang.String SERIAL_PORT = "android.permission.SERIAL_PORT";
     field public static final java.lang.String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
-    field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
-    field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
-    field public static final java.lang.String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
     field public static final java.lang.String SET_HARMFUL_APP_WARNINGS = "android.permission.SET_HARMFUL_APP_WARNINGS";
     field public static final java.lang.String SET_MEDIA_KEY_LISTENER = "android.permission.SET_MEDIA_KEY_LISTENER";
     field public static final java.lang.String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
     field public static final java.lang.String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
-    field public static final java.lang.String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT";
     field public static final java.lang.String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY";
-    field public static final java.lang.String SET_TIME = "android.permission.SET_TIME";
     field public static final java.lang.String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
     field public static final java.lang.String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final java.lang.String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final java.lang.String SHUTDOWN = "android.permission.SHUTDOWN";
-    field public static final java.lang.String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
-    field public static final java.lang.String STATUS_BAR = "android.permission.STATUS_BAR";
     field public static final java.lang.String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final java.lang.String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
     field public static final java.lang.String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
@@ -210,18 +175,14 @@
     field public static final java.lang.String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
     field public static final java.lang.String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
     field public static final java.lang.String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
-    field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
     field public static final java.lang.String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
     field public static final java.lang.String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
     field public static final java.lang.String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
     field public static final java.lang.String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
-    field public static final java.lang.String WRITE_APN_SETTINGS = "android.permission.WRITE_APN_SETTINGS";
     field public static final java.lang.String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
     field public static final java.lang.String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
-    field public static final java.lang.String WRITE_GSERVICES = "android.permission.WRITE_GSERVICES";
     field public static final java.lang.String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final java.lang.String WRITE_OBB = "android.permission.WRITE_OBB";
-    field public static final java.lang.String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS";
   }
 
   public static final class Manifest.permission_group {
@@ -245,6 +206,7 @@
   }
 
   public static final class R.dimen {
+    field public static final int config_mediaMetadataBitmapMaxSize = 17104904; // 0x1050008
     field public static final int config_restrictedIconSize = 17104903; // 0x1050007
   }
 
@@ -582,10 +544,6 @@
 
 package android.app.admin {
 
-  public class DeviceAdminReceiver extends android.content.BroadcastReceiver {
-    method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent);
-  }
-
   public class DevicePolicyManager {
     method public java.lang.String getDeviceOwner();
     method public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -634,10 +592,7 @@
   }
 
   public final class SystemUpdatePolicy implements android.os.Parcelable {
-    method public int describeContents();
     method public android.app.admin.SystemUpdatePolicy.InstallationOption getInstallationOptionAt(long);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdatePolicy> CREATOR;
     field public static final int TYPE_PAUSE = 4; // 0x4
   }
 
@@ -946,6 +901,7 @@
   }
 
   public static final class UsageEvents.Event {
+    method public int getInstanceId();
     method public java.lang.String getNotificationChannelId();
     field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc
     field public static final int NOTIFICATION_SEEN = 10; // 0xa
@@ -1054,7 +1010,7 @@
     method public void setDetectNotResponding(long);
   }
 
-  public abstract class ContentResolver {
+  public abstract class ContentResolver implements android.content.ContentInterface {
     method public android.graphics.drawable.Drawable getTypeDrawable(java.lang.String);
   }
 
@@ -1261,6 +1217,7 @@
     method public abstract boolean arePermissionsIndividuallyControlled();
     method public boolean canSuspendPackage(java.lang.String);
     method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
+    method public android.content.pm.ApplicationInfo getApplicationInfoAsUser(java.lang.String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.dex.ArtManager getArtManager();
     method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
     method public java.lang.CharSequence getHarmfulAppWarning(java.lang.String);
@@ -1272,11 +1229,13 @@
     method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
     method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int);
     method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
-    method public java.lang.String getWellbeingPackageName();
     method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public abstract int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle);
+    method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(android.content.Intent, int, android.os.UserHandle);
+    method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(android.content.Intent, int, android.os.UserHandle);
+    method public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(android.content.Intent, int, android.os.UserHandle);
     method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
     method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public deprecated void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName);
@@ -2804,7 +2763,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);
@@ -2812,6 +2773,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);
@@ -3251,7 +3214,6 @@
     method public void addBlockedRating(android.media.tv.TvContentRating);
     method public boolean captureFrame(java.lang.String, android.view.Surface, android.media.tv.TvStreamConfig);
     method public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(java.lang.String);
-    method public java.util.List<android.media.tv.TvContentRating> getBlockedRatings();
     method public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList();
     method public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList();
     method public boolean isSingleSessionActive();
@@ -3497,11 +3459,9 @@
   }
 
   public class TrafficStats {
-    method public static void clearThreadStatsUid();
     method public static void setThreadStatsTagApp();
     method public static void setThreadStatsTagBackup();
     method public static void setThreadStatsTagRestore();
-    method public static void setThreadStatsUid(int);
   }
 
   public class VpnService extends android.app.Service {
@@ -3523,14 +3483,6 @@
 
 }
 
-package android.net.http {
-
-  public class X509TrustManagerExtensions {
-    method public boolean isSameTrustConfiguration(java.lang.String, java.lang.String);
-  }
-
-}
-
 package android.net.wifi {
 
   public deprecated class RttManager {
@@ -3754,7 +3706,6 @@
     method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
     method public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method public int getWifiApState();
-    method public boolean isDeviceToApRttSupported();
     method public boolean isDeviceToDeviceRttSupported();
     method public boolean isPortableHotspotSupported();
     method public boolean isWifiApEnabled();
@@ -3938,7 +3889,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 {
@@ -4025,6 +3980,19 @@
     field public static final java.lang.String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
   }
 
+  public class Binder implements android.os.IBinder {
+    method public static final long clearCallingWorkSource();
+    method public static final int getCallingWorkSourceUid();
+    method public static final void restoreCallingWorkSource(long);
+    method public static final long setCallingWorkSourceUid(int);
+    method public static void setProxyTransactListener(android.os.Binder.ProxyTransactListener);
+  }
+
+  public static abstract interface Binder.ProxyTransactListener {
+    method public abstract void onTransactEnded(java.lang.Object);
+    method public abstract java.lang.Object onTransactStarted(android.os.IBinder, int);
+  }
+
   public final class ConfigUpdate {
     field public static final java.lang.String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB";
     field public static final java.lang.String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
@@ -4688,6 +4656,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";
   }
 
@@ -5205,28 +5174,6 @@
     field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
   }
 
-  public final class Condition implements android.os.Parcelable {
-    ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int);
-    method public android.service.notification.Condition copy();
-    method public static boolean isValidId(android.net.Uri, java.lang.String);
-    method public static android.net.Uri.Builder newId(android.content.Context);
-    method public static java.lang.String relevanceToString(int);
-    method public static java.lang.String stateToString(int);
-    field public static final int FLAG_RELEVANT_ALWAYS = 2; // 0x2
-    field public static final int FLAG_RELEVANT_NOW = 1; // 0x1
-    field public static final java.lang.String SCHEME = "condition";
-    field public static final int STATE_ERROR = 3; // 0x3
-    field public static final int STATE_UNKNOWN = 2; // 0x2
-    field public final int flags;
-    field public final int icon;
-    field public final java.lang.String line1;
-    field public final java.lang.String line2;
-  }
-
-  public abstract class ConditionProviderService extends android.app.Service {
-    method public void onRequestConditions(int);
-  }
-
   public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
     ctor public NotificationAssistantService();
     method public final void adjustNotification(android.service.notification.Adjustment);
@@ -5420,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";
   }
 
@@ -5709,7 +5657,6 @@
     method public void clearPhoneAccounts();
     method public android.telecom.TelecomAnalytics dumpAnalytics();
     method public void enablePhoneAccount(android.telecom.PhoneAccountHandle, boolean);
-    method public boolean endCall();
     method public java.util.List<android.telecom.PhoneAccountHandle> getAllPhoneAccountHandles();
     method public java.util.List<android.telecom.PhoneAccount> getAllPhoneAccounts();
     method public int getAllPhoneAccountsCount();
@@ -5721,8 +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 isTtySupported();
-    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";
@@ -5746,7 +5692,6 @@
     method public void overrideConfig(int, android.os.PersistableBundle);
     method public void updateConfigForPhoneId(int, java.lang.String);
     field public static final java.lang.String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
-    field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
   }
 
   public class MbmsDownloadSession implements java.lang.AutoCloseable {
@@ -5795,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 {
@@ -5861,7 +5805,6 @@
 
   public final class SmsManager {
     method public void sendMultipartTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
-    method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
     field public static final int RESULT_CANCELLED = 23; // 0x17
     field public static final int RESULT_ENCODING_ERROR = 18; // 0x12
     field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 6; // 0x6
@@ -5888,15 +5831,9 @@
 
   public class SubscriptionManager {
     method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
-    method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
     method public void requestEmbeddedSubscriptionInfoListRefresh();
     method public void setDefaultDataSubId(int);
     method public void setDefaultSmsSubId(int);
-    method public void setSubscriptionOverrideCongested(int, boolean, long);
-    method public void setSubscriptionOverrideUnmetered(int, boolean, long);
-    method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
-    field public static final java.lang.String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS";
-    field public static final java.lang.String ACTION_REFRESH_SUBSCRIPTION_PLANS = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS";
     field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
     field public static final android.net.Uri VT_ENABLED_CONTENT_URI;
     field public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
@@ -5905,37 +5842,10 @@
     field public static final android.net.Uri WFC_ROAMING_MODE_CONTENT_URI;
   }
 
-  public final class SubscriptionPlan implements android.os.Parcelable {
-    method public java.util.Iterator<android.util.Range<java.time.ZonedDateTime>> cycleIterator();
-    method public int describeContents();
-    method public int getDataLimitBehavior();
-    method public long getDataLimitBytes();
-    method public long getDataUsageBytes();
-    method public long getDataUsageTime();
-    method public java.lang.CharSequence getSummary();
-    method public java.lang.CharSequence getTitle();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final long BYTES_UNKNOWN = -1L; // 0xffffffffffffffffL
-    field public static final long BYTES_UNLIMITED = 9223372036854775807L; // 0x7fffffffffffffffL
-    field public static final android.os.Parcelable.Creator<android.telephony.SubscriptionPlan> CREATOR;
-    field public static final int LIMIT_BEHAVIOR_BILLED = 1; // 0x1
-    field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0
-    field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2
-    field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff
-    field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
-  }
-
   public static class SubscriptionPlan.Builder {
-    method public android.telephony.SubscriptionPlan build();
-    method public static android.telephony.SubscriptionPlan.Builder createNonrecurring(java.time.ZonedDateTime, java.time.ZonedDateTime);
-    method public static android.telephony.SubscriptionPlan.Builder createRecurring(java.time.ZonedDateTime, java.time.Period);
     method public static deprecated android.telephony.SubscriptionPlan.Builder createRecurringDaily(java.time.ZonedDateTime);
     method public static deprecated android.telephony.SubscriptionPlan.Builder createRecurringMonthly(java.time.ZonedDateTime);
     method public static deprecated android.telephony.SubscriptionPlan.Builder createRecurringWeekly(java.time.ZonedDateTime);
-    method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
-    method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
-    method public android.telephony.SubscriptionPlan.Builder setSummary(java.lang.CharSequence);
-    method public android.telephony.SubscriptionPlan.Builder setTitle(java.lang.CharSequence);
   }
 
   public final class TelephonyHistogram implements android.os.Parcelable {
@@ -5968,6 +5878,7 @@
     method public void enableVideoCalling(boolean);
     method public java.lang.String getAidForAppType(int);
     method public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
+    method public int getCardIdForDefaultEuicc();
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
     method public java.lang.String getCdmaMdn();
@@ -6034,6 +5945,7 @@
     field public static final java.lang.String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
     field public static final java.lang.String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
     field public static final java.lang.String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
+    field public static final int INVALID_CARD_ID = -1; // 0xffffffff
     field public static final long MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS = 60000L; // 0xea60L
     field public static final int NETWORK_MODE_CDMA_EVDO = 4; // 0x4
     field public static final int NETWORK_MODE_CDMA_NO_EVDO = 5; // 0x5
@@ -6176,20 +6088,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);
@@ -6657,6 +6568,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
@@ -6680,26 +6592,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
@@ -6709,9 +6634,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
@@ -7354,6 +7281,7 @@
 package android.view.accessibility {
 
   public final class AccessibilityManager {
+    method public int getAccessibilityWindowId(android.os.IBinder);
     method public void performAccessibilityShortcut();
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 4717969..627ef22 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5,7 +5,6 @@
     field public static final java.lang.String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final java.lang.String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
-    field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
     field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
     field public static final java.lang.String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
     field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
@@ -363,6 +362,7 @@
     method public abstract java.lang.String getPermissionControllerPackageName();
     method public abstract java.lang.String getServicesSystemSharedLibraryPackageName();
     method public abstract java.lang.String getSharedSystemSharedLibraryPackageName();
+    method public java.lang.String getWellbeingPackageName();
     method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     field public static final java.lang.String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
@@ -1710,11 +1710,6 @@
     method public static int getLongPressTooltipHideTimeout();
   }
 
-  public final class ViewTreeObserver {
-    method public void registerFrameCommitCallback(java.lang.Runnable);
-    method public boolean unregisterFrameCommitCallback(java.lang.Runnable);
-  }
-
   public abstract interface WindowManager implements android.view.ViewManager {
     method public default void setShouldShowIme(int, boolean);
     method public default void setShouldShowSystemDecors(int, boolean);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 7fa05be..04173b2 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -424,13 +424,14 @@
     dprintf(out, "\n                     be removed from memory and disk!\n");
     dprintf(out, "\n");
     dprintf(out,
-            "usage: adb shell cmd stats dump-report [UID] NAME [--include_current_bucket] "
-            "[--proto]\n");
+            "usage: adb shell cmd stats dump-report [UID] NAME [--keep_data] "
+            "[--include_current_bucket] [--proto]\n");
     dprintf(out, "  Dump all metric data for a configuration.\n");
     dprintf(out, "  UID           The uid of the configuration. It is only possible to pass\n");
     dprintf(out, "                the UID parameter on eng builds. If UID is omitted the\n");
     dprintf(out, "                calling uid is used.\n");
     dprintf(out, "  NAME          The name of the configuration\n");
+    dprintf(out, "  --keep_data   Do NOT erase the data upon dumping it.\n");
     dprintf(out, "  --proto       Print proto binary.\n");
     dprintf(out, "\n");
     dprintf(out, "\n");
@@ -590,6 +591,7 @@
         bool good = false;
         bool proto = false;
         bool includeCurrentBucket = false;
+        bool eraseData = true;
         int uid;
         string name;
         if (!std::strcmp("--proto", args[argCount-1].c_str())) {
@@ -600,6 +602,10 @@
             includeCurrentBucket = true;
             argCount -= 1;
         }
+        if (!std::strcmp("--keep_data", args[argCount-1].c_str())) {
+            eraseData = false;
+            argCount -= 1;
+        }
         if (argCount == 2) {
             // Automatically pick the UID
             uid = IPCThreadState::self()->getCallingUid();
@@ -627,7 +633,7 @@
         if (good) {
             vector<uint8_t> data;
             mProcessor->onDumpReport(ConfigKey(uid, StrToInt64(name)), getElapsedRealtimeNs(),
-                                     includeCurrentBucket, true /* erase_data */, ADB_DUMP, &data);
+                                     includeCurrentBucket, eraseData, ADB_DUMP, &data);
             if (proto) {
                 for (size_t i = 0; i < data.size(); i ++) {
                     dprintf(out, "%c", data[i]);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f58caff..9ee0786 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -1222,7 +1222,7 @@
         STATE_CONNECTED = 1;
     }
     optional State state = 6;
-    optional int64 last_connect_duration_ms = 7;
+    optional int64 last_connect_duration_millis = 7;
 }
 
 
@@ -2394,16 +2394,23 @@
 }
 
 /**
- * Pulls low power state information. This includes platform and subsystem sleep state information,
- * PowerStatePlatformSleepState, PowerStateVoter or PowerStateSubsystemSleepState as defined in
+ * Pulls low power state information. If power.stats HAL is not available, this
+ * includes platform and subsystem sleep state information,
+ * PowerStatePlatformSleepState, PowerStateVoter or PowerStateSubsystemSleepState
+ * as defined in:
  *   hardware/interfaces/power/1.0/types.hal
  *   hardware/interfaces/power/1.1/types.hal
+ * If power.stats HAL is available, this includes PowerEntityStateResidencyResult
+ * as defined in:
+ *   hardware/interfaces/power/stats/1.0/types.hal
  */
 message SubsystemSleepState {
     // Subsystem name
     optional string subsystem_name = 1;
     // For PlatformLowPowerStats (hal 1.0), this is the voter name, which could be empty.
     // For SubsystemLowPowerStats (hal 1.1), this is the sleep state name.
+    // For PowerEntityStateResidencyResult (hal power/stats/1.0) this is the
+    //    powerEntityStateName from the corresponding PowerEntityStateInfo.
     optional string subname = 2;
     // The number of times it entered, or voted for entering the sleep state
     optional uint64 count = 3;
@@ -2540,15 +2547,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
@@ -2558,12 +2569,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.
@@ -2576,12 +2593,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
@@ -2591,6 +2611,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.
@@ -2603,13 +2624,17 @@
 
 /*
  * 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.
  */
 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
@@ -3586,4 +3611,4 @@
  */
 message DocsUIInvalidScopedAccessRequestReported {
     optional android.stats.docsui.InvalidScopedAccess type = 1;
-}
\ No newline at end of file
+}
diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
index 4501b64..c8c3920 100644
--- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
+++ b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
@@ -19,6 +19,8 @@
 
 #include <android/hardware/power/1.0/IPower.h>
 #include <android/hardware/power/1.1/IPower.h>
+#include <android/hardware/power/stats/1.0/IPowerStats.h>
+
 #include <fcntl.h>
 #include <hardware/power.h>
 #include <hardware_legacy/power.h>
@@ -42,9 +44,12 @@
 using android::hardware::power::V1_0::IPower;
 using android::hardware::power::V1_0::PowerStatePlatformSleepState;
 using android::hardware::power::V1_0::PowerStateVoter;
-using android::hardware::power::V1_0::Status;
 using android::hardware::power::V1_1::PowerStateSubsystem;
 using android::hardware::power::V1_1::PowerStateSubsystemSleepState;
+using android::hardware::power::stats::V1_0::PowerEntityInfo;
+using android::hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
+using android::hardware::power::stats::V1_0::PowerEntityStateSpace;
+
 using android::hardware::Return;
 using android::hardware::Void;
 
@@ -55,44 +60,209 @@
 namespace os {
 namespace statsd {
 
+std::function<bool(vector<shared_ptr<LogEvent>>* data)> gPuller = {};
+
 sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr;
 sp<android::hardware::power::V1_1::IPower> gPowerHalV1_1 = nullptr;
+sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHalV1_0 = nullptr;
+
+std::unordered_map<uint32_t, std::string> gEntityNames = {};
+std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> gStateNames = {};
+
 std::mutex gPowerHalMutex;
-bool gPowerHalExists = true;
 
-bool getPowerHal() {
-    if (gPowerHalExists && gPowerHalV1_0 == nullptr) {
-        gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService();
-        if (gPowerHalV1_0 != nullptr) {
-            gPowerHalV1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
-            ALOGI("Loaded power HAL service");
-        } else {
-            ALOGW("Couldn't load power HAL service");
-            gPowerHalExists = false;
-        }
+// The caller must be holding gPowerHalMutex.
+void deinitPowerStatsLocked() {
+    gPowerHalV1_0 = nullptr;
+    gPowerHalV1_1 = nullptr;
+    gPowerStatsHalV1_0 = nullptr;
+}
+
+struct PowerHalDeathRecipient : virtual public hardware::hidl_death_recipient {
+    virtual void serviceDied(uint64_t cookie,
+        const wp<android::hidl::base::V1_0::IBase>& who) override {
+        // The HAL just died. Reset all handles to HAL services.
+        std::lock_guard<std::mutex> lock(gPowerHalMutex);
+        deinitPowerStatsLocked();
     }
-    return gPowerHalV1_0 != nullptr;
+};
+
+sp<PowerHalDeathRecipient> gDeathRecipient = new PowerHalDeathRecipient();
+
+SubsystemSleepStatePuller::SubsystemSleepStatePuller() :
+    StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) {
 }
 
-SubsystemSleepStatePuller::SubsystemSleepStatePuller() : StatsPuller(android::util::SUBSYSTEM_SLEEP_STATE) {
+// The caller must be holding gPowerHalMutex.
+bool checkResultLocked(const Return<void> &ret, const char* function) {
+    if (!ret.isOk()) {
+        ALOGE("%s failed: requested HAL service not available. Description: %s",
+            function, ret.description().c_str());
+        if (ret.isDeadObject()) {
+            deinitPowerStatsLocked();
+        }
+        return false;
+    }
+    return true;
 }
 
-bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
-    std::lock_guard<std::mutex> lock(gPowerHalMutex);
+// The caller must be holding gPowerHalMutex.
+// gPowerStatsHalV1_0 must not be null
+bool initializePowerStats() {
+    using android::hardware::power::stats::V1_0::Status;
 
-    if (!getPowerHal()) {
-        ALOGE("Power Hal not loaded");
+    // Clear out previous content if we are re-initializing
+    gEntityNames.clear();
+    gStateNames.clear();
+
+    Return<void> ret;
+    ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) {
+        if (status != Status::SUCCESS) {
+            ALOGE("Error getting power entity info");
+            return;
+        }
+
+        // construct lookup table of powerEntityId to power entity name
+        for (auto info : infos) {
+            gEntityNames.emplace(info.powerEntityId, info.powerEntityName);
+        }
+    });
+    if (!checkResultLocked(ret, __func__)) {
+        return false;
+    }
+
+    ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) {
+        if (status != Status::SUCCESS) {
+            ALOGE("Error getting state info");
+            return;
+        }
+
+        // construct lookup table of powerEntityId, powerEntityStateId to power entity state name
+        for (auto stateSpace : stateSpaces) {
+            std::unordered_map<uint32_t, std::string> stateNames = {};
+            for (auto state : stateSpace.states) {
+                stateNames.emplace(state.powerEntityStateId,
+                    state.powerEntityStateName);
+            }
+            gStateNames.emplace(stateSpace.powerEntityId, stateNames);
+        }
+    });
+    if (!checkResultLocked(ret, __func__)) {
+        return false;
+    }
+
+    return (!gEntityNames.empty()) && (!gStateNames.empty());
+}
+
+// The caller must be holding gPowerHalMutex.
+bool getPowerStatsHalLocked() {
+    if(gPowerStatsHalV1_0 == nullptr) {
+        gPowerStatsHalV1_0 = android::hardware::power::stats::V1_0::IPowerStats::getService();
+        if (gPowerStatsHalV1_0 == nullptr) {
+            ALOGE("Unable to get power.stats HAL service.");
+            return false;
+        }
+
+        // Link death recipient to power.stats service handle
+        hardware::Return<bool> linked = gPowerStatsHalV1_0->linkToDeath(gDeathRecipient, 0);
+        if (!linked.isOk()) {
+            ALOGE("Transaction error in linking to power.stats HAL death: %s",
+                    linked.description().c_str());
+            deinitPowerStatsLocked();
+            return false;
+        } else if (!linked) {
+            ALOGW("Unable to link to power.stats HAL death notifications");
+            // We should still continue even though linking failed
+        }
+        return initializePowerStats();
+    }
+    return true;
+}
+
+// The caller must be holding gPowerHalMutex.
+bool getIPowerStatsDataLocked(vector<shared_ptr<LogEvent>>* data) {
+    using android::hardware::power::stats::V1_0::Status;
+
+    if(!getPowerStatsHalLocked()) {
         return false;
     }
 
     int64_t wallClockTimestampNs = getWallClockNs();
     int64_t elapsedTimestampNs = getElapsedRealtimeNs();
 
-    data->clear();
+    // Get power entity state residency data
+    bool success = false;
+    Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({},
+        [&data, &success, wallClockTimestampNs, elapsedTimestampNs]
+        (auto results, auto status) {
+        if (status == Status::NOT_SUPPORTED) {
+            ALOGW("getPowerEntityStateResidencyData is not supported");
+            success = false;
+            return;
+        }
 
-    Return<void> ret;
+        for(auto result : results) {
+            for(auto stateResidency : result.stateResidencyData) {
+                auto statePtr = make_shared<LogEvent>(
+                        android::util::SUBSYSTEM_SLEEP_STATE,
+                        wallClockTimestampNs, elapsedTimestampNs);
+                statePtr->write(gEntityNames.at(result.powerEntityId));
+                statePtr->write(gStateNames.at(result.powerEntityId)
+                    .at(stateResidency.powerEntityStateId));
+                statePtr->write(stateResidency.totalStateEntryCount);
+                statePtr->write(stateResidency.totalTimeInStateMs);
+                statePtr->init();
+                data->emplace_back(statePtr);
+            }
+        }
+        success = true;
+    });
+    // Intentionally not returning early here.
+    // bool success determines if this succeeded or not.
+    checkResultLocked(ret, __func__);
+
+    return success;
+}
+
+// The caller must be holding gPowerHalMutex.
+bool getPowerHalLocked() {
+    if(gPowerHalV1_0 == nullptr) {
+        gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService();
+        if(gPowerHalV1_0 == nullptr) {
+            ALOGE("Unable to get power HAL service.");
+            return false;
+        }
+        gPowerHalV1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
+
+        // Link death recipient to power service handle
+        hardware::Return<bool> linked = gPowerHalV1_0->linkToDeath(gDeathRecipient, 0);
+        if (!linked.isOk()) {
+            ALOGE("Transaction error in linking to power HAL death: %s",
+                    linked.description().c_str());
+            gPowerHalV1_0 = nullptr;
+            return false;
+        } else if (!linked) {
+            ALOGW("Unable to link to power. death notifications");
+            // We should still continue even though linking failed
+        }
+    }
+    return true;
+}
+
+// The caller must be holding gPowerHalMutex.
+bool getIPowerDataLocked(vector<shared_ptr<LogEvent>>* data) {
+    using android::hardware::power::V1_0::Status;
+
+    if(!getPowerHalLocked()) {
+        return false;
+    }
+
+    int64_t wallClockTimestampNs = getWallClockNs();
+    int64_t elapsedTimestampNs = getElapsedRealtimeNs();
+        Return<void> ret;
         ret = gPowerHalV1_0->getPlatformLowPowerStats(
-                [&data, wallClockTimestampNs, elapsedTimestampNs](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
+                [&data, wallClockTimestampNs, elapsedTimestampNs]
+                    (hidl_vec<PowerStatePlatformSleepState> states, Status status) {
                     if (status != Status::SUCCESS) return;
 
                     for (size_t i = 0; i < states.size(); i++) {
@@ -128,9 +298,7 @@
                         }
                     }
                 });
-        if (!ret.isOk()) {
-            ALOGE("getLowPowerStats() failed: power HAL service not available");
-            gPowerHalV1_0 = nullptr;
+        if (!checkResultLocked(ret, __func__)) {
             return false;
         }
 
@@ -139,35 +307,68 @@
                 android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
         if (gPowerHal_1_1 != nullptr) {
             ret = gPowerHal_1_1->getSubsystemLowPowerStats(
-                    [&data, wallClockTimestampNs, elapsedTimestampNs](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
-                        if (status != Status::SUCCESS) return;
+            [&data, wallClockTimestampNs, elapsedTimestampNs]
+            (hidl_vec<PowerStateSubsystem> subsystems, Status status) {
+                if (status != Status::SUCCESS) return;
 
-                        if (subsystems.size() > 0) {
-                            for (size_t i = 0; i < subsystems.size(); i++) {
-                                const PowerStateSubsystem& subsystem = subsystems[i];
-                                for (size_t j = 0; j < subsystem.states.size(); j++) {
-                                    const PowerStateSubsystemSleepState& state =
-                                            subsystem.states[j];
-                                    auto subsystemStatePtr = make_shared<LogEvent>(
-                                        android::util::SUBSYSTEM_SLEEP_STATE,
-                                        wallClockTimestampNs, elapsedTimestampNs);
-                                    subsystemStatePtr->write(subsystem.name);
-                                    subsystemStatePtr->write(state.name);
-                                    subsystemStatePtr->write(state.totalTransitions);
-                                    subsystemStatePtr->write(state.residencyInMsecSinceBoot);
-                                    subsystemStatePtr->init();
-                                    data->push_back(subsystemStatePtr);
-                                    VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
-                                         subsystem.name.c_str(), state.name.c_str(),
-                                         (long long)state.residencyInMsecSinceBoot,
-                                         (long long)state.totalTransitions,
-                                         (long long)state.lastEntryTimestampMs);
-                                }
-                            }
+                if (subsystems.size() > 0) {
+                    for (size_t i = 0; i < subsystems.size(); i++) {
+                        const PowerStateSubsystem& subsystem = subsystems[i];
+                        for (size_t j = 0; j < subsystem.states.size(); j++) {
+                            const PowerStateSubsystemSleepState& state =
+                                    subsystem.states[j];
+                            auto subsystemStatePtr = make_shared<LogEvent>(
+                                android::util::SUBSYSTEM_SLEEP_STATE,
+                                wallClockTimestampNs, elapsedTimestampNs);
+                            subsystemStatePtr->write(subsystem.name);
+                            subsystemStatePtr->write(state.name);
+                            subsystemStatePtr->write(state.totalTransitions);
+                            subsystemStatePtr->write(state.residencyInMsecSinceBoot);
+                            subsystemStatePtr->init();
+                            data->push_back(subsystemStatePtr);
+                            VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
+                                 subsystem.name.c_str(), state.name.c_str(),
+                                 (long long)state.residencyInMsecSinceBoot,
+                                 (long long)state.totalTransitions,
+                                 (long long)state.lastEntryTimestampMs);
                         }
-                    });
+                    }
+                }
+            });
         }
-    return true;
+        return true;
+}
+
+// The caller must be holding gPowerHalMutex.
+std::function<bool(vector<shared_ptr<LogEvent>>* data)> getPullerLocked() {
+    std::function<bool(vector<shared_ptr<LogEvent>>* data)> ret = {};
+
+    // First see if power.stats HAL is available. Fall back to power HAL if
+    // power.stats HAL is unavailable.
+    if(android::hardware::power::stats::V1_0::IPowerStats::getService() != nullptr) {
+        ALOGI("Using power.stats HAL");
+        ret = getIPowerStatsDataLocked;
+    } else if(android::hardware::power::V1_0::IPower::getService() != nullptr) {
+        ALOGI("Using power HAL");
+        ret = getIPowerDataLocked;
+    }
+
+    return ret;
+}
+
+bool SubsystemSleepStatePuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
+    std::lock_guard<std::mutex> lock(gPowerHalMutex);
+
+    if(!gPuller) {
+        gPuller = getPullerLocked();
+    }
+
+    if(gPuller) {
+        return gPuller(data);
+    }
+
+    ALOGE("Unable to load Power Hal or power.stats HAL");
+    return false;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 355df29..237f8b9 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -240,6 +240,49 @@
     EXPECT_EQ(2, report.annotation(0).field_int32());
 }
 
+TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) {
+    // Setup a simple config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = wakelockAcquireMatcher;
+
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(123456);
+    countMetric->set_what(wakelockAcquireMatcher.id());
+    countMetric->set_bucket(FIVE_MINUTES);
+
+    ConfigKey cfgKey;
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(1, 1, config, cfgKey);
+
+    std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
+    auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 2);
+    processor->OnLogEvent(event.get());
+
+    vector<uint8_t> bytes;
+    ConfigMetricsReportList output;
+
+    // Dump report WITHOUT erasing data.
+    processor->onDumpReport(cfgKey, 3, true, false /* Do NOT erase data. */, ADB_DUMP, &bytes);
+    output.ParseFromArray(bytes.data(), bytes.size());
+    EXPECT_EQ(output.reports_size(), 1);
+    EXPECT_EQ(output.reports(0).metrics_size(), 1);
+    EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);
+
+    // Dump report WITH erasing data. There should be data since we didn't previously erase it.
+    processor->onDumpReport(cfgKey, 4, true, true /* DO erase data. */, ADB_DUMP, &bytes);
+    output.ParseFromArray(bytes.data(), bytes.size());
+    EXPECT_EQ(output.reports_size(), 1);
+    EXPECT_EQ(output.reports(0).metrics_size(), 1);
+    EXPECT_EQ(output.reports(0).metrics(0).count_metrics().data_size(), 1);
+
+    // Dump report again. There should be no data since we erased it.
+    processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, &bytes);
+    output.ParseFromArray(bytes.data(), bytes.size());
+    bool noData = (output.reports_size() == 0) || (output.reports(0).metrics_size() == 0);
+    EXPECT_TRUE(noData);
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tools/localtools/Android.bp b/cmds/statsd/tools/localtools/Android.bp
new file mode 100644
index 0000000..75a57a3
--- /dev/null
+++ b/cmds/statsd/tools/localtools/Android.bp
@@ -0,0 +1,25 @@
+java_binary_host {
+    name: "statsd_localdrive",
+    manifest: "localdrive_manifest.txt",
+    srcs: [
+        "src/com/android/statsd/shelltools/localdrive/*.java",
+        "src/com/android/statsd/shelltools/Utils.java",
+    ],
+    static_libs: [
+        "platformprotos",
+        "guava",
+    ],
+}
+
+java_binary_host {
+    name: "statsd_testdrive",
+    manifest: "testdrive_manifest.txt",
+    srcs: [
+        "src/com/android/statsd/shelltools/testdrive/*.java",
+        "src/com/android/statsd/shelltools/Utils.java",
+    ],
+    static_libs: [
+        "platformprotos",
+        "guava",
+    ],
+}
\ No newline at end of file
diff --git a/cmds/statsd/tools/localtools/localdrive_manifest.txt b/cmds/statsd/tools/localtools/localdrive_manifest.txt
new file mode 100644
index 0000000..035cea1
--- /dev/null
+++ b/cmds/statsd/tools/localtools/localdrive_manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.statsd.shelltools.localdrive.LocalDrive
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
new file mode 100644
index 0000000..8464b8df
--- /dev/null
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.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.statsd.shelltools;
+
+import com.android.os.StatsLog.ConfigMetricsReportList;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * Utilities for local use of statsd.
+ */
+public class Utils {
+
+    public static final String CMD_UPDATE_CONFIG = "cmd stats config update";
+    public static final String CMD_DUMP_REPORT = "cmd stats dump-report";
+    public static final String CMD_REMOVE_CONFIG = "cmd stats config remove";
+
+    public static final String SHELL_UID = "2000"; // Use shell, even if rooted.
+
+    /**
+     * Runs adb shell command with output directed to outputFile if non-null.
+     */
+    public static void runCommand(File outputFile, Logger logger, String... commands)
+            throws IOException, InterruptedException {
+        ProcessBuilder pb = new ProcessBuilder(commands);
+        if (outputFile != null && outputFile.exists() && outputFile.canWrite()) {
+            pb.redirectOutput(outputFile);
+        }
+        Process process = pb.start();
+
+        // Capture any errors
+        StringBuilder err = new StringBuilder();
+        BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+        for (String line = br.readLine(); line != null; line = br.readLine()) {
+            err.append(line).append('\n');
+        }
+        logger.severe(err.toString());
+
+        // Check result
+        if (process.waitFor() == 0) {
+            logger.fine("Adb command successful.");
+        } else {
+            logger.severe("Abnormal adb shell cmd termination for: " + String.join(",", commands));
+            throw new RuntimeException("Error running adb command: " + err.toString());
+        }
+    }
+
+    /**
+     * 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,
+            boolean useShellUid, Logger logger) throws IOException, InterruptedException {
+        try {
+            File outputFile = File.createTempFile("statsdret", ".bin");
+            outputFile.deleteOnExit();
+            runCommand(
+                    outputFile,
+                    logger,
+                    "adb",
+                    "shell",
+                    CMD_DUMP_REPORT,
+                    useShellUid ? SHELL_UID : "",
+                    String.valueOf(configId),
+                    clearData ? "" : "--keep_data",
+                    "--include_current_bucket",
+                    "--proto");
+            ConfigMetricsReportList reportList =
+                    ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile));
+            return reportList;
+        } 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 "
+                            + (useShellUid ? ("uid=" + SHELL_UID + ", ") : "")
+                            + "configId=" + configId
+                            + ".");
+            throw (e);
+        }
+    }
+
+    public static void setUpLogger(Logger logger, boolean debug) {
+        ConsoleHandler handler = new ConsoleHandler();
+        handler.setFormatter(new LocalToolsFormatter());
+        logger.setUseParentHandlers(false);
+        if (debug) {
+            handler.setLevel(Level.ALL);
+            logger.setLevel(Level.ALL);
+        }
+        logger.addHandler(handler);
+    }
+
+    public static class LocalToolsFormatter extends Formatter {
+        public String format(LogRecord record) {
+            return record.getMessage() + "\n";
+        }
+    }
+}
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
new file mode 100644
index 0000000..67fb906
--- /dev/null
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
@@ -0,0 +1,343 @@
+/*
+ * 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.statsd.shelltools.localdrive;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.statsd.shelltools.Utils;
+
+import com.google.common.io.Files;
+import com.google.protobuf.TextFormat;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * Tool for using statsd locally. Can upload a config and get the data. Handles
+ * both binary and human-readable protos.
+ * To make: make statsd_localdrive
+ * To run: statsd_localdrive     (i.e.  ./out/host/linux-x86/bin/statsd_localdrive)
+ */
+public class LocalDrive {
+    private static final boolean DEBUG = false;
+
+    public static final long DEFAULT_CONFIG_ID = 56789;
+
+    public static final String BINARY_FLAG = "--binary";
+    public static final String CLEAR_DATA = "--clear";
+    public static final String NO_UID_MAP_FLAG = "--no-uid-map";
+
+    public static final String HELP_STRING =
+        "Usage:\n\n" +
+
+        "statsd_local upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+        "  Uploads the given statsd config file (in binary or human-readable-text format).\n" +
+        "  If a config with this id already exists, removes it first.\n" +
+        "    CONFIG_FILE    Location of config file on host.\n" +
+        "    CONFIG_ID      Long ID to associate with this config. If absent, uses "
+                                                                + DEFAULT_CONFIG_ID + ".\n" +
+        "    --binary       Config is in binary format; otherwise, assumed human-readable text.\n" +
+        // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
+        "\n" +
+
+        "statsd_local update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+        "  Same as upload, but does not remove the old config first (if it already exists).\n" +
+        // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
+        "\n" +
+
+        "statsd_local get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
+        "  Prints the output statslog data (in binary or human-readable-text format).\n" +
+        "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
+        "    --binary       Output should be in binary, instead of default human-readable text.\n" +
+        "                       Binary output can be redirected as usual (e.g. > FILENAME).\n" +
+        "    --no-uid-map   Do not include the uid-map (the very lengthy uid<-->pkgName map).\n" +
+        "    --clear        Erase the data from statsd afterwards. Does not remove the config.\n" +
+        // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID [--keep_data]
+        //                                                      --include_current_bucket --proto
+        "\n" +
+
+        "statsd_local remove [CONFIG_ID]\n" +
+        "  Removes the config.\n" +
+        "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
+        // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID
+        "\n" +
+
+        "statsd_local clear [CONFIG_ID]\n" +
+        "  Clears the data associated with the config.\n" +
+        "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
+        // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID
+        //                                                      --include_current_bucket --proto
+        "";
+
+
+    private static final Logger sLogger = Logger.getLogger(LocalDrive.class.getName());
+
+    /** Usage: make statsd_localdrive && statsd_localdrive */
+    public static void main(String[] args) {
+        Utils.setUpLogger(sLogger, DEBUG);
+
+        if (args.length > 0) {
+            switch (args[0]) {
+                case "clear":
+                    cmdClear(args);
+                    return;
+                case "get-data":
+                    cmdGetData(args);
+                    return;
+                case "remove":
+                    cmdRemove(args);
+                    return;
+                case "update":
+                    cmdUpdate(args);
+                    return;
+                case "upload":
+                    cmdUpload(args);
+                    return;
+            }
+        }
+        printHelp();
+    }
+
+    private static void printHelp() {
+        sLogger.info(HELP_STRING);
+    }
+
+    // upload CONFIG_FILE [CONFIG_ID] [--binary]
+    private static boolean cmdUpload(String[] args) {
+        return updateConfig(args, true);
+    }
+
+    // update CONFIG_FILE [CONFIG_ID] [--binary]
+    private static boolean cmdUpdate(String[] args) {
+        return updateConfig(args, false);
+    }
+
+    private static boolean updateConfig(String[] args, boolean removeOldConfig) {
+        int argCount = args.length - 1; // Used up one for upload/update.
+
+        // Get CONFIG_FILE
+        if (argCount < 1) {
+            sLogger.severe("No config file provided.");
+            printHelp();
+            return false;
+        }
+        final String origConfigLocation = args[1];
+        if (!new File(origConfigLocation).exists()) {
+            sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation);
+            return false;
+        }
+        argCount--;
+
+        // Get --binary
+        boolean binary = contains(args, 2, BINARY_FLAG);
+        if (binary) argCount --;
+
+        // Get CONFIG_ID
+        long configId;
+        try {
+            configId = getConfigId(argCount < 1, args, 2);
+        } catch (NumberFormatException e) {
+            sLogger.severe("Invalid config id provided.");
+            printHelp();
+            return false;
+        }
+        sLogger.fine(String.format("updateConfig with %s %d %b %b",
+                origConfigLocation, configId, binary, removeOldConfig));
+
+        // Remove the old config.
+        if (removeOldConfig) {
+            try {
+                Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
+                        Utils.SHELL_UID, String.valueOf(configId));
+                Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
+            } catch (InterruptedException | IOException e) {
+                sLogger.severe("Failed to remove config: " + e.getMessage());
+                return false;
+            }
+        }
+
+        // Upload the config.
+        String configLocation;
+        if (binary) {
+            configLocation = origConfigLocation;
+        } else {
+            StatsdConfig.Builder builder = StatsdConfig.newBuilder();
+            try {
+                TextFormat.merge(new FileReader(origConfigLocation), builder);
+            } catch (IOException e) {
+                sLogger.severe("Failed to read config file " + origConfigLocation + ": "
+                        + e.getMessage());
+                return false;
+            }
+
+            try {
+                File tempConfigFile = File.createTempFile("statsdconfig", ".config");
+                tempConfigFile.deleteOnExit();
+                Files.write(builder.build().toByteArray(), tempConfigFile);
+                configLocation = tempConfigFile.getAbsolutePath();
+            } catch (IOException e) {
+                sLogger.severe("Failed to write temp config file: " + e.getMessage());
+                return false;
+            }
+        }
+        String remotePath = "/data/local/tmp/statsdconfig.config";
+        try {
+            Utils.runCommand(null, sLogger, "adb", "push", configLocation, remotePath);
+            Utils.runCommand(null, sLogger, "adb", "shell", "cat", remotePath, "|",
+                    Utils.CMD_UPDATE_CONFIG, Utils.SHELL_UID, String.valueOf(configId));
+        } catch (InterruptedException | IOException e) {
+            sLogger.severe("Failed to update config: " + e.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]
+    private static boolean cmdGetData(String[] args) {
+        boolean binary = contains(args, 1, BINARY_FLAG);
+        boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG);
+        boolean clearData = contains(args, 1, CLEAR_DATA);
+
+        // Get CONFIG_ID
+        int argCount = args.length - 1; // Used up one for get-data.
+        if (binary) argCount--;
+        if (noUidMap) argCount--;
+        if (clearData) argCount--;
+        long configId;
+        try {
+            configId = getConfigId(argCount < 1, args, 1);
+        } catch (NumberFormatException e) {
+            sLogger.severe("Invalid config id provided.");
+            printHelp();
+            return false;
+        }
+        sLogger.fine(String.format("cmdGetData with %d %b %b %b",
+                configId, clearData, binary, noUidMap));
+
+        // Get the StatsLog
+        // 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, true /* SHELL_UID */, sLogger);
+        } catch (IOException | InterruptedException e) {
+            sLogger.severe("Failed to get report list: " + e.getMessage());
+            return false;
+        }
+        if (noUidMap) {
+            ConfigMetricsReportList.Builder builder
+                    = ConfigMetricsReportList.newBuilder(reportList);
+            // Clear the reports, then add them back without their UidMap.
+            builder.clearReports();
+            for (ConfigMetricsReport report : reportList.getReportsList()) {
+                builder.addReports(ConfigMetricsReport.newBuilder(report).clearUidMap());
+            }
+            reportList = builder.build();
+        }
+
+        if (!binary) {
+            sLogger.info(reportList.toString());
+        } else {
+            try {
+                System.out.write(reportList.toByteArray());
+            } catch (IOException e) {
+                sLogger.severe("Failed to output binary statslog proto: "
+                        + e.getMessage());
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // clear [CONFIG_ID]
+    private static boolean cmdClear(String[] args) {
+        // Get CONFIG_ID
+        long configId;
+        try {
+            configId = getConfigId(false, args, 1);
+        } catch (NumberFormatException e) {
+            sLogger.severe("Invalid config id provided.");
+            printHelp();
+            return false;
+        }
+        sLogger.fine(String.format("cmdClear with %d", configId));
+
+        try {
+            Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
+        } catch (IOException | InterruptedException e) {
+            sLogger.severe("Failed to get report list: " + e.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    // remove [CONFIG_ID]
+    private static boolean cmdRemove(String[] args) {
+        // Get CONFIG_ID
+        long configId;
+        try {
+            configId = getConfigId(false, args, 1);
+        } catch (NumberFormatException e) {
+            sLogger.severe("Invalid config id provided.");
+            printHelp();
+            return false;
+        }
+        sLogger.fine(String.format("cmdRemove with %d", configId));
+
+        try {
+            Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
+                    Utils.SHELL_UID, String.valueOf(configId));
+        } catch (InterruptedException | IOException e) {
+            sLogger.severe("Failed to remove config: " + e.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Searches through the array to see if it contains (precisely) the given value, starting
+     * at the given firstIdx.
+     */
+    private static boolean contains(String[] array, int firstIdx, String value) {
+        if (value == null) return false;
+        if (firstIdx < 0) return false;
+        for (int i = firstIdx; i < array.length; i++) {
+            if (value.equals(array[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets the config id from args[idx], or returns DEFAULT_CONFIG_ID if args[idx] does not exist.
+     * If justUseDefault, overrides and just uses DEFAULT_CONFIG_ID instead.
+     */
+    private static long getConfigId(boolean justUseDefault, String[] args, int idx)
+            throws NumberFormatException {
+        if (justUseDefault || args.length <= idx || idx < 0) {
+            return DEFAULT_CONFIG_ID;
+        }
+        try {
+            return Long.valueOf(args[idx]);
+        } catch (NumberFormatException e) {
+            sLogger.severe("Bad config id provided: " + args[idx]);
+            throw e;
+        }
+    }
+}
diff --git a/cmds/statsd/tools/statsd-testdrive/src/com/android/statsd/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
similarity index 68%
rename from cmds/statsd/tools/statsd-testdrive/src/com/android/statsd/testdrive/TestDrive.java
rename to cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index cc4e386..e3fe928 100644
--- a/cmds/statsd/tools/statsd-testdrive/src/com/android/statsd/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.statsd.testdrive;
+package com.android.statsd.shelltools.testdrive;
 
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
@@ -21,20 +21,15 @@
 import com.android.os.AtomsProto.Atom;
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.statsd.shelltools.Utils;
 
 import com.google.common.io.Files;
 import com.google.protobuf.TextFormat;
 import com.google.protobuf.TextFormat.ParseException;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.logging.ConsoleHandler;
-import java.util.logging.Formatter;
 import java.util.logging.Level;
-import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 public class TestDrive {
@@ -42,10 +37,6 @@
     public static final int PULL_ATOM_START = 10000;
     public static final long ATOM_MATCHER_ID = 1234567;
 
-    public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
-    public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
-    public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
-    public static final String CONFIG_UID = "2000"; // shell uid
     public static final long CONFIG_ID = 54321;
 
     private static boolean mIsPushedAtom = false;
@@ -53,6 +44,9 @@
     private static final Logger logger = Logger.getLogger(TestDrive.class.getName());
 
     public static void main(String[] args) {
+        TestDrive testDrive = new TestDrive();
+        Utils.setUpLogger(logger, false);
+
         if (args.length != 1) {
             logger.log(Level.SEVERE, "Usage: ./test_drive <atomId>");
             return;
@@ -70,12 +64,6 @@
         }
         mIsPushedAtom = atomId < PULL_ATOM_START;
 
-        TestDrive testDrive = new TestDrive();
-        TestDriveFormatter formatter = new TestDriveFormatter();
-        ConsoleHandler handler = new ConsoleHandler();
-        handler.setFormatter(formatter);
-        logger.addHandler(handler);
-        logger.setUseParentHandlers(false);
 
         try {
             StatsdConfig config = testDrive.createConfig(atomId);
@@ -109,55 +97,21 @@
         configFile.deleteOnExit();
         Files.write(config.toByteArray(), configFile);
         String remotePath = "/data/local/tmp/" + configFile.getName();
-        runCommand(null, "adb", "push", configFile.getAbsolutePath(), remotePath);
-        runCommand(
-                null, "adb", "shell", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
+        Utils.runCommand(null, logger, "adb", "push", configFile.getAbsolutePath(), remotePath);
+        Utils.runCommand(null, logger,
+                "adb", "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG,
                 String.valueOf(CONFIG_ID));
     }
 
     private void removeConfig() {
         try {
-            runCommand(null, "adb", "shell", REMOVE_CONFIG_CMD, String.valueOf(CONFIG_ID));
+            Utils.runCommand(null, logger, 
+                    "adb", "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID));
         } catch (Exception e) {
             logger.log(Level.SEVERE, "Failed to remove config: " + e.getMessage());
         }
     }
 
-    // Runs a shell command. Output should go to outputFile. Returns error string.
-    private String runCommand(File outputFile, String... commands)
-            throws IOException, InterruptedException {
-        // Run macro on target
-        ProcessBuilder pb = new ProcessBuilder(commands);
-        // pb.redirectErrorStream(true);
-
-        if (outputFile != null && outputFile.exists() && outputFile.canWrite()) {
-            pb.redirectOutput(outputFile);
-        }
-        Process process = pb.start();
-
-        // capture any errors
-        StringBuilder out = new StringBuilder();
-        // Read output
-        BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
-        String line = null, previous = null;
-        while ((line = br.readLine()) != null) {
-            if (!line.equals(previous)) {
-                previous = line;
-                out.append(line).append('\n');
-                logger.fine(line);
-            }
-        }
-
-        // Check result
-        if (process.waitFor() == 0) {
-            logger.fine("Success!");
-        } else {
-            // Abnormal termination: Log command parameters and output and throw ExecutionException
-            logger.log(Level.SEVERE, out.toString());
-        }
-        return out.toString();
-    }
-
     private StatsdConfig createConfig(int atomId) {
         try {
             if (mIsPushedAtom) {
@@ -210,37 +164,8 @@
         return builder;
     }
 
-    private ConfigMetricsReportList getReportList() throws Exception {
-        try {
-            File outputFile = File.createTempFile("statsdret", ".bin");
-            outputFile.deleteOnExit();
-            runCommand(
-                    outputFile,
-                    "adb",
-                    "shell",
-                    DUMP_REPORT_CMD,
-                    String.valueOf(CONFIG_ID),
-                    "--include_current_bucket",
-                    "--proto");
-            ConfigMetricsReportList reportList =
-                    ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile));
-            return reportList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            logger.log(
-                    Level.SEVERE,
-                    "Failed to fetch and parse the statsd output report. "
-                            + "Perhaps there is not a valid statsd config for the requested "
-                            + "uid="
-                            + CONFIG_UID
-                            + ", id="
-                            + CONFIG_ID
-                            + ".");
-            throw (e);
-        }
-    }
-
     private void dumpMetrics() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
+        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.
@@ -294,9 +219,4 @@
                     + "\n"
                     + "hash_strings_in_metric_report: false";
 
-    public static class TestDriveFormatter extends Formatter {
-        public String format(LogRecord record) {
-            return record.getMessage() + "\n";
-        }
-    }
 }
diff --git a/cmds/statsd/tools/localtools/testdrive_manifest.txt b/cmds/statsd/tools/localtools/testdrive_manifest.txt
new file mode 100644
index 0000000..625ebfa
--- /dev/null
+++ b/cmds/statsd/tools/localtools/testdrive_manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.statsd.shelltools.testdrive.TestDrive
diff --git a/cmds/statsd/tools/statsd-testdrive/Android.bp b/cmds/statsd/tools/statsd-testdrive/Android.bp
deleted file mode 100644
index f566bc7..0000000
--- a/cmds/statsd/tools/statsd-testdrive/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-java_binary_host {
-    name: "statsd_testdrive",
-    manifest: "manifest.txt",
-    srcs: [
-        "src/**/*.java",
-    ],
-    static_libs: [
-        "platformprotos",
-        "guava",
-    ],
-}
diff --git a/cmds/statsd/tools/statsd-testdrive/manifest.txt b/cmds/statsd/tools/statsd-testdrive/manifest.txt
deleted file mode 100644
index 0266d11..0000000
--- a/cmds/statsd/tools/statsd-testdrive/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-class: com.android.statsd.testdrive.TestDrive
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index bacb991..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
@@ -2861,7 +2859,6 @@
 Lcom/android/internal/telephony/dataconnection/DataConnection;->mActiveState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcActiveState;
 Lcom/android/internal/telephony/dataconnection/DataConnection;->mConnectionParams:Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;
 Lcom/android/internal/telephony/dataconnection/DataConnection;->mDataRegState:I
-Lcom/android/internal/telephony/dataconnection/DataConnection;->mDcFailCause:Lcom/android/internal/telephony/dataconnection/DcFailCause;
 Lcom/android/internal/telephony/dataconnection/DataConnection;->mDct:Lcom/android/internal/telephony/dataconnection/DcTracker;
 Lcom/android/internal/telephony/dataconnection/DataConnection;->mDisconnectingErrorCreatingConnection:Lcom/android/internal/telephony/dataconnection/DataConnection$DcDisconnectionErrorCreatingConnection;
 Lcom/android/internal/telephony/dataconnection/DataConnection;->mDisconnectingState:Lcom/android/internal/telephony/dataconnection/DataConnection$DcDisconnectingState;
@@ -2872,10 +2869,8 @@
 Lcom/android/internal/telephony/dataconnection/DataConnection;->mNetworkInfo:Landroid/net/NetworkInfo;
 Lcom/android/internal/telephony/dataconnection/DataConnection;->mPhone:Lcom/android/internal/telephony/Phone;
 Lcom/android/internal/telephony/dataconnection/DataConnection;->mRilRat:I
-Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllDisconnectCompleted(Lcom/android/internal/telephony/dataconnection/DcFailCause;)V
 Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllOfConnected(Ljava/lang/String;)V
 Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyAllOfDisconnectDcRetrying(Ljava/lang/String;)V
-Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyConnectCompleted(Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;Lcom/android/internal/telephony/dataconnection/DcFailCause;Z)V
 Lcom/android/internal/telephony/dataconnection/DataConnection;->notifyDisconnectCompleted(Lcom/android/internal/telephony/dataconnection/DataConnection$DisconnectParams;Z)V
 Lcom/android/internal/telephony/dataconnection/DataConnection;->onConnect(Lcom/android/internal/telephony/dataconnection/DataConnection$ConnectionParams;)V
 Lcom/android/internal/telephony/dataconnection/DataConnection;->tearDownData(Ljava/lang/Object;)V
@@ -2884,22 +2879,6 @@
 Lcom/android/internal/telephony/dataconnection/DcController;->mDcListActiveByCid:Ljava/util/HashMap;
 Lcom/android/internal/telephony/dataconnection/DcController;->mDct:Lcom/android/internal/telephony/dataconnection/DcTracker;
 Lcom/android/internal/telephony/dataconnection/DcController;->mDcTesterDeactivateAll:Lcom/android/internal/telephony/dataconnection/DcTesterDeactivateAll;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ACTIVATION_REJECT_GGSN:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ACTIVATION_REJECT_UNSPECIFIED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->APN_TYPE_CONFLICT:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->INSUFFICIENT_RESOURCES:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->MISSING_UNKNOWN_APN:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->NSAPI_IN_USE:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ONLY_IPV4_ALLOWED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ONLY_IPV6_ALLOWED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->ONLY_SINGLE_BEARER_ALLOWED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->OPERATOR_BARRED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->PROTOCOL_ERRORS:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->SERVICE_OPTION_NOT_SUBSCRIBED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->SERVICE_OPTION_NOT_SUPPORTED:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->SERVICE_OPTION_OUT_OF_ORDER:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->UNKNOWN_PDP_ADDRESS_TYPE:Lcom/android/internal/telephony/dataconnection/DcFailCause;
-Lcom/android/internal/telephony/dataconnection/DcFailCause;->USER_AUTHENTICATION:Lcom/android/internal/telephony/dataconnection/DcFailCause;
 Lcom/android/internal/telephony/dataconnection/DcTracker$RecoveryAction;->isAggressiveRecovery(I)Z
 Lcom/android/internal/telephony/dataconnection/DcTracker;->cancelReconnectAlarm(Lcom/android/internal/telephony/dataconnection/ApnContext;)V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->cleanUpAllConnections(Ljava/lang/String;)V
@@ -2934,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
@@ -3763,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/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5e445d14..b584d5d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2545,7 +2545,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/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index af3da0c..b42d53a 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -196,8 +196,26 @@
 
     public abstract void updateOomAdj();
     public abstract void updateCpuStats();
-    public abstract void updateUsageStats(
+
+    /**
+     * Update battery stats on activity usage.
+     * @param activity
+     * @param uid
+     * @param userId
+     * @param started
+     */
+    public abstract void updateBatteryStats(
             ComponentName activity, int uid, int userId, boolean resumed);
+
+    /**
+     * Update UsageStats of the activity.
+     * @param activity
+     * @param userId
+     * @param event
+     * @param appToken ActivityRecord's appToken.
+     */
+    public abstract void updateActivityUsageStats(
+            ComponentName activity, int userId, int event, IBinder appToken);
     public abstract void updateForegroundTimeIfOnBattery(
             String packageName, int uid, long cpuTimeDiff);
     public abstract void sendForegroundProfileChanged(int userId);
@@ -288,6 +306,6 @@
     public abstract void setDebugFlagsForStartingActivity(ActivityInfo aInfo, int startFlags,
             ProfilerInfo profilerInfo, Object wmLock);
 
-    /** Checks if process running with given pid has access to full external storage or not */
-    public abstract boolean isAppStorageSandboxed(int pid, int uid);
+    /** Returns mount mode for process running with given pid */
+    public abstract int getStorageMountMode(int pid, int uid);
 }
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 6fdf7c8..4d8c856 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -303,9 +303,7 @@
      * @param stackId Id of stack to move the top activity to pinned stack.
      * @param bounds Bounds to use for pinned stack.
      * @return True if the top activity of stack was successfully moved to the pinned stack.
-     * @hide
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
         try {
@@ -318,9 +316,7 @@
     /**
      * Start to enter lock task mode for given task by system(UI).
      * @param taskId Id of task to lock.
-     * @hide
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     public void startSystemLockTaskMode(int taskId) {
         try {
@@ -332,9 +328,7 @@
 
     /**
      * Stop lock task mode by system(UI).
-     * @hide
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     public void stopSystemLockTaskMode() {
         try {
@@ -349,9 +343,7 @@
      * @param taskId Id of the task to move.
      * @param stackId Id of the stack for task moving.
      * @param toTop Whether the given task should shown to top of stack.
-     * @hide
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
         try {
@@ -366,9 +358,7 @@
      * @param stackId Id of the stack to resize.
      * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
      * @param animate Whether we should play an animation for resizing stack.
-     * @hide
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     public void resizeStack(int stackId, Rect bounds, boolean animate) {
         try {
@@ -383,9 +373,7 @@
      * Resize task to given bounds.
      * @param taskId Id of task to resize.
      * @param bounds Bounds to resize task.
-     * @hide
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     public void resizeTask(int taskId, Rect bounds) {
         try {
@@ -399,9 +387,7 @@
      * Resize docked stack & its task to given stack & task bounds.
      * @param stackBounds Bounds to resize stack.
      * @param taskBounds Bounds to resize task.
-     * @hide
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     public void resizeDockedStack(Rect stackBounds, Rect taskBounds) {
         try {
@@ -413,9 +399,7 @@
 
     /**
      * List all activity stacks information.
-     * @hide
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     public String listAllStacks() {
         final List<ActivityManager.StackInfo> stacks;
@@ -438,7 +422,6 @@
      * Clears launch params for the given package.
      * @param packageNames the names of the packages of which the launch params are to be cleared
      */
-    @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
     public void clearLaunchParamsForPackages(List<String> packageNames) {
         try {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 67d9ad6e..2b81c86 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -44,6 +44,7 @@
 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.PackageInstaller;
 import android.content.pm.PackageItemInfo;
@@ -791,6 +792,16 @@
         throw new NameNotFoundException("No shared userid for user:"+sharedUserName);
     }
 
+    @Override
+    public List<ModuleInfo> getInstalledModules(int flags) {
+        return null;
+    }
+
+    @Override
+    public ModuleInfo getModuleInfo(String packageName, int flags) throws NameNotFoundException {
+        return null;
+    }
+
     @SuppressWarnings("unchecked")
     @Override
     public List<PackageInfo> getInstalledPackages(int flags) {
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/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java
index 6747004..7cc3dab 100644
--- a/core/java/android/app/RecoverableSecurityException.java
+++ b/core/java/android/app/RecoverableSecurityException.java
@@ -16,15 +16,13 @@
 
 package android.app;
 
-import android.content.ContentProvider;
-import android.content.ContentResolver;
+import android.annotation.NonNull;
 import android.content.Context;
-import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
 
 /**
  * Specialization of {@link SecurityException} that contains additional
@@ -35,18 +33,11 @@
  * authentication credentials, or granting access.
  * <p>
  * If the receiving app is actively involved with the user, it should present
- * the contained recovery details to help the user make forward progress. The
- * {@link #showAsDialog(Activity)} and
- * {@link #showAsNotification(Context, String)} methods are provided as a
- * convenience, but receiving apps are encouraged to use
- * {@link #getUserMessage()} and {@link #getUserAction()} to integrate in a more
- * natural way if relevant.
+ * the contained recovery details to help the user make forward progress.
  * <p class="note">
  * Note: legacy code that receives this exception may treat it as a general
  * {@link SecurityException}, and thus there is no guarantee that the messages
  * contained will be shown to the end user.
- *
- * @hide
  */
 public final class RecoverableSecurityException extends SecurityException implements Parcelable {
     private static final String TAG = "RecoverableSecurityException";
@@ -78,53 +69,28 @@
      *            apps that observe {@link Activity#RESULT_OK} may choose to
      *            immediately retry their operation.
      */
-    public RecoverableSecurityException(Throwable cause, CharSequence userMessage,
-            RemoteAction userAction) {
+    public RecoverableSecurityException(@NonNull Throwable cause, @NonNull CharSequence userMessage,
+            @NonNull RemoteAction userAction) {
         super(cause.getMessage());
-        mUserMessage = Preconditions.checkNotNull(userMessage);
-        mUserAction = Preconditions.checkNotNull(userAction);
-    }
-
-    /** {@hide} */
-    @Deprecated
-    public RecoverableSecurityException(Throwable cause, CharSequence userMessage,
-            CharSequence userActionTitle, PendingIntent userAction) {
-        this(cause, userMessage,
-                new RemoteAction(
-                        Icon.createWithResource("android",
-                                com.android.internal.R.drawable.ic_restart),
-                        userActionTitle, userActionTitle, userAction));
+        mUserMessage = Objects.requireNonNull(userMessage);
+        mUserAction = Objects.requireNonNull(userAction);
     }
 
     /**
      * Return short message describing the issue for end user audiences, which
      * may be shown in a notification or dialog.
      */
-    public CharSequence getUserMessage() {
+    public @NonNull CharSequence getUserMessage() {
         return mUserMessage;
     }
 
     /**
      * Return primary action that will initiate the recovery.
      */
-    public RemoteAction getUserAction() {
+    public @NonNull RemoteAction getUserAction() {
         return mUserAction;
     }
 
-    /** @removed */
-    @Deprecated
-    public void showAsNotification(Context context) {
-        final NotificationManager nm = context.getSystemService(NotificationManager.class);
-
-        // Create a channel per-sender, since we don't want one poorly behaved
-        // remote app to cause all of our notifications to be blocked
-        final String channelId = TAG + "_" + mUserAction.getActionIntent().getCreatorUid();
-        nm.createNotificationChannel(new NotificationChannel(channelId, TAG,
-                NotificationManager.IMPORTANCE_DEFAULT));
-
-        showAsNotification(context, channelId);
-    }
-
     /**
      * Convenience method that will show a very simple notification populated
      * with the details from this exception.
@@ -142,6 +108,7 @@
      * @param channelId the {@link NotificationChannel} to use, which must have
      *            been already created using
      *            {@link NotificationManager#createNotificationChannel}.
+     * @hide
      */
     public void showAsNotification(Context context, String channelId) {
         final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -167,6 +134,8 @@
      * <p>
      * This method will only display the most recent exception from any single
      * remote UID; dialogs from older exceptions will always be replaced.
+     *
+     * @hide
      */
     public void showAsDialog(Activity activity) {
         final LocalDialog dialog = new LocalDialog();
diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java
index 85fe99d..392921e 100644
--- a/core/java/android/app/RemoteInput.java
+++ b/core/java/android/app/RemoteInput.java
@@ -93,6 +93,22 @@
     /** The user selected one of the choices from {@link #getChoices}. */
     public static final int SOURCE_CHOICE = 1;
 
+    /** @hide */
+    @IntDef(prefix = {"EDIT_CHOICES_BEFORE_SENDING_"},
+            value = {EDIT_CHOICES_BEFORE_SENDING_AUTO, EDIT_CHOICES_BEFORE_SENDING_DISABLED,
+                    EDIT_CHOICES_BEFORE_SENDING_ENABLED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EditChoicesBeforeSending {}
+
+    /** The platform will determine whether choices will be edited before being sent to the app. */
+    public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0;
+
+    /** Tapping on a choice should send the input immediately, without letting the user edit it. */
+    public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1;
+
+    /** Tapping on a choice should let the user edit the input before it is sent to the app. */
+    public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2;
+
     // Flags bitwise-ored to mFlags
     private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1;
 
@@ -103,17 +119,25 @@
     private final CharSequence mLabel;
     private final CharSequence[] mChoices;
     private final int mFlags;
+    @EditChoicesBeforeSending private final int mEditChoicesBeforeSending;
     private final Bundle mExtras;
     private final ArraySet<String> mAllowedDataTypes;
 
     private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
-            int flags, Bundle extras, ArraySet<String> allowedDataTypes) {
+            int flags, int editChoicesBeforeSending, Bundle extras,
+            ArraySet<String> allowedDataTypes) {
         this.mResultKey = resultKey;
         this.mLabel = label;
         this.mChoices = choices;
         this.mFlags = flags;
+        this.mEditChoicesBeforeSending = editChoicesBeforeSending;
         this.mExtras = extras;
         this.mAllowedDataTypes = allowedDataTypes;
+        if (getEditChoicesBeforeSending() == EDIT_CHOICES_BEFORE_SENDING_ENABLED
+                && !getAllowFreeFormInput()) {
+            throw new IllegalArgumentException(
+                    "setEditChoicesBeforeSending requires setAllowFreeFormInput");
+        }
     }
 
     /**
@@ -149,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() {
@@ -169,6 +193,15 @@
     }
 
     /**
+     * Gets whether tapping on a choice should let the user edit the input before it is sent to the
+     * app.
+     */
+    @EditChoicesBeforeSending
+    public int getEditChoicesBeforeSending() {
+        return mEditChoicesBeforeSending;
+    }
+
+    /**
      * Get additional metadata carried around with this remote input.
      */
     public Bundle getExtras() {
@@ -185,6 +218,8 @@
         private CharSequence mLabel;
         private CharSequence[] mChoices;
         private int mFlags = DEFAULT_FLAGS;
+        @EditChoicesBeforeSending
+        private int mEditChoicesBeforeSending = EDIT_CHOICES_BEFORE_SENDING_AUTO;
 
         /**
          * Create a builder object for {@link RemoteInput} objects.
@@ -269,7 +304,20 @@
          */
         @NonNull
         public Builder setAllowFreeFormInput(boolean allowFreeFormTextInput) {
-            setFlag(mFlags, allowFreeFormTextInput);
+            setFlag(FLAG_ALLOW_FREE_FORM_INPUT, allowFreeFormTextInput);
+            return this;
+        }
+
+        /**
+         * Specifies whether tapping on a choice should let the user edit the input before it is
+         * sent to the app. The default is {@link #EDIT_CHOICES_BEFORE_SENDING_AUTO}.
+         *
+         * It cannot be used if {@link #setAllowFreeFormInput} has been set to false.
+         */
+        @NonNull
+        public Builder setEditChoicesBeforeSending(
+                @EditChoicesBeforeSending int editChoicesBeforeSending) {
+            mEditChoicesBeforeSending = editChoicesBeforeSending;
             return this;
         }
 
@@ -312,8 +360,8 @@
          */
         @NonNull
         public RemoteInput build() {
-            return new RemoteInput(
-                    mResultKey, mLabel, mChoices, mFlags, mExtras, mAllowedDataTypes);
+            return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mEditChoicesBeforeSending,
+                    mExtras, mAllowedDataTypes);
         }
     }
 
@@ -322,6 +370,7 @@
         mLabel = in.readCharSequence();
         mChoices = in.readCharSequenceArray();
         mFlags = in.readInt();
+        mEditChoicesBeforeSending = in.readInt();
         mExtras = in.readBundle();
         mAllowedDataTypes = (ArraySet<String>) in.readArraySet(null);
     }
@@ -507,6 +556,7 @@
         out.writeCharSequence(mLabel);
         out.writeCharSequenceArray(mChoices);
         out.writeInt(mFlags);
+        out.writeInt(mEditChoicesBeforeSending);
         out.writeBundle(mExtras);
         out.writeArraySet(mAllowedDataTypes);
     }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 43e1836..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;
@@ -154,7 +155,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccManager;
-import android.telephony.rcs.RcsManager;
+import android.telephony.ims.RcsManager;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
@@ -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/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 257122d..2990b57 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -673,6 +673,15 @@
     }
 
     /**
+     * Returns true if the windowingMode represents a split window.
+     * @hide
+     */
+    public static boolean isSplitScreenWindowingMode(int windowingMode) {
+        return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+    }
+
+    /**
      * Returns true if the windows associated with this window configuration can receive input keys.
      * @hide
      */
diff --git a/core/java/android/app/admin/DelegatedAdminReceiver.java b/core/java/android/app/admin/DelegatedAdminReceiver.java
new file mode 100644
index 0000000..9605382
--- /dev/null
+++ b/core/java/android/app/admin/DelegatedAdminReceiver.java
@@ -0,0 +1,129 @@
+/*
+ * 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.app.admin;
+
+import static android.app.admin.DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS;
+import static android.app.admin.DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT;
+import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.security.KeyChain;
+import android.util.Log;
+
+/**
+ * Base class for delegated apps to handle callbacks related to their delegated capabilities.
+ *
+ * <p>Delegated apps are apps that receive additional capabilities from the profile owner or
+ * device owner apps. Some of these capabilities involve the framework calling into the apps.
+ * To receive these callbacks, delegated apps should subclass this class and override the
+ * appropriate methods here. The subclassed receiver needs to be published in the app's
+ * manifest, with appropriate intent filters to mark which callbacks the receiver is interested
+ * in. An app can have multiple receivers as long as they listen for disjoint set of callbacks.
+ * For the manifest definitions, it must be protected by the
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission to ensure only
+ * the system can trigger these callbacks.
+ *
+ * <p>The callback methods happen on the main thread of the process.  Thus long running
+ * operations must be done on another thread.  Note that because a receiver
+ * is done once returning from its onReceive function, such long-running operations
+ * should probably be done in a {@link Service}.
+ *
+ * @see DevicePolicyManager#setDelegatedScopes
+ * @see DeviceAdminReceiver
+ */
+public class DelegatedAdminReceiver extends BroadcastReceiver {
+    private static final String TAG = "DelegatedAdminReceiver";
+
+    /**
+     * Allows this receiver to select the alias for a private key and certificate pair for
+     * authentication.  If this method returns null, the default {@link android.app.Activity} will
+     * be shown that lets the user pick a private key and certificate pair.
+     *
+     * <p> This callback is only applicable if the delegated app has
+     * {@link DevicePolicyManager#DELEGATION_CERT_SELECTION} capability. Additionally, it must
+     * declare an intent fitler for {@link DeviceAdminReceiver#ACTION_CHOOSE_PRIVATE_KEY_ALIAS}
+     * in the receiver's manifest in order to receive this callback.
+     *
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @param uid The uid asking for the private key and certificate pair.
+     * @param uri The URI to authenticate, may be null.
+     * @param alias The alias preselected by the client, or null.
+     * @return The private key alias to return and grant access to.
+     * @see KeyChain#choosePrivateKeyAlias
+     */
+    public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri,
+            String alias) {
+        return null;
+    }
+
+    /**
+     * Called each time a new batch of network logs can be retrieved. This callback method will only
+     * ever be called when network logging is enabled. The logs can only be retrieved while network
+     * logging is enabled.
+     *
+     * <p>If a secondary user or profile is created, this callback won't be received until all users
+     * become affiliated again (even if network logging is enabled). It will also no longer be
+     * possible to retrieve the network logs batch with the most recent {@code batchToken} provided
+     * by this callback. See {@link DevicePolicyManager#setAffiliationIds}.
+     *
+     * <p> This callback is only applicable if the delegated app has
+     * {@link DevicePolicyManager#DELEGATION_NETWORK_LOGGING} capability. Additionally, it must
+     * declare an intent fitler for {@link DeviceAdminReceiver#ACTION_NETWORK_LOGS_AVAILABLE} in the
+     * receiver's manifest in order to receive this callback.
+     *
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @param batchToken The token representing the current batch of network logs.
+     * @param networkLogsCount The total count of events in the current batch of network logs.
+     * @see DevicePolicyManager#retrieveNetworkLogs
+     */
+    public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
+            int networkLogsCount) {
+    }
+
+    /**
+     * Intercept delegated device administrator broadcasts. Implementations should not override
+     * this method; implement the convenience callbacks for each action instead.
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+
+        if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) {
+            int uid = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, -1);
+            Uri uri = intent.getParcelableExtra(EXTRA_CHOOSE_PRIVATE_KEY_URI);
+            String alias = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_ALIAS);
+            String chosenAlias = onChoosePrivateKeyAlias(context, intent, uid, uri, alias);
+            setResultData(chosenAlias);
+        } else if (ACTION_NETWORK_LOGS_AVAILABLE.equals(action)) {
+            long batchToken = intent.getLongExtra(EXTRA_NETWORK_LOGS_TOKEN, -1);
+            int networkLogsCount = intent.getIntExtra(EXTRA_NETWORK_LOGS_COUNT, 0);
+            onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount);
+        } else {
+            Log.w(TAG, "Unhandled broadcast: " + action);
+        }
+    }
+}
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 1c9477d..5a7124e 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -23,7 +23,6 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -297,7 +296,7 @@
     /**
      * Broadcast action: notify that a new batch of network logs is ready to be collected.
      * @see DeviceAdminReceiver#onNetworkLogsAvailable
-     * @hide
+     * @see DelegatedAdminReceiver#onNetworkLogsAvailable
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @BroadcastBehavior(explicitOnly = true)
@@ -426,7 +425,11 @@
      */
     public static final int BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE = 1;
 
-    /** @hide */
+    /**
+     * Broadcast action: notify that some app is attempting to choose a KeyChain key.
+     * @see DeviceAdminReceiver#onChoosePrivateKeyAlias
+     * @see DelegatedAdminReceiver#onChoosePrivateKeyAlias
+     */
     public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS =
             "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS";
 
@@ -755,7 +758,6 @@
      * @deprecated Do not use
      */
     @Deprecated
-    @SystemApi
     public void onReadyForUserInitialization(Context context, Intent intent) {
     }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 81eac5a..03e5933 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,6 +16,7 @@
 
 package android.app.admin;
 
+import android.Manifest.permission;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
@@ -66,6 +67,7 @@
 import android.os.UserManager;
 import android.os.UserManager.UserOperationException;
 import android.os.UserManager.UserOperationResult;
+import android.provider.CalendarContract;
 import android.provider.ContactsContract.Directory;
 import android.provider.Settings;
 import android.security.AttestedKeyPair;
@@ -1381,6 +1383,73 @@
             = "android.app.action.SET_NEW_PASSWORD";
 
     /**
+     * Constant for {@link #getPasswordComplexity()}: no password.
+     *
+     * <p>Note that these complexity constants are ordered so that higher values are more complex.
+     */
+    public static final int PASSWORD_COMPLEXITY_NONE = 0;
+
+    /**
+     * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following:
+     * <ul>
+     * <li>pattern
+     * <li>PIN with repeating (4444) or ordered (1234, 4321, 2468) sequences
+     * </ul>
+     *
+     * <p>Note that these complexity constants are ordered so that higher values are more complex.
+     *
+     * @see #PASSWORD_QUALITY_SOMETHING
+     * @see #PASSWORD_QUALITY_NUMERIC
+     */
+    public static final int PASSWORD_COMPLEXITY_LOW = 0x10000;
+
+    /**
+     * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following:
+     * <ul>
+     * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at
+     * least 4
+     * <li>alphabetic, length at least 4
+     * <li>alphanumeric, length at least 4
+     * </ul>
+     *
+     * <p>Note that these complexity constants are ordered so that higher values are more complex.
+     *
+     * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX
+     * @see #PASSWORD_QUALITY_ALPHABETIC
+     * @see #PASSWORD_QUALITY_ALPHANUMERIC
+     */
+    public static final int PASSWORD_COMPLEXITY_MEDIUM = 0x30000;
+
+    /**
+     * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following:
+     * <ul>
+     * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at
+     * least 4
+     * <li>alphabetic, length at least 6
+     * <li>alphanumeric, length at least 6
+     * </ul>
+     *
+     * <p>Note that these complexity constants are ordered so that higher values are more complex.
+     *
+     * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX
+     * @see #PASSWORD_QUALITY_ALPHABETIC
+     * @see #PASSWORD_QUALITY_ALPHANUMERIC
+     */
+    public static final int PASSWORD_COMPLEXITY_HIGH = 0x50000;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"PASSWORD_COMPLEXITY_"}, value = {
+            PASSWORD_COMPLEXITY_NONE,
+            PASSWORD_COMPLEXITY_LOW,
+            PASSWORD_COMPLEXITY_MEDIUM,
+            PASSWORD_COMPLEXITY_HIGH,
+    })
+    public @interface PasswordComplexity {}
+
+    /**
      * Activity action: have the user enter a new password for the parent profile.
      * If the intent is launched from within a managed profile, this will trigger
      * entering a new password for the parent of the profile. In all other cases
@@ -1546,12 +1615,46 @@
 
     /**
      * Delegation of management of uninstalled packages. This scope grants access to the
-     * {@code #setKeepUninstalledPackages} and {@code #getKeepUninstalledPackages} APIs.
+     * {@link #setKeepUninstalledPackages} and {@link #getKeepUninstalledPackages} APIs.
      */
     public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES =
             "delegation-keep-uninstalled-packages";
 
     /**
+     * Grants access to {@link #setNetworkLoggingEnabled}, {@link #isNetworkLoggingEnabled} and
+     * {@link #retrieveNetworkLogs}. Once granted the delegated app will start receiving
+     * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner will no longer
+     * receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
+     * There can be at most one app that has this delegation.
+     * If another app already had delegated network logging access,
+     * it will lose the delegation when a new app is delegated.
+     *
+     * <p> Can only be granted by Device Owner.
+     */
+    public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+
+    /**
+     * Grants access to selection of KeyChain certificates on behalf of requesting apps.
+     * Once granted the app will start receiving
+     * DelegatedAdminReceiver.onChoosePrivateKeyAlias. The caller (PO/DO) will
+     * no longer receive {@link DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+     * There can be at most one app that has this delegation.
+     * If another app already had delegated certificate selection access,
+     * it will lose the delegation when a new app is delegated.
+     *
+     * <p> Can be granted by Device Owner or Profile Owner.
+     */
+    public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection";
+
+
+    /**
+     * Delegation of silent APK installation via {@link android.content.pm.PackageInstaller} APIs.
+     *
+     * <p> Can only be delegated by Device Owner.
+     */
+    public static final String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation";
+
+    /**
      * No management for current user in-effect. This is the default.
      * @hide
      */
@@ -3071,6 +3174,33 @@
     }
 
     /**
+     * Returns how complex the current user's screen lock is.
+     *
+     * <p>Note that when called from a profile which uses an unified challenge with its parent, the
+     * screen lock complexity of the parent will be returned. However, this API does not support
+     * explicitly querying the parent profile screen lock complexity via {@link
+     * #getParentProfileInstance}.
+     *
+     * @throws IllegalStateException if the user is not unlocked.
+     * @throws SecurityException if the calling application does not have the permission
+     *                           {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}
+     */
+    @PasswordComplexity
+    @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY)
+    public int getPasswordComplexity() {
+        throwIfParentInstance("getPasswordComplexity");
+        if (mService == null) {
+            return PASSWORD_COMPLEXITY_NONE;
+        }
+
+        try {
+            return mService.getPasswordComplexity();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * When called by a profile owner of a managed profile returns true if the profile uses unified
      * challenge with its parent user.
      *
@@ -9238,7 +9368,8 @@
     }
 
     /**
-     * Called by a device owner to control the network logging feature.
+     * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to
+     * control the network logging feature.
      *
      * <p> Network logs contain DNS lookup and connect() library call events. The following library
      *     functions are recorded while network logging is active:
@@ -9275,16 +9406,17 @@
      * all users to become affiliated. Therefore it's recommended that affiliation ids are set for
      * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *        {@code null} if called by a delegated app.
      * @param enabled whether network logging should be enabled or not.
      * @throws SecurityException if {@code admin} is not a device owner.
      * @see #setAffiliationIds
      * @see #retrieveNetworkLogs
      */
-    public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) {
+    public void setNetworkLoggingEnabled(@Nullable ComponentName admin, boolean enabled) {
         throwIfParentInstance("setNetworkLoggingEnabled");
         try {
-            mService.setNetworkLoggingEnabled(admin, enabled);
+            mService.setNetworkLoggingEnabled(admin, mContext.getPackageName(), enabled);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -9294,7 +9426,8 @@
      * Return whether network logging is enabled by a device owner.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only
-     * be {@code null} if the caller has MANAGE_USERS permission.
+     * be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING}
+     * or has MANAGE_USERS permission.
      * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not a device owner and caller has
      * no MANAGE_USERS permission
@@ -9302,14 +9435,15 @@
     public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) {
         throwIfParentInstance("isNetworkLoggingEnabled");
         try {
-            return mService.isNetworkLoggingEnabled(admin);
+            return mService.isNetworkLoggingEnabled(admin, mContext.getPackageName());
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Called by device owner to retrieve the most recent batch of network logging events.
+     * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve
+     * the most recent batch of network logging events.
      * A device owner has to provide a batchToken provided as part of
      * {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the
      * token of the most recent available batch of logs, {@code null} will be returned.
@@ -9328,7 +9462,8 @@
      * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See
      * {@link DevicePolicyManager#setAffiliationIds}.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *        {@code null} if called by a delegated app.
      * @param batchToken A token of the batch to retrieve
      * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
      *        {@code null} if the batch represented by batchToken is no longer available or if
@@ -9338,11 +9473,11 @@
      * @see #setAffiliationIds
      * @see DeviceAdminReceiver#onNetworkLogsAvailable
      */
-    public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin,
+    public @Nullable List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin,
             long batchToken) {
         throwIfParentInstance("retrieveNetworkLogs");
         try {
-            return mService.retrieveNetworkLogs(admin, batchToken);
+            return mService.retrieveNetworkLogs(admin, mContext.getPackageName(), batchToken);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -10308,4 +10443,33 @@
         }
         return false;
     }
+
+    /**
+     * Starts an activity to view calendar events in the managed profile.
+     *
+     * @param eventId the id of the event to be viewed.
+     * @param start the start time of the event.
+     * @param end the end time of the event.
+     * @param allDay if the event is an all-day event.
+     * @param flags flags to be set for the intent
+     * @return {@code true} if the activity is started successfully. {@code false} otherwise.
+     *
+     * @see CalendarContract#startViewCalendarEventInManagedProfile(Context, String, long, long,
+     * long, boolean, int)
+     *
+     * @hide
+     */
+    public boolean startViewCalendarEventInManagedProfile(long eventId, long start, long end,
+            boolean allDay, int flags) {
+        throwIfParentInstance("startViewCalendarEventInManagedProfile");
+        if (mService != null) {
+            try {
+                return mService.startViewCalendarEventInManagedProfile(mContext.getPackageName(),
+                        eventId, start, end, allDay, flags);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index de92978..8765760 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -118,6 +118,12 @@
     public abstract boolean isUserAffiliatedWithDevice(int userId);
 
     /**
+     * Returns whether the calling package can install or uninstall packages without user
+     * interaction.
+     */
+    public abstract boolean canSilentlyInstallPackage(String callerPackage, int callerUid);
+
+    /**
      * Reports that a profile has changed to use a unified or separate credential.
      *
      * @param userId User ID of the profile.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1ff4146..568becf 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -82,6 +82,7 @@
 
     boolean isActivePasswordSufficient(int userHandle, boolean parent);
     boolean isProfileActivePasswordSufficientForParent(int userHandle);
+    int getPasswordComplexity();
     boolean isUsingUnifiedPassword(in ComponentName admin);
     int getCurrentFailedPasswordAttempts(int userHandle, boolean parent);
     int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent);
@@ -366,9 +367,9 @@
     void setBackupServiceEnabled(in ComponentName admin, boolean enabled);
     boolean isBackupServiceEnabled(in ComponentName admin);
 
-    void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled);
-    boolean isNetworkLoggingEnabled(in ComponentName admin);
-    List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, long batchToken);
+    void setNetworkLoggingEnabled(in ComponentName admin, in String packageName, boolean enabled);
+    boolean isNetworkLoggingEnabled(in ComponentName admin, in String packageName);
+    List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, in String packageName, long batchToken);
 
     boolean bindDeviceAdminServiceAsUser(in ComponentName admin,
         IApplicationThread caller, IBinder token, in Intent service,
@@ -431,4 +432,6 @@
 
     boolean isManagedKiosk();
     boolean isUnattendedManagedKiosk();
+
+    boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, long start, long end, boolean allDay, int flags);
 }
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 5fee853..8b41755 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -16,8 +16,14 @@
 
 package android.app.admin;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -35,6 +41,8 @@
     // consider it a complex PIN/password.
     public static final int MAX_ALLOWED_SEQUENCE = 3;
 
+    // TODO(b/120536847): refactor isActivePasswordSufficient logic so that the actual password
+    // quality is not overwritten
     public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
     public int length = 0;
     public int letters = 0;
@@ -46,6 +54,10 @@
 
     public PasswordMetrics() {}
 
+    public PasswordMetrics(int quality) {
+        this.quality = quality;
+    }
+
     public PasswordMetrics(int quality, int length) {
         this.quality = quality;
         this.length = length;
@@ -173,6 +185,15 @@
                 && this.nonLetter == o.nonLetter;
     }
 
+    private boolean satisfiesBucket(PasswordMetrics... bucket) {
+        for (PasswordMetrics metrics : bucket) {
+            if (this.quality == metrics.quality) {
+                return this.length >= metrics.length;
+            }
+        }
+        return false;
+    }
+
     /*
      * Returns the maximum length of a sequential characters. A sequence is defined as
      * monotonically increasing characters with a constant interval or the same character repeated.
@@ -254,4 +275,99 @@
                 return 0;
         }
     }
+
+    /** Determines the {@link PasswordComplexity} of this {@link PasswordMetrics}. */
+    @PasswordComplexity
+    public int determineComplexity() {
+        for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
+            if (satisfiesBucket(bucket.getMetrics())) {
+                return bucket.mComplexityLevel;
+            }
+        }
+        return PASSWORD_COMPLEXITY_NONE;
+    }
+
+    /**
+     * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
+     */
+    public static class PasswordComplexityBucket {
+        /**
+         * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
+         * {@link PasswordMetrics}.
+         */
+        private static final PasswordComplexityBucket HIGH =
+                new PasswordComplexityBucket(
+                        PASSWORD_COMPLEXITY_HIGH,
+                        new PasswordMetrics(
+                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6),
+                        new PasswordMetrics(
+                                DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
+                        new PasswordMetrics(
+                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+                                8));
+
+        /**
+         * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
+         * {@link PasswordMetrics}.
+         */
+        private static final PasswordComplexityBucket MEDIUM =
+                new PasswordComplexityBucket(
+                        PASSWORD_COMPLEXITY_MEDIUM,
+                        new PasswordMetrics(
+                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4),
+                        new PasswordMetrics(
+                                DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
+                        new PasswordMetrics(
+                                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
+                                4));
+
+        /**
+         * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_LOW} in terms of
+         * {@link PasswordMetrics}.
+         */
+        private static final PasswordComplexityBucket LOW =
+                new PasswordComplexityBucket(
+                        PASSWORD_COMPLEXITY_LOW,
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
+                        new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING));
+
+        /**
+         * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
+         */
+        private static final PasswordComplexityBucket NONE =
+                new PasswordComplexityBucket(PASSWORD_COMPLEXITY_NONE, new PasswordMetrics());
+
+        /** Array containing all buckets from high to low. */
+        private static final PasswordComplexityBucket[] BUCKETS =
+                new PasswordComplexityBucket[] {HIGH, MEDIUM, LOW};
+
+        @PasswordComplexity
+        private final int mComplexityLevel;
+        private final PasswordMetrics[] mMetrics;
+
+        private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
+                PasswordMetrics... metrics) {
+            this.mComplexityLevel = complexityLevel;
+            this.mMetrics = metrics;
+        }
+
+        /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */
+        public PasswordMetrics[] getMetrics() {
+            return mMetrics;
+        }
+
+        /** Returns the bucket that {@code complexityLevel} represents. */
+        public static PasswordComplexityBucket complexityLevelToBucket(
+                @PasswordComplexity int complexityLevel) {
+            for (PasswordComplexityBucket bucket : BUCKETS) {
+                if (bucket.mComplexityLevel == complexityLevel) {
+                    return bucket;
+                }
+            }
+            return NONE;
+        }
+    }
 }
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index bcd5f6c..dd72845 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -689,13 +689,11 @@
                 mFreezePeriods.stream().map(n -> n.toString()).collect(Collectors.joining(",")));
     }
 
-    @SystemApi
     @Override
     public int describeContents() {
         return 0;
     }
 
-    @SystemApi
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mPolicyType);
@@ -712,7 +710,6 @@
         }
     }
 
-    @SystemApi
     public static final Parcelable.Creator<SystemUpdatePolicy> CREATOR =
             new Parcelable.Creator<SystemUpdatePolicy>() {
 
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/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/app/usage/EventList.java b/core/java/android/app/usage/EventList.java
index aaae57e5..a79ad2f 100644
--- a/core/java/android/app/usage/EventList.java
+++ b/core/java/android/app/usage/EventList.java
@@ -103,4 +103,21 @@
         }
         return result;
     }
+
+    /**
+     * Remove events of certain type on or after a timestamp.
+     * @param type The type of event to remove.
+     * @param timeStamp the timeStamp on or after which to remove the event.
+     */
+    public void removeOnOrAfter(int type, long timeStamp) {
+        for (int i = mEvents.size() - 1; i >= 0; i--) {
+            UsageEvents.Event event = mEvents.get(i);
+            if (event.mTimeStamp < timeStamp) {
+                break;
+            }
+            if (event.mEventType == type) {
+                mEvents.remove(i);
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 3a5975a..a06213d 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -50,13 +50,27 @@
         public static final int NONE = 0;
 
         /**
+         * @deprecated by {@link #ACTIVITY_RESUMED}
+         */
+        @Deprecated
+        public static final int MOVE_TO_FOREGROUND = 1;
+
+        /**
          * An event type denoting that an {@link android.app.Activity} moved to the foreground.
          * This event has a package name and class name associated with it and can be retrieved
          * using {@link #getPackageName()} and {@link #getClassName()}.
          * If a package has multiple activities, this event is reported for each activity that moves
          * to foreground.
+         * This event is corresponding to {@link android.app.Activity#onResume()} of the
+         * activity's lifecycle.
          */
-        public static final int MOVE_TO_FOREGROUND = 1;
+        public static final int ACTIVITY_RESUMED = MOVE_TO_FOREGROUND;
+
+        /**
+         * @deprecated by {@link #ACTIVITY_PAUSED}
+         */
+        @Deprecated
+        public static final int MOVE_TO_BACKGROUND = 2;
 
         /**
          * An event type denoting that an {@link android.app.Activity} moved to the background.
@@ -64,19 +78,21 @@
          * using {@link #getPackageName()} and {@link #getClassName()}.
          * If a package has multiple activities, this event is reported for each activity that moves
          * to background.
+         * This event is corresponding to {@link android.app.Activity#onPause()} of the activity's
+         * lifecycle.
          */
-        public static final int MOVE_TO_BACKGROUND = 2;
+        public static final int ACTIVITY_PAUSED = MOVE_TO_BACKGROUND;
 
         /**
          * An event type denoting that a component was in the foreground when the stats
-         * rolled-over. This is effectively treated as a {@link #MOVE_TO_BACKGROUND}.
+         * rolled-over. This is effectively treated as a {@link #ACTIVITY_PAUSED}.
          * {@hide}
          */
         public static final int END_OF_DAY = 3;
 
         /**
          * An event type denoting that a component was in the foreground the previous day.
-         * This is effectively treated as a {@link #MOVE_TO_FOREGROUND}.
+         * This is effectively treated as a {@link #ACTIVITY_RESUMED}.
          * {@hide}
          */
         public static final int CONTINUE_PREVIOUS_DAY = 4;
@@ -207,10 +223,31 @@
         public static final int ROLLOVER_FOREGROUND_SERVICE = 22;
 
         /**
+         * An activity becomes invisible on the UI, corresponding to
+         * {@link android.app.Activity#onStop()} of the activity's lifecycle.
+         */
+        public static final int ACTIVITY_STOPPED = 23;
+
+        /**
+         * An activity object is destroyed, corresponding to
+         * {@link android.app.Activity#onDestroy()} of the activity's lifecycle.
+         * {@hide}
+         */
+        public static final int ACTIVITY_DESTROYED = 24;
+
+        /**
+         * The event type demoting that a flush of UsageStatsDatabase to file system. Before the
+         * flush all usage stats need to be updated to latest timestamp to make sure the most
+         * up to date stats are persisted.
+         * @hide
+         */
+        public static final int FLUSH_TO_DISK = 25;
+
+        /**
          * Keep in sync with the greatest event type value.
          * @hide
          */
-        public static final int MAX_EVENT_TYPE = 22;
+        public static final int MAX_EVENT_TYPE = 25;
 
         /** @hide */
         public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
@@ -240,6 +277,12 @@
         @UnsupportedAppUsage
         public String mClass;
 
+
+        /**
+         * {@hide}
+         */
+        public int mInstanceId;
+
         /**
          * {@hide}
          */
@@ -311,9 +354,16 @@
         }
 
         /** @hide */
+        public Event(int type,  long timeStamp) {
+            mEventType = type;
+            mTimeStamp = timeStamp;
+        }
+
+        /** @hide */
         public Event(Event orig) {
             mPackage = orig.mPackage;
             mClass = orig.mClass;
+            mInstanceId = orig.mInstanceId;
             mTimeStamp = orig.mTimeStamp;
             mEventType = orig.mEventType;
             mConfiguration = orig.mConfiguration;
@@ -342,6 +392,16 @@
         }
 
         /**
+         *  An activity can be instantiated multiple times, this is the unique activity instance ID.
+         *  For non-activity class, instance ID is always zero.
+         *  @hide
+         */
+        @SystemApi
+        public int getInstanceId() {
+            return mInstanceId;
+        }
+
+        /**
          * The time at which this event occurred, measured in milliseconds since the epoch.
          * <p/>
          * See {@link System#currentTimeMillis()}.
@@ -352,12 +412,14 @@
 
         /**
          * The event type.
-         *
-         * @see #MOVE_TO_BACKGROUND
-         * @see #MOVE_TO_FOREGROUND
+         * @see #ACTIVITY_PAUSED
+         * @see #ACTIVITY_RESUMED
          * @see #CONFIGURATION_CHANGE
          * @see #USER_INTERACTION
          * @see #STANDBY_BUCKET_CHANGED
+         * @see #FOREGROUND_SERVICE_START
+         * @see #FOREGROUND_SERVICE_STOP
+         * @see #ACTIVITY_STOPPED
          */
         public int getEventType() {
             return mEventType;
@@ -576,6 +638,7 @@
         }
         p.writeInt(packageIndex);
         p.writeInt(classIndex);
+        p.writeInt(event.mInstanceId);
         p.writeInt(event.mEventType);
         p.writeLong(event.mTimeStamp);
 
@@ -618,6 +681,7 @@
         } else {
             eventOut.mClass = null;
         }
+        eventOut.mInstanceId = p.readInt();
         eventOut.mEventType = p.readInt();
         eventOut.mTimeStamp = p.readLong();
 
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 73426e4..8fb7f4c 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -16,13 +16,15 @@
 
 package android.app.usage;
 
-import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
 import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
 import static android.app.usage.UsageEvents.Event.END_OF_DAY;
+import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START;
 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP;
-import static android.app.usage.UsageEvents.Event.MOVE_TO_BACKGROUND;
-import static android.app.usage.UsageEvents.Event.MOVE_TO_FOREGROUND;
 import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
 
 import android.annotation.SystemApi;
@@ -31,6 +33,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
+import android.util.SparseIntArray;
 
 /**
  * Contains usage statistics for an app package for a specific
@@ -57,13 +60,20 @@
     public long mEndTimeStamp;
 
     /**
-     * Last time used by the user with an explicit action (notification, activity launch)
+     * Last time an activity is at foreground (have focus), this is corresponding to
+     * {@link android.app.usage.UsageEvents.Event#ACTIVITY_RESUMED} event.
      * {@hide}
      */
     @UnsupportedAppUsage
     public long mLastTimeUsed;
 
     /**
+     * Last time an activity is visible.
+     * @hide
+     */
+    public long mLastTimeVisible;
+
+    /**
      * Total time this package's activity is in foreground.
      * {@hide}
      */
@@ -71,6 +81,12 @@
     public long mTotalTimeInForeground;
 
     /**
+     * Total time this package's activity is visible.
+     * {@hide}
+     */
+    public long mTotalTimeVisible;
+
+    /**
      * Last time foreground service is started.
      * {@hide}
      */
@@ -93,31 +109,32 @@
      */
     public int mAppLaunchCount;
 
-    /** Last activity MOVE_TO_FOREGROUND or MOVE_TO_BACKGROUND event.
+    /** Last activity ACTIVITY_RESUMED or ACTIVITY_PAUSED event.
      * {@hide}
-     * @deprecated use {@link #mLastForegroundActivityEventMap} instead.
+     * @deprecated use {@link #mActivities} instead.
      */
     @UnsupportedAppUsage
     @Deprecated
     public int mLastEvent;
 
     /**
-     * If an activity is in foreground, it has one entry in this map.
-     * When activity moves to background, it is removed from this map.
-     * Key is activity class name.
-     * Value is last time this activity MOVE_TO_FOREGROUND or MOVE_TO_BACKGROUND event.
+     * If an activity is visible(onStart(), onPause() states) or in foreground (onResume() state),
+     * it has one entry in this map. When an activity becomes invisible (onStop() or onDestroy()),
+     * it is removed from this map.
+     * Key is instanceId of the activity (ActivityRecode appToken hashCode)..
+     * Value is this activity's last event, one of ACTIVITY_RESUMED or
+     * ACTIVITY_PAUSED.
      * {@hide}
      */
-    public ArrayMap<String, Integer> mLastForegroundActivityEventMap = new ArrayMap<>();
-
+    public SparseIntArray mActivities = new SparseIntArray();
     /**
      * If a foreground service is started, it has one entry in this map.
-     * When a foreground service is stopped, it is removed from this map.
+     * When a foreground service is stopped, it is removed from this set.
      * Key is foreground service class name.
-     * Value is last foreground service FOREGROUND_SERVICE_START ot FOREGROUND_SERVICE_STOP event.
+     * Value is the foreground service's last event, it is FOREGROUND_SERVICE_START.
      * {@hide}
      */
-    public ArrayMap<String, Integer> mLastForegroundServiceEventMap = new ArrayMap<>();
+    public ArrayMap<String, Integer> mForegroundServices = new ArrayMap<>();
 
     /**
      * {@hide}
@@ -135,14 +152,16 @@
         mBeginTimeStamp = stats.mBeginTimeStamp;
         mEndTimeStamp = stats.mEndTimeStamp;
         mLastTimeUsed = stats.mLastTimeUsed;
+        mLastTimeVisible = stats.mLastTimeVisible;
         mLastTimeForegroundServiceUsed = stats.mLastTimeForegroundServiceUsed;
         mTotalTimeInForeground = stats.mTotalTimeInForeground;
+        mTotalTimeVisible = stats.mTotalTimeVisible;
         mTotalTimeForegroundServiceUsed = stats.mTotalTimeForegroundServiceUsed;
         mLaunchCount = stats.mLaunchCount;
         mAppLaunchCount = stats.mAppLaunchCount;
         mLastEvent = stats.mLastEvent;
-        mLastForegroundActivityEventMap = stats.mLastForegroundActivityEventMap;
-        mLastForegroundServiceEventMap = stats.mLastForegroundServiceEventMap;
+        mActivities = stats.mActivities;
+        mForegroundServices = stats.mForegroundServices;
         mChooserCounts = stats.mChooserCounts;
     }
 
@@ -191,6 +210,14 @@
     }
 
     /**
+     * Get the last time this package's activity is visible in the UI, measured in milliseconds
+     * since the epoch.
+     */
+    public long getLastTimeVisible() {
+        return mLastTimeVisible;
+    }
+
+    /**
      * Get the total time this package spent in the foreground, measured in milliseconds.
      */
     public long getTotalTimeInForeground() {
@@ -198,6 +225,13 @@
     }
 
     /**
+     * Get the total time this package's activity is visible in the UI, measured in milliseconds.
+     */
+    public long getTotalTimeVisible() {
+        return mTotalTimeVisible;
+    }
+
+    /**
      * Get the last time this package's foreground service was used, measured in milliseconds since
      * the epoch.
      * <p/>
@@ -224,6 +258,20 @@
         return mAppLaunchCount;
     }
 
+    private void mergeEventMap(SparseIntArray left, SparseIntArray right) {
+        final int size = right.size();
+        for (int i = 0; i < size; i++) {
+            final int instanceId = right.keyAt(i);
+            final int event = right.valueAt(i);
+            final int index = left.indexOfKey(instanceId);
+            if (index >= 0) {
+                left.put(instanceId, Math.max(left.valueAt(index), event));
+            } else {
+                left.put(instanceId, event);
+            }
+        }
+    }
+
     private void mergeEventMap(ArrayMap<String, Integer> left, ArrayMap<String, Integer> right) {
         final int size = right.size();
         for (int i = 0; i < size; i++) {
@@ -255,15 +303,17 @@
         if (right.mBeginTimeStamp > mBeginTimeStamp) {
             // Even though incoming UsageStat begins after this one, its last time used fields
             // may somehow be empty or chronologically preceding the older UsageStat.
-            mergeEventMap(mLastForegroundActivityEventMap, right.mLastForegroundActivityEventMap);
-            mergeEventMap(mLastForegroundServiceEventMap, right.mLastForegroundServiceEventMap);
+            mergeEventMap(mActivities, right.mActivities);
+            mergeEventMap(mForegroundServices, right.mForegroundServices);
             mLastTimeUsed = Math.max(mLastTimeUsed, right.mLastTimeUsed);
+            mLastTimeVisible = Math.max(mLastTimeVisible, right.mLastTimeVisible);
             mLastTimeForegroundServiceUsed = Math.max(mLastTimeForegroundServiceUsed,
                     right.mLastTimeForegroundServiceUsed);
         }
         mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp);
         mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
         mTotalTimeInForeground += right.mTotalTimeInForeground;
+        mTotalTimeVisible += right.mTotalTimeVisible;
         mTotalTimeForegroundServiceUsed += right.mTotalTimeForegroundServiceUsed;
         mLaunchCount += right.mLaunchCount;
         mAppLaunchCount += right.mAppLaunchCount;
@@ -290,36 +340,76 @@
     }
 
     /**
-     * Tell if an event indicate activity is in foreground or not.
-     * @param event the activity event.
-     * @return true if activity is in foreground, false otherwise.
-     * @hide
+     * Tell if any activity is in foreground.
+     * @return
      */
-    private boolean isActivityInForeground(int event) {
-        return event == MOVE_TO_FOREGROUND
-                || event == CONTINUE_PREVIOUS_DAY;
+    private boolean hasForegroundActivity() {
+        final int size = mActivities.size();
+        for (int i = 0; i < size; i++) {
+            if (mActivities.valueAt(i) == ACTIVITY_RESUMED) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
-     * Tell if an event indicate foreground sevice is started or not.
-     * @param event the foreground service event.
-     * @return true if foreground service is started, false if stopped.
-     * @hide
+     * Tell if any activity is visible.
+     * @return
      */
-    private boolean isForegroundServiceStarted(int event) {
-        return event == FOREGROUND_SERVICE_START
-                || event == CONTINUING_FOREGROUND_SERVICE;
+    private boolean hasVisibleActivity() {
+        final int size = mActivities.size();
+        for (int i = 0; i < size; i++) {
+            final int type = mActivities.valueAt(i);
+            if (type == ACTIVITY_RESUMED
+                    || type == ACTIVITY_PAUSED) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
-     * If any activity in foreground or any foreground service is started, the app is considered in
-     * use.
-     * @return true if in use, false otherwise.
-     * @hide
+     * Tell if any foreground service is started.
+     * @return
      */
-    private boolean isAppInUse() {
-        return !mLastForegroundActivityEventMap.isEmpty()
-                || !mLastForegroundServiceEventMap.isEmpty();
+    private boolean anyForegroundServiceStarted() {
+        return !mForegroundServices.isEmpty();
+    }
+
+    /**
+     * Increment total time in foreground and update last time in foreground.
+     * @param timeStamp current timestamp.
+     */
+    private void incrementTimeUsed(long timeStamp) {
+        if (timeStamp > mLastTimeUsed) {
+            mTotalTimeInForeground += timeStamp - mLastTimeUsed;
+            mLastTimeUsed = timeStamp;
+        }
+    }
+
+    /**
+     * Increment total time visible and update last time visible.
+     * @param timeStamp current timestmap.
+     */
+    private void incrementTimeVisible(long timeStamp) {
+        if (timeStamp > mLastTimeVisible) {
+            mTotalTimeVisible += timeStamp - mLastTimeVisible;
+            mLastTimeVisible = timeStamp;
+        }
+    }
+
+    /**
+     * Increment total time foreground service is used and update last time foreground service is
+     * used.
+     * @param timeStamp current timestamp.
+     */
+    private void incrementServiceTimeUsed(long timeStamp) {
+        if (timeStamp > mLastTimeForegroundServiceUsed) {
+            mTotalTimeForegroundServiceUsed +=
+                    timeStamp - mLastTimeForegroundServiceUsed;
+            mLastTimeForegroundServiceUsed = timeStamp;
+        }
     }
 
     /**
@@ -327,33 +417,63 @@
      * @param className className of the activity.
      * @param timeStamp timeStamp of the event.
      * @param eventType type of the event.
+     * @param instanceId hashCode of the ActivityRecord's appToken.
      * @hide
      */
-    private void updateForegroundActivity(String className, long timeStamp, int eventType) {
-        if (eventType != MOVE_TO_BACKGROUND
-                && eventType != MOVE_TO_FOREGROUND
-                && eventType != END_OF_DAY) {
+    private void updateActivity(String className, long timeStamp, int eventType, int instanceId) {
+        if (eventType != ACTIVITY_RESUMED
+                && eventType != ACTIVITY_PAUSED
+                && eventType != ACTIVITY_STOPPED
+                && eventType != ACTIVITY_DESTROYED) {
             return;
         }
 
-        final Integer lastEvent = mLastForegroundActivityEventMap.get(className);
-        if (lastEvent != null) {
-            if (isActivityInForeground(lastEvent)) {
-                if (timeStamp > mLastTimeUsed) {
-                    mTotalTimeInForeground += timeStamp - mLastTimeUsed;
+        // update usage.
+        final int index = mActivities.indexOfKey(instanceId);
+        if (index >= 0) {
+            final int lastEvent = mActivities.valueAt(index);
+            switch (lastEvent) {
+                case ACTIVITY_RESUMED:
+                    incrementTimeUsed(timeStamp);
+                    incrementTimeVisible(timeStamp);
+                    break;
+                case ACTIVITY_PAUSED:
+                    incrementTimeVisible(timeStamp);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        // update current event.
+        switch(eventType) {
+            case ACTIVITY_RESUMED:
+                if (!hasVisibleActivity()) {
+                    // this is the first visible activity.
+                    mLastTimeUsed = timeStamp;
+                    mLastTimeVisible = timeStamp;
+                } else if (!hasForegroundActivity()) {
+                    // this is the first foreground activity.
                     mLastTimeUsed = timeStamp;
                 }
-            }
-            if (eventType == MOVE_TO_BACKGROUND) {
-                mLastForegroundActivityEventMap.remove(className);
-            } else {
-                mLastForegroundActivityEventMap.put(className, eventType);
-            }
-        } else if (eventType == MOVE_TO_FOREGROUND) {
-            if (!isAppInUse()) {
-                mLastTimeUsed = timeStamp;
-            }
-            mLastForegroundActivityEventMap.put(className, eventType);
+                mActivities.put(instanceId, eventType);
+                break;
+            case ACTIVITY_PAUSED:
+                if (!hasVisibleActivity()) {
+                    // this is the first visible activity.
+                    mLastTimeVisible = timeStamp;
+                }
+                mActivities.put(instanceId, eventType);
+                break;
+            case ACTIVITY_STOPPED:
+                mActivities.put(instanceId, eventType);
+                break;
+            case ACTIVITY_DESTROYED:
+                // remove activity from the map.
+                mActivities.delete(instanceId);
+                break;
+            default:
+                break;
         }
     }
 
@@ -366,80 +486,97 @@
      */
     private void updateForegroundService(String className, long timeStamp, int eventType) {
         if (eventType != FOREGROUND_SERVICE_STOP
-                && eventType != FOREGROUND_SERVICE_START
-                && eventType != ROLLOVER_FOREGROUND_SERVICE) {
+                && eventType != FOREGROUND_SERVICE_START) {
             return;
         }
-        final Integer lastEvent = mLastForegroundServiceEventMap.get(className);
+        final Integer lastEvent = mForegroundServices.get(className);
+        // update usage.
         if (lastEvent != null) {
-            if (isForegroundServiceStarted(lastEvent)) {
-                if (timeStamp > mLastTimeForegroundServiceUsed) {
-                    mTotalTimeForegroundServiceUsed +=
-                            timeStamp - mLastTimeForegroundServiceUsed;
+            switch (lastEvent) {
+                case FOREGROUND_SERVICE_START:
+                case CONTINUING_FOREGROUND_SERVICE:
+                    incrementServiceTimeUsed(timeStamp);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        // update current event.
+        switch (eventType) {
+            case FOREGROUND_SERVICE_START:
+                if (!anyForegroundServiceStarted()) {
                     mLastTimeForegroundServiceUsed = timeStamp;
                 }
-            }
-            if (eventType == FOREGROUND_SERVICE_STOP) {
-                mLastForegroundServiceEventMap.remove(className);
-            } else {
-                mLastForegroundServiceEventMap.put(className, eventType);
-            }
-        } else if (eventType == FOREGROUND_SERVICE_START) {
-            if (!isAppInUse()) {
-                mLastTimeForegroundServiceUsed = timeStamp;
-            }
-            mLastForegroundServiceEventMap.put(className, eventType);
+                mForegroundServices.put(className, eventType);
+                break;
+            case FOREGROUND_SERVICE_STOP:
+                mForegroundServices.remove(className);
+                break;
+            default:
+                break;
         }
     }
 
     /**
      * Update the UsageStats by a activity or foreground service event.
-     * @param className class name of a activity or foreground service, could be null to mark
-     *                  END_OF_DAY or rollover.
+     * @param className class name of a activity or foreground service, could be null to if this
+     *                  is sent to all activities/services in this package.
      * @param timeStamp Epoch timestamp in milliseconds.
      * @param eventType event type as in {@link UsageEvents.Event}
+     * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken.
+     *                 if className is not an activity, instanceId is not used.
      * @hide
      */
-    public void update(String className, long timeStamp, int eventType) {
+    public void update(String className, long timeStamp, int eventType, int instanceId) {
         switch(eventType) {
-            case MOVE_TO_BACKGROUND:
-            case MOVE_TO_FOREGROUND:
-                updateForegroundActivity(className, timeStamp, eventType);
+            case ACTIVITY_RESUMED:
+            case ACTIVITY_PAUSED:
+            case ACTIVITY_STOPPED:
+            case ACTIVITY_DESTROYED:
+                updateActivity(className, timeStamp, eventType, instanceId);
                 break;
             case END_OF_DAY:
-                // END_OF_DAY means updating all activities.
-                final int size = mLastForegroundActivityEventMap.size();
-                for (int i = 0; i < size; i++) {
-                    final String name = mLastForegroundActivityEventMap.keyAt(i);
-                    updateForegroundActivity(name, timeStamp, eventType);
+                // END_OF_DAY updates all activities.
+                if (hasForegroundActivity()) {
+                    incrementTimeUsed(timeStamp);
+                }
+                if (hasVisibleActivity()) {
+                    incrementTimeVisible(timeStamp);
                 }
                 break;
-            case CONTINUE_PREVIOUS_DAY:
-                mLastTimeUsed = timeStamp;
-                mLastForegroundActivityEventMap.put(className, eventType);
-                break;
-            case FOREGROUND_SERVICE_STOP:
             case FOREGROUND_SERVICE_START:
+            case FOREGROUND_SERVICE_STOP:
                 updateForegroundService(className, timeStamp, eventType);
                 break;
             case ROLLOVER_FOREGROUND_SERVICE:
-                // ROLLOVER_FOREGROUND_SERVICE means updating all foreground services.
-                final int size2 = mLastForegroundServiceEventMap.size();
-                for (int i = 0; i < size2; i++) {
-                    final String name = mLastForegroundServiceEventMap.keyAt(i);
-                    updateForegroundService(name, timeStamp, eventType);
+                // ROLLOVER_FOREGROUND_SERVICE updates all foreground services.
+                if (anyForegroundServiceStarted()) {
+                    incrementServiceTimeUsed(timeStamp);
                 }
                 break;
             case CONTINUING_FOREGROUND_SERVICE:
                 mLastTimeForegroundServiceUsed = timeStamp;
-                mLastForegroundServiceEventMap.put(className, eventType);
+                mForegroundServices.put(className, eventType);
+                break;
+            case FLUSH_TO_DISK:
+                // update usage of all active activities/services.
+                if (hasForegroundActivity()) {
+                    incrementTimeUsed(timeStamp);
+                }
+                if (hasVisibleActivity()) {
+                    incrementTimeVisible(timeStamp);
+                }
+                if (anyForegroundServiceStarted()) {
+                    incrementServiceTimeUsed(timeStamp);
+                }
                 break;
             default:
                 break;
         }
         mEndTimeStamp = timeStamp;
 
-        if (eventType == MOVE_TO_FOREGROUND) {
+        if (eventType == ACTIVITY_RESUMED) {
             mLaunchCount += 1;
         }
     }
@@ -455,8 +592,10 @@
         dest.writeLong(mBeginTimeStamp);
         dest.writeLong(mEndTimeStamp);
         dest.writeLong(mLastTimeUsed);
+        dest.writeLong(mLastTimeVisible);
         dest.writeLong(mLastTimeForegroundServiceUsed);
         dest.writeLong(mTotalTimeInForeground);
+        dest.writeLong(mTotalTimeVisible);
         dest.writeLong(mTotalTimeForegroundServiceUsed);
         dest.writeInt(mLaunchCount);
         dest.writeInt(mAppLaunchCount);
@@ -477,21 +616,26 @@
         }
         dest.writeBundle(allCounts);
 
-        final Bundle foregroundActivityEventBundle = new Bundle();
-        final int foregroundEventSize = mLastForegroundActivityEventMap.size();
-        for (int i = 0; i < foregroundEventSize; i++) {
-            foregroundActivityEventBundle.putInt(mLastForegroundActivityEventMap.keyAt(i),
-                    mLastForegroundActivityEventMap.valueAt(i));
-        }
-        dest.writeBundle(foregroundActivityEventBundle);
+        writeSparseIntArray(dest, mActivities);
+        dest.writeBundle(eventMapToBundle(mForegroundServices));
+    }
 
-        final Bundle foregroundServiceEventBundle = new Bundle();
-        final int foregroundServiceEventSize = mLastForegroundServiceEventMap.size();
-        for (int i = 0; i < foregroundServiceEventSize; i++) {
-            foregroundServiceEventBundle.putInt(mLastForegroundServiceEventMap.keyAt(i),
-                    mLastForegroundServiceEventMap.valueAt(i));
+    private void writeSparseIntArray(Parcel dest, SparseIntArray arr) {
+        final int size = arr.size();
+        dest.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            dest.writeInt(arr.keyAt(i));
+            dest.writeInt(arr.valueAt(i));
         }
-        dest.writeBundle(foregroundServiceEventBundle);
+    }
+
+    private Bundle eventMapToBundle(ArrayMap<String, Integer> eventMap) {
+        final Bundle bundle = new Bundle();
+        final int size = eventMap.size();
+        for (int i = 0; i < size; i++) {
+            bundle.putInt(eventMap.keyAt(i), eventMap.valueAt(i));
+        }
+        return bundle;
     }
 
     public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() {
@@ -502,8 +646,10 @@
             stats.mBeginTimeStamp = in.readLong();
             stats.mEndTimeStamp = in.readLong();
             stats.mLastTimeUsed = in.readLong();
+            stats.mLastTimeVisible = in.readLong();
             stats.mLastTimeForegroundServiceUsed = in.readLong();
             stats.mTotalTimeInForeground = in.readLong();
+            stats.mTotalTimeVisible = in.readLong();
             stats.mTotalTimeForegroundServiceUsed = in.readLong();
             stats.mLaunchCount = in.readInt();
             stats.mAppLaunchCount = in.readInt();
@@ -527,12 +673,21 @@
                     }
                 }
             }
-            readBundleToEventMap(stats.mLastForegroundActivityEventMap, in.readBundle());
-            readBundleToEventMap(stats.mLastForegroundServiceEventMap, in.readBundle());
+            readSparseIntArray(in, stats.mActivities);
+            readBundleToEventMap(in.readBundle(), stats.mForegroundServices);
             return stats;
         }
 
-        private void readBundleToEventMap(ArrayMap<String, Integer> eventMap, Bundle bundle) {
+        private void readSparseIntArray(Parcel in, SparseIntArray arr) {
+            final int size = in.readInt();
+            for (int i = 0; i < size; i++) {
+                final int key = in.readInt();
+                final int value = in.readInt();
+                arr.put(key, value);
+            }
+        }
+
+        private void readBundleToEventMap(Bundle bundle, ArrayMap<String, Integer> eventMap) {
             if (bundle != null) {
                 for (String className : bundle.keySet()) {
                     final int event = bundle.getInt(className);
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 1a656ab..2edad35 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -37,9 +37,12 @@
      * @param component The component for which this event occurred.
      * @param userId The user id to which the component belongs to.
      * @param eventType The event that occurred. Valid values can be found at
-     * {@link UsageEvents}
+     *                  {@link UsageEvents}
+     * @param instanceId For activity, hashCode of ActivityRecord's appToken.
+     *                   For non-activity, it is not used.
      */
-    public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType);
+    public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType,
+            int instanceId);
 
     /**
      * Reports an event to the UsageStatsManager.
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/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 d7d3cb5..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.
      *
@@ -4462,7 +4475,7 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve an
-     * {@link android.telephony.rcs.RcsManager}.
+     * {@link android.telephony.ims.RcsManager}.
      * @hide
      */
     public static final String TELEPHONY_RCS_SERVICE = "ircs";
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 07d6e47..0c438af 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -631,6 +631,13 @@
      */
     public static final int PRIVATE_FLAG_PROFILEABLE_BY_SHELL = 1 << 23;
 
+    /**
+     * Indicates whether this package requires access to non-SDK APIs.
+     * Only system apps and tests are allowed to use this property.
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_HAS_FRAGILE_USER_DATA = 1 << 24;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
             PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
@@ -655,6 +662,7 @@
             PRIVATE_FLAG_STATIC_SHARED_LIBRARY,
             PRIVATE_FLAG_VENDOR,
             PRIVATE_FLAG_VIRTUAL_PRELOAD,
+            PRIVATE_FLAG_HAS_FRAGILE_USER_DATA,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoPrivateFlags {}
@@ -704,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;
@@ -1730,6 +1747,17 @@
         return (privateFlags & PRIVATE_FLAG_USES_NON_SDK_API) != 0;
     }
 
+    /**
+     * Whether an app needs to keep the app data on uninstall.
+     *
+     * @return {@code true} if the app indicates that it needs to keep the app data
+     *
+     * @hide
+     */
+    public boolean hasFragileUserData() {
+        return (privateFlags & PRIVATE_FLAG_HAS_FRAGILE_USER_DATA) != 0;
+    }
+
     private boolean isAllowedToUseHiddenApis() {
         if (isSignedWithPlatformKey()) {
             return true;
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index ecc8cd6..a251c00 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -42,6 +42,8 @@
     ParceledListSlice getAllSessions(int userId);
     ParceledListSlice getMySessions(String installerPackageName, int userId);
 
+    ParceledListSlice getStagedSessions();
+
     void registerCallback(IPackageInstallerCallback callback, int userId);
     void unregisterCallback(IPackageInstallerCallback callback);
 
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index cef21f6..04e15c7 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -38,9 +38,12 @@
     void commit(in IntentSender statusReceiver, boolean forTransferred);
     void transfer(in String packageName);
     void abandon();
+
     boolean isMultiPackage();
     int[] getChildSessionIds();
     void addChildSessionId(in int sessionId);
     void removeChildSessionId(in int sessionId);
     int getParentSessionId();
+
+    boolean isStaged();
 }
diff --git a/core/java/android/content/pm/ModuleInfo.java b/core/java/android/content/pm/ModuleInfo.java
new file mode 100644
index 0000000..07e640b
--- /dev/null
+++ b/core/java/android/content/pm/ModuleInfo.java
@@ -0,0 +1,146 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information you can retrieve about a particular system
+ * module.
+ */
+public final class ModuleInfo implements Parcelable {
+
+     // NOTE: When adding new data members be sure to update the copy-constructor, Parcel
+     // constructor, and writeToParcel.
+
+    /** Public name of this module. */
+    private String mName;
+
+    /** The package name of this module. */
+    private String mPackageName;
+
+    /** Whether or not this module is hidden from the user. */
+    private boolean mHidden;
+
+    // TODO: Decide whether we need an additional metadata bundle to support out of band
+    // updates to ModuleInfo.
+    //
+    // private Bundle mMetadata;
+
+    /** @hide */
+    public ModuleInfo() {
+    }
+
+    /** @hide */
+    public ModuleInfo(ModuleInfo orig) {
+        mName = orig.mName;
+        mPackageName = orig.mPackageName;
+        mHidden = orig.mHidden;
+    }
+
+    /** @hide Sets the public name of this module. */
+    public ModuleInfo setName(String name) {
+        mName = name;
+        return this;
+    }
+
+    /** Gets the public name of this module. */
+    public @Nullable String getName() {
+        return mName;
+    }
+
+    /** @hide Sets the package name of this module. */
+    public ModuleInfo setPackageName(String packageName) {
+        mPackageName = packageName;
+        return this;
+    }
+
+    /** Gets the package name of this module. */
+    public @Nullable String getPackageName() {
+        return mPackageName;
+    }
+
+    /** @hide Sets whether or not this package is hidden. */
+    public ModuleInfo setHidden(boolean hidden) {
+        mHidden = hidden;
+        return this;
+    }
+
+    /** Gets whether or not this package is hidden. */
+    public boolean isHidden() {
+        return mHidden;
+    }
+
+    /** Returns a string representation of this object. */
+    public String toString() {
+        return "ModuleInfo{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + mName + "}";
+    }
+
+    /** Describes the kinds of special objects contained in this object. */
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        int hashCode = 0;
+        hashCode = 31 * hashCode + Objects.hashCode(mName);
+        hashCode = 31 * hashCode + Objects.hashCode(mPackageName);
+        hashCode = 31 * hashCode + Boolean.hashCode(mHidden);
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ModuleInfo)) {
+            return false;
+        }
+        final ModuleInfo other = (ModuleInfo) obj;
+        return Objects.equals(mName, other.mName)
+                && Objects.equals(mPackageName, other.mPackageName)
+                && mHidden == other.mHidden;
+    }
+
+    /** Flattens this object into the given {@link Parcel}. */
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeString(mName);
+        dest.writeString(mPackageName);
+        dest.writeBoolean(mHidden);
+    }
+
+    private ModuleInfo(Parcel source) {
+        mName = source.readString();
+        mPackageName = source.readString();
+        mHidden = source.readBoolean();
+    }
+
+    public static final Parcelable.Creator<ModuleInfo> CREATOR =
+            new Parcelable.Creator<ModuleInfo>() {
+        public ModuleInfo createFromParcel(Parcel source) {
+            return new ModuleInfo(source);
+        }
+        public ModuleInfo[] newArray(int size) {
+            return new ModuleInfo[size];
+        }
+    };
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 07672d9..f81eb76 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -68,7 +68,14 @@
  * {@link PackageInstaller.Session}, which any app can create. Once the session
  * is created, the installer can stream one or more APKs into place until it
  * decides to either commit or destroy the session. Committing may require user
- * intervention to complete the installation.
+ * intervention to complete the installation, unless the caller falls into one of the
+ * following categories, in which case the installation will complete automatically.
+ * <ul>
+ * <li>the device owner
+ * <li>the affiliated profile owner
+ * <li>the device owner delegated app with
+ *     {@link android.app.admin.DevicePolicyManager#DELEGATION_PACKAGE_INSTALLATION}
+ * </ul>
  * <p>
  * Sessions can install brand new apps, upgrade existing apps, or add new splits
  * into an existing app.
@@ -463,12 +470,26 @@
     }
 
     /**
+     * Return list of all staged install sessions.
+     */
+    public @NonNull List<SessionInfo> getStagedSessions() {
+        try {
+            // TODO: limit this to the mUserId?
+            return mInstaller.getStagedSessions().getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Uninstall the given package, removing it completely from the device. This
      * method is available to:
      * <ul>
      * <li>the current "installer of record" for the package
      * <li>the device owner
      * <li>the affiliated profile owner
+     * <li>the device owner delegated app with
+     *     {@link android.app.admin.DevicePolicyManager#DELEGATION_PACKAGE_INSTALLATION}
      * </ul>
      *
      * @param packageName The package to uninstall.
@@ -779,6 +800,11 @@
      * individual session IDs can be added with {@link #addChildSessionId(int)}
      * and commit of the multi-package session will result in all child sessions
      * being committed atomically.
+     * <p>
+     * If a package requires to be installed only at reboot, the session should
+     * be marked as a staged session by calling {@link SessionParams#setStaged()}
+     * with {@code true}. This can also apply to a multi-package session, in
+     * which case all the packages in the session will be applied at reboot.
      */
     public static class Session implements Closeable {
         /** {@hide} */
@@ -1105,6 +1131,17 @@
         }
 
         /**
+         * @return {@code true} if this session will be staged and applied at next reboot.
+         */
+        public boolean isStaged() {
+            try {
+                return mSession.isStaged();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * @return the session ID of the multi-package session that this belongs to or
          * {@link SessionInfo#INVALID_ID} if it does not belong to a multi-package session.
          */
@@ -1227,6 +1264,8 @@
         public String installerPackageName;
         /** {@hide} */
         public boolean isMultiPackage;
+        /** {@hide} */
+        public boolean isStaged;
 
         /**
          * Construct parameters for a new package install session.
@@ -1257,6 +1296,7 @@
             grantedRuntimePermissions = source.readStringArray();
             installerPackageName = source.readString();
             isMultiPackage = source.readBoolean();
+            isStaged = source.readBoolean();
         }
 
         /**
@@ -1471,6 +1511,17 @@
             this.isMultiPackage = true;
         }
 
+        /**
+         * Set this session to be staged to be installed at reboot.
+         *
+         * Staged sessions are scheduled to be installed at next reboot. Staged sessions can also be
+         * multi-package. In that case, if any of the children sessions fail to install at reboot,
+         * all the other children sessions are aborted as well.
+         */
+        public void setStaged() {
+            this.isStaged = true;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
@@ -1488,6 +1539,7 @@
             pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions);
             pw.printPair("installerPackageName", installerPackageName);
             pw.printPair("isMultiPackage", isMultiPackage);
+            pw.printPair("isStaged", isStaged);
             pw.println();
         }
 
@@ -1514,6 +1566,7 @@
             dest.writeStringArray(grantedRuntimePermissions);
             dest.writeString(installerPackageName);
             dest.writeBoolean(isMultiPackage);
+            dest.writeBoolean(isStaged);
         }
 
         public static final Parcelable.Creator<SessionParams>
@@ -1593,6 +1646,8 @@
         /** {@hide} */
         public boolean isMultiPackage;
         /** {@hide} */
+        public boolean isStaged;
+        /** {@hide} */
         public int parentSessionId = INVALID_ID;
         /** {@hide} */
         public int[] childSessionIds = NO_SESSIONS;
@@ -1625,6 +1680,7 @@
             grantedRuntimePermissions = source.readStringArray();
             installFlags = source.readInt();
             isMultiPackage = source.readBoolean();
+            isStaged = source.readBoolean();
             parentSessionId = source.readInt();
             childSessionIds = source.createIntArray();
             if (childSessionIds == null) {
@@ -1892,6 +1948,13 @@
         }
 
         /**
+         * Returns true if this session is a staged session which will be applied at next reboot.
+         */
+        public boolean isStaged() {
+            return isStaged;
+        }
+
+        /**
          * Returns the parent multi-package session ID if this session belongs to one,
          * {@link #INVALID_ID} otherwise.
          */
@@ -1935,6 +1998,7 @@
             dest.writeStringArray(grantedRuntimePermissions);
             dest.writeInt(installFlags);
             dest.writeBoolean(isMultiPackage);
+            dest.writeBoolean(isStaged);
             dest.writeInt(parentSessionId);
             dest.writeIntArray(childSessionIds);
         }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index da39b63..566017b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -220,6 +220,12 @@
 
     /** @hide */
     @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ModuleInfoFlags {}
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
             GET_META_DATA,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -3377,6 +3383,33 @@
             @ApplicationInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException;
 
     /**
+     * Retrieve all of the information we know about a particular
+     * package/application, for a specific user.
+     *
+     * @param packageName The full name (i.e. com.google.apps.contacts) of an
+     *            application.
+     * @param flags Additional option flags to modify the data returned.
+     * @return An {@link ApplicationInfo} containing information about the
+     *         package. If flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if
+     *         the package is not found in the list of installed applications,
+     *         the application information is retrieved from the list of
+     *         uninstalled applications (which includes installed applications
+     *         as well as applications with data directory i.e. applications
+     *         which had been deleted with {@code DONT_DELETE_DATA} flag set).
+     * @throws NameNotFoundException if a package with the given name cannot be
+     *             found on the system.
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    @SystemApi
+    public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
+            @ApplicationInfoFlags int flags, @NonNull UserHandle user)
+            throws NameNotFoundException {
+        return getApplicationInfoAsUser(packageName, flags, user.getIdentifier());
+    }
+
+    /**
      * Retrieve all of the information we know about a particular activity
      * class.
      *
@@ -3440,6 +3473,35 @@
             @ComponentInfoFlags int flags) throws NameNotFoundException;
 
     /**
+     * Retrieve information for a particular module.
+     *
+     * @param packageName The name of the module.
+     * @param flags Additional option flags to modify the data returned.
+     * @return A {@link ModuleInfo} object containing information about the
+     *         module.
+     * @throws NameNotFoundException if a module with the given name cannot be
+     *             found on the system.
+     */
+    public ModuleInfo getModuleInfo(String packageName, @ModuleInfoFlags int flags)
+            throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getModuleInfo not implemented in subclass");
+    }
+
+    /**
+     * Return a List of all modules that are installed.
+     *
+     * @param flags Additional option flags to modify the data returned.
+     * @return A {@link List} of {@link ModuleInfo} objects, one for each installed
+     *         module, containing information about the module. In the unlikely case
+     *         there are no installed modules, an empty list is returned.
+     */
+    public @NonNull List<ModuleInfo> getInstalledModules(@ModuleInfoFlags int flags) {
+        throw new UnsupportedOperationException(
+                "getInstalledModules not implemented in subclass");
+    }
+
+    /**
      * Return a List of all packages that are installed for the current user.
      *
      * @param flags Additional option flags to modify the data returned.
@@ -4203,6 +4265,32 @@
             @ResolveInfoFlags int flags, @UserIdInt int userId);
 
     /**
+     * Retrieve all activities that can be performed for the given intent, for a
+     * specific user.
+     *
+     * @param intent The desired intent as per resolveActivity().
+     * @param flags Additional option flags to modify the data returned. The
+     *            most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
+     *            resolution to only those activities that support the
+     *            {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set
+     *            {@link #MATCH_ALL} to prevent any filtering of the results.
+     * @param user The user being queried.
+     * @return Returns a List of ResolveInfo objects containing one entry for
+     *         each matching activity, ordered from best to worst. In other
+     *         words, the first item is what would be returned by
+     *         {@link #resolveActivity}. If there are no matching activities, an
+     *         empty list is returned.
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    @SystemApi
+    public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent,
+            @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+        return queryIntentActivitiesAsUser(intent, flags, user.getIdentifier());
+    }
+
+    /**
      * Retrieve a set of activities that should be presented to the user as
      * similar options. This is like {@link #queryIntentActivities}, except it
      * also allows you to supply a list of more explicit Intents that you would
@@ -4334,6 +4422,27 @@
             @ResolveInfoFlags int flags, @UserIdInt int userId);
 
     /**
+     * Retrieve all services that can match the given intent for a given user.
+     *
+     * @param intent The desired intent as per resolveService().
+     * @param flags Additional option flags to modify the data returned.
+     * @param user The user being queried.
+     * @return Returns a List of ResolveInfo objects containing one entry for
+     *         each matching service, ordered from best to worst. In other
+     *         words, the first item is what would be returned by
+     *         {@link #resolveService}. If there are no matching services, an
+     *         empty list or null is returned.
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    @SystemApi
+    public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
+            @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+        return queryIntentServicesAsUser(intent, flags, user.getIdentifier());
+    }
+
+    /**
      * Retrieve all providers that can match the given intent.
      *
      * @param intent An intent containing all of the desired specification
@@ -4355,6 +4464,26 @@
      * @param intent An intent containing all of the desired specification
      *            (action, data, type, category, and/or component).
      * @param flags Additional option flags to modify the data returned.
+     * @param user The user being queried.
+     * @return Returns a List of ResolveInfo objects containing one entry for
+     *         each matching provider, ordered from best to worst. If there are
+     *         no matching services, an empty list or null is returned.
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    @SystemApi
+    public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent,
+            @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+        return queryIntentContentProvidersAsUser(intent, flags, user.getIdentifier());
+    }
+
+    /**
+     * Retrieve all providers that can match the given intent.
+     *
+     * @param intent An intent containing all of the desired specification
+     *            (action, data, type, category, and/or component).
+     * @param flags Additional option flags to modify the data returned.
      * @return Returns a List of ResolveInfo objects containing one entry for
      *         each matching provider, ordered from best to worst. If there are
      *         no matching services, an empty list or null is returned.
@@ -6448,7 +6577,7 @@
      *
      * @hide
      */
-    @SystemApi
+    @TestApi
     public String getWellbeingPackageName() {
         throw new UnsupportedOperationException(
                 "getWellbeingPackageName not implemented in subclass");
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d0de9a1..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,
@@ -3710,6 +3714,12 @@
             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API;
         }
 
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestApplication_hasFragileUserData,
+                false)) {
+            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA;
+        }
+
         if (outError[0] == null) {
             CharSequence pname;
             if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
@@ -3989,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);
 
@@ -4486,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);
 
@@ -4745,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.
@@ -4882,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;
@@ -7767,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);
@@ -7807,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{");
@@ -7827,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/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index f220205..4fc7f5d 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -143,7 +143,6 @@
     /**
      * contains statistics about a database
      */
-    @TestApi
     public static class DbStats {
         /** name of the database */
         public String dbName;
@@ -175,7 +174,6 @@
      * return all pager and database stats for the current process.
      * @return {@link PagerStats}
      */
-    @TestApi
     public static PagerStats getDatabaseInfo() {
         PagerStats stats = new PagerStats();
         nativeGetPagerStats(stats);
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/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 1d9330d..9d37d99 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -28,6 +28,21 @@
  */
 public interface BiometricFaceConstants {
     //
+    // Accessibility constants
+    //
+    /**
+     * Require the user to look at the device during enrollment and
+     * authentication. Note this is to accommodate people who have limited
+     * vision.
+     */
+    public static final int FEATURE_REQUIRE_ATTENTION = 1;
+    /**
+     * Require a diverse set of poses during enrollment. Note this is to
+     * accommodate people with limited mobility.
+     */
+    public static final int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2;
+
+    //
     // Error messages from face authentication hardware during initialization, enrollment,
     // authentication or removal. Must agree with the list in HAL h file
     //
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/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 9d61f02..1af9cde 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.hardware.display;
 
+import android.annotation.Nullable;
 import android.hardware.SensorManager;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -195,6 +196,44 @@
     public abstract void onOverlayChanged();
 
     /**
+     * Get the attributes available for display color sampling.
+     * @param displayId id of the display to collect the sample from.
+     *
+     * @return The attributes the display supports, or null if sampling is not supported.
+     */
+    @Nullable
+    public abstract DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes(
+            int displayId);
+
+    /**
+     * Enable or disable the collection of color samples.
+     *
+     * @param displayId id of the display to collect the sample from.
+     * @param componentMask a bitmask of the color channels to collect samples for, or zero for all
+     *                      available.
+     * @param maxFrames maintain a ringbuffer of the last maxFrames.
+     * @param enable True to enable, False to disable.
+     *
+     * @return True if sampling was enabled, false if failure.
+     */
+    public abstract boolean setDisplayedContentSamplingEnabled(
+            int displayId, boolean enable, int componentMask, int maxFrames);
+
+    /**
+     * Accesses the color histogram statistics of displayed frames on devices that support sampling.
+     *
+     * @param displayId id of the display to collect the sample from
+     * @param maxFrames limit the statistics to the last maxFrames number of frames.
+     * @param timestamp discard statistics that were collected prior to timestamp, where timestamp
+     *                  is given as CLOCK_MONOTONIC.
+     * @return The statistics representing a histogram of the color distribution of the frames
+     *         displayed on-screen, or null if sampling is not supported.
+    */
+    @Nullable
+    public abstract DisplayedContentSample getDisplayedContentSample(
+            int displayId, long maxFrames, long timestamp);
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/display/DisplayedContentSample.java b/core/java/android/hardware/display/DisplayedContentSample.java
new file mode 100644
index 0000000..0610377
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayedContentSample.java
@@ -0,0 +1,94 @@
+/*
+ * 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.hardware.display;
+
+/**
+ * @hide
+ */
+public final class DisplayedContentSample {
+    private long mNumFrames;
+    private long[] mSamplesComponent0;
+    private long[] mSamplesComponent1;
+    private long[] mSamplesComponent2;
+    private long[] mSamplesComponent3;
+
+    /**
+     * Construct an object representing a color histogram of pixels that were displayed on screen.
+     *
+     * @param numFrames The number of frames represented by this sample.
+     * @param mSamplesComponent0 is a histogram counting how many times a pixel of a given value
+     * was displayed onscreen for FORMAT_COMPONENT_0. The buckets of the histogram are evenly
+     * weighted, the number of buckets is device specific.
+     * eg, for RGBA_8888, if sampleComponent0 is {10, 6, 4, 1} this means that 10 red pixels were
+     * displayed onscreen in range 0x00->0x3F, 6 red pixels were displayed onscreen in range
+     * 0x40->0x7F, etc.
+     * @param mSamplesComponent1 is the same sample definition as sampleComponent0, but for the
+     * second component of format.
+     * @param mSamplesComponent2 is the same sample definition as sampleComponent0, but for the
+     * third component of format.
+     * @param mSamplesComponent3 is the same sample definition as sampleComponent0, but for the
+     * fourth component of format.
+     */
+    public DisplayedContentSample(long numFrames,
+            long[] sampleComponent0,
+            long[] sampleComponent1,
+            long[] sampleComponent2,
+            long[] sampleComponent3) {
+        mNumFrames = numFrames;
+        mSamplesComponent0 = sampleComponent0;
+        mSamplesComponent1 = sampleComponent1;
+        mSamplesComponent2 = sampleComponent2;
+        mSamplesComponent3 = sampleComponent3;
+    }
+
+    public enum ColorComponent {
+        CHANNEL0,
+        CHANNEL1,
+        CHANNEL2,
+        CHANNEL3,
+    }
+
+    /**
+     * Returns a color histogram according to component channel.
+     *
+     * @param component the component to return, according to the PixelFormat ordering
+     * (eg, for RGBA, CHANNEL0 is R, CHANNEL1 is G, etc).
+     *
+     * @return an evenly weighted histogram counting how many times a pixel was
+     *         displayed onscreen that fell into the corresponding bucket, with the first entry
+     *         corresponding to the normalized 0.0 value, and the last corresponding to the 1.0
+     *         value for that PixelFormat component.
+     */
+    public long[] getSampleComponent(ColorComponent component) {
+        switch (component) {
+            case CHANNEL0: return mSamplesComponent0;
+            case CHANNEL1: return mSamplesComponent1;
+            case CHANNEL2: return mSamplesComponent2;
+            case CHANNEL3: return mSamplesComponent3;
+            default: throw new ArrayIndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * Return the number of frames this sample was collected over.
+     *
+     * @return  the number of frames that this sample was collected over.
+     */
+    public long getNumFrames() {
+        return mNumFrames;
+    }
+}
diff --git a/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java b/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java
new file mode 100644
index 0000000..aad68d9
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java
@@ -0,0 +1,67 @@
+/*
+ * 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.hardware.display;
+
+/**
+ * @hide
+ */
+public final class DisplayedContentSamplingAttributes {
+    private int mPixelFormat;
+    private int mDataspace;
+    private int mComponentMask;
+
+    /* Creates the attributes reported by the display hardware about what capabilities
+     * are present.
+     *
+     * NOTE: the format and ds constants must match the values from graphics/common/x.x/types.hal
+     * @param format the format that the display hardware samples in.
+     * @param ds the dataspace in use when sampling.
+     * @param componentMask a mask of which of the format components are supported.
+    */
+    public DisplayedContentSamplingAttributes(int format, int ds, int componentMask) {
+        mPixelFormat = format;
+        mDataspace = ds;
+        mComponentMask = componentMask;
+    }
+
+    /* Returns the pixel format that the display hardware uses when sampling.
+     *
+     * NOTE: the returned constant matches the values from graphics/common/x.x/types.hal
+     * @return the format that the samples were collected in.
+     */
+    public int getPixelFormat() {
+        return mPixelFormat;
+    }
+
+    /* Returns the dataspace that the display hardware uses when sampling.
+     *
+     * NOTE: the returned constant matches the values from graphics/common/x.x/types.hal
+     * @return the dataspace that the samples were collected in.
+     */
+    public int getDataspace() {
+        return mDataspace;
+    }
+
+    /* Returns a mask of which components can be collected by the sampling engine.
+     *
+     * @return a mask of the components which are supported by the engine. The lowest
+     * bit corresponds to the lowest component (ie, 0x1 corresponds to A for RGBA).
+     */
+    public int getComponentMask() {
+        return mComponentMask;
+    }
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 322863a..bac23b3 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -207,11 +207,8 @@
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
-    public void enroll(byte[] token, CancellationSignal cancel, int flags,
-            int userId, EnrollmentCallback callback) {
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = getCurrentUserId();
-        }
+    public void enroll(byte[] token, CancellationSignal cancel,
+            EnrollmentCallback callback, int[] disabledFeatures) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
@@ -228,8 +225,8 @@
         if (mService != null) {
             try {
                 mEnrollmentCallback = callback;
-                mService.enroll(mToken, token, userId, mServiceReceiver, flags,
-                        mContext.getOpPackageName());
+                mService.enroll(mToken, token, mServiceReceiver,
+                        mContext.getOpPackageName(), disabledFeatures);
             } catch (RemoteException e) {
                 Log.w(TAG, "Remote exception in enroll: ", e);
                 if (callback != null) {
@@ -284,10 +281,10 @@
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
-    public void setRequireAttention(boolean requireAttention, byte[] token) {
+    public void setFeature(int feature, boolean enabled, byte[] token) {
         if (mService != null) {
             try {
-                mService.setRequireAttention(requireAttention, token);
+                mService.setFeature(feature, enabled, token);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -298,11 +295,11 @@
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
-    public boolean getRequireAttention(byte[] token) {
+    public boolean getFeature(int feature) {
         boolean result = true;
         if (mService != null) {
             try {
-                mService.getRequireAttention(token);
+                result = mService.getFeature(feature);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index a15dcec..a1c88f8 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -50,8 +50,8 @@
             int callingUid, int callingPid, int callingUserId, boolean fromClient);
 
     // Start face enrollment
-    void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver,
-                int flags, String opPackageName);
+    void enroll(IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver,
+                String opPackageName, in int [] disabledFeatures);
 
     // Cancel enrollment in progress
     void cancelEnrollment(IBinder token);
@@ -98,9 +98,9 @@
     // Enumerate all faces
     void enumerate(IBinder token, int userId, IFaceServiceReceiver receiver);
 
-    int setRequireAttention(boolean requireAttention, in byte [] token);
+    int setFeature(int feature, boolean enabled, in byte [] token);
 
-    boolean getRequireAttention(in byte [] token);
+    boolean getFeature(int feature);
 
     void userActivity();
 }
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/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 22dd4fc..bbf8f97 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -274,7 +274,6 @@
      * Changes only take effect during subsequent calls to
      * {@link #tagSocket(Socket)}.
      */
-    @SystemApi
     @SuppressLint("Doclava125")
     public static void setThreadStatsUid(int uid) {
         NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
@@ -313,7 +312,6 @@
      *
      * @see #setThreadStatsUid(int)
      */
-    @SystemApi
     @SuppressLint("Doclava125")
     public static void clearThreadStatsUid() {
         NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index f9b6dfc..280dad0 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -16,7 +16,6 @@
 
 package android.net.http;
 
-import android.annotation.SystemApi;
 import android.security.net.config.UserCertificateSource;
 
 import com.android.org.conscrypt.TrustManagerImpl;
@@ -133,7 +132,6 @@
      * Returns {@code true} if the TrustManager uses the same trust configuration for the provided
      * hostnames.
      */
-    @SystemApi
     public boolean isSameTrustConfiguration(String hostname1, String hostname2) {
         if (mIsSameTrustConfiguration == null) {
             return true;
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/Binder.java b/core/java/android/os/Binder.java
index d45fa11..9939a3c 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -399,6 +400,9 @@
      * reasons, we only support one UID. This UID represents the original user responsible for the
      * binder calls.
      *
+     * <p>{@link Binder#restoreCallingWorkSource(long)} must always be called after setting the
+     * worksource.
+     *
      * <p>A typical use case would be
      * <pre>
      * long token = Binder.setCallingWorkSourceUid(uid);
@@ -417,6 +421,7 @@
      * @hide
      **/
     @CriticalNative
+    @SystemApi
     public static final native long setCallingWorkSourceUid(int workSource);
 
     /**
@@ -430,6 +435,7 @@
      * @hide
      */
     @CriticalNative
+    @SystemApi
     public static final native int getCallingWorkSourceUid();
 
     /**
@@ -438,10 +444,24 @@
      * <p>The work source will be propagated for future outgoing binder transactions
      * executed on this thread.
      *
+     * <p>{@link Binder#restoreCallingWorkSource(long)} must always be called after clearing the
+     * worksource.
+     *
+     * <p>A typical use case would be
+     * <pre>
+     * long token = Binder.clearCallingWorkSource();
+     * try {
+     *   // Call an API.
+     * } finally {
+     *   Binder.restoreCallingWorkSource(token);
+     * }
+     * </pre>
+     *
      * @return token to restore original work source.
      * @hide
      **/
     @CriticalNative
+    @SystemApi
     public static final native long clearCallingWorkSource();
 
     /**
@@ -461,6 +481,7 @@
      * @hide
      **/
     @CriticalNative
+    @SystemApi
     public static final native void restoreCallingWorkSource(long token);
 
     /**
@@ -601,6 +622,7 @@
      * See {@link setProxyTransactListener}.
      * @hide
      */
+    @SystemApi
     public interface ProxyTransactListener {
         /**
          * Called before onTransact.
@@ -663,6 +685,7 @@
      * <li>Never execute another binder transaction inside the listener.
      * @hide
      */
+    @SystemApi
     public static void setProxyTransactListener(@Nullable ProxyTransactListener listener) {
         BinderProxy.setTransactListener(listener);
     }
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 7abe913..124d7b1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -71,7 +70,7 @@
      */
     public void setup(Context context, Bundle coreSettings) {
         setupGpuLayers(context, coreSettings);
-        setupAngle(context, context.getPackageName());
+        setupAngle(context, coreSettings, context.getPackageName());
         chooseDriver(context, coreSettings);
     }
 
@@ -213,10 +212,9 @@
     }
 
 
-    private static List<String> getGlobalSettingsString(Context context, String globalSetting) {
+    private static List<String> getGlobalSettingsString(Bundle bundle, String globalSetting) {
         List<String> valueList = null;
-        ContentResolver contentResolver = context.getContentResolver();
-        String settingsValue = Settings.Global.getString(contentResolver, globalSetting);
+        String settingsValue = bundle.getString(globalSetting);
 
         if (settingsValue != null) {
             valueList = new ArrayList<>(Arrays.asList(settingsValue.split(",")));
@@ -238,23 +236,18 @@
         return -1;
     }
 
-    private static String getDriverForPkg(Context context, String packageName) {
-        try {
-            ContentResolver contentResolver = context.getContentResolver();
-            int allUseAngle = Settings.Global.getInt(contentResolver,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
-            if (allUseAngle == 1) {
-                return sDriverMap.get(OpenGlDriverChoice.ANGLE);
-            }
-        } catch (Settings.SettingNotFoundException e) {
-            // Do nothing and move on
+    private static String getDriverForPkg(Bundle bundle, String packageName) {
+        String allUseAngle =
+                bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
+        if ((allUseAngle != null) && allUseAngle.equals("1")) {
+            return sDriverMap.get(OpenGlDriverChoice.ANGLE);
         }
 
         List<String> globalSettingsDriverPkgs =
-                getGlobalSettingsString(context,
+                getGlobalSettingsString(bundle,
                         Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS);
         List<String> globalSettingsDriverValues =
-                getGlobalSettingsString(context,
+                getGlobalSettingsString(bundle,
                         Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES);
 
         // Make sure we have a good package name
@@ -285,8 +278,8 @@
     /**
      * Pass ANGLE details down to trigger enable logic
      */
-    private void setupAngle(Context context, String packageName) {
-        String devOptIn = getDriverForPkg(context, packageName);
+    private void setupAngle(Context context, Bundle bundle, String packageName) {
+        String devOptIn = getDriverForPkg(bundle, packageName);
 
         if (DEBUG) {
             Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index 228fe7a..3de3494 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -32,10 +32,7 @@
     /**
      * Create and initialize a HwBinder object and the native objects
      * used to allow this to participate in hwbinder transactions.
-     *
-     * @hide
      */
-    @SystemApi
     public HwBinder() {
         native_setup();
 
@@ -44,7 +41,6 @@
                 mNativeContext);
     }
 
-    /** @hide */
     @Override
     public final native void transact(
             int code, HwParcel request, HwParcel reply, int flags)
@@ -57,10 +53,7 @@
      * @param request parceled transaction
      * @param reply object to parcel reply into
      * @param flags transaction flags to be chosen by wire protocol
-     *
-     * @hide
      */
-    @SystemApi
     public abstract void onTransact(
             int code, HwParcel request, HwParcel reply, int flags)
         throws RemoteException;
@@ -69,9 +62,7 @@
      * Registers this service with the hwservicemanager.
      *
      * @param serviceName instance name of the service
-     * @hide
      */
-    @SystemApi
     public native final void registerService(String serviceName)
         throws RemoteException;
 
@@ -81,9 +72,7 @@
      * @param iface fully-qualified interface name for example foo.bar@1.3::IBaz
      * @param serviceName the instance name of the service for example default.
      * @throws NoSuchElementException when the service is unavailable
-     * @hide
      */
-    @SystemApi
     public static final IHwBinder getService(
             String iface,
             String serviceName)
@@ -96,9 +85,7 @@
      * @param serviceName the instance name of the service for example default.
      * @param retry whether to wait for the service to start if it's not already started
      * @throws NoSuchElementException when the service is unavailable
-     * @hide
      */
-    @SystemApi
     public static native final IHwBinder getService(
             String iface,
             String serviceName,
@@ -112,9 +99,7 @@
      * @param maxThreads total number of threads to create (includes this thread if
      *     callerWillJoin is true)
      * @param callerWillJoin whether joinRpcThreadpool will be called in advance
-     * @hide
      */
-    @SystemApi
     public static native final void configureRpcThreadpool(
             long maxThreads, boolean callerWillJoin);
 
@@ -124,10 +109,7 @@
      * a threadpool with callerWillJoin true and then registering
      * the provided service if this thread doesn't need to do
      * anything else.
-     *
-     * @hide
      */
-    @SystemApi
     public static native final void joinRpcThreadpool();
 
     // Returns address of the "freeFunction".
@@ -155,10 +137,7 @@
      * - tries to enable atracing (if enabled)
      * - tries to enable coverage dumps (if running in VTS)
      * - tries to enable record and replay (if running in VTS)
-     *
-     * @hide
      */
-    @SystemApi
     public static void enableInstrumentation() {
         native_report_sysprop_change();
     }
diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java
index fbdf27e..249eb3a 100644
--- a/core/java/android/os/IHwBinder.java
+++ b/core/java/android/os/IHwBinder.java
@@ -28,10 +28,7 @@
      * @param request parceled transaction
      * @param reply object to parcel reply into
      * @param flags transaction flags to be chosen by wire protocol
-     *
-     * @hide
      */
-    @SystemApi
     public void transact(
             int code, HwParcel request, HwParcel reply, int flags)
         throws RemoteException;
@@ -40,23 +37,19 @@
      * Return as IHwInterface instance only if this implements descriptor.
      *
      * @param descriptor for example foo.bar@1.0::IBaz
-     * @hide
      */
-    @SystemApi
     public IHwInterface queryLocalInterface(String descriptor);
 
     /**
      * Interface for receiving a callback when the process hosting a service
      * has gone away.
      */
-    @SystemApi
     public interface DeathRecipient {
         /**
          * Callback for a registered process dying.
          *
          * @param cookie cookie this death recipient was registered with.
          */
-        @SystemApi
         public void serviceDied(long cookie);
     }
 
@@ -67,13 +60,11 @@
      * @param recipient callback object to be called on object death.
      * @param cookie value to be given to callback on object death.
      */
-    @SystemApi
     public boolean linkToDeath(DeathRecipient recipient, long cookie);
     /**
      * Unregisters the death recipient from this binder.
      *
      * @param recipient callback to no longer recieve death notifications on this binder.
      */
-    @SystemApi
     public boolean unlinkToDeath(DeathRecipient recipient);
 }
diff --git a/core/java/android/os/IHwInterface.java b/core/java/android/os/IHwInterface.java
index 1d9e2b0..f9edd5b 100644
--- a/core/java/android/os/IHwInterface.java
+++ b/core/java/android/os/IHwInterface.java
@@ -23,6 +23,5 @@
     /**
      * @return the binder object that corresponds to this interface.
      */
-    @SystemApi
     public IHwBinder asBinder();
 }
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index c9c4205..be8cf0e 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -18,7 +18,6 @@
 package android.os;
 
 import android.net.InterfaceConfiguration;
-import android.net.INetd;
 import android.net.INetworkManagementEventObserver;
 import android.net.ITetheringStatsProvider;
 import android.net.Network;
@@ -47,11 +46,6 @@
     void unregisterObserver(INetworkManagementEventObserver obs);
 
     /**
-     * Retrieve an INetd to talk to netd.
-     */
-    INetd getNetdService();
-
-    /**
      * Returns a list of currently known network interfaces
      */
     String[] listInterfaces();
diff --git a/core/java/android/os/SystemUpdateManager.java b/core/java/android/os/SystemUpdateManager.java
index ce3e2259..9146731 100644
--- a/core/java/android/os/SystemUpdateManager.java
+++ b/core/java/android/os/SystemUpdateManager.java
@@ -34,62 +34,51 @@
     private static final String TAG = "SystemUpdateManager";
 
     /** The status key of the system update info, expecting an int value. */
-    @SystemApi
     public static final String KEY_STATUS = "status";
 
     /** The title of the current update, expecting a String value. */
-    @SystemApi
     public static final String KEY_TITLE = "title";
 
     /** Whether it is a security update, expecting a boolean value. */
-    @SystemApi
     public static final String KEY_IS_SECURITY_UPDATE = "is_security_update";
 
     /** The build fingerprint after installing the current update, expecting a String value. */
-    @SystemApi
     public static final String KEY_TARGET_BUILD_FINGERPRINT = "target_build_fingerprint";
 
     /** The security patch level after installing the current update, expecting a String value. */
-    @SystemApi
     public static final String KEY_TARGET_SECURITY_PATCH_LEVEL = "target_security_patch_level";
 
     /**
      * The KEY_STATUS value that indicates there's no update status info available.
      */
-    @SystemApi
     public static final int STATUS_UNKNOWN = 0;
 
     /**
      * The KEY_STATUS value that indicates there's no pending update.
      */
-    @SystemApi
     public static final int STATUS_IDLE = 1;
 
     /**
      * The KEY_STATUS value that indicates an update is available for download, but pending user
      * approval to start.
      */
-    @SystemApi
     public static final int STATUS_WAITING_DOWNLOAD = 2;
 
     /**
      * The KEY_STATUS value that indicates an update is in progress (i.e. downloading or installing
      * has started).
      */
-    @SystemApi
     public static final int STATUS_IN_PROGRESS = 3;
 
     /**
      * The KEY_STATUS value that indicates an update is available for install.
      */
-    @SystemApi
     public static final int STATUS_WAITING_INSTALL = 4;
 
     /**
      * The KEY_STATUS value that indicates an update will be installed after a reboot. This applies
      * to both of A/B and non-A/B OTAs.
      */
-    @SystemApi
     public static final int STATUS_WAITING_REBOOT = 5;
 
     private final ISystemUpdateManager mService;
@@ -110,7 +99,6 @@
      *
      * @throws SecurityException if the caller is not allowed to read the info.
      */
-    @SystemApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_SYSTEM_UPDATE_INFO,
             android.Manifest.permission.RECOVERY,
@@ -137,7 +125,6 @@
      * @throws IllegalArgumentException if @link #KEY_STATUS} does not exist.
      * @throws SecurityException if the caller is not allowed to update the info.
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.RECOVERY)
     public void updateSystemUpdateInfo(PersistableBundle infoBundle) {
         if (infoBundle == null || !infoBundle.containsKey(KEY_STATUS)) {
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 24c9c91..8f2826c 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -54,7 +54,6 @@
      * Error code from the update engine. Values must agree with the ones in
      * system/update_engine/common/error_code.h.
      */
-    @SystemApi
     public static final class ErrorCodeConstants {
         public static final int SUCCESS = 0;
         public static final int ERROR = 1;
@@ -74,7 +73,6 @@
      * Update status code from the update engine. Values must agree with the
      * ones in system/update_engine/client_library/include/update_engine/update_status.h.
      */
-    @SystemApi
     public static final class UpdateStatusConstants {
         public static final int IDLE = 0;
         public static final int CHECKING_FOR_UPDATE = 1;
@@ -95,7 +93,6 @@
     /**
      * Creates a new instance.
      */
-    @SystemApi
     public UpdateEngine() {
         mUpdateEngine = IUpdateEngine.Stub.asInterface(
                 ServiceManager.getService(UPDATE_ENGINE_SERVICE));
@@ -106,7 +103,6 @@
      * status change, and when the update completes. A handler can be supplied
      * to control which thread runs the callback, or null.
      */
-    @SystemApi
     public boolean bind(final UpdateEngineCallback callback, final Handler handler) {
         synchronized (mUpdateEngineCallbackLock) {
             mUpdateEngineCallback = new IUpdateEngineCallback.Stub() {
@@ -150,7 +146,6 @@
     /**
      * Equivalent to {@code bind(callback, null)}.
      */
-    @SystemApi
     public boolean bind(final UpdateEngineCallback callback) {
         return bind(callback, null);
     }
@@ -183,7 +178,6 @@
      * };
      * </pre>
      */
-    @SystemApi
     public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) {
         try {
             mUpdateEngine.applyPayload(url, offset, size, headerKeyValuePairs);
@@ -201,7 +195,6 @@
      * <p>See {@link #suspend} for a way to temporarily stop an in-progress
      * update with the ability to resume it later.
      */
-    @SystemApi
     public void cancel() {
         try {
             mUpdateEngine.cancel();
@@ -214,7 +207,6 @@
      * Suspends an in-progress update. This can be undone by calling
      * {@link #resume}.
      */
-    @SystemApi
     public void suspend() {
         try {
             mUpdateEngine.suspend();
@@ -226,7 +218,6 @@
     /**
      * Resumes a suspended update.
      */
-    @SystemApi
     public void resume() {
         try {
             mUpdateEngine.resume();
@@ -244,7 +235,6 @@
      * {@code UPDATED_NEED_REBOOT}, so your callback can remove any outstanding
      * notification that rebooting into the new system is possible.
      */
-    @SystemApi
     public void resetStatus() {
         try {
             mUpdateEngine.resetStatus();
@@ -256,7 +246,6 @@
     /**
      * Unbinds the last bound callback function.
      */
-    @SystemApi
     public boolean unbind() {
         synchronized (mUpdateEngineCallbackLock) {
             if (mUpdateEngineCallback == null) {
@@ -281,7 +270,6 @@
      * @param payloadMetadataFilename the location of the metadata without the
      * {@code file://} prefix.
      */
-    @SystemApi
     public boolean verifyPayloadMetadata(String payloadMetadataFilename) {
         try {
             return mUpdateEngine.verifyPayloadApplicable(payloadMetadataFilename);
diff --git a/core/java/android/os/UpdateEngineCallback.java b/core/java/android/os/UpdateEngineCallback.java
index afff60a..f07294e 100644
--- a/core/java/android/os/UpdateEngineCallback.java
+++ b/core/java/android/os/UpdateEngineCallback.java
@@ -37,7 +37,6 @@
      * be one of the values from {@link UpdateEngine.UpdateStatusConstants},
      * and {@code percent} will be valid [TODO: in which cases?].
      */
-    @SystemApi
     public abstract void onStatusUpdate(int status, float percent);
 
     /**
@@ -45,6 +44,5 @@
      * unsuccessfully. The value of {@code errorCode} will be one of the
      * values from {@link UpdateEngine.ErrorCodeConstants}.
      */
-    @SystemApi
     public abstract void onPayloadApplicationComplete(int errorCode);
 }
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/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 7fd0a4b..f136cd6 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -398,6 +398,8 @@
             argsForZygote.add("--mount-external-write");
         } else if (mountExternal == Zygote.MOUNT_EXTERNAL_FULL) {
             argsForZygote.add("--mount-external-full");
+        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) {
+            argsForZygote.add("--mount-external-installer");
         }
 
         argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 865b8f8..c167ea1 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.UnsupportedAppUsage;
@@ -41,6 +42,8 @@
 import android.text.format.Time;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 /**
  * <p>
  * The contract between the calendar provider and applications. Contains
@@ -129,6 +132,13 @@
         "android.provider.calendar.action.HANDLE_CUSTOM_EVENT";
 
     /**
+     * Action used to help apps show calendar events in the managed profile.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_VIEW_WORK_CALENDAR_EVENT =
+            "android.provider.calendar.action.VIEW_WORK_CALENDAR_EVENT";
+
+    /**
      * Intent Extras key: {@link EventsColumns#CUSTOM_APP_URI} for the event in
      * the {@link #ACTION_HANDLE_CUSTOM_EVENT} intent
      */
@@ -153,6 +163,11 @@
     public static final String EXTRA_EVENT_ALL_DAY = "allDay";
 
     /**
+     * Intent Extras key: The id of an event.
+     */
+    public static final String EXTRA_EVENT_ID = "id";
+
+    /**
      * This authority is used for writing to or querying from the calendar
      * provider. Note: This is set at first run and cannot be changed without
      * breaking apps that access the provider.
@@ -195,6 +210,43 @@
     private CalendarContract() {}
 
     /**
+     * Starts an activity to view calendar events in the managed profile.
+     *
+     * When this API is called, the system will attempt to start an activity
+     * in the managed profile with an intent targeting the same caller package.
+     * The intent will have its action set to
+     * {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras
+     * corresponding to the API's arguments. A calendar app intending to support
+     * cross profile events viewing should handle this intent, parse the arguments
+     * and show the appropriate UI.
+     *
+     * @param context the context.
+     * @param eventId the id of the event to be viewed. Will be put into {@link #EXTRA_EVENT_ID}
+     *                field of the intent.
+     * @param start the start time of the event. Will be put into {@link #EXTRA_EVENT_BEGIN_TIME}
+     *              field of the intent.
+     * @param end the end time of the event. Will be put into {@link #EXTRA_EVENT_END_TIME} field
+     *            of the intent.
+     * @param allDay if the event is an all-day event. Will be put into
+     *               {@link #EXTRA_EVENT_ALL_DAY} field of the intent.
+     * @param flags flags to be set on the intent via {@link Intent#setFlags}
+     * @return {@code true} if the activity is started successfully. {@code false} otherwise.
+     *
+     * @see #EXTRA_EVENT_ID
+     * @see #EXTRA_EVENT_BEGIN_TIME
+     * @see #EXTRA_EVENT_END_TIME
+     * @see #EXTRA_EVENT_ALL_DAY
+     */
+    public static boolean startViewCalendarEventInManagedProfile(@NonNull Context context,
+            long eventId, long start, long end, boolean allDay, int flags) {
+        Preconditions.checkNotNull(context, "Context is null");
+        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+                Context.DEVICE_POLICY_SERVICE);
+        return dpm.startViewCalendarEventInManagedProfile(eventId, start,
+                end, allDay, flags);
+    }
+
+    /**
      * Generic columns for use by sync adapters. The specific functions of these
      * columns are private to the sync adapter. Other clients of the API should
      * not attempt to either read or write this column. These columns are
@@ -695,7 +747,7 @@
         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars");
 
         /**
-         * The content:// style URL for querying Calendars table in the work profile. Appending a
+         * The content:// style URL for querying Calendars table in the managed profile. Appending a
          * calendar id using {@link ContentUris#withAppendedId(Uri, long)} will
          * specify a single calendar.
          *
@@ -715,9 +767,9 @@
          * projection of the query to this uri that are not contained in the above list.
          *
          * <p>This uri will return an empty cursor if the calling user is not a parent profile
-         * of a work profile, or cross profile calendar is disabled in Settings, or this uri is
-         * queried from a package that is not whitelisted by profile owner of the work profile via
-         * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+         * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+         * queried from a package that is not whitelisted by profile owner of the managed profile
+         * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
          *
          * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
          * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1673,7 +1725,7 @@
                 Uri.parse("content://" + AUTHORITY + "/events");
 
         /**
-         * The content:// style URL for querying Events table in the work profile. Appending an
+         * The content:// style URL for querying Events table in the managed profile. Appending an
          * event id using {@link ContentUris#withAppendedId(Uri, long)} will
          * specify a single event.
          *
@@ -1706,9 +1758,9 @@
          * projection of the query to this uri that are not contained in the above list.
          *
          * <p>This uri will return an empty cursor if the calling user is not a parent profile
-         * of a work profile, or cross profile calendar is disabled in Settings, or this uri is
-         * queried from a package that is not whitelisted by profile owner of the work profile via
-         * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+         * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+         * queried from a package that is not whitelisted by profile owner of the managed profile
+         * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
          *
          * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
          * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1896,7 +1948,7 @@
             Uri.parse("content://" + AUTHORITY + "/instances/searchbyday");
 
         /**
-         * The content:// style URL for querying an instance range in the work profile.
+         * The content:// style URL for querying an instance range in the managed profile.
          * It supports similar semantics as {@link #CONTENT_URI}.
          *
          * <p>The following columns plus the columns that are whitelisted by
@@ -1916,9 +1968,9 @@
          * projection of the query to this uri that are not contained in the above list.
          *
          * <p>This uri will return an empty cursor if the calling user is not a parent profile
-         * of a work profile, or cross profile calendar for the work profile is disabled in
+         * of a managed profile, or cross profile calendar for the managed profile is disabled in
          * Settings, or this uri is queried from a package that is not whitelisted by
-         * profile owner of the work profile via
+         * profile owner of the managed profile via
          * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
          *
          * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
@@ -1929,7 +1981,7 @@
 
         /**
          * The content:// style URL for querying an instance range by Julian
-         * Day in the work profile. It supports similar semantics as {@link #CONTENT_BY_DAY_URI}
+         * Day in the managed profile. It supports similar semantics as {@link #CONTENT_BY_DAY_URI}
          * and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}.
          */
         public static final Uri ENTERPRISE_CONTENT_BY_DAY_URI =
@@ -1937,7 +1989,7 @@
 
         /**
          * The content:// style URL for querying an instance range with a search
-         * term in the work profile. It supports similar semantics as {@link #CONTENT_SEARCH_URI}
+         * term in the managed profile. It supports similar semantics as {@link #CONTENT_SEARCH_URI}
          * and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}.
          */
         public static final Uri ENTERPRISE_CONTENT_SEARCH_URI =
@@ -1945,7 +1997,7 @@
 
         /**
          * The content:// style URL for querying an instance range with a search
-         * term in the work profile. It supports similar semantics as
+         * term in the managed profile. It supports similar semantics as
          * {@link #CONTENT_SEARCH_BY_DAY_URI} and performs similar checks as
          * {@link #ENTERPRISE_CONTENT_URI}.
          */
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/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 9380695..be304b5 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);
         }
 
         /**
@@ -13879,7 +13950,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 +13957,6 @@
          */
         public static final String NATIVE_FLAGS_HEALTH_CHECK_ENABLED =
                 "native_flags_health_check_enabled";
-
     }
 
     /**
@@ -13933,13 +14002,6 @@
         /**
          * Store a name/value pair into the database.
          * <p>
-         * The method takes an optional tag to associate with the setting which can be used to clear
-         * only settings made by your package and associated with this tag by passing the tag to
-         * {@link #resetToDefaults(ContentResolver, String)}. The value of this setting can be
-         * overridden by future calls to this or other put methods, and the tag provided in those
-         * calls, which may be null, will override the tag provided in this call. Any call to a put
-         * method which does not accept a tag will effectively set the tag to null.
-         * </p><p>
          * Also the method takes an argument whether to make the value the default for this setting.
          * If the system already specified a default value, then the one passed in here will
          * <strong>not</strong> be set as the default.
@@ -13948,46 +14010,47 @@
          * @param resolver to access the database with.
          * @param name to store.
          * @param value to associate with the name.
-         * @param tag to associated with the setting.
          * @param makeDefault whether to make the value the default one.
          * @return true if the value was set, false on database errors.
          *
-         * @see #resetToDefaults(ContentResolver, String)
+         * @see #resetToDefaults(ContentResolver, int, String)
          *
          * @hide
          */
         // TODO(b/117663715): require a new write permission restricted to a single source
         @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
-        static boolean putString(@NonNull ContentResolver resolver,
-                @NonNull String name, @Nullable String value, @Nullable String tag,
-                boolean makeDefault) {
-            return sNameValueCache.putStringForUser(resolver, name, value, tag, makeDefault,
+        static boolean putString(@NonNull ContentResolver resolver, @NonNull String name,
+                @Nullable String value, boolean makeDefault) {
+            return sNameValueCache.putStringForUser(resolver, name, value, null, makeDefault,
                     resolver.getUserId());
         }
 
         /**
-         * Reset the settings to their defaults. This would reset <strong>only</strong> settings set
-         * by the caller's package. Passing in the optional tag will reset only settings changed by
-         * your package and associated with this tag.
+         * Reset the values to their defaults.
+         * <p>
+         * The method accepts an optional prefix parameter. If provided, only pairs with a name that
+         * starts with the exact prefix will be reset. Otherwise all will be reset.
          *
          * @param resolver Handle to the content resolver.
-         * @param tag Optional tag which should be associated with the settings to reset.
+         * @param resetMode The reset mode to use.
+         * @param prefix Optionally, to limit which which pairs are reset.
          *
-         * @see #putString(ContentResolver, String, String, String, boolean)
+         * @see #putString(ContentResolver, String, String, boolean)
          *
          * @hide
          */
         // TODO(b/117663715): require a new write permission restricted to a single source
         @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
-        static void resetToDefaults(@NonNull ContentResolver resolver,
-                @Nullable String tag) {
+        static void resetToDefaults(@NonNull ContentResolver resolver, @ResetMode int resetMode,
+                @Nullable String prefix) {
             try {
                 Bundle arg = new Bundle();
                 arg.putInt(CALL_METHOD_USER_KEY, resolver.getUserId());
-                if (tag != null) {
-                    arg.putString(CALL_METHOD_TAG_KEY, tag);
+                arg.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, resetMode);
+                if (prefix != null) {
+                    arg.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
                 }
-                arg.putInt(CALL_METHOD_RESET_MODE_KEY, RESET_MODE_PACKAGE_DEFAULTS);
+                arg.putInt(CALL_METHOD_RESET_MODE_KEY, resetMode);
                 IContentProvider cp = sProviderHolder.getProvider(resolver);
                 cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(),
                         CALL_METHOD_RESET_CONFIG, null, arg);
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/carrier/CarrierIdentifier.java b/core/java/android/service/carrier/CarrierIdentifier.java
index e930f40..568ca0f 100644
--- a/core/java/android/service/carrier/CarrierIdentifier.java
+++ b/core/java/android/service/carrier/CarrierIdentifier.java
@@ -71,10 +71,8 @@
      * @param gid2 group id level 2
      * @param carrierid carrier unique identifier {@link TelephonyManager#getSimCarrierId()}, used
      *                  to uniquely identify the carrier and look up the carrier configurations.
-     * @param preciseCarrierId precise carrier identifier {@link TelephonyManager#getSimPreciseCarrierId()}
-     * @hide
-     *
-     * TODO: expose this to public API
+     * @param preciseCarrierId precise carrier identifier
+     * {@link TelephonyManager#getSimPreciseCarrierId()}
      */
     public CarrierIdentifier(String mcc, String mnc, @Nullable String spn,
                              @Nullable String imsi, @Nullable String gid1, @Nullable String gid2,
@@ -155,16 +153,16 @@
     }
 
     /**
-     * Get the carrier id {@link TelephonyManager#getSimCarrierId() }
-     * @hide
+     * Returns the carrier id.
+     * @see TelephonyManager#getSimCarrierId()
      */
     public int getCarrierId() {
         return mCarrierId;
     }
 
     /**
-     * Get the precise carrier id {@link TelephonyManager#getSimPreciseCarrierId()}
-     * @hide
+     * Returns the precise carrier id.
+     * @see TelephonyManager#getSimPreciseCarrierId()
      */
     public int getPreciseCarrierId() {
         return mPreciseCarrierId;
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 5a7a83f..af7e93e 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -17,7 +17,6 @@
 package android.service.notification;
 
 import android.annotation.IntDef;
-import android.annotation.SystemApi;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Parcel;
@@ -35,7 +34,6 @@
  */
 public final class Condition implements Parcelable {
 
-    @SystemApi
     public static final String SCHEME = "condition";
 
     /** @hide */
@@ -59,14 +57,10 @@
      */
     public static final int STATE_TRUE = 1;
 
-    @SystemApi
     public static final int STATE_UNKNOWN = 2;
-    @SystemApi
     public static final int STATE_ERROR = 3;
 
-    @SystemApi
     public static final int FLAG_RELEVANT_NOW = 1 << 0;
-    @SystemApi
     public static final int FLAG_RELEVANT_ALWAYS = 1 << 1;
 
     /**
@@ -81,9 +75,7 @@
      */
     public final String summary;
 
-    @SystemApi
     public final String line1;
-    @SystemApi
     public final String line2;
 
     /**
@@ -94,9 +86,7 @@
     @State
     public final int state;
 
-    @SystemApi
     public final int flags;
-    @SystemApi
     public final int icon;
 
     /**
@@ -108,7 +98,6 @@
         this(id, summary, "", "", -1, state, FLAG_RELEVANT_ALWAYS);
     }
 
-    @SystemApi
     public Condition(Uri id, String summary, String line1, String line2, int icon,
             int state, int flags) {
         if (id == null) throw new IllegalArgumentException("id is required");
@@ -177,7 +166,6 @@
         proto.end(token);
     }
 
-    @SystemApi
     public static String stateToString(int state) {
         if (state == STATE_FALSE) return "STATE_FALSE";
         if (state == STATE_TRUE) return "STATE_TRUE";
@@ -186,7 +174,6 @@
         throw new IllegalArgumentException("state is invalid: " + state);
     }
 
-    @SystemApi
     public static String relevanceToString(int flags) {
         final boolean now = (flags & FLAG_RELEVANT_NOW) != 0;
         final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0;
@@ -219,7 +206,6 @@
         return 0;
     }
 
-    @SystemApi
     public Condition copy() {
         final Parcel parcel = Parcel.obtain();
         try {
@@ -231,14 +217,12 @@
         }
     }
 
-    @SystemApi
     public static Uri.Builder newId(Context context) {
         return new Uri.Builder()
                 .scheme(Condition.SCHEME)
                 .authority(context.getPackageName());
     }
 
-    @SystemApi
     public static boolean isValidId(Uri id, String pkg) {
         return id != null && SCHEME.equals(id.getScheme()) && pkg.equals(id.getAuthority());
     }
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 6fc689a..5203c8f 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -17,7 +17,6 @@
 package android.service.notification;
 
 import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
@@ -107,7 +106,6 @@
      */
     abstract public void onConnected();
 
-    @SystemApi
     public void onRequestConditions(int relevance) {}
 
     /**
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/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 d7359f1..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;
@@ -81,7 +82,6 @@
      * {@link android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE} permission so
      * that other applications can not abuse it.
      */
-    @SystemApi
     public static final String SERVICE_INTERFACE =
             "android.service.textclassifier.TextClassifierService";
 
@@ -194,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,
@@ -369,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
@@ -407,9 +433,7 @@
      * Callbacks for TextClassifierService results.
      *
      * @param <T> the type of the result
-     * @hide
      */
-    @SystemApi
     public interface Callback<T> {
         /**
          * Returns the result.
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index f9370a8..7e41878 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -377,6 +377,9 @@
      * @param start the inclusive start offset of the target region in the text
      * @param end the exclusive end offset of the target region in the text
      * @param textDir the text direction
+     * @param computeHyphenation true if need to compute hyphenation, otherwise false
+     * @param computeLayout true if need to compute full layout, otherwise false.
+     * @param hint pass if you already have measured paragraph.
      * @param recycle pass existing MeasuredParagraph if you want to recycle it.
      *
      * @return measured text
@@ -389,12 +392,18 @@
             @NonNull TextDirectionHeuristic textDir,
             boolean computeHyphenation,
             boolean computeLayout,
+            @Nullable MeasuredParagraph hint,
             @Nullable MeasuredParagraph recycle) {
         final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
         mt.resetAndAnalyzeBidi(text, start, end, textDir);
-        final MeasuredText.Builder builder = new MeasuredText.Builder(mt.mCopiedBuffer);
-        builder.setComputeHyphenation(computeHyphenation);
-        builder.setComputeLayout(computeLayout);
+        final MeasuredText.Builder builder;
+        if (hint == null) {
+            builder = new MeasuredText.Builder(mt.mCopiedBuffer)
+                    .setComputeHyphenation(computeHyphenation)
+                    .setComputeLayout(computeLayout);
+        } else {
+            builder = new MeasuredText.Builder(hint.mMeasuredText);
+        }
         if (mt.mTextLength == 0) {
             // Need to build empty native measured text for StaticLayout.
             // TODO: Stop creating empty measured text for empty lines.
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index b7ea012..08741d6 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -17,6 +17,7 @@
 package android.text;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -25,6 +26,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -119,6 +122,16 @@
             }
 
             /**
+             * Builder constructor from existing params.
+             */
+            public Builder(@NonNull Params params) {
+                mPaint = params.mPaint;
+                mTextDir = params.mTextDir;
+                mBreakStrategy = params.mBreakStrategy;
+                mHyphenationFrequency = params.mHyphenationFrequency;
+            }
+
+            /**
              * Set the line break strategy.
              *
              * The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}.
@@ -220,13 +233,41 @@
         }
 
         /** @hide */
-        public boolean isSameTextMetricsInternal(@NonNull TextPaint paint,
+        @IntDef(value = { UNUSABLE, NEED_RECOMPUTE, USABLE })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface CheckResultUsableResult {}
+
+        /**
+         * Constant for returning value of checkResultUsable indicating that given parameter is not
+         * compatible.
+         * @hide
+         */
+        public static final int UNUSABLE = 0;
+
+        /**
+         * Constant for returning value of checkResultUsable indicating that given parameter is not
+         * compatible but partially usable for creating new PrecomputedText.
+         * @hide
+         */
+        public static final int NEED_RECOMPUTE = 1;
+
+        /**
+         * Constant for returning value of checkResultUsable indicating that given parameter is
+         * compatible.
+         * @hide
+         */
+        public static final int USABLE = 2;
+
+        /** @hide */
+        public @CheckResultUsableResult int checkResultUsable(@NonNull TextPaint paint,
                 @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy,
                 @Layout.HyphenationFrequency int frequency) {
-            return mTextDir == textDir
-                && mBreakStrategy == strategy
-                && mHyphenationFrequency == frequency
-                && mPaint.equalsForTextMeasurement(paint);
+            if (mBreakStrategy == strategy && mHyphenationFrequency == frequency
+                    && mPaint.equalsForTextMeasurement(paint)) {
+                return mTextDir == textDir ? USABLE : NEED_RECOMPUTE;
+            } else {
+                return UNUSABLE;
+            }
         }
 
         /**
@@ -243,8 +284,8 @@
                 return false;
             }
             Params param = (Params) o;
-            return isSameTextMetricsInternal(param.mPaint, param.mTextDir, param.mBreakStrategy,
-                    param.mHyphenationFrequency);
+            return checkResultUsable(param.mPaint, param.mTextDir, param.mBreakStrategy,
+                    param.mHyphenationFrequency) == Params.USABLE;
         }
 
         @Override
@@ -321,11 +362,55 @@
      * @return A {@link PrecomputedText}
      */
     public static PrecomputedText create(@NonNull CharSequence text, @NonNull Params params) {
-        ParagraphInfo[] paraInfo = createMeasuredParagraphs(
-                text, params, 0, text.length(), true /* computeLayout */);
+        ParagraphInfo[] paraInfo = null;
+        if (text instanceof PrecomputedText) {
+            final PrecomputedText hintPct = (PrecomputedText) text;
+            final PrecomputedText.Params hintParams = hintPct.getParams();
+            final @Params.CheckResultUsableResult int checkResult =
+                    hintParams.checkResultUsable(params.mPaint, params.mTextDir,
+                            params.mBreakStrategy, params.mHyphenationFrequency);
+            switch (checkResult) {
+                case Params.USABLE:
+                    return hintPct;
+                case Params.NEED_RECOMPUTE:
+                    // To be able to use PrecomputedText for new params, at least break strategy and
+                    // hyphenation frequency must be the same.
+                    if (params.getBreakStrategy() == hintParams.getBreakStrategy()
+                            && params.getHyphenationFrequency()
+                                == hintParams.getHyphenationFrequency()) {
+                        paraInfo = createMeasuredParagraphsFromPrecomputedText(
+                                hintPct, params, true /* compute layout */);
+                    }
+                    break;
+                case Params.UNUSABLE:
+                    // Unable to use anything in PrecomputedText. Create PrecomputedText as the
+                    // normal text input.
+            }
+
+        }
+        if (paraInfo == null) {
+            paraInfo = createMeasuredParagraphs(
+                    text, params, 0, text.length(), true /* computeLayout */);
+        }
         return new PrecomputedText(text, 0, text.length(), params, paraInfo);
     }
 
+    private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText(
+            @NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) {
+        final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+                && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
+        ArrayList<ParagraphInfo> result = new ArrayList<>();
+        for (int i = 0; i < pct.getParagraphCount(); ++i) {
+            final int paraStart = pct.getParagraphStart(i);
+            final int paraEnd = pct.getParagraphEnd(i);
+            result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+                    params.getTextPaint(), pct, paraStart, paraEnd, params.getTextDirection(),
+                    needHyphenation, computeLayout, pct.getMeasuredParagraph(i),
+                    null /* no recycle */)));
+        }
+        return result.toArray(new ParagraphInfo[result.size()]);
+    }
+
     /** @hide */
     public static ParagraphInfo[] createMeasuredParagraphs(
             @NonNull CharSequence text, @NonNull Params params,
@@ -350,7 +435,8 @@
 
             result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
                     params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(),
-                    needHyphenation, computeLayout, null /* no recycle */)));
+                    needHyphenation, computeLayout, null /* no hint */,
+                    null /* no recycle */)));
         }
         return result.toArray(new ParagraphInfo[result.size()]);
     }
@@ -434,12 +520,15 @@
      * Returns true if the given TextPaint gives the same result of text layout for this text.
      * @hide
      */
-    public boolean canUseMeasuredResult(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
-            @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint,
-            @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) {
-        return mStart == start
-            && mEnd == end
-            && mParams.isSameTextMetricsInternal(paint, textDir, strategy, frequency);
+    public @Params.CheckResultUsableResult int checkResultUsable(@IntRange(from = 0) int start,
+            @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir,
+            @NonNull TextPaint paint, @Layout.BreakStrategy int strategy,
+            @Layout.HyphenationFrequency int frequency) {
+        if (mStart != start || mEnd != end) {
+            return Params.UNUSABLE;
+        } else {
+            return mParams.checkResultUsable(paint, textDir, strategy, frequency);
+        }
     }
 
     /** @hide */
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 8cb18b2..3d0c662 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -650,10 +650,26 @@
         final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
         if (source instanceof PrecomputedText) {
             PrecomputedText precomputed = (PrecomputedText) source;
-            if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint,
-                      b.mBreakStrategy, b.mHyphenationFrequency)) {
-                // Some parameters are different from the ones when measured text is created.
-                paragraphInfo = precomputed.getParagraphInfo();
+            final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
+                    precomputed.checkResultUsable(bufStart, bufEnd, textDir, paint,
+                            b.mBreakStrategy, b.mHyphenationFrequency);
+            switch (checkResult) {
+                case PrecomputedText.Params.UNUSABLE:
+                    break;
+                case PrecomputedText.Params.NEED_RECOMPUTE:
+                    final PrecomputedText.Params newParams =
+                            new PrecomputedText.Params.Builder(paint)
+                                .setBreakStrategy(b.mBreakStrategy)
+                                .setHyphenationFrequency(b.mHyphenationFrequency)
+                                .setTextDirection(textDir)
+                                .build();
+                    precomputed = PrecomputedText.create(precomputed, newParams);
+                    paragraphInfo = precomputed.getParagraphInfo();
+                    break;
+                case PrecomputedText.Params.USABLE:
+                    // Some parameters are different from the ones when measured text is created.
+                    paragraphInfo = precomputed.getParagraphInfo();
+                    break;
             }
         }
 
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/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/InputEventCompatProcessor.java b/core/java/android/view/InputEventCompatProcessor.java
new file mode 100644
index 0000000..ff8407a
--- /dev/null
+++ b/core/java/android/view/InputEventCompatProcessor.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Compatibility processor for InputEvents that allows events to be adjusted before and
+ * after it is sent to the application.
+ *
+ * {@hide}
+ */
+public class InputEventCompatProcessor {
+
+    protected Context mContext;
+    protected int mTargetSdkVersion;
+
+    /** List of events to be used to return the processed events */
+    private List<InputEvent> mProcessedEvents;
+
+    public InputEventCompatProcessor(Context context) {
+        mContext = context;
+        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+        mProcessedEvents = new ArrayList<>();
+    }
+
+    /**
+     * Processes the InputEvent for compatibility before it is sent to the app, allowing for the
+     * generation of more than one event if necessary.
+     *
+     * @param e The InputEvent to process
+     * @return The list of adjusted events, or null if no adjustments are needed. Do not keep a
+     *         reference to the output as the list is reused.
+     */
+    public List<InputEvent> processInputEventForCompatibility(InputEvent e) {
+        if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
+            mProcessedEvents.clear();
+            MotionEvent motion = (MotionEvent) e;
+            final int mask =
+                    MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
+            final int buttonState = motion.getButtonState();
+            final int compatButtonState = (buttonState & mask) >> 4;
+            if (compatButtonState != 0) {
+                motion.setButtonState(buttonState | compatButtonState);
+            }
+            mProcessedEvents.add(motion);
+            return mProcessedEvents;
+        }
+        return null;
+    }
+
+    /**
+     * Processes the InputEvent for compatibility before it is finished by calling
+     * InputEventReceiver#finishInputEvent().
+     *
+     * @param e The InputEvent to process
+     * @return The InputEvent to finish, or null if it should not be finished
+     */
+    public InputEvent processInputEventBeforeFinish(InputEvent e) {
+        // No changes needed
+        return e;
+    }
+}
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/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ab01085..a006e5d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -36,6 +36,8 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -129,6 +131,12 @@
             int width, int height);
     private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
             IBinder displayToken);
+    private static native DisplayedContentSamplingAttributes
+            nativeGetDisplayedContentSamplingAttributes(IBinder displayToken);
+    private static native boolean nativeSetDisplayedContentSamplingEnabled(IBinder displayToken,
+            boolean enable, int componentMask, int maxFrames);
+    private static native DisplayedContentSample nativeGetDisplayedContentSample(
+            IBinder displayToken, long numFrames, long timestamp);
     private static native int nativeGetActiveConfig(IBinder displayToken);
     private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
     private static native int[] nativeGetDisplayColorModes(IBinder displayToken);
@@ -1164,6 +1172,45 @@
         return nativeGetActiveConfig(displayToken);
     }
 
+    /**
+     * @hide
+     */
+    public static DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes(
+            IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        return nativeGetDisplayedContentSamplingAttributes(displayToken);
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean setDisplayedContentSamplingEnabled(
+            IBinder displayToken, boolean enable, int componentMask, int maxFrames) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        final int maxColorComponents = 4;
+        if ((componentMask >> maxColorComponents) != 0) {
+            throw new IllegalArgumentException("invalid componentMask when enabling sampling");
+        }
+        return nativeSetDisplayedContentSamplingEnabled(
+                displayToken, enable, componentMask, maxFrames);
+    }
+
+    /**
+     * @hide
+     */
+    public static DisplayedContentSample getDisplayedContentSample(
+            IBinder displayToken, long maxFrames, long timestamp) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+        return nativeGetDisplayedContentSample(displayToken, maxFrames, timestamp);
+    }
+
+
     public static boolean setActiveConfig(IBinder displayToken, int id) {
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4b97fbe..3f7a512 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -124,6 +124,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
 
@@ -543,6 +544,8 @@
 
     private boolean mNeedsRendererSetup;
 
+    private final InputEventCompatProcessor mInputCompatProcessor;
+
     /**
      * Consistency verifier for debugging purposes.
      */
@@ -598,6 +601,25 @@
         mChoreographer = Choreographer.getInstance();
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
 
+        String processorOverrideName = context.getResources().getString(
+                                    R.string.config_inputEventCompatProcessorOverrideClassName);
+        if (processorOverrideName.isEmpty()) {
+            // No compatibility processor override, using default.
+            mInputCompatProcessor = new InputEventCompatProcessor(context);
+        } else {
+            InputEventCompatProcessor compatProcessor = null;
+            try {
+                final Class<? extends InputEventCompatProcessor> klass =
+                        (Class<? extends InputEventCompatProcessor>) Class.forName(
+                                processorOverrideName);
+                compatProcessor = klass.getConstructor(Context.class).newInstance(context);
+            } catch (Exception e) {
+                Log.e(TAG, "Unable to create the InputEventCompatProcessor. ", e);
+            } finally {
+                mInputCompatProcessor = compatProcessor;
+            }
+        }
+
         if (!sCompatibilityDone) {
             sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
 
@@ -1854,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);
@@ -1863,6 +1886,7 @@
             insets = insets.consumeDisplayCutout();
         }
         host.dispatchApplyWindowInsets(insets);
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
     InsetsController getInsetsController() {
@@ -7158,6 +7182,7 @@
         public static final int FLAG_FINISHED_HANDLED = 1 << 3;
         public static final int FLAG_RESYNTHESIZED = 1 << 4;
         public static final int FLAG_UNHANDLED = 1 << 5;
+        public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6;
 
         public QueuedInputEvent mNext;
 
@@ -7250,7 +7275,6 @@
     @UnsupportedAppUsage
     void enqueueInputEvent(InputEvent event,
             InputEventReceiver receiver, int flags, boolean processImmediately) {
-        adjustInputEventForCompatibility(event);
         QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
 
         // Always enqueue the input event in order, regardless of its time stamp.
@@ -7353,7 +7377,22 @@
 
         if (q.mReceiver != null) {
             boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
-            q.mReceiver.finishInputEvent(q.mEvent, handled);
+            boolean modified = (q.mFlags & QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY) != 0;
+            if (modified) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventBeforeFinish");
+                InputEvent processedEvent;
+                try {
+                    processedEvent =
+                            mInputCompatProcessor.processInputEventBeforeFinish(q.mEvent);
+                } finally {
+                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                }
+                if (processedEvent != null) {
+                    q.mReceiver.finishInputEvent(processedEvent, handled);
+                }
+            } else {
+                q.mReceiver.finishInputEvent(q.mEvent, handled);
+            }
         } else {
             q.mEvent.recycleIfNeededAfterDispatch();
         }
@@ -7361,19 +7400,6 @@
         recycleQueuedInputEvent(q);
     }
 
-    private void adjustInputEventForCompatibility(InputEvent e) {
-        if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
-            MotionEvent motion = (MotionEvent) e;
-            final int mask =
-                MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
-            final int buttonState = motion.getButtonState();
-            final int compatButtonState = (buttonState & mask) >> 4;
-            if (compatButtonState != 0) {
-                motion.setButtonState(buttonState | compatButtonState);
-            }
-        }
-    }
-
     static boolean isTerminalInputEvent(InputEvent event) {
         if (event instanceof KeyEvent) {
             final KeyEvent keyEvent = (KeyEvent)event;
@@ -7444,7 +7470,28 @@
 
         @Override
         public void onInputEvent(InputEvent event) {
-            enqueueInputEvent(event, this, 0, true);
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
+            List<InputEvent> processedEvents;
+            try {
+                processedEvents =
+                    mInputCompatProcessor.processInputEventForCompatibility(event);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+            if (processedEvents != null) {
+                if (processedEvents.isEmpty()) {
+                    // InputEvent consumed by mInputCompatProcessor
+                    finishInputEvent(event, true);
+                } else {
+                    for (int i = 0; i < processedEvents.size(); i++) {
+                        enqueueInputEvent(
+                                processedEvents.get(i), this,
+                                QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
+                    }
+                }
+            } else {
+                enqueueInputEvent(event, this, 0, true);
+            }
         }
 
         @Override
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index f7c9a0b..763ce4f 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Rect;
@@ -749,7 +748,6 @@
      *
      * @param callback The callback to invoke when the frame is committed.
      */
-    @TestApi
     public void registerFrameCommitCallback(@NonNull Runnable callback) {
         checkIsAlive();
         if (mOnFrameCommitListeners == null) {
@@ -772,7 +770,6 @@
      *         not be invoked. If false is returned then the callback was either never added
      *         or may already be pending execution and was unable to be removed
      */
-    @TestApi
     public boolean unregisterFrameCommitCallback(@NonNull Runnable callback) {
         checkIsAlive();
         if (mOnFrameCommitListeners == null) {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 88b9c80..c5c1bca 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1004,6 +1004,36 @@
     }
 
     /**
+     * Returns accessibility window id from window token. Accessibility window id is the one
+     * returned from AccessibilityWindowInfo.getId(). Only available for the system process.
+     *
+     * @param windowToken Window token to find accessibility window id.
+     * @return Accessibility window id for the window token.
+     *   AccessibilityWindowInfo.UNDEFINED_WINDOW_ID if accessibility window id not available for
+     *   the token.
+     * @hide
+     */
+    @SystemApi
+    public int getAccessibilityWindowId(IBinder windowToken) {
+        if (windowToken == null) {
+            return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+        }
+
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+            }
+        }
+        try {
+            return service.getAccessibilityWindowId(windowToken);
+        } catch (RemoteException e) {
+            return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+        }
+    }
+
+    /**
      * Sets the current state and notifies listeners, if necessary.
      *
      * @param stateFlags The state flags.
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 2767a82..38dac94 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -76,5 +76,8 @@
     // System process only
     boolean sendFingerprintGesture(int gestureKeyCode);
 
+    // System process only
+    int getAccessibilityWindowId(IBinder windowToken);
+
     long getRecommendedTimeoutMillis();
 }
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 1889692..cc0264a 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -31,7 +31,9 @@
 import android.os.HandlerThread;
 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;
@@ -98,6 +100,12 @@
      */
     public static final int STATE_DISABLED = 3;
 
+    /**
+     * Handler message used to flush the buffer.
+     */
+    private static final int MSG_FLUSH = 1;
+
+
     private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
 
     /**
@@ -106,6 +114,12 @@
     // TODO(b/111276913): use settings
     private static final int MAX_BUFFER_SIZE = 100;
 
+    /**
+     * Frequency the buffer is flushed if stale.
+     */
+    // TODO(b/111276913): use settings
+    private static final int FLUSHING_FREQUENCY_MS = 5_000;
+
     @NonNull
     private final AtomicBoolean mDisabled = new AtomicBoolean();
 
@@ -136,6 +150,9 @@
     // held at the Application level
     private final Handler mHandler;
 
+    // Used just for debugging purposes (on dump)
+    private long mNextFlush;
+
     /** @hide */
     public ContentCaptureManager(@NonNull Context context,
             @Nullable IContentCaptureManager service) {
@@ -207,9 +224,17 @@
             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...
+
+        // 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;
         }
 
@@ -236,10 +261,38 @@
             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(ContentCaptureManager::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() {
+        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
@@ -307,6 +360,7 @@
         mApplicationToken = null;
         mComponentName = null;
         mEvents = null;
+        mHandler.removeMessages(MSG_FLUSH);
     }
 
     /**
@@ -443,7 +497,7 @@
             pw.print(prefix2); pw.print("component name: ");
             pw.println(mComponentName.flattenToShortString());
         }
-        if (mEvents != null) {
+        if (mEvents != null && !mEvents.isEmpty()) {
             final int numberEvents = mEvents.size();
             pw.print(prefix2); pw.print("buffered events: "); pw.print(numberEvents);
             pw.print('/'); pw.println(MAX_BUFFER_SIZE);
@@ -455,6 +509,9 @@
                     pw.println();
                 }
             }
+            pw.print(prefix2); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
+            pw.print(prefix2); pw.print("next flush: ");
+            TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println();
         }
     }
 
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
index 797b861..b41096c 100644
--- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -16,7 +16,6 @@
 
 package android.view.textclassifier;
 
-import android.annotation.NonNull;
 import android.app.Person;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -30,6 +29,7 @@
 import java.util.Deque;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -57,9 +57,9 @@
      * </ul>
      * User A will be encoded as 2, user B will be encoded as 1 and local user will be encoded as 0.
      */
-    @NonNull
     public static ActionsSuggestionsModel.ConversationMessage[] toNativeMessages(
-            @NonNull List<ConversationActions.Message> messages) {
+            List<ConversationActions.Message> messages,
+            Function<CharSequence, String> languageDetector) {
         List<ConversationActions.Message> messagesWithText =
                 messages.stream()
                         .filter(message -> !TextUtils.isEmpty(message.getText()))
@@ -67,31 +67,18 @@
         if (messagesWithText.isEmpty()) {
             return new ActionsSuggestionsModel.ConversationMessage[0];
         }
-        int size = messagesWithText.size();
-        // If the last message (the most important one) does not have the Person object, we will
-        // just use the last message and consider this message is sent from a remote user.
-        ConversationActions.Message lastMessage = messages.get(size - 1);
-        boolean useLastMessageOnly = lastMessage.getAuthor() == null;
-        if (useLastMessageOnly) {
-            return new ActionsSuggestionsModel.ConversationMessage[]{
-                    new ActionsSuggestionsModel.ConversationMessage(
-                            FIRST_NON_LOCAL_USER,
-                            lastMessage.getText().toString(),
-                            0,
-                            null)};
-        }
-
-        // Encode the messages in the reverse order, stop whenever the Person object is missing.
         Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>();
         PersonEncoder personEncoder = new PersonEncoder();
+        int size = messagesWithText.size();
         for (int i = size - 1; i >= 0; i--) {
             ConversationActions.Message message = messagesWithText.get(i);
-            if (message.getAuthor() == null) {
-                break;
-            }
+            long referenceTime = message.getReferenceTime() == null
+                    ? 0
+                    : message.getReferenceTime().toInstant().toEpochMilli();
             nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage(
                     personEncoder.encode(message.getAuthor()),
-                    message.getText().toString(), 0, null));
+                    message.getText().toString(), referenceTime,
+                    languageDetector.apply(message.getText())));
         }
         return nativeMessages.toArray(
                 new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]);
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 04b94b0..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 {
 
@@ -349,17 +365,31 @@
         /**
          * Represents the local user.
          *
-         * @see Builder#setAuthor(Person)
+         * @see Builder#Builder(Person)
          */
         public static final Person PERSON_USER_LOCAL =
                 new Person.Builder()
                         .setKey("text-classifier-conversation-actions-local-user")
                         .build();
 
+        /**
+         * Represents the remote user.
+         * <p>
+         * If possible, you are suggested to create a {@link Person} object that can identify
+         * the remote user better, so that the underlying model could differentiate between
+         * different remote users.
+         *
+         * @see Builder#Builder(Person)
+         */
+        public static final Person PERSON_USER_REMOTE =
+                new Person.Builder()
+                        .setKey("text-classifier-conversation-actions-remote-user")
+                        .build();
+
         @Nullable
         private final Person mAuthor;
         @Nullable
-        private final ZonedDateTime mComposeTime;
+        private final ZonedDateTime mReferenceTime;
         @Nullable
         private final CharSequence mText;
         @NonNull
@@ -367,18 +397,18 @@
 
         private Message(
                 @Nullable Person author,
-                @Nullable ZonedDateTime composeTime,
+                @Nullable ZonedDateTime referenceTime,
                 @Nullable CharSequence text,
                 @NonNull Bundle bundle) {
             mAuthor = author;
-            mComposeTime = composeTime;
+            mReferenceTime = referenceTime;
             mText = text;
             mExtras = Preconditions.checkNotNull(bundle);
         }
 
         private Message(Parcel in) {
             mAuthor = in.readParcelable(null);
-            mComposeTime =
+            mReferenceTime =
                     in.readInt() == 0
                             ? null
                             : ZonedDateTime.parse(
@@ -390,9 +420,9 @@
         @Override
         public void writeToParcel(Parcel parcel, int flags) {
             parcel.writeParcelable(mAuthor, flags);
-            parcel.writeInt(mComposeTime != null ? 1 : 0);
-            if (mComposeTime != null) {
-                parcel.writeString(mComposeTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+            parcel.writeInt(mReferenceTime != null ? 1 : 0);
+            if (mReferenceTime != null) {
+                parcel.writeString(mReferenceTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
             }
             parcel.writeCharSequence(mText);
             parcel.writeBundle(mExtras);
@@ -417,15 +447,18 @@
                 };
 
         /** Returns the person that composed the message. */
-        @Nullable
+        @NonNull
         public Person getAuthor() {
             return mAuthor;
         }
 
-        /** Returns the compose time of the message. */
+        /**
+         * Returns the reference time of the message, for example it could be the compose or send
+         * time of this message.
+         */
         @Nullable
-        public ZonedDateTime getTime() {
-            return mComposeTime;
+        public ZonedDateTime getReferenceTime() {
+            return mReferenceTime;
         }
 
         /** Returns the text of the message. */
@@ -451,34 +484,38 @@
             @Nullable
             private Person mAuthor;
             @Nullable
-            private ZonedDateTime mComposeTime;
+            private ZonedDateTime mReferenceTime;
             @Nullable
             private CharSequence mText;
             @Nullable
             private Bundle mExtras;
 
             /**
-             * Sets the person who composed this message.
-             * <p>
-             * Use {@link #PERSON_USER_LOCAL} to represent the local user.
+             * Constructs a builder.
+             *
+             * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL}
+             *               to represent the local user. If it is not possible to identify the
+             *               remote user that the local user is conversing with, use
+             *               {@link #PERSON_USER_REMOTE} to represent a remote user.
              */
-            @NonNull
-            public Builder setAuthor(@Nullable Person author) {
-                mAuthor = author;
-                return this;
+            public Builder(@NonNull Person author) {
+                mAuthor = Preconditions.checkNotNull(author);
             }
 
-            /** Sets the text of this message */
+            /** Sets the text of this message. */
             @NonNull
             public Builder setText(@Nullable CharSequence text) {
                 mText = text;
                 return this;
             }
 
-            /** Sets the compose time of this message */
+            /**
+             * Sets the reference time of this message, for example it could be the compose or send
+             * time of this message.
+             */
             @NonNull
-            public Builder setComposeTime(@Nullable ZonedDateTime composeTime) {
-                mComposeTime = composeTime;
+            public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
+                mReferenceTime = referenceTime;
                 return this;
             }
 
@@ -494,7 +531,7 @@
             public Message build() {
                 return new Message(
                         mAuthor,
-                        mComposeTime,
+                        mReferenceTime,
                         mText == null ? null : new SpannedString(mText),
                         mExtras == null ? new Bundle() : mExtras.deepCopy());
             }
@@ -657,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;
@@ -696,6 +735,7 @@
             parcel.writeParcelableList(mConversation, flags);
             parcel.writeParcelable(mTypeConfig, flags);
             parcel.writeInt(mMaxSuggestions);
+            parcel.writeString(mConversationId);
             parcel.writeStringList(mHints);
             parcel.writeString(mCallingPackageName);
         }
@@ -738,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
@@ -773,6 +823,8 @@
             private TypeConfig mTypeConfig;
             private int mMaxSuggestions;
             @Nullable
+            private String mConversationId;
+            @Nullable
             @Hint
             private List<String> mHints;
 
@@ -802,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.
              */
@@ -812,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() {
@@ -819,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 a2536cb..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";
 
     /**
@@ -162,6 +164,14 @@
     TextClassifier NO_OP = new TextClassifier() {};
 
     /**
+     * Used as a boolean value to indicate the intent is generated by TextClassifier.
+     * <p>
+     * All {@link TextClassifier} implementations should set this boolean extra to be true in their
+     * generated intents.
+     */
+    String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
+
+    /**
      * Returns suggested text selection start and end indices, recognized entity types, and their
      * associated confidence scores. The entity types are ordered from highest to lowest scoring.
      *
@@ -341,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/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl b/core/java/android/view/textclassifier/TextClassifierEvent.aidl
similarity index 76%
copy from telephony/java/com/android/internal/telephony/rcs/IRcs.aidl
copy to core/java/android/view/textclassifier/TextClassifierEvent.aidl
index 4c289ac..86fa4f8 100644
--- a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.aidl
@@ -14,12 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony.rcs;
+package android.view.textclassifier;
 
-interface IRcs {
-    // RcsManager APIs
-    void deleteThread(int threadId);
-
-    // RcsThread APIs
-    int getMessageCount(int rcsThreadId);
-}
\ No newline at end of file
+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 8e14dfd..9b0f9c6 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -139,7 +139,7 @@
                         FACTORY_MODEL_DIR,
                         LANG_ID_FACTORY_MODEL_FILENAME_REGEX,
                         UPDATED_LANG_ID_MODEL_FILE,
-                        fd -> -1, // TODO: Replace this with LangIdModel.getVersion(fd)
+                        LangIdModel::getVersion,
                         fd -> ModelFileManager.ModelFile.LANGUAGE_INDEPENDENT));
         mActionsModelFileManager = new ModelFileManager(
                 new ModelFileManager.ModelFileSupplierImpl(
@@ -341,6 +341,11 @@
         }
     }
 
+    @Override
+    public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
+        // TODO: Implement.
+    }
+
     /** @inheritDoc */
     @Override
     public TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
@@ -374,7 +379,8 @@
                 return mFallback.suggestConversationActions(request);
             }
             ActionsSuggestionsModel.ConversationMessage[] nativeMessages =
-                    ActionsSuggestionsHelper.toNativeMessages(request.getConversation());
+                    ActionsSuggestionsHelper.toNativeMessages(request.getConversation(),
+                            this::detectLanguageTagsFromText);
             if (nativeMessages.length == 0) {
                 return mFallback.suggestConversationActions(request);
             }
@@ -386,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();
@@ -399,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);
@@ -407,6 +416,26 @@
         return mFallback.suggestConversationActions(request);
     }
 
+    @Nullable
+    private String detectLanguageTagsFromText(CharSequence text) {
+        TextLanguage.Request request = new TextLanguage.Request.Builder(text).build();
+        TextLanguage textLanguage = detectLanguage(request);
+        int localeHypothesisCount = textLanguage.getLocaleHypothesisCount();
+        List<String> languageTags = new ArrayList<>();
+        // TODO: Reconsider this and probably make the score threshold configurable.
+        for (int i = 0; i < localeHypothesisCount; i++) {
+            ULocale locale = textLanguage.getLocale(i);
+            if (textLanguage.getConfidenceScore(locale) < 0.5) {
+                break;
+            }
+            languageTags.add(locale.toLanguageTag());
+        }
+        if (languageTags.isEmpty()) {
+            return LocaleList.getDefault().toLanguageTags();
+        }
+        return String.join(",", languageTags);
+    }
+
     private Collection<String> resolveActionTypesFromRequest(ConversationActions.Request request) {
         List<String> defaultActionTypes =
                 request.getHints().contains(ConversationActions.HINT_FOR_NOTIFICATION)
@@ -777,6 +806,9 @@
             if (foreignText) {
                 insertTranslateAction(actions, context, text);
             }
+            actions.forEach(
+                    action -> action.getIntent()
+                            .putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, true));
             return actions;
         }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 522be81..ddf1190 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -719,7 +719,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 +5095,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;
     }
@@ -6028,14 +6039,22 @@
             if (mTextDir == null) {
                 mTextDir = getTextDirectionHeuristic();
             }
-            if (!precomputed.getParams().isSameTextMetricsInternal(
-                    getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency)) {
-                throw new IllegalArgumentException(
+            final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
+                    precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
+                            mHyphenationFrequency);
+            switch (checkResult) {
+                case PrecomputedText.Params.UNUSABLE:
+                    throw new IllegalArgumentException(
                         "PrecomputedText's Parameters don't match the parameters of this TextView."
                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
                         + "to override the settings of this TextView: "
                         + "PrecomputedText: " + precomputed.getParams()
                         + "TextView: " + getTextMetricsParams());
+                case PrecomputedText.Params.NEED_RECOMPUTE:
+                    precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
+                    break;
+                case PrecomputedText.Params.USABLE:
+                    // pass through
             }
         } else if (type == BufferType.SPANNABLE || mMovement != null) {
             text = mSpannableFactory.newSpannable(text);
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 19d8a83..9ee583a 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -1396,10 +1396,10 @@
         return as;
     }
 
-    // See b/118826162 -- to avoid logspaming, we rate limit the WTF.
-    private static final long INVERSE_PROC_STATE_WTF_MIN_INTERVAL_MS = 10_000L;
-    private long mNextInverseProcStateWtfUptime;
-    private int mSkippedInverseProcStateWtfCount;
+    // See b/118826162 -- to avoid logspaming, we rate limit the warnings.
+    private static final long INVERSE_PROC_STATE_WARNING_MIN_INTERVAL_MS = 10_000L;
+    private long mNextInverseProcStateWarningUptime;
+    private int mSkippedInverseProcStateWarningCount;
 
     public void updateTrackingAssociationsLocked(int curSeq, long now) {
         final int NUM = mTrackingAssociations.size();
@@ -1423,18 +1423,19 @@
                         act.stopActive(now);
                         if (act.mProcState < procState) {
                             final long nowUptime = SystemClock.uptimeMillis();
-                            if (mNextInverseProcStateWtfUptime > nowUptime) {
-                                mSkippedInverseProcStateWtfCount++;
+                            if (mNextInverseProcStateWarningUptime > nowUptime) {
+                                mSkippedInverseProcStateWarningCount++;
                             } else {
                                 // TODO We still see it during boot related to GMS-core.
                                 // b/118826162
-                                Slog.wtf(TAG, "Tracking association " + act + " whose proc state "
+                                Slog.w(TAG, "Tracking association " + act + " whose proc state "
                                         + act.mProcState + " is better than process " + proc
                                         + " proc state " + procState
-                                        + " (" +  mSkippedInverseProcStateWtfCount + " skipped)");
-                                mSkippedInverseProcStateWtfCount = 0;
-                                mNextInverseProcStateWtfUptime =
-                                        nowUptime + INVERSE_PROC_STATE_WTF_MIN_INTERVAL_MS;
+                                        + " (" +  mSkippedInverseProcStateWarningCount
+                                        + " skipped)");
+                                mSkippedInverseProcStateWarningCount = 0;
+                                mNextInverseProcStateWarningUptime =
+                                        nowUptime + INVERSE_PROC_STATE_WARNING_MIN_INTERVAL_MS;
                             }
                         }
                     }
@@ -1474,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++) {
@@ -1484,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) {
@@ -1502,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);
@@ -1549,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())) {
@@ -1578,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: ");
@@ -1596,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/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 65213c0..65b9fad 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -81,6 +81,11 @@
     public static final int MOUNT_EXTERNAL_READ = IVold.REMOUNT_MODE_READ;
     /** Read-write external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE;
+    /**
+     * Mount mode for package installers which should give them access to
+     * all obb dirs in addition to their package sandboxes
+     */
+    public static final int MOUNT_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
     /** Read-write external storage should be mounted instead of package sandbox */
     public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL;
 
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 4a94ec4..f182c4d 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -656,7 +656,9 @@
                     mountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
                 } else if (arg.equals("--mount-external-full")) {
                     mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
-                }  else if (arg.equals("--query-abi-list")) {
+                }  else if (arg.equals("--mount-external-installer")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
+                } else if (arg.equals("--query-abi-list")) {
                     abiListQuery = true;
                 } else if (arg.equals("--get-pid")) {
                     pidQuery = true;
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/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index b00e6fd..841e5b6 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -25,6 +25,7 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.storage.StorageManager;
 import android.permission.PermissionManager.SplitPermissionInfo;
 import android.text.TextUtils;
@@ -66,8 +67,12 @@
     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
+    private static final String SKU_PROPERTY = "ro.boot.product.hardware.sku";
+
     // Group-ids that are given to all packages as read from etc/permissions/*.xml.
     int[] mGlobalGids;
 
@@ -191,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) {
@@ -316,6 +327,10 @@
         return Collections.emptyMap();
     }
 
+    public ArrayMap<String, ArraySet<String>> getAllowedAssociations() {
+        return mAllowedAssociations;
+    }
+
     SystemConfig() {
         // Read configuration from system
         readPermissions(Environment.buildPath(
@@ -325,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);
@@ -344,8 +360,19 @@
         readPermissions(Environment.buildPath(
                 Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
 
-        // Allow OEM to customize features and OEM permissions
-        int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS;
+        String skuProperty = SystemProperties.get(SKU_PROPERTY, "");
+        if (!skuProperty.isEmpty()) {
+            String skuDir = "sku_" + skuProperty;
+
+            readPermissions(Environment.buildPath(
+                    Environment.getOdmDirectory(), "etc", "sysconfig", skuDir), odmPermissionFlag);
+            readPermissions(Environment.buildPath(
+                    Environment.getOdmDirectory(), "etc", "permissions", skuDir),
+                    odmPermissionFlag);
+        }
+
+        // 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(
@@ -380,6 +407,10 @@
         // Iterate over the files in the directory and scan .xml files
         File platformFile = null;
         for (File f : libraryDir.listFiles()) {
+            if (!f.isFile()) {
+                continue;
+            }
+
             // We'll read platform.xml last
             if (f.getPath().endsWith("etc/permissions/platform.xml")) {
                 platformFile = f;
@@ -404,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 {
@@ -434,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) {
@@ -449,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.bp b/core/jni/Android.bp
index 8962e1d..43f8d00 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -161,6 +161,7 @@
         "android/graphics/pdf/PdfUtils.cpp",
         "android/graphics/text/LineBreaker.cpp",
         "android/graphics/text/MeasuredText.cpp",
+        "android_media_AudioEffectDescriptor.cpp",
         "android_media_AudioRecord.cpp",
         "android_media_AudioSystem.cpp",
         "android_media_AudioTrack.cpp",
@@ -263,6 +264,7 @@
         "libEGL",
         "libGLESv1_CM",
         "libGLESv2",
+        "libGLESv3",
         "libvulkan",
         "libziparchive",
         "libETC1",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f9879cc..687b105 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -103,6 +103,7 @@
 extern int register_android_hardware_UsbRequest(JNIEnv *env);
 extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
 
+extern int register_android_media_AudioEffectDescriptor(JNIEnv *env);
 extern int register_android_media_AudioRecord(JNIEnv *env);
 extern int register_android_media_AudioSystem(JNIEnv *env);
 extern int register_android_media_AudioTrack(JNIEnv *env);
@@ -1456,6 +1457,7 @@
     REG_JNI(register_android_hardware_UsbDeviceConnection),
     REG_JNI(register_android_hardware_UsbRequest),
     REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
+    REG_JNI(register_android_media_AudioEffectDescriptor),
     REG_JNI(register_android_media_AudioSystem),
     REG_JNI(register_android_media_AudioRecord),
     REG_JNI(register_android_media_AudioTrack),
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/graphics/text/MeasuredText.cpp b/core/jni/android/graphics/text/MeasuredText.cpp
index 0bfadb4..d7d96fb 100644
--- a/core/jni/android/graphics/text/MeasuredText.cpp
+++ b/core/jni/android/graphics/text/MeasuredText.cpp
@@ -84,14 +84,14 @@
 
 // Regular JNI
 static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr,
-                                      jcharArray javaText, jboolean computeHyphenation,
-                                      jboolean computeLayout) {
+                                jlong hintPtr, jcharArray javaText, jboolean computeHyphenation,
+                                jboolean computeLayout) {
     ScopedCharArrayRO text(env, javaText);
     const minikin::U16StringPiece textBuffer(text.get(), text.size());
 
     // Pass the ownership to Java.
-    return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation,
-                                                computeLayout).release());
+    return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation, computeLayout,
+                                                toMeasuredParagraph(hintPtr)).release());
 }
 
 // Regular JNI
@@ -147,7 +147,7 @@
     {"nInitBuilder", "()J", (void*) nInitBuilder},
     {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
     {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
-    {"nBuildMeasuredText", "(J[CZZ)J", (void*) nBuildMeasuredText},
+    {"nBuildMeasuredText", "(JJ[CZZ)J", (void*) nBuildMeasuredText},
     {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
 };
 
diff --git a/core/jni/android_media_AudioEffectDescriptor.cpp b/core/jni/android_media_AudioEffectDescriptor.cpp
new file mode 100644
index 0000000..5175a05
--- /dev/null
+++ b/core/jni/android_media_AudioEffectDescriptor.cpp
@@ -0,0 +1,123 @@
+/*
+ * 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 "core_jni_helpers.h"
+#include "android_media_AudioErrors.h"
+#include "media/AudioEffect.h"
+
+using namespace android;
+
+static jclass gAudioEffectDescriptorClass;
+static jmethodID gAudioEffectDescriptorCstor;
+
+namespace android {
+
+jclass audioEffectDescriptorClass() {
+    return gAudioEffectDescriptorClass;
+}
+
+jint convertAudioEffectDescriptorFromNative(JNIEnv* env, jobject* jDescriptor,
+        const effect_descriptor_t* nDescriptor)
+{
+    jstring jType;
+    jstring jUuid;
+    jstring jConnect;
+    jstring jName;
+    jstring jImplementor;
+    char str[EFFECT_STRING_LEN_MAX];
+
+    if ((nDescriptor->flags & EFFECT_FLAG_TYPE_MASK)
+        == EFFECT_FLAG_TYPE_AUXILIARY) {
+        jConnect = env->NewStringUTF("Auxiliary");
+    } else if ((nDescriptor->flags & EFFECT_FLAG_TYPE_MASK)
+        == EFFECT_FLAG_TYPE_INSERT) {
+        jConnect = env->NewStringUTF("Insert");
+    } else if ((nDescriptor->flags & EFFECT_FLAG_TYPE_MASK)
+        == EFFECT_FLAG_TYPE_PRE_PROC) {
+        jConnect = env->NewStringUTF("Pre Processing");
+    } else {
+        return (jint) AUDIO_JAVA_BAD_VALUE;
+    }
+
+    AudioEffect::guidToString(&nDescriptor->type, str, EFFECT_STRING_LEN_MAX);
+    jType = env->NewStringUTF(str);
+
+    AudioEffect::guidToString(&nDescriptor->uuid, str, EFFECT_STRING_LEN_MAX);
+    jUuid = env->NewStringUTF(str);
+
+    jName = env->NewStringUTF(nDescriptor->name);
+    jImplementor = env->NewStringUTF(nDescriptor->implementor);
+
+    *jDescriptor = env->NewObject(gAudioEffectDescriptorClass,
+                                  gAudioEffectDescriptorCstor,
+                                  jType,
+                                  jUuid,
+                                  jConnect,
+                                  jName,
+                                  jImplementor);
+    env->DeleteLocalRef(jType);
+    env->DeleteLocalRef(jUuid);
+    env->DeleteLocalRef(jConnect);
+    env->DeleteLocalRef(jName);
+    env->DeleteLocalRef(jImplementor);
+
+    return (jint) AUDIO_JAVA_SUCCESS;
+}
+
+void convertAudioEffectDescriptorVectorFromNative(JNIEnv *env, jobjectArray *jDescriptors,
+        const std::vector<effect_descriptor_t>& nDescriptors)
+{
+    jobjectArray temp = env->NewObjectArray(nDescriptors.size(),
+                                            audioEffectDescriptorClass(), NULL);
+    size_t actualSize = 0;
+    for (size_t i = 0; i < nDescriptors.size(); i++) {
+        jobject jdesc;
+        if (convertAudioEffectDescriptorFromNative(env,
+                                                   &jdesc,
+                                                   &nDescriptors[i])
+            != AUDIO_JAVA_SUCCESS) {
+            continue;
+        }
+
+        env->SetObjectArrayElement(temp, actualSize++, jdesc);
+        env->DeleteLocalRef(jdesc);
+    }
+
+    *jDescriptors = env->NewObjectArray(actualSize, audioEffectDescriptorClass(), NULL);
+    for (size_t i = 0; i < actualSize; i++) {
+        env->SetObjectArrayElement(*jDescriptors,
+                                   i,
+                                   env->GetObjectArrayElement(temp, i));
+    }
+    env->DeleteLocalRef(temp);
+}
+
+}; // namespace android
+
+int register_android_media_AudioEffectDescriptor(JNIEnv* env) {
+    jclass audioEffectDescriptorClass =
+        FindClassOrDie(env, "android/media/audiofx/AudioEffect$Descriptor");
+    gAudioEffectDescriptorClass =
+        MakeGlobalRefOrDie(env, audioEffectDescriptorClass);
+    gAudioEffectDescriptorCstor =
+        GetMethodIDOrDie(env,
+                         audioEffectDescriptorClass,
+                         "<init>",
+                         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+
+    env->DeleteLocalRef(audioEffectDescriptorClass);
+    return 0;
+}
diff --git a/core/jni/android_media_AudioEffectDescriptor.h b/core/jni/android_media_AudioEffectDescriptor.h
new file mode 100644
index 0000000..d07188c
--- /dev/null
+++ b/core/jni/android_media_AudioEffectDescriptor.h
@@ -0,0 +1,38 @@
+/*
+ * 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 ANDROID_MEDIA_AUDIOEFFECT_DESCRIPTOR_H
+#define ANDROID_MEDIA_AUDIOEFFECT_DESCRIPTOR_H
+
+#include <system/audio.h>
+#include <system/audio_effect.h>
+
+#include "jni.h"
+
+namespace android {
+
+// Conversion from C effect_descriptor_t to Java AudioEffect.Descriptor object
+
+extern jclass audioEffectDescriptorClass();
+
+extern jint convertAudioEffectDescriptorFromNative(JNIEnv *env, jobject *jDescriptor,
+        const effect_descriptor_t *nDescriptor);
+
+extern void convertAudioEffectDescriptorVectorFromNative(JNIEnv *env, jobjectArray *jDescriptors,
+        const std::vector<effect_descriptor_t>& nDescriptors);
+} // namespace android
+
+#endif
\ No newline at end of file
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 4f8bbc1..3329e20 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -919,7 +919,7 @@
     return IPCThreadState::self()->clearCallingWorkSource();
 }
 
-static void android_os_Binder_restoreCallingWorkSource(long token)
+static void android_os_Binder_restoreCallingWorkSource(jlong token)
 {
     IPCThreadState::self()->restoreCallingWorkSource(token);
 }
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 377e65c..102a0b7 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1137,7 +1137,7 @@
     UniqueFile file = MakeUniqueFile(status_path.c_str(), "re");
 
     char line[256];
-    while (fgets(line, sizeof(line), file.get())) {
+    while (file != nullptr && fgets(line, sizeof(line), file.get())) {
         jlong v;
         if ( sscanf(line, "VmRSS: %" SCNd64 " kB", &v) == 1) {
             rss[0] = v;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index ea6e017..c745c16 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -37,6 +37,7 @@
 #include <stdio.h>
 #include <system/graphics.h>
 #include <ui/DisplayInfo.h>
+#include <ui/DisplayedFrameStats.h>
 #include <ui/FrameStats.h>
 #include <ui/GraphicTypes.h>
 #include <ui/HdrCapabilities.h>
@@ -97,6 +98,16 @@
     jmethodID builder;
 } gGraphicBufferClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gDisplayedContentSampleClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gDisplayedContentSamplingAttributesClassInfo;
+
 // ----------------------------------------------------------------------------
 
 static jlong nativeCreateTransaction(JNIEnv* env, jclass clazz) {
@@ -398,6 +409,73 @@
     return javaObjectForIBinder(env, token);
 }
 
+static jobject nativeGetDisplayedContentSamplingAttributes(JNIEnv* env, jclass clazz,
+        jobject tokenObj) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+
+    ui::PixelFormat format;
+    ui::Dataspace dataspace;
+    uint8_t componentMask;
+    status_t err = SurfaceComposerClient::getDisplayedContentSamplingAttributes(
+            token, &format, &dataspace, &componentMask);
+    if (err != OK) {
+        return nullptr;
+    }
+    return env->NewObject(gDisplayedContentSamplingAttributesClassInfo.clazz,
+                          gDisplayedContentSamplingAttributesClassInfo.ctor,
+                          format, dataspace, componentMask);
+}
+
+static jboolean nativeSetDisplayedContentSamplingEnabled(JNIEnv* env, jclass clazz,
+        jobject tokenObj, jboolean enable, jint componentMask, jint maxFrames) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+    return SurfaceComposerClient::setDisplayContentSamplingEnabled(
+            token, enable, componentMask, maxFrames);
+}
+
+static jobject nativeGetDisplayedContentSample(JNIEnv* env, jclass clazz, jobject tokenObj,
+    jlong maxFrames, jlong timestamp) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+
+    DisplayedFrameStats stats;
+    status_t err = SurfaceComposerClient::getDisplayedContentSample(
+            token, maxFrames, timestamp, &stats);
+    if (err != OK) {
+        return nullptr;
+    }
+
+    jlongArray histogramComponent0 = env->NewLongArray(stats.component_0_sample.size());
+    jlongArray histogramComponent1 = env->NewLongArray(stats.component_1_sample.size());
+    jlongArray histogramComponent2 = env->NewLongArray(stats.component_2_sample.size());
+    jlongArray histogramComponent3 = env->NewLongArray(stats.component_3_sample.size());
+    if ((histogramComponent0 == nullptr) ||
+        (histogramComponent1 == nullptr) ||
+        (histogramComponent2 == nullptr) ||
+        (histogramComponent3 == nullptr)) {
+        return JNI_FALSE;
+    }
+
+    env->SetLongArrayRegion(histogramComponent0, 0,
+            stats.component_0_sample.size(),
+            reinterpret_cast<jlong*>(stats.component_0_sample.data()));
+    env->SetLongArrayRegion(histogramComponent1, 0,
+            stats.component_1_sample.size(),
+            reinterpret_cast<jlong*>(stats.component_1_sample.data()));
+    env->SetLongArrayRegion(histogramComponent2, 0,
+            stats.component_2_sample.size(),
+            reinterpret_cast<jlong*>(stats.component_2_sample.data()));
+    env->SetLongArrayRegion(histogramComponent3, 0,
+            stats.component_3_sample.size(),
+            reinterpret_cast<jlong*>(stats.component_3_sample.data()));
+    return env->NewObject(gDisplayedContentSampleClassInfo.clazz,
+                          gDisplayedContentSampleClassInfo.ctor,
+                          stats.numFrames,
+                          histogramComponent0,
+                          histogramComponent1,
+                          histogramComponent2,
+                          histogramComponent3);
+}
+
 static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj,
         jboolean secure) {
     ScopedUtfChars name(env, nameObj);
@@ -955,6 +1033,14 @@
             (void*)nativeCaptureLayers },
     {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V",
      (void*)nativeSetInputWindowInfo },
+    {"nativeGetDisplayedContentSamplingAttributes",
+            "(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;",
+            (void*)nativeGetDisplayedContentSamplingAttributes },
+    {"nativeSetDisplayedContentSamplingEnabled", "(Landroid/os/IBinder;ZII)Z",
+            (void*)nativeSetDisplayedContentSamplingEnabled },
+    {"nativeGetDisplayedContentSample",
+            "(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;",
+            (void*)nativeGetDisplayedContentSample },
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
@@ -1009,6 +1095,18 @@
     gGraphicBufferClassInfo.builder = GetStaticMethodIDOrDie(env, graphicsBufferClazz,
             "createFromExisting", "(IIIIJ)Landroid/graphics/GraphicBuffer;");
 
+    jclass displayedContentSampleClazz = FindClassOrDie(env,
+            "android/hardware/display/DisplayedContentSample");
+    gDisplayedContentSampleClassInfo.clazz = MakeGlobalRefOrDie(env, displayedContentSampleClazz);
+    gDisplayedContentSampleClassInfo.ctor = GetMethodIDOrDie(env,
+            displayedContentSampleClazz, "<init>", "(J[J[J[J[J)V");
+
+    jclass displayedContentSamplingAttributesClazz = FindClassOrDie(env,
+            "android/hardware/display/DisplayedContentSamplingAttributes");
+    gDisplayedContentSamplingAttributesClassInfo.clazz = MakeGlobalRefOrDie(env,
+            displayedContentSamplingAttributesClazz);
+    gDisplayedContentSamplingAttributesClassInfo.ctor = GetMethodIDOrDie(env,
+            displayedContentSamplingAttributesClazz, "<init>", "(III)V");
     return err;
 }
 
diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
index b708735..24bafca 100644
--- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
@@ -35,7 +35,6 @@
 #include "bpf/BpfUtils.h"
 #include "netdbpf/BpfNetworkStats.h"
 
-using android::bpf::hasBpfSupport;
 using android::bpf::parseBpfNetworkStatsDetail;
 using android::bpf::stats_line;
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 4aa88e7..7032081 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -99,7 +99,8 @@
   MOUNT_EXTERNAL_DEFAULT = 1,
   MOUNT_EXTERNAL_READ = 2,
   MOUNT_EXTERNAL_WRITE = 3,
-  MOUNT_EXTERNAL_FULL = 4,
+  MOUNT_EXTERNAL_INSTALLER = 4,
+  MOUNT_EXTERNAL_FULL = 5,
 };
 
 // Must match values in com.android.internal.os.Zygote.
@@ -446,6 +447,22 @@
     return true;
 }
 
+static bool bindMount(const std::string& sourceDir, const std::string& targetDir,
+        std::string* error_msg) {
+    if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(),
+            nullptr, MS_BIND | MS_REC, nullptr)) == -1) {
+        *error_msg = CREATE_ERROR("Failed to mount %s to %s: %s",
+                sourceDir.c_str(), targetDir.c_str(), strerror(errno));
+        return false;
+    }
+    if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(),
+            nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) {
+        *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str());
+        return false;
+    }
+    return true;
+}
+
 static bool mountPkgSpecificDir(const std::string& mntSourceRoot,
         const std::string& mntTargetRoot, const std::string& packageName,
         const char* dirName, std::string* error_msg) {
@@ -453,22 +470,12 @@
             mntSourceRoot.c_str(), dirName, packageName.c_str());
     std::string mntTargetDir = StringPrintf("%s/Android/%s/%s",
             mntTargetRoot.c_str(), dirName, packageName.c_str());
-    if (TEMP_FAILURE_RETRY(mount(mntSourceDir.c_str(), mntTargetDir.c_str(),
-            nullptr, MS_BIND | MS_REC, nullptr)) == -1) {
-        *error_msg = CREATE_ERROR("Failed to mount %s to %s: %s",
-                mntSourceDir.c_str(), mntTargetDir.c_str(), strerror(errno));
-        return false;
-    }
-    if (TEMP_FAILURE_RETRY(mount(nullptr, mntTargetDir.c_str(),
-            nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) {
-        *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", mntTargetDir.c_str());
-        return false;
-    }
-    return true;
+    return bindMount(mntSourceDir, mntTargetDir, error_msg);
 }
 
 static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames,
-        const std::vector<std::string>& volumeLabels, userid_t userId, std::string* error_msg) {
+        const std::vector<std::string>& volumeLabels, bool mountAllObbs,
+        userid_t userId, std::string* error_msg) {
     for (auto& label : volumeLabels) {
         std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str());
         std::string mntTarget = StringPrintf("/storage/%s", label.c_str());
@@ -479,7 +486,14 @@
         for (auto& package : packageNames) {
             mountPkgSpecificDir(mntSource, mntTarget, package, "data", error_msg);
             mountPkgSpecificDir(mntSource, mntTarget, package, "media", error_msg);
-            mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg);
+            if (!mountAllObbs) {
+                mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg);
+            }
+        }
+        if (mountAllObbs) {
+            StringAppendF(&mntSource, "/Android/obb");
+            StringAppendF(&mntTarget, "/Android/obb");
+            bindMount(mntSource, mntTarget, error_msg);
         }
     }
     return true;
@@ -500,7 +514,7 @@
         storageSource = "/mnt/runtime/read";
     } else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
         storageSource = "/mnt/runtime/write";
-    } else if (mount_mode != MOUNT_EXTERNAL_FULL && !force_mount_namespace) {
+    } else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {
         // Sane default of no storage visible
         return true;
     }
@@ -568,12 +582,28 @@
                         pkgSandboxDir.c_str(), strerror(errno));
                 return false;
             }
+            if (access("/storage/obb_mount", F_OK) == 0) {
+                if (mount_mode != MOUNT_EXTERNAL_INSTALLER) {
+                    remove("/storage/obb_mount");
+                }
+            } else {
+                if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
+                    int fd = TEMP_FAILURE_RETRY(open("/storage/obb_mount",
+                            O_RDWR | O_CREAT, 0660));
+                    if (fd == -1) {
+                        *error_msg = CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
+                                strerror(errno));
+                        return false;
+                    }
+                    close(fd);
+                }
+            }
             // If the sandbox was already created by vold, only then set up the bind mounts for
             // pkg specific directories. Otherwise, leave as is and bind mounts will be taken
             // care of by vold later.
             if (sandboxAlreadyCreated) {
                 if (!preparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
-                        user_id, error_msg)) {
+                        mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, error_msg)) {
                     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 c2bc7bf..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 {
@@ -417,7 +428,8 @@
 
             optional int64 start_time_elapsed = 1;
             optional int64 end_time_elapsed = 2;
-            optional int32 job_count = 3;
+            // The number of background jobs that ran during this session.
+            optional int32 bg_job_count = 3;
         }
 
         message Timer {
@@ -428,8 +440,9 @@
             optional bool is_active = 2;
             // The time this timer last became active. Only valid if is_active is true.
             optional int64 start_time_elapsed = 3;
-            // How many are currently running. Valid only if the device is_active is true.
-            optional int32 job_count = 4;
+            // How many background jobs are currently running. Valid only if the device is_active
+            // is true.
+            optional int32 bg_job_count = 4;
             // All of the jobs that the Timer is currently tracking.
             repeated JobStatusShortInfoProto running_jobs = 5;
         }
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index 3d60a86..528c1a4 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -54,6 +54,9 @@
     // Time attributes stored as an offset of the IntervalStats's beginTime.
     optional int64 last_time_service_used_ms = 8;
     optional int64 total_time_service_used_ms = 9;
+    // Time attributes stored as an offset of the IntervalStats's beginTime.
+    optional int64 last_time_visible_ms = 10;
+    optional int64 total_time_visible_ms = 11;
   }
 
   // Stores the relevant information an IntervalStats will have about a Configuration
@@ -82,6 +85,9 @@
     optional string notification_channel = 12;
     // notification_channel_index contains the index + 1 of the channel name in the string pool
     optional int32 notification_channel_index = 13;
+    // If class field is an Activity, instance_id is a unique id of the
+    // Activity object.
+    optional int32 instance_id = 14;
   }
 
   // The following fields contain supplemental data used to build IntervalStats, such as a string
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 9c55c75..7fa3e66 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1120,6 +1120,18 @@
                 android:description="@string/permdesc_manageOwnCalls"
                 android:protectionLevel="normal" />
 
+    <!--Allows an app which implements the
+        {@link android.telecom.InCallService InCallService} API to be eligible to be enabled as a
+        calling companion app. This means that the Telecom framework will bind to the app's
+        InCallService implementation when there are calls active. The app can use the InCallService
+        API to view information about calls on the system and control these calls.
+        <p>Protection level: normal
+    -->
+    <permission android:name="android.permission.CALL_COMPANION_APP"
+                android:label="@string/permlab_callCompanionApp"
+                android:description="@string/permdesc_callCompanionApp"
+                android:protectionLevel="normal" />
+
     <!-- Allows a calling app to continue a call which was started in another app.  An example is a
          video calling app that wants to continue a voice call on the user's mobile network.<p>
          When the handover of a call from one app to another takes place, there are two devices
@@ -1368,7 +1380,7 @@
     <!-- ================================== -->
     <eat-comment />
 
-    <!-- @SystemApi Allows an application (Phone) to send a request to other applications
+    <!-- Allows an application (Phone) to send a request to other applications
          to handle the respond-via-message action during incoming calls.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SEND_RESPOND_VIA_MESSAGE"
@@ -1452,7 +1464,7 @@
         android:description="@string/permdesc_accessLocationExtraCommands"
         android:protectionLevel="normal" />
 
-    <!-- @SystemApi Allows an application to install a location provider into the Location Manager.
+    <!-- Allows an application to install a location provider into the Location Manager.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.INSTALL_LOCATION_PROVIDER"
         android:protectionLevel="signature|privileged" />
@@ -1463,7 +1475,7 @@
     <permission android:name="android.permission.HDMI_CEC"
         android:protectionLevel="signature|privileged|vendorPrivileged" />
 
-    <!-- @SystemApi Allows an application to use location features in hardware,
+    <!-- Allows an application to use location features in hardware,
          such as the geofencing api.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.LOCATION_HARDWARE"
@@ -1658,7 +1670,7 @@
         android:label="@string/permlab_bluetoothAdmin"
         android:protectionLevel="normal" />
 
-    <!-- @SystemApi Allows applications to pair bluetooth devices without user interaction, and to
+    <!-- Allows applications to pair bluetooth devices without user interaction, and to
          allow or disallow phonebook access or message access.
          This is not available to third party applications. -->
     <permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
@@ -1749,7 +1761,7 @@
         android:usageInfoRequired="true" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
 
-    <!-- @SystemApi Allows applications to call into AccountAuthenticators.
+    <!-- Allows applications to call into AccountAuthenticators.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.ACCOUNT_MANAGER"
         android:protectionLevel="signature" />
@@ -1928,7 +1940,7 @@
     <!-- =========================================== -->
     <eat-comment />
 
-    <!-- @SystemApi Allows modification of the telephony state - power on, mmi, etc.
+    <!-- Allows modification of the telephony state - power on, mmi, etc.
          Does not include placing calls.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.MODIFY_PHONE_STATE"
@@ -2093,10 +2105,9 @@
 
     <!-- @hide Allows an application to cache content.
          <p>Not for use by third-party applications.
-         <p>Protection level: signature
     -->
     <permission android:name="android.permission.CACHE_CONTENT"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|documenter" />
 
     <!-- @SystemApi @hide
          Allows an application to aggressively allocate disk space.
@@ -2127,6 +2138,15 @@
         android:label="@string/permlab_disableKeyguard"
         android:protectionLevel="normal" />
 
+    <!-- Allows an application to get the screen lock complexity and prompt users to update the
+     screen lock to a certain complexity level.
+     <p>Protection level: normal
+    -->
+    <permission android:name="android.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY"
+                android:label="@string/permlab_getAndRequestScreenLockComplexity"
+                android:description="@string/permdesc_getAndRequestScreenLockComplexity"
+                android:protectionLevel="normal" />
+
     <!-- ================================== -->
     <!-- Permissions to access other installed applications  -->
     <!-- ================================== -->
@@ -2371,7 +2391,7 @@
     <!-- ============================================ -->
     <eat-comment />
 
-    <!-- @SystemApi Allows applications to set the system time.
+    <!-- Allows applications to set the system time.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SET_TIME"
         android:protectionLevel="signature|privileged" />
@@ -2461,7 +2481,7 @@
     <permission android:name="android.permission.SET_SCREEN_COMPATIBILITY"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi @TestApi Allows an application to modify the current configuration, such
+    <!-- Allows an application to modify the current configuration, such
          as locale. -->
     <permission android:name="android.permission.CHANGE_CONFIGURATION"
         android:protectionLevel="signature|privileged|development" />
@@ -2483,7 +2503,7 @@
         android:description="@string/permdesc_writeSettings"
         android:protectionLevel="signature|preinstalled|appop|pre23" />
 
-    <!-- @SystemApi Allows an application to modify the Google service map.
+    <!-- Allows an application to modify the Google service map.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WRITE_GSERVICES"
         android:protectionLevel="signature|privileged" />
@@ -2499,7 +2519,7 @@
     <permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Modify the global animation scaling factor.
+    <!-- Modify the global animation scaling factor.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SET_ANIMATION_SCALE"
         android:protectionLevel="signature|privileged|development" />
@@ -2554,12 +2574,12 @@
         android:description="@string/permdesc_broadcastSticky"
         android:protectionLevel="normal" />
 
-    <!-- @SystemApi Allows mounting and unmounting file systems for removable storage.
+    <!-- Allows mounting and unmounting file systems for removable storage.
     <p>Not for use by third-party applications.-->
     <permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows formatting file systems for removable storage.
+    <!-- Allows formatting file systems for removable storage.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS"
         android:protectionLevel="signature|privileged" />
@@ -2593,7 +2613,7 @@
     <permission android:name="android.permission.ASEC_RENAME"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows applications to write the apn settings and read sensitive fields of
+    <!-- Allows applications to write the apn settings and read sensitive fields of
          an existing apn settings like user and password.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WRITE_APN_SETTINGS"
@@ -2694,40 +2714,40 @@
     <!-- ========================================= -->
     <eat-comment />
 
-    <!-- @SystemApi Allows an application to read or write the secure system settings.
+    <!-- Allows an application to read or write the secure system settings.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WRITE_SECURE_SETTINGS"
         android:protectionLevel="signature|privileged|development" />
 
-    <!-- @SystemApi Allows an application to retrieve state dump information from system services.
+    <!-- Allows an application to retrieve state dump information from system services.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.DUMP"
         android:protectionLevel="signature|privileged|development" />
 
-    <!-- @SystemApi Allows an application to read the low-level system log files.
+    <!-- Allows an application to read the low-level system log files.
     <p>Not for use by third-party applications, because
     Log entries can contain the user's private information. -->
     <permission android:name="android.permission.READ_LOGS"
         android:protectionLevel="signature|privileged|development" />
 
-    <!-- @SystemApi Configure an application for debugging.
+    <!-- Configure an application for debugging.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SET_DEBUG_APP"
         android:protectionLevel="signature|privileged|development" />
 
-    <!-- @SystemApi Allows an application to set the maximum number of (not needed)
+    <!-- Allows an application to set the maximum number of (not needed)
          application processes that can be running.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SET_PROCESS_LIMIT"
         android:protectionLevel="signature|privileged|development" />
 
-    <!-- @SystemApi Allows an application to control whether activities are immediately
+    <!-- Allows an application to control whether activities are immediately
          finished when put in the background.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SET_ALWAYS_FINISH"
         android:protectionLevel="signature|privileged|development" />
 
-    <!-- @SystemApi Allow an application to request that a signal be sent to all persistent processes.
+    <!-- Allow an application to request that a signal be sent to all persistent processes.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
         android:protectionLevel="signature|privileged|development" />
@@ -2737,7 +2757,7 @@
     <!-- ==================================== -->
     <eat-comment />
 
-    <!-- @SystemApi Allows access to the list of accounts in the Accounts Service. -->
+    <!-- Allows access to the list of accounts in the Accounts Service. -->
     <permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED"
         android:protectionLevel="signature|privileged" />
 
@@ -2746,12 +2766,12 @@
     <permission android:name="android.permission.GET_PASSWORD"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows applications to RW to diagnostic resources.
+    <!-- Allows applications to RW to diagnostic resources.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.DIAGNOSTIC"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to open, close, or disable the status bar
+    <!-- Allows an application to open, close, or disable the status bar
          and its icons.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.STATUS_BAR"
@@ -2776,7 +2796,7 @@
     <permission android:name="android.permission.FORCE_BACK"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to update device statistics.
+    <!-- Allows an application to update device statistics.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.UPDATE_DEVICE_STATS"
         android:protectionLevel="signature|privileged" />
@@ -3223,7 +3243,7 @@
         android:description="@string/permdesc_requestDeletePackages"
         android:protectionLevel="normal" />
 
-    <!-- @SystemApi Allows an application to install packages.
+    <!-- Allows an application to install packages.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.INSTALL_PACKAGES"
       android:protectionLevel="signature|privileged" />
@@ -3296,7 +3316,7 @@
     <permission android:name="android.permission.FORCE_PERSISTABLE_URI_PERMISSIONS"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Old permission for deleting an app's cache files, no longer used,
+    <!-- Old permission for deleting an app's cache files, no longer used,
          but signals for us to quietly ignore calls instead of throwing an exception. -->
     <permission android:name="android.permission.DELETE_CACHE_FILES"
         android:protectionLevel="signature|privileged" />
@@ -3306,7 +3326,7 @@
     <permission android:name="android.permission.INTERNAL_DELETE_CACHE_FILES"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to delete packages.
+    <!-- Allows an application to delete packages.
          <p>Not for use by third-party applications.
          <p>Starting in {@link android.os.Build.VERSION_CODES#N}, user confirmation is requested
          when the application deleting the package is not the same application that installed the
@@ -3319,7 +3339,7 @@
     <permission android:name="android.permission.MOVE_PACKAGE"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to change whether an application component (other than its own) is
+    <!-- Allows an application to change whether an application component (other than its own) is
          enabled or not.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"
@@ -3447,7 +3467,7 @@
         android:protectionLevel="signature|privileged" />
     <uses-permission android:name="android.permission.CONTROL_VPN" />
 
-    <!-- @SystemApi Allows an application to capture audio output.
+    <!-- Allows an application to capture audio output.
          <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"
         android:protectionLevel="signature|privileged" />
@@ -3485,7 +3505,7 @@
     <permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to know what content is playing and control its playback.
+    <!-- Allows an application to know what content is playing and control its playback.
          <p>Not for use by third-party applications due to privacy of media consumption</p>  -->
     <permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
         android:protectionLevel="signature|privileged" />
@@ -3512,7 +3532,7 @@
     <permission android:name="android.permission.BRICK"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Required to be able to reboot the device.
+    <!-- Required to be able to reboot the device.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.REBOOT"
         android:protectionLevel="signature|privileged" />
@@ -3573,11 +3593,11 @@
     <permission android:name="android.permission.BROADCAST_NETWORK_PRIVILEGED"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Not for use by third-party applications. -->
+    <!-- Not for use by third-party applications. -->
     <permission android:name="android.permission.MASTER_CLEAR"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to call any phone number, including emergency
+    <!-- Allows an application to call any phone number, including emergency
          numbers, without going through the Dialer user interface for the user
          to confirm the call being placed.
          <p>Not for use by third-party applications. -->
@@ -3592,19 +3612,19 @@
     <permission android:name="android.permission.PERFORM_SIM_ACTIVATION"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows enabling/disabling location update notifications from
+    <!-- Allows enabling/disabling location update notifications from
          the radio.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.CONTROL_LOCATION_UPDATES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows read/write access to the "properties" table in the checkin
+    <!-- Allows read/write access to the "properties" table in the checkin
          database, to change values that get uploaded.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to collect component usage
+    <!-- Allows an application to collect component usage
          statistics
          <p>Declaring the permission implies intention to use the API and the user of the
          device can grant permission through the Settings application. -->
@@ -3637,7 +3657,7 @@
         android:description="@string/permdesc_requestIgnoreBatteryOptimizations"
         android:protectionLevel="normal" />
 
-    <!-- @SystemApi Allows an application to collect battery statistics -->
+    <!-- Allows an application to collect battery statistics -->
     <permission android:name="android.permission.BATTERY_STATS"
         android:protectionLevel="signature|privileged|development" />
 
@@ -3666,12 +3686,12 @@
     <permission android:name="android.permission.CONFIRM_FULL_BACKUP"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Must be required by a {@link android.widget.RemoteViewsService},
+    <!-- Must be required by a {@link android.widget.RemoteViewsService},
          to ensure that only the system can bind to it. -->
     <permission android:name="android.permission.BIND_REMOTEVIEWS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to tell the AppWidget service which application
+    <!-- Allows an application to tell the AppWidget service which application
          can access AppWidget's data.  The normal user flow is that a user
          picks an AppWidget to go into a particular host, thereby giving that
          host application access to the private data from the AppWidget app.
@@ -3702,7 +3722,7 @@
     <permission android:name="android.permission.CHANGE_BACKGROUND_DATA_SETTING"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi This permission can be used on content providers to allow the global
+    <!-- This permission can be used on content providers to allow the global
          search system to access their data.  Typically it used when the
          provider has some permissions protecting it (which global search
          would not be expected to hold), and added as a read-only permission
@@ -4322,6 +4342,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"
@@ -4676,6 +4700,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.server.pm.DynamicCodeLoggingService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.PruneInstantAppsJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
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 ab4bd05..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
@@ -1603,6 +1618,9 @@
         <!-- Declares that this application should be invoked without non-SDK API enforcement -->
         <attr name="usesNonSdkApi" />
 
+        <!-- If {@code true} the user is prompted to keep the app's data on uninstall -->
+        <attr name="hasFragileUserData" />
+
     </declare-styleable>
     <!-- The <code>permission</code> tag declares a security permission that can be
          used to control access from other packages to specific components or
@@ -2383,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/config.xml b/core/res/res/values/config.xml
index 3c5e42f..ecff835 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -222,6 +222,11 @@
          so that applications can still use their own mechanisms. -->
     <bool name="config_enableAutoPowerModes">false</bool>
 
+    <!-- Whether (if true) this is a kind of device that can be moved around (eg. phone/laptop),
+         or (if false) something for which movement is either not measurable or should not count
+         toward power states (eg. tv/soundbar). -->
+    <bool name="config_autoPowerModeUseMotionSensor">true</bool>
+
     <!-- The threshold angle for any motion detection in auto-power save modes.
          In hundreths of a degree. -->
     <integer name="config_autoPowerModeThresholdAngle">200</integer>
@@ -3596,4 +3601,9 @@
     <!-- Component name for default assistant on this device -->
     <string name="config_defaultAssistantComponentName">#+UNSET</string>
 
+    <!-- Class name for the InputEvent compatibility processor override.
+         Empty string means use the default compatibility processor
+         (android.view.InputEventCompatProcessor). -->
+    <string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string>
+
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d480121..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" />
@@ -2930,6 +2932,8 @@
         <public name="dataRetentionTime" />
         <public name="selectionDividerHeight" />
         <public name="foregroundServiceType" />
+        <public name="hasFragileUserData" />
+        <public name="minAspectRatio" />
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b4">
@@ -2970,6 +2974,8 @@
     <public-group type="dimen" first-id="0x01050007">
         <!-- @hide @SystemApi -->
         <public name="config_restrictedIconSize" />
+        <!-- @hide @SystemApi -->
+        <public name="config_mediaMetadataBitmapMaxSize" />
     </public-group>
 
   <!-- ===============================================================
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a33f6b2..f25427a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1215,6 +1215,15 @@
     <string name="permdesc_manageOwnCalls">Allows the app to route its calls through the system in
         order to improve the calling experience.</string>
 
+    <!-- Title of an application permission. When granted the app is allowed to be enabled as
+        a companion app. [CHAR LIMIT=NONE]-->
+    <string name="permlab_callCompanionApp">see and control calls through the system.</string>
+    <!-- Description of an application permission. When granted the app is allowed to be enabled as
+        a companion app. [CHAR LIMIT=NONE]-->
+    <string name="permdesc_callCompanionApp">Allows the app to see and control ongoing calls on the
+        device. This includes information such as call numbers for calls and the state of the
+        calls.</string>
+
     <!-- Title of an application permission.  When granted the user is giving access to a third
          party app to continue a call which originated in another app.  For example, the user
          could be in a voice call over their carrier's mobile network, and a third party video
@@ -1397,6 +1406,16 @@
       re-enables the keylock when the call is finished.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permlab_getAndRequestScreenLockComplexity">get and request screen lock complexity</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permdesc_getAndRequestScreenLockComplexity">Allows the app to learn the screen
+        lock complexity level (high, medium, low or none), which indicates the possible range of
+        length and type of the screen lock. The app can also suggest to users that they update the
+        screen lock to a certain level but users can freely ignore and navigate away. Note that the
+        screen lock is not stored in plaintext so the app does not know the exact password.
+    </string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
     <string name="permlab_useBiometric">use biometric hardware</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
     <string name="permdesc_useBiometric">Allows the app to use biometric hardware for authentication</string>
@@ -3181,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>
@@ -3323,6 +3344,15 @@
     <!-- Notification action name for opening the wifi picker, showing the user all the nearby networks. -->
     <string name="wifi_available_action_all_networks">All networks</string>
 
+    <!-- Notification title for a connection to a app suggested wireless network.-->
+    <string name="wifi_suggestion_title">Connected to Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g></string>
+    <!-- Notification content for a connection to a app suggested wireless network.-->
+    <string name="wifi_suggestion_content">Do you want to let <xliff:g id="name" example="App123">%s</xliff:g> propose networks for you?</string>
+    <!-- Notification action for allowing app specified in the notification body.-->
+    <string name="wifi_suggestion_action_allow_app">Yes</string>
+    <!-- Notification action for disallowing app specified in the notification body.-->
+    <string name="wifi_suggestion_action_disallow_app">No</string>
+
     <!--Notification title for Wi-Fi Wake onboarding. This is displayed the first time a user disables Wi-Fi with the feature enabled. -->
     <string name="wifi_wakeup_onboarding_title">Wi\u2011Fi will turn on automatically</string>
     <!--Notification subtext for Wi-Fi Wake onboarding.-->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2c6c094..f44976e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -265,6 +265,7 @@
   <java-symbol type="integer" name="config_autoPowerModeAnyMotionSensor" />
   <java-symbol type="bool" name="config_autoPowerModePreferWristTilt" />
   <java-symbol type="bool" name="config_autoPowerModePrefetchLocation" />
+  <java-symbol type="bool" name="config_autoPowerModeUseMotionSensor" />
   <java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" />
   <java-symbol type="bool" name="config_enable_puk_unlock_screen" />
   <java-symbol type="bool" name="config_disableLockscreenByDefault" />
@@ -301,6 +302,7 @@
   <java-symbol type="bool" name="config_enableWallpaperService" />
   <java-symbol type="bool" name="config_checkWallpaperAtBoot" />
   <java-symbol type="string" name="config_wallpaperManagerServiceName" />
+  <java-symbol type="string" name="config_inputEventCompatProcessorOverrideClassName" />
   <java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" />
   <java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" />
   <java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" />
@@ -1989,6 +1991,10 @@
   <java-symbol type="string" name="wifi_available_content_failed_to_connect" />
   <java-symbol type="string" name="wifi_available_action_connect" />
   <java-symbol type="string" name="wifi_available_action_all_networks" />
+  <java-symbol type="string" name="wifi_suggestion_title" />
+  <java-symbol type="string" name="wifi_suggestion_content" />
+  <java-symbol type="string" name="wifi_suggestion_action_allow_app" />
+  <java-symbol type="string" name="wifi_suggestion_action_disallow_app" />
   <java-symbol type="string" name="wifi_wakeup_onboarding_title" />
   <java-symbol type="string" name="wifi_wakeup_onboarding_subtext" />
   <java-symbol type="string" name="wifi_wakeup_onboarding_action_disable" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index fec101a..c0c677a 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1646,8 +1646,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 +1661,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" />
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index d289f1f..9b5b725 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -16,9 +16,16 @@
 
 package android.app.admin;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import android.app.admin.PasswordMetrics.PasswordComplexityBucket;
 import android.os.Parcel;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -164,4 +171,126 @@
 
 
     }
+
+    @Test
+    public void testConstructQuality() {
+        PasswordMetrics expected = new PasswordMetrics();
+        expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+
+        PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testDetermineComplexity_none() {
+        assertEquals(PASSWORD_COMPLEXITY_NONE,
+                PasswordMetrics.computeForPassword("").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_lowSomething() {
+        assertEquals(PASSWORD_COMPLEXITY_LOW,
+                new PasswordMetrics(PASSWORD_QUALITY_SOMETHING).determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_lowNumeric() {
+        assertEquals(PASSWORD_COMPLEXITY_LOW,
+                PasswordMetrics.computeForPassword("1234").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_lowNumericComplex() {
+        assertEquals(PASSWORD_COMPLEXITY_LOW,
+                PasswordMetrics.computeForPassword("124").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_lowAlphabetic() {
+        assertEquals(PASSWORD_COMPLEXITY_LOW,
+                PasswordMetrics.computeForPassword("a!").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_lowAlphanumeric() {
+        assertEquals(PASSWORD_COMPLEXITY_LOW,
+                PasswordMetrics.computeForPassword("a!1").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_mediumNumericComplex() {
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
+                PasswordMetrics.computeForPassword("1238").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_mediumAlphabetic() {
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
+                PasswordMetrics.computeForPassword("ab!c").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_mediumAlphanumeric() {
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
+                PasswordMetrics.computeForPassword("ab!1").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_highNumericComplex() {
+        assertEquals(PASSWORD_COMPLEXITY_HIGH,
+                PasswordMetrics.computeForPassword("12389647!").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_highAlphabetic() {
+        assertEquals(PASSWORD_COMPLEXITY_HIGH,
+                PasswordMetrics.computeForPassword("alphabetic!").determineComplexity());
+    }
+
+    @Test
+    public void testDetermineComplexity_highAlphanumeric() {
+        assertEquals(PASSWORD_COMPLEXITY_HIGH,
+                PasswordMetrics.computeForPassword("alphanumeric123!").determineComplexity());
+    }
+
+    @Test
+    public void testComplexityLevelToBucket_none() {
+        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
+                PASSWORD_COMPLEXITY_NONE).getMetrics();
+
+        for (PasswordMetrics metrics : bucket) {
+            assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity());
+        }
+    }
+
+    @Test
+    public void testComplexityLevelToBucket_low() {
+        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
+                PASSWORD_COMPLEXITY_LOW).getMetrics();
+
+        for (PasswordMetrics metrics : bucket) {
+            assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity());
+        }
+    }
+
+    @Test
+    public void testComplexityLevelToBucket_medium() {
+        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
+                PASSWORD_COMPLEXITY_MEDIUM).getMetrics();
+
+        for (PasswordMetrics metrics : bucket) {
+            assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity());
+        }
+    }
+
+    @Test
+    public void testComplexityLevelToBucket_high() {
+        PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket(
+                PASSWORD_COMPLEXITY_HIGH).getMetrics();
+
+        for (PasswordMetrics metrics : bucket) {
+            assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity());
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
index 1f047f9e..28aaf1e 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
@@ -16,18 +16,22 @@
 
 package android.app.usage;
 
-import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
 import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
 import static android.app.usage.UsageEvents.Event.END_OF_DAY;
+import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START;
 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP;
-import static android.app.usage.UsageEvents.Event.MOVE_TO_BACKGROUND;
-import static android.app.usage.UsageEvents.Event.MOVE_TO_FOREGROUND;
 import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import android.app.usage.UsageEvents.Event;
 import android.os.Parcel;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -120,10 +124,10 @@
         left.mBeginTimeStamp = 100000;
         left.mTotalTimeInForeground = 10;
 
-        left.mLastForegroundActivityEventMap.put("com.test.activity1", MOVE_TO_FOREGROUND);
-        left.mLastForegroundActivityEventMap.put("com.test.activity2", MOVE_TO_FOREGROUND);
-        left.mLastForegroundServiceEventMap.put("com.test.service1", FOREGROUND_SERVICE_START);
-        left.mLastForegroundServiceEventMap.put("com.test.service2", FOREGROUND_SERVICE_START);
+        left.mActivities.put(1, Event.ACTIVITY_RESUMED);
+        left.mActivities.put(2, Event.ACTIVITY_RESUMED);
+        left.mForegroundServices.put("com.test.service1", FOREGROUND_SERVICE_START);
+        left.mForegroundServices.put("com.test.service2", FOREGROUND_SERVICE_START);
 
         Parcel p = Parcel.obtain();
         left.writeToParcel(p, 0);
@@ -133,37 +137,38 @@
     }
 
     @Test
-    public void testForegroundActivity() {
+    public void testActivity() {
         left.mPackageName = "com.test";
         left.mBeginTimeStamp = 100000;
 
-        left.update("com.test.activity1", 200000, MOVE_TO_FOREGROUND);
+        left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1);
         assertEquals(left.mLastTimeUsed, 200000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(MOVE_TO_FOREGROUND));
+        assertEquals(left.mLastTimeVisible, 200000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
         assertEquals(left.mLaunchCount, 1);
+        assertEquals(left.mTotalTimeInForeground, 0);
+        assertEquals(left.mTotalTimeVisible, 0);
 
-        left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
+        left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1);
         assertEquals(left.mLastTimeUsed, 350000);
-        assertFalse(left.mLastForegroundActivityEventMap.containsKey("com.test.activity1"));
+        assertEquals(left.mLastTimeVisible, 350000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
         assertEquals(left.mTotalTimeInForeground, 350000 - 200000);
-    }
+        assertEquals(left.mTotalTimeVisible, 350000 - 200000);
 
-    @Test
-    public void testEvent_CONTINUE_PREVIOUS_DAY() {
-        left.mPackageName = "com.test";
-        left.mBeginTimeStamp = 100000;
-
-        left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
-        assertEquals(left.mLastTimeUsed, 100000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(CONTINUE_PREVIOUS_DAY));
-        assertEquals(left.mLaunchCount, 0);
-
-        left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
+        left.update("com.test.activity1", 400000, ACTIVITY_STOPPED, 1);
         assertEquals(left.mLastTimeUsed, 350000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
-        assertEquals(left.mTotalTimeInForeground, 350000 - 100000);
+        assertEquals(left.mLastTimeVisible, 400000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED);
+        assertEquals(left.mTotalTimeInForeground, 350000 - 200000);
+        assertEquals(left.mTotalTimeVisible, 400000 - 200000);
+
+        left.update("com.test.activity1", 500000, ACTIVITY_DESTROYED, 1);
+        assertEquals(left.mLastTimeUsed, 350000);
+        assertEquals(left.mLastTimeVisible, 400000);
+        assertTrue(left.mActivities.indexOfKey(1) < 0);
+        assertEquals(left.mTotalTimeInForeground, 350000 - 200000);
+        assertEquals(left.mTotalTimeVisible, 400000 - 200000);
     }
 
     @Test
@@ -171,93 +176,143 @@
         left.mPackageName = "com.test";
         left.mBeginTimeStamp = 100000;
 
-        left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
+        left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
         assertEquals(left.mLastTimeUsed, 100000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(CONTINUE_PREVIOUS_DAY));
-        assertEquals(left.mLaunchCount, 0);
-
-        left.update(null, 350000, END_OF_DAY);
-        assertEquals(left.mLastTimeUsed, 350000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(END_OF_DAY));
-        assertEquals(left.mTotalTimeInForeground, 350000 - 100000);
-    }
-
-    @Test
-    public void testForegroundActivityEventSequence() {
-        left.mPackageName = "com.test";
-        left.mBeginTimeStamp = 100000;
-
-        left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
-        assertEquals(left.mLastTimeUsed, 100000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(CONTINUE_PREVIOUS_DAY));
-        assertEquals(left.mLaunchCount, 0);
-
-        left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
-        assertEquals(left.mLastTimeUsed, 350000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
-        assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/);
-
-        left.update("com.test.activity1", 450000, MOVE_TO_FOREGROUND);
-        assertEquals(left.mLastTimeUsed, 450000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(MOVE_TO_FOREGROUND));
-        assertEquals(left.mTotalTimeInForeground, 250000);
-
-        left.update("com.test.activity1", 500000, MOVE_TO_BACKGROUND);
-        assertEquals(left.mLastTimeUsed, 500000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
-        assertEquals(left.mTotalTimeInForeground, 250000 + 50000 /*500000 - 450000*/);
-    }
-
-    @Test
-    public void testForegroundActivityEventOutOfSequence() {
-        left.mPackageName = "com.test";
-        left.mBeginTimeStamp = 100000;
-
-        left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
-        assertEquals(left.mLastTimeUsed, 100000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(CONTINUE_PREVIOUS_DAY));
-        assertEquals(left.mLaunchCount, 0);
-
-        left.update("com.test.activity1", 150000, MOVE_TO_FOREGROUND);
-        assertEquals(left.mLastTimeUsed, 150000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(MOVE_TO_FOREGROUND));
+        assertEquals(left.mLastTimeVisible, 100000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
         assertEquals(left.mLaunchCount, 1);
-        assertEquals(left.mTotalTimeInForeground, 50000 /*150000 - 100000*/);
 
-        left.update("com.test.activity1", 200000, MOVE_TO_FOREGROUND);
+        left.update(null, 350000, END_OF_DAY, 0);
+        assertEquals(left.mLastTimeUsed, 350000);
+        assertEquals(left.mLastTimeVisible, 350000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+        assertEquals(left.mTotalTimeInForeground, 350000 - 100000);
+        assertEquals(left.mTotalTimeVisible, 350000 - 100000);
+    }
+
+    @Test
+    public void testEvent_ACTIVITY_PAUSED() {
+        left.mPackageName = "com.test";
+        left.mBeginTimeStamp = 100000;
+
+        left.update("com.test.activity1", 100000, ACTIVITY_PAUSED, 1);
+        assertEquals(left.mLastTimeUsed, 0);
+        assertEquals(left.mLastTimeVisible, 100000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
+
+        left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1);
         assertEquals(left.mLastTimeUsed, 200000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(MOVE_TO_FOREGROUND));
+        assertEquals(left.mLastTimeVisible, 200000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+        assertEquals(left.mTotalTimeInForeground, 0);
+        assertEquals(left.mTotalTimeVisible, 200000 - 100000);
+
+        left.update("com.test.activity1", 300000, ACTIVITY_PAUSED, 1);
+        assertEquals(left.mLastTimeUsed, 300000);
+        assertEquals(left.mLastTimeVisible, 300000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
+        assertEquals(left.mTotalTimeInForeground, 300000 - 200000);
+        assertEquals(left.mTotalTimeVisible, 300000 - 100000);
+
+        left.update("com.test.activity1", 400000, ACTIVITY_STOPPED, 1);
+        assertEquals(left.mLastTimeUsed, 300000);
+        assertEquals(left.mLastTimeVisible, 400000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED);
+        assertEquals(left.mTotalTimeInForeground, 300000 - 200000);
+        assertEquals(left.mTotalTimeVisible, 400000 - 100000);
+    }
+
+    @Test
+    public void testEvent_CHANGE_TO_INVISIBLE() {
+        left.mPackageName = "com.test";
+        left.mBeginTimeStamp = 100000;
+
+        left.update("com.test.activity1", 100000, ACTIVITY_RESUMED, 1);
+        assertEquals(left.mLastTimeUsed, 100000);
+        assertEquals(left.mLastTimeVisible, 100000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED);
+
+        left.update("com.test.activity1", 200000, ACTIVITY_STOPPED, 1);
+        assertEquals(left.mLastTimeUsed, 200000);
+        assertEquals(left.mLastTimeVisible, 200000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED);
+        assertEquals(left.mTotalTimeInForeground, 200000 - 100000);
+        assertEquals(left.mTotalTimeVisible, 200000 - 100000);
+
+        left.update("com.test.activity1", 300000, ACTIVITY_RESUMED, 1);
+        assertEquals(left.mLastTimeUsed, 300000);
+        assertEquals(left.mLastTimeVisible, 300000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED);
+        assertEquals(left.mTotalTimeInForeground, 200000 - 100000);
+        assertEquals(left.mTotalTimeVisible, 200000 - 100000);
+    }
+
+    @Test
+    public void testEvent_ACTIVITY_DESTROYED() {
+        left.mPackageName = "com.test";
+        left.mBeginTimeStamp = 100000;
+
+        left.update("com.test.activity1", 100000, ACTIVITY_RESUMED, 1);
+        assertEquals(left.mLastTimeUsed, 100000);
+        assertEquals(left.mLastTimeVisible, 100000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED);
+
+        left.update("com.test.activity1", 200000, ACTIVITY_DESTROYED, 1);
+        assertEquals(left.mLastTimeUsed, 200000);
+        assertEquals(left.mLastTimeVisible, 200000);
+        assertTrue(left.mActivities.indexOfKey(1) < 0);
+        assertEquals(left.mTotalTimeInForeground, 200000 - 100000);
+        assertEquals(left.mTotalTimeVisible, 200000 - 100000);
+    }
+
+    @Test
+    public void testActivityEventOutOfOrder() {
+        left.mPackageName = "com.test";
+        left.mBeginTimeStamp = 100000;
+
+        left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
+        assertEquals(left.mLastTimeUsed, 100000);
+        assertEquals(left.mLastTimeVisible, 100000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+        assertEquals(left.mLaunchCount, 1);
+        assertEquals(left.mTotalTimeInForeground, 0);
+        assertEquals(left.mTotalTimeVisible, 0);
+
+        left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1);
+        assertEquals(left.mLastTimeUsed, 200000);
+        assertEquals(left.mLastTimeVisible, 200000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
         assertEquals(left.mLaunchCount, 2);
         assertEquals(left.mTotalTimeInForeground, 100000);
+        assertEquals(left.mTotalTimeVisible, 100000 /*200000 - 100000*/);
 
-        left.update("com.test.activity1", 250000, MOVE_TO_BACKGROUND);
+        left.update("com.test.activity1", 250000, ACTIVITY_PAUSED, 1);
         assertEquals(left.mLastTimeUsed, 250000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
+        assertEquals(left.mLastTimeVisible, 250000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
         assertEquals(left.mTotalTimeInForeground, 150000);
+        assertEquals(left.mTotalTimeVisible, 150000 /*250000 - 100000*/);
 
-        left.update("com.test.activity1", 300000, MOVE_TO_BACKGROUND);
+        left.update("com.test.activity1", 300000, ACTIVITY_PAUSED, 1);
         assertEquals(left.mLastTimeUsed, 250000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
+        assertEquals(left.mLastTimeVisible, 300000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
         assertEquals(left.mTotalTimeInForeground, 150000);
+        assertEquals(left.mTotalTimeVisible, 200000 /*300000 - 100000*/);
 
-        left.update("com.test.activity1", 350000, MOVE_TO_FOREGROUND);
+        left.update("com.test.activity1", 350000, Event.ACTIVITY_RESUMED, 1);
         assertEquals(left.mLastTimeUsed, 350000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(MOVE_TO_FOREGROUND));
+        assertEquals(left.mLastTimeVisible, 350000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
         assertEquals(left.mTotalTimeInForeground, 150000);
+        assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/);
 
-        left.update("com.test.activity1", 400000, END_OF_DAY);
+        left.update("com.test.activity1", 400000, END_OF_DAY, 1);
         assertEquals(left.mLastTimeUsed, 400000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(END_OF_DAY));
+        assertEquals(left.mLastTimeVisible, 400000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
         assertEquals(left.mTotalTimeInForeground, 200000);
+        assertEquals(left.mTotalTimeVisible, 300000 /*400000 - 100000*/);
     }
 
     @Test
@@ -265,28 +320,41 @@
         left.mPackageName = "com.test";
         left.mBeginTimeStamp = 100000;
 
-        left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
-        left.update("com.test.activity2", 100000, CONTINUE_PREVIOUS_DAY);
+        left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
+        left.update("com.test.activity2", 100000, Event.ACTIVITY_RESUMED, 2);
         assertEquals(left.mLastTimeUsed, 100000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(CONTINUE_PREVIOUS_DAY));
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"),
-                new Integer(CONTINUE_PREVIOUS_DAY));
-        assertEquals(left.mLaunchCount, 0);
+        assertEquals(left.mLastTimeVisible, 100000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+        assertEquals(left.mActivities.get(2), Event.ACTIVITY_RESUMED);
+        assertEquals(left.mLaunchCount, 2);
 
-        left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
+        left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1);
         assertEquals(left.mLastTimeUsed, 350000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
+        assertEquals(left.mLastTimeVisible, 350000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
         assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/);
+        assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/);
 
-        left.update("com.test.activity2", 450000, MOVE_TO_BACKGROUND);
+        left.update("com.test.activity2", 450000, ACTIVITY_PAUSED, 2);
         assertEquals(left.mLastTimeUsed, 450000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), null);
+        assertEquals(left.mLastTimeVisible, 450000);
+        assertEquals(left.mActivities.get(2), ACTIVITY_PAUSED);
         assertEquals(left.mTotalTimeInForeground, 250000 + 100000 /*450000 - 350000*/);
+        assertEquals(left.mTotalTimeVisible, 250000 + 100000 /*450000 - 350000*/);
 
-        left.update(null, 500000, END_OF_DAY);
+        left.update("com.test.activity1", 550000, ACTIVITY_STOPPED, 1);
         assertEquals(left.mLastTimeUsed, 450000);
+        assertEquals(left.mLastTimeVisible, 550000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED);
         assertEquals(left.mTotalTimeInForeground, 350000);
+        assertEquals(left.mTotalTimeVisible, 350000 + 100000 /*550000 - 450000*/);
+
+        left.update("com.test.activity2", 650000, ACTIVITY_STOPPED, 2);
+        assertEquals(left.mLastTimeUsed, 450000);
+        assertEquals(left.mLastTimeVisible, 650000);
+        assertEquals(left.mActivities.get(2), ACTIVITY_STOPPED);
+        assertEquals(left.mTotalTimeInForeground, 350000);
+        assertEquals(left.mTotalTimeVisible, 450000 + 100000 /*650000 - 550000*/);
     }
 
     @Test
@@ -294,15 +362,14 @@
         left.mPackageName = "com.test";
         left.mBeginTimeStamp = 100000;
 
-        left.update("com.test.service1", 200000, FOREGROUND_SERVICE_START);
+        left.update("com.test.service1", 200000, FOREGROUND_SERVICE_START, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 200000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+        assertEquals(left.mForegroundServices.get("com.test.service1"),
                 new Integer(FOREGROUND_SERVICE_START));
-        assertEquals(left.mLaunchCount, 0);
 
-        left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP);
+        left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+        assertEquals(left.mForegroundServices.get("com.test.service1"), null);
         assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 200000);
     }
 
@@ -311,15 +378,15 @@
         left.mPackageName = "com.test";
         left.mBeginTimeStamp = 100000;
 
-        left.update("com.test.service1", 100000, CONTINUING_FOREGROUND_SERVICE);
+        left.update("com.test.service1", 100000,
+                CONTINUING_FOREGROUND_SERVICE, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+        assertEquals(left.mForegroundServices.get("com.test.service1"),
                 new Integer(CONTINUING_FOREGROUND_SERVICE));
-        assertEquals(left.mLaunchCount, 0);
 
-        left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP);
+        left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+        assertEquals(left.mForegroundServices.get("com.test.service1"), null);
         assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 100000);
     }
 
@@ -329,16 +396,15 @@
         left.mBeginTimeStamp = 100000;
 
         left.update("com.test.service1", 100000,
-                CONTINUING_FOREGROUND_SERVICE);
+                CONTINUING_FOREGROUND_SERVICE, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+        assertEquals(left.mForegroundServices.get("com.test.service1"),
                 new Integer(CONTINUING_FOREGROUND_SERVICE));
-        assertEquals(left.mLaunchCount, 0);
 
-        left.update(null, 350000, ROLLOVER_FOREGROUND_SERVICE);
+        left.update(null, 350000, ROLLOVER_FOREGROUND_SERVICE, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
-                new Integer(ROLLOVER_FOREGROUND_SERVICE));
+        assertEquals(left.mForegroundServices.get("com.test.service1"),
+                new Integer(CONTINUING_FOREGROUND_SERVICE));
         assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 100000);
     }
 
@@ -348,27 +414,28 @@
         left.mBeginTimeStamp = 100000;
 
         left.update("com.test.service1", 100000,
-                CONTINUING_FOREGROUND_SERVICE);
+                CONTINUING_FOREGROUND_SERVICE, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+        assertEquals(left.mForegroundServices.get("com.test.service1"),
                 new Integer(CONTINUING_FOREGROUND_SERVICE));
         assertEquals(left.mLaunchCount, 0);
 
-        left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP);
+        left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+        assertEquals(left.mForegroundServices.get("com.test.service1"), null);
         assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 /*350000 - 100000*/);
 
-        left.update("com.test.service1", 450000, FOREGROUND_SERVICE_START);
+        left.update("com.test.service1", 450000, FOREGROUND_SERVICE_START, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 450000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+        assertEquals(left.mForegroundServices.get("com.test.service1"),
                 new Integer(FOREGROUND_SERVICE_START));
         assertEquals(left.mTotalTimeForegroundServiceUsed, 250000);
 
-        left.update("com.test.service1", 500000, FOREGROUND_SERVICE_STOP);
+        left.update("com.test.service1", 500000, FOREGROUND_SERVICE_STOP, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 500000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
-        assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 + 50000 /*500000 - 450000*/);
+        assertEquals(left.mForegroundServices.get("com.test.service1"), null);
+        assertEquals(left.mTotalTimeForegroundServiceUsed,
+                250000 + 50000 /*500000 - 450000*/);
     }
 
     @Test
@@ -377,27 +444,27 @@
         left.mBeginTimeStamp = 100000;
 
         left.update("com.test.service1", 100000,
-                CONTINUING_FOREGROUND_SERVICE);
+                CONTINUING_FOREGROUND_SERVICE, 0);
         left.update("com.test.service2", 100000,
-                CONTINUING_FOREGROUND_SERVICE);
+                CONTINUING_FOREGROUND_SERVICE, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+        assertEquals(left.mForegroundServices.get("com.test.service1"),
                 new Integer(CONTINUING_FOREGROUND_SERVICE));
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"),
+        assertEquals(left.mForegroundServices.get("com.test.service2"),
                 new Integer(CONTINUING_FOREGROUND_SERVICE));
-        assertEquals(left.mLaunchCount, 0);
 
-        left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP);
+        left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 350000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+        assertEquals(left.mForegroundServices.get("com.test.service1"), null);
         assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 /*350000 - 100000*/);
 
-        left.update("com.test.service2", 450000, FOREGROUND_SERVICE_STOP);
+        left.update("com.test.service2", 450000, FOREGROUND_SERVICE_STOP, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 450000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), null);
-        assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 + 100000 /*450000 - 350000*/);
+        assertEquals(left.mForegroundServices.get("com.test.service2"), null);
+        assertEquals(left.mTotalTimeForegroundServiceUsed,
+                250000 + 100000 /*450000 - 350000*/);
 
-        left.update(null, 500000, ROLLOVER_FOREGROUND_SERVICE);
+        left.update(null, 500000, ROLLOVER_FOREGROUND_SERVICE, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 450000);
         assertEquals(left.mTotalTimeForegroundServiceUsed, 350000);
     }
@@ -407,76 +474,117 @@
         left.mPackageName = "com.test";
         left.mBeginTimeStamp = 100000;
 
-        left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY);
-        left.update("com.test.activity2", 100000, CONTINUE_PREVIOUS_DAY);
+        left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
+        left.update("com.test.activity2", 100000, Event.ACTIVITY_RESUMED, 2);
         left.update("com.test.service1", 100000,
-                CONTINUING_FOREGROUND_SERVICE);
+                CONTINUING_FOREGROUND_SERVICE, 0);
         left.update("com.test.service2", 100000,
-                CONTINUING_FOREGROUND_SERVICE);
+                CONTINUING_FOREGROUND_SERVICE, 0);
         assertEquals(left.mLastTimeUsed, 100000);
         assertEquals(left.mLastTimeForegroundServiceUsed, 100000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"),
-                new Integer(CONTINUE_PREVIOUS_DAY));
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"),
-                new Integer(CONTINUE_PREVIOUS_DAY));
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"),
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+        assertEquals(left.mActivities.get(2), Event.ACTIVITY_RESUMED);
+        assertEquals(left.mForegroundServices.get("com.test.service1"),
                 new Integer(CONTINUING_FOREGROUND_SERVICE));
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"),
+        assertEquals(left.mForegroundServices.get("com.test.service2"),
                 new Integer(CONTINUING_FOREGROUND_SERVICE));
-        assertEquals(left.mLaunchCount, 0);
+        assertEquals(left.mLaunchCount, 2);
 
-        left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND);
+        left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1);
         assertEquals(left.mLastTimeUsed, 350000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null);
+        assertEquals(left.mLastTimeVisible, 350000);
+        assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED);
         assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/);
+        assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/);
 
-        left.update("com.test.service1", 400000, FOREGROUND_SERVICE_STOP);
+        left.update("com.test.service1", 400000, FOREGROUND_SERVICE_STOP, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 400000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null);
+        assertEquals(left.mForegroundServices.get("com.test.service1"), null);
         assertEquals(left.mTotalTimeForegroundServiceUsed, 300000 /*400000 - 100000*/);
 
-        left.update("com.test.activity2", 450000, MOVE_TO_BACKGROUND);
+        left.update("com.test.activity2", 450000, ACTIVITY_PAUSED, 2);
         assertEquals(left.mLastTimeUsed, 450000);
-        assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), null);
+        assertEquals(left.mLastTimeVisible, 450000);
+        assertEquals(left.mActivities.get(2), ACTIVITY_PAUSED);
         assertEquals(left.mTotalTimeInForeground, 250000 + 100000 /*450000 - 350000*/);
+        assertEquals(left.mTotalTimeVisible, 250000 + 100000 /*450000 - 350000*/);
 
-        left.update("com.test.service2", 500000, FOREGROUND_SERVICE_STOP);
+        left.update("com.test.service2", 500000, FOREGROUND_SERVICE_STOP, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 500000);
-        assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), null);
-        assertEquals(left.mTotalTimeForegroundServiceUsed, 300000 + 100000 /*500000 - 400000*/);
+        assertEquals(left.mForegroundServices.get("com.test.service2"), null);
+        assertEquals(left.mTotalTimeForegroundServiceUsed,
+                300000 + 100000 /*500000 - 400000*/);
 
 
-        left.update(null, 550000, END_OF_DAY);
+        left.update(null, 550000, END_OF_DAY, 0);
         assertEquals(left.mLastTimeUsed, 450000);
+        assertEquals(left.mLastTimeVisible, 550000);
         assertEquals(left.mTotalTimeInForeground, 350000);
-        left.update(null, 550000, ROLLOVER_FOREGROUND_SERVICE);
+        assertEquals(left.mTotalTimeVisible, 450000);
+
+        left.update(null, 550000, ROLLOVER_FOREGROUND_SERVICE, 0);
         assertEquals(left.mLastTimeForegroundServiceUsed, 500000);
         assertEquals(left.mTotalTimeForegroundServiceUsed, 400000);
     }
 
+    @Test
+    public void testEvent_FLUSH_TO_DISK() {
+        testClosingEvent(FLUSH_TO_DISK);
+    }
+
+    private void testClosingEvent(int eventType) {
+        // When these three closing events are received, all open activities/services need to be
+        // closed and usage stats are updated.
+        if (eventType != FLUSH_TO_DISK) {
+            fail("Closing eventType must be one of FLUSH_TO_DISK");
+        }
+
+        left.mPackageName = "com.test";
+        left.mBeginTimeStamp = 100000;
+
+        left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1);
+        assertEquals(left.mLastTimeUsed, 100000);
+        assertEquals(left.mLastTimeVisible, 100000);
+        assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED);
+
+        left.update("com.test.service1", 150000, FOREGROUND_SERVICE_START, 0);
+        assertEquals(left.mLastTimeForegroundServiceUsed, 150000);
+        assertEquals(left.mForegroundServices.get("com.test.service1"),
+                new Integer(FOREGROUND_SERVICE_START));
+
+        left.update(null, 200000, eventType, 0);
+        assertEquals(left.mLastTimeUsed, 200000);
+        assertEquals(left.mLastTimeVisible, 200000);
+        assertEquals(left.mTotalTimeInForeground, 200000 - 100000);
+        assertEquals(left.mTotalTimeVisible, 200000 - 100000);
+        assertEquals(left.mLastTimeForegroundServiceUsed, 200000);
+        assertEquals(left.mTotalTimeForegroundServiceUsed, 200000 - 150000);
+    }
+
     void compareUsageStats(UsageStats us1, UsageStats us2) {
         assertEquals(us1.mPackageName, us2.mPackageName);
         assertEquals(us1.mBeginTimeStamp, us2.mBeginTimeStamp);
         assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed);
+        assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible);
         assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed);
         assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground);
         assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed);
         assertEquals(us1.mAppLaunchCount, us2.mAppLaunchCount);
-        assertEquals(us1.mLastForegroundActivityEventMap.size(),
-                us2.mLastForegroundActivityEventMap.size());
-        for (int i = 0; i < us1.mLastForegroundActivityEventMap.size(); i++) {
-            assertEquals(us1.mLastForegroundActivityEventMap.keyAt(i),
-                    us2.mLastForegroundActivityEventMap.keyAt(i));
-            assertEquals(us1.mLastForegroundActivityEventMap.valueAt(i),
-                    us2.mLastForegroundActivityEventMap.valueAt(i));
+        assertEquals(us1.mActivities.size(),
+                us2.mActivities.size());
+        for (int i = 0; i < us1.mActivities.size(); i++) {
+            assertEquals(us1.mActivities.keyAt(i),
+                    us2.mActivities.keyAt(i));
+            assertEquals(us1.mActivities.valueAt(i),
+                    us2.mActivities.valueAt(i));
         }
-        assertEquals(us1.mLastForegroundServiceEventMap.size(),
-                us2.mLastForegroundServiceEventMap.size());
-        for (int i = 0; i < us1.mLastForegroundServiceEventMap.size(); i++) {
-            assertEquals(us1.mLastForegroundServiceEventMap.keyAt(i),
-                    us2.mLastForegroundServiceEventMap.keyAt(i));
-            assertEquals(us1.mLastForegroundServiceEventMap.valueAt(i),
-                    us2.mLastForegroundServiceEventMap.valueAt(i));
+        assertEquals(us1.mForegroundServices.size(),
+                us2.mForegroundServices.size());
+        for (int i = 0; i < us1.mForegroundServices.size(); i++) {
+            assertEquals(us1.mForegroundServices.keyAt(i),
+                    us2.mForegroundServices.keyAt(i));
+            assertEquals(us1.mForegroundServices.valueAt(i),
+                    us2.mForegroundServices.valueAt(i));
         }
         assertEquals(us1.mChooserCounts, us2.mChooserCounts);
     }
diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
index d1dbd3c..5664df6 100644
--- a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.platform.test.annotations.Presubmit;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -37,6 +38,7 @@
  * Test whether Binder calls work source is propagated correctly.
  */
 @LargeTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class BinderWorkSourceTest {
     private static Context sContext;
@@ -125,8 +127,10 @@
         Binder.setCallingWorkSourceUid(UID);
         long token = Binder.clearCallingWorkSource();
         Binder.restoreCallingWorkSource(token);
+        assertEquals(UID, Binder.getCallingWorkSourceUid());
 
         assertEquals(UID, mService.getIncomingWorkSourceUid());
+        // Still the same after the binder transaction.
         assertEquals(UID, Binder.getCallingWorkSourceUid());
     }
 
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 3a37fb6..29bc4c4 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -584,6 +584,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 +606,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 +619,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 +643,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/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index 3d15eb9..a0dca2c 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -133,7 +133,8 @@
     public void buildForStaticLayout() {
         MeasuredParagraph mt = null;
 
-        mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, false, false, null);
+        mt = MeasuredParagraph.buildForStaticLayout(
+                PAINT, "XXX", 0, 3, LTR, false, false, null /* no hint */, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -147,8 +148,8 @@
         assertNotNull(mt.getMeasuredText());
 
         // Recycle it
-        MeasuredParagraph mt2 =
-                MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, false, false, mt);
+        MeasuredParagraph mt2 = MeasuredParagraph.buildForStaticLayout(
+                PAINT, "_VVV_", 1, 4, RTL, false, false, null /* no hint */, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
index f0faaf6..4a6c093 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
@@ -16,6 +16,9 @@
 
 package android.view.textclassifier;
 
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL;
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Person;
@@ -27,16 +30,26 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Locale;
+import java.util.function.Function;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ActionsSuggestionsHelperTest {
+    private static final String LOCALE_TAG = Locale.US.toLanguageTag();
+    private static final Function<CharSequence, String> LANGUAGE_DETECTOR =
+            charSequence -> LOCALE_TAG;
+
     @Test
     public void testToNativeMessages_emptyInput() {
         ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
-                ActionsSuggestionsHelper.toNativeMessages(Collections.emptyList());
+                ActionsSuggestionsHelper.toNativeMessages(
+                        Collections.emptyList(), LANGUAGE_DETECTOR);
 
         assertThat(conversationMessages).isEmpty();
     }
@@ -44,114 +57,89 @@
     @Test
     public void testToNativeMessages_noTextMessages() {
         ConversationActions.Message messageWithoutText =
-                new ConversationActions.Message.Builder().build();
+                new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build();
 
         ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
                 ActionsSuggestionsHelper.toNativeMessages(
-                        Collections.singletonList(messageWithoutText));
+                        Collections.singletonList(messageWithoutText), LANGUAGE_DETECTOR);
 
         assertThat(conversationMessages).isEmpty();
     }
 
     @Test
-    public void testToNativeMessages_missingPersonInFirstMessage() {
-        ConversationActions.Message firstMessage =
-                new ConversationActions.Message.Builder()
-                        .setText("first")
-                        .build();
-        ConversationActions.Message secondMessage =
-                new ConversationActions.Message.Builder()
-                        .setText("second")
-                        .setAuthor(new Person.Builder().build())
-                        .build();
-        ConversationActions.Message thirdMessage =
-                new ConversationActions.Message.Builder()
-                        .setText("third")
-                        .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL)
-                        .build();
-
-        ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
-                ActionsSuggestionsHelper.toNativeMessages(
-                        Arrays.asList(firstMessage, secondMessage, thirdMessage));
-
-        assertThat(conversationMessages).hasLength(2);
-        assertNativeMessage(conversationMessages[0], secondMessage.getText(), 1);
-        assertNativeMessage(conversationMessages[1], thirdMessage.getText(), 0);
-    }
-
-    @Test
-    public void testToNativeMessages_missingPersonInMiddleOfConversation() {
-        ConversationActions.Message firstMessage =
-                new ConversationActions.Message.Builder()
-                        .setText("first")
-                        .setAuthor(new Person.Builder().setName("first").build())
-                        .build();
-        ConversationActions.Message secondMessage =
-                new ConversationActions.Message.Builder()
-                        .setText("second")
-                        .build();
-        ConversationActions.Message thirdMessage =
-                new ConversationActions.Message.Builder()
-                        .setText("third")
-                        .setAuthor(new Person.Builder().setName("third").build())
-                        .build();
-        ConversationActions.Message fourthMessage =
-                new ConversationActions.Message.Builder()
-                        .setText("fourth")
-                        .setAuthor(new Person.Builder().setName("fourth").build())
-                        .build();
-
-        ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
-                ActionsSuggestionsHelper.toNativeMessages(
-                        Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage));
-
-        assertThat(conversationMessages).hasLength(2);
-        assertNativeMessage(conversationMessages[0], thirdMessage.getText(), 2);
-        assertNativeMessage(conversationMessages[1], fourthMessage.getText(), 1);
-    }
-
-    @Test
     public void testToNativeMessages_userIdEncoding() {
         Person userA = new Person.Builder().setName("userA").build();
         Person userB = new Person.Builder().setName("userB").build();
 
         ConversationActions.Message firstMessage =
-                new ConversationActions.Message.Builder()
+                new ConversationActions.Message.Builder(userB)
                         .setText("first")
-                        .setAuthor(userB)
                         .build();
         ConversationActions.Message secondMessage =
-                new ConversationActions.Message.Builder()
+                new ConversationActions.Message.Builder(userA)
                         .setText("second")
-                        .setAuthor(userA)
                         .build();
         ConversationActions.Message thirdMessage =
-                new ConversationActions.Message.Builder()
+                new ConversationActions.Message.Builder(PERSON_USER_LOCAL)
                         .setText("third")
-                        .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL)
                         .build();
         ConversationActions.Message fourthMessage =
-                new ConversationActions.Message.Builder()
+                new ConversationActions.Message.Builder(userA)
                         .setText("fourth")
-                        .setAuthor(userA)
                         .build();
 
         ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
                 ActionsSuggestionsHelper.toNativeMessages(
-                        Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage));
+                        Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage),
+                        LANGUAGE_DETECTOR);
 
         assertThat(conversationMessages).hasLength(4);
-        assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2);
-        assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1);
-        assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0);
-        assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1);
+        assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0);
+        assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0);
+        assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0);
+        assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0);
+    }
+
+    @Test
+    public void testToNativeMessages_referenceTime() {
+        ConversationActions.Message firstMessage =
+                new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+                        .setText("first")
+                        .setReferenceTime(createZonedDateTimeFromMsUtc(1000))
+                        .build();
+        ConversationActions.Message secondMessage =
+                new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+                        .setText("second")
+                        .build();
+        ConversationActions.Message thirdMessage =
+                new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+                        .setText("third")
+                        .setReferenceTime(createZonedDateTimeFromMsUtc(2000))
+                        .build();
+
+        ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
+                ActionsSuggestionsHelper.toNativeMessages(
+                        Arrays.asList(firstMessage, secondMessage, thirdMessage),
+                        LANGUAGE_DETECTOR);
+
+        assertThat(conversationMessages).hasLength(3);
+        assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000);
+        assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0);
+        assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000);
+    }
+
+    private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) {
+        return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC"));
     }
 
     private static void assertNativeMessage(
             ActionsSuggestionsModel.ConversationMessage nativeMessage,
             CharSequence text,
-            int userId) {
+            int userId,
+            long referenceTimeInMsUtc) {
         assertThat(nativeMessage.getText()).isEqualTo(text.toString());
         assertThat(nativeMessage.getUserId()).isEqualTo(userId);
+        assertThat(nativeMessage.getLocales()).isEqualTo(LOCALE_TAG);
+        assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc);
     }
 }
diff --git a/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java
index bae2be3..aaadefb 100644
--- a/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/IntentFactoryTest.java
@@ -56,5 +56,7 @@
         Intent intent = labeledIntent.getIntent();
         assertThat(intent.getAction()).isEqualTo(Intent.ACTION_DEFINE);
         assertThat(intent.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(TEXT);
+        assertThat(
+                intent.getBooleanExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, false)).isTrue();
     }
 }
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index aec4571..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;
 
@@ -373,7 +372,10 @@
     public void testSuggestConversationActions_textReplyOnly_maxThree() {
         if (isTextClassifierDisabled()) return;
         ConversationActions.Message message =
-                new ConversationActions.Message.Builder().setText("Where are you?").build();
+                new ConversationActions.Message.Builder(
+                        ConversationActions.Message.PERSON_USER_REMOTE)
+                        .setText("Where are you?")
+                        .build();
         ConversationActions.TypeConfig typeConfig =
                 new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false)
                         .setIncludedTypes(
@@ -381,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));
         }
     }
 
@@ -495,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/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/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0a2f057..540f749 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -142,6 +142,7 @@
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.CLEAR_APP_USER_DATA"/>
+        <permission name="android.permission.PACKAGE_USAGE_STATS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.permissioncontroller">
@@ -385,6 +386,7 @@
         <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"/>
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/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/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index 4ee45bf..70c90eb 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -65,7 +65,7 @@
      */
     public DrawableWrapper(@Nullable Drawable dr) {
         mState = null;
-        mDrawable = dr;
+        setDrawable(dr);
     }
 
     /**
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 6f78651..2536619 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -19,6 +19,7 @@
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.Px;
 import android.graphics.Paint;
 import android.graphics.Rect;
@@ -49,12 +50,17 @@
  */
 public class MeasuredText {
     private long mNativePtr;
+    private boolean mComputeHyphenation;
+    private boolean mComputeLayout;
     private @NonNull char[] mChars;
 
     // Use builder instead.
-    private MeasuredText(long ptr, @NonNull char[] chars) {
+    private MeasuredText(long ptr, @NonNull char[] chars, boolean computeHyphenation,
+            boolean computeLayout) {
         mNativePtr = ptr;
         mChars = chars;
+        mComputeHyphenation = computeHyphenation;
+        mComputeLayout = computeLayout;
     }
 
     /**
@@ -172,6 +178,7 @@
         private boolean mComputeHyphenation = false;
         private boolean mComputeLayout = true;
         private int mCurrentOffset = 0;
+        private @Nullable MeasuredText mHintMt = null;
 
         /**
          * Construct a builder.
@@ -188,6 +195,27 @@
         }
 
         /**
+         * Construct a builder with existing MeasuredText.
+         *
+         * The MeasuredText returned by build method will hold a reference of the text. Developer is
+         * not supposed to modify the text.
+         *
+         * @param text a text
+         */
+        public Builder(@NonNull MeasuredText text) {
+            Preconditions.checkNotNull(text);
+            mText = text.mChars;
+            mNativePtr = nInitBuilder();
+            if (!text.mComputeLayout) {
+                throw new IllegalArgumentException(
+                    "The input MeasuredText must not be created with setComputeLayout(false).");
+            }
+            mComputeHyphenation = text.mComputeHyphenation;
+            mComputeLayout = text.mComputeLayout;
+            mHintMt = text;
+        }
+
+        /**
          * Apply styles to the given length.
          *
          * Keeps an internal offset which increases at every append. The initial value for this
@@ -282,10 +310,16 @@
             if (mCurrentOffset != mText.length) {
                 throw new IllegalStateException("Style info has not been provided for all text.");
             }
+            if (mHintMt != null && mHintMt.mComputeHyphenation != mComputeHyphenation) {
+                throw new IllegalArgumentException(
+                        "The hyphenation configuration is different from given hint MeasuredText");
+            }
             try {
-                long ptr = nBuildMeasuredText(mNativePtr, mText, mComputeHyphenation,
+                long hintPtr = (mHintMt == null) ? 0 : mHintMt.getNativePtr();
+                long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation,
                         mComputeLayout);
-                MeasuredText res = new MeasuredText(ptr, mText);
+                final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation,
+                        mComputeLayout);
                 sRegistry.registerNativeAllocation(res, ptr);
                 return res;
             } finally {
@@ -339,6 +373,7 @@
 
         private static native long nBuildMeasuredText(
                 /* Non Zero */ long nativeBuilderPtr,
+                long hintMtPtr,
                 @NonNull char[] text,
                 boolean computeHyphenation,
                 boolean computeLayout);
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 23ec5ab..f1903a5 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -1,2 +1,3 @@
 set noparent
 toddke@google.com
+rtmitchell@google.com
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 91261aa..cf2d8fb 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1622,7 +1622,7 @@
 {
   struct ResChunk_header header;
 
-  enum PolicyFlags {
+  enum PolicyFlags : uint32_t {
     // Any overlay can overlay these resources.
     POLICY_PUBLIC = 0x00000001,
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d22eaf3..0503f36 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -60,6 +60,7 @@
         "libutils",
         "libEGL",
         "libGLESv2",
+        "libGLESv3",
         "libvulkan",
         "libui",
         "libgui",
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/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/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/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/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/location/java/android/location/SettingInjectorService.java b/location/java/android/location/SettingInjectorService.java
index fcd2cde..c201770 100644
--- a/location/java/android/location/SettingInjectorService.java
+++ b/location/java/android/location/SettingInjectorService.java
@@ -17,6 +17,7 @@
 package android.location;
 
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -26,7 +27,7 @@
 import android.util.Log;
 
 /**
- * Dynamically specifies the enabled status of a preference injected into
+ * Dynamically specifies the summary (subtitle) and enabled status of a preference injected into
  * the list of app settings displayed by the system settings app
  * <p/>
  * For use only by apps that are included in the system image, for preferences that affect multiple
@@ -71,12 +72,13 @@
  * </ul>
  *
  * To ensure a good user experience, your {@link android.app.Application#onCreate()},
- * and {@link #onGetEnabled()} methods must all be fast. If either is slow,
- * it can delay the display of settings values for other apps as well. Note further that these
- * methods are called on your app's UI thread.
+ * {@link #onGetSummary()}, and {@link #onGetEnabled()} methods must all be fast. If any are slow,
+ * it can delay the display of settings values for other apps as well. Note further that all are
+ * called on your app's UI thread.
  * <p/>
  * For compactness, only one copy of a given setting should be injected. If each account has a
- * distinct value for the setting, then only {@code settingsActivity} should display the value for
+ * distinct value for the setting, then the {@link #onGetSummary()} value should represent a summary
+ * of the state across all of the accounts and {@code settingsActivity} should display the value for
  * each account.
  */
 public abstract class SettingInjectorService extends Service {
@@ -108,6 +110,14 @@
             "android.location.InjectedSettingChanged";
 
     /**
+     * Name of the bundle key for the string specifying the summary for the setting (e.g., "ON" or
+     * "OFF").
+     *
+     * @hide
+     */
+    public static final String SUMMARY_KEY = "summary";
+
+    /**
      * Name of the bundle key for the string specifying whether the setting is currently enabled.
      *
      * @hide
@@ -150,36 +160,41 @@
     }
 
     private void onHandleIntent(Intent intent) {
-
-        boolean enabled;
+        String summary = null;
+        boolean enabled = false;
         try {
+            summary = onGetSummary();
             enabled = onGetEnabled();
-        } catch (RuntimeException e) {
-            // Exception. Send status anyway, so that settings injector can immediately start
-            // loading the status of the next setting.
-            sendStatus(intent, true);
-            throw e;
+        } finally {
+            // If exception happens, send status anyway, so that settings injector can immediately
+            // start loading the status of the next setting. But leave the exception uncaught to
+            // crash the injector service itself.
+            sendStatus(intent, summary, enabled);
         }
-
-        sendStatus(intent, enabled);
     }
 
     /**
      * Send the enabled values back to the caller via the messenger encoded in the
      * intent.
      */
-    private void sendStatus(Intent intent, boolean enabled) {
+    private void sendStatus(Intent intent, String summary, boolean enabled) {
+        Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY);
+        // Bail out to avoid crashing GmsCore with incoming malicious Intent.
+        if (messenger == null) {
+            return;
+        }
+
         Message message = Message.obtain();
         Bundle bundle = new Bundle();
+        bundle.putString(SUMMARY_KEY, summary);
         bundle.putBoolean(ENABLED_KEY, enabled);
         message.setData(bundle);
 
         if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, mName + ": received " + intent
+            Log.d(TAG, mName + ": received " + intent + ", summary=" + summary
                     + ", enabled=" + enabled + ", sending message: " + message);
         }
 
-        Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY);
         try {
             messenger.send(message);
         } catch (RemoteException e) {
@@ -188,14 +203,12 @@
     }
 
     /**
-     * This method is no longer called, because status values are no longer shown for any injected
-     * setting.
+     * Returns the {@link android.preference.Preference#getSummary()} value (allowed to be null or
+     * empty). Should not perform unpredictably-long operations such as network access--see the
+     * running-time comments in the class-level javadoc.
      *
-     * @return ignored
-     *
-     * @deprecated not called any more
+     * @return the {@link android.preference.Preference#getSummary()} value
      */
-    @Deprecated
     protected abstract String onGetSummary();
 
     /**
@@ -217,4 +230,12 @@
      * @return the {@link android.preference.Preference#isEnabled()} value
      */
     protected abstract boolean onGetEnabled();
+
+    /**
+     * Sends a broadcast to refresh the injected settings on location settings page.
+     */
+    public static final void refreshSettings(Context context) {
+        Intent intent = new Intent(ACTION_INJECTED_SETTING_CHANGED);
+        context.sendBroadcast(intent);
+    }
 }
diff --git a/media/Android.bp b/media/Android.bp
new file mode 100644
index 0000000..d5da6f2
--- /dev/null
+++ b/media/Android.bp
@@ -0,0 +1,36 @@
+java_library {
+    // TODO: include media2.jar in the media apex and add it to the bootclasspath.
+    name: "media2",
+
+    srcs: [
+        ":media2-srcs",
+        ":framework-media-annotation-srcs",
+    ],
+
+    static_libs: [
+        "mediaplayer2-protos",
+    ],
+
+    // Make sure that the implementaion only relies on SDK or system APIs.
+    sdk_version: "system_current",
+}
+
+filegroup {
+    name: "media2-srcs",
+    srcs: [
+        "java/android/media/CloseGuard.java",
+        "java/android/media/DataSourceCallback.java",
+        "java/android/media/DataSourceDesc.java",
+        "java/android/media/UriDataSourceDesc.java",
+        "java/android/media/FileDataSourceDesc.java",
+        "java/android/media/CallbackDataSourceDesc.java",
+        "java/android/media/VideoSize.java",
+        "java/android/media/Media2Utils.java",
+        "java/android/media/MediaPlayer2Utils.java",
+        "java/android/media/MediaPlayer2.java",
+        "java/android/media/Media2HTTPService.java",
+        "java/android/media/Media2HTTPConnection.java",
+        "java/android/media/RoutingDelegate.java",
+        "java/android/media/BufferingParams.java",
+    ],
+}
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index 5467a69..0a9ca02 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -80,16 +80,12 @@
      * The audio attributes for the audio focus request.
      * @return non-null {@link AudioAttributes}.
      */
-    @SystemApi
     public AudioAttributes getAttributes() { return mAttributes; }
 
-    @SystemApi
     public int getClientUid() { return mClientUid; }
 
-    @SystemApi
     public String getClientId() { return mClientId; }
 
-    @SystemApi
     public String getPackageName() { return mPackageName; }
 
     /**
@@ -99,7 +95,6 @@
      *     {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK},
      *     {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
      */
-    @SystemApi
     public int getGainRequest() { return mGainRequest; }
 
     /**
@@ -109,7 +104,6 @@
      *   {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT} or
      *   {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
      */
-    @SystemApi
     public int getLossReceived() { return mLossReceived; }
 
     /** @hide */
@@ -124,7 +118,6 @@
      *     {@link AudioManager#AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}, and
      *     {@link AudioManager#AUDIOFOCUS_FLAG_LOCK}.
      */
-    @SystemApi
     public int getFlags() { return mFlags; }
 
     @Override
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 823af65..fca7074 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -254,6 +254,22 @@
                 mLabels.hashCode());
     }
 
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName() + " ");
+        sb.append("{ presentation id=" + mPresentationId);
+        sb.append(", program id=" + mProgramId);
+        sb.append(", language=" + mLanguage);
+        sb.append(", labels=" + mLabels);
+        sb.append(", mastering indication=" + mMasteringIndication);
+        sb.append(", audio description=" + mAudioDescriptionAvailable);
+        sb.append(", spoken subtitles=" + mSpokenSubtitlesAvailable);
+        sb.append(", dialogue enhancement=" + mDialogueEnhancementAvailable);
+        sb.append(" }");
+        return sb.toString();
+    }
+
     /**
      * A builder class for creating {@link AudioPresentation} objects.
      */
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/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 242ae46..0375de3 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -27,7 +27,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.IHwBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -1427,6 +1426,24 @@
     <td>&#9094;</td>
    </tr>
    <tr>
+    <td>(29+)</td>
+    <td>29+</td>
+    <td>29+</td>
+    <td>29+</td>
+    <td>(29+)</td>
+    <td>(29+)</td>
+    <td>-</td>
+    <td class=fn>{@link #setAudioPresentation setAudioPresentation}</td>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+   </tr>
+   <tr>
     <td>-</td>
     <td>-</td>
     <td>18+</td>
@@ -3260,6 +3277,19 @@
     public native final void setVideoScalingMode(@VideoScalingMode int mode);
 
     /**
+     * Sets the audio presentation.
+     * @param presentation see {@link AudioPresentation}. In particular, id should be set.
+     */
+    public void setAudioPresentation(@NonNull AudioPresentation presentation) {
+        if (presentation == null) {
+            throw new IllegalArgumentException("audio presentation is null");
+        }
+        native_setAudioPresentation(presentation.getPresentationId(), presentation.getProgramId());
+    }
+
+    private native void native_setAudioPresentation(int presentationId, int programId);
+
+    /**
      * Get the component name. If the codec was created by createDecoderByType
      * or createEncoderByType, what component is chosen is not known beforehand.
      * @throws IllegalStateException if in the Released state.
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/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index c91d4d3..0fb392b 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -18,10 +18,10 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.media.MediaCodec;
 import android.media.MediaCodec.BufferInfo;
+
 import dalvik.system.CloseGuard;
 
 import java.io.FileDescriptor;
@@ -269,8 +269,10 @@
         public static final int MUXER_OUTPUT_3GPP   = MUXER_OUTPUT_FIRST + 2;
         /** HEIF media file format*/
         public static final int MUXER_OUTPUT_HEIF   = MUXER_OUTPUT_FIRST + 3;
+        /** Ogg media file format*/
+        public static final int MUXER_OUTPUT_OGG   = MUXER_OUTPUT_FIRST + 4;
         /** @hide */
-        public static final int MUXER_OUTPUT_LAST   = MUXER_OUTPUT_HEIF;
+        public static final int MUXER_OUTPUT_LAST   = MUXER_OUTPUT_OGG;
     };
 
     /** @hide */
@@ -279,6 +281,7 @@
         OutputFormat.MUXER_OUTPUT_WEBM,
         OutputFormat.MUXER_OUTPUT_3GPP,
         OutputFormat.MUXER_OUTPUT_HEIF,
+        OutputFormat.MUXER_OUTPUT_OGG,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Format {}
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index 9038f72..d4b1c7f 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -3960,7 +3960,12 @@
                     textBounds = new Rect(left, top, right, bottom);
                 }
             }
+            return null;
+            /* TimedText c-tor usage is temporarily commented out.
+             * TODO(b/117527789): use SUBTITLE path for MEDIA_MIMETYPE_TEXT_3GPP track
+             *                    and remove TimedText path from MediaPlayer2.
             return new TimedText(textChars, textBounds);
+            */
         }
     }
 
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index d4bfd61..8ced021 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -22,17 +22,17 @@
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
 import android.hardware.Camera;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Surface;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -41,8 +41,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import com.android.internal.annotations.GuardedBy;
-
 /**
  * Used to record audio and video. The recording control is based on a
  * simple state machine (see below).
@@ -450,6 +448,9 @@
 
         /** VP8/VORBIS data in a WEBM container */
         public static final int WEBM = 9;
+
+        /** Opus data in a Ogg container */
+        public static final int OGG = 11;
     };
 
     /**
@@ -474,6 +475,8 @@
         public static final int AAC_ELD = 5;
         /** Ogg Vorbis audio codec */
         public static final int VORBIS = 6;
+        /** Opus audio codec */
+        public static final int OPUS = 7;
     }
 
     /**
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/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 8bc1d35..7fb3aa6 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -81,14 +81,12 @@
      * An audio mix behavior where the output of the mix is sent to the original destination of
      * the audio signal, i.e. an output device for an output mix, or a recording for an input mix.
      */
-    @SystemApi
     public static final int ROUTE_FLAG_RENDER    = 0x1;
     /**
      * An audio mix behavior where the output of the mix is rerouted back to the framework and
      * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord}
      * APIs.
      */
-    @SystemApi
     public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
 
     private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK;
@@ -113,31 +111,23 @@
 
     // MIX_STATE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
     /**
-     * @hide
      * State of a mix before its policy is enabled.
      */
-    @SystemApi
     public static final int MIX_STATE_DISABLED = -1;
     /**
-     * @hide
      * State of a mix when there is no audio to mix.
      */
-    @SystemApi
     public static final int MIX_STATE_IDLE = 0;
     /**
-     * @hide
      * State of a mix that is actively mixing audio.
      */
-    @SystemApi
     public static final int MIX_STATE_MIXING = 1;
 
     /**
-     * @hide
      * The current mixing state.
      * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE},
      *          {@link #MIX_STATE_MIXING}.
      */
-    @SystemApi
     public int getMixState() {
         return mMixState;
     }
@@ -201,9 +191,7 @@
 
     /**
      * Builder class for {@link AudioMix} objects
-     *
      */
-    @SystemApi
     public static class Builder {
         private AudioMixingRule mRule = null;
         private AudioFormat mFormat = null;
@@ -224,7 +212,6 @@
          * @param rule a non-null {@link AudioMixingRule} instance.
          * @throws IllegalArgumentException
          */
-        @SystemApi
         public Builder(AudioMixingRule rule)
                 throws IllegalArgumentException {
             if (rule == null) {
@@ -284,7 +271,6 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        @SystemApi
         public Builder setFormat(AudioFormat format)
                 throws IllegalArgumentException {
             if (format == null) {
@@ -302,7 +288,6 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        @SystemApi
         public Builder setRouteFlags(@RouteFlags int routeFlags)
                 throws IllegalArgumentException {
             if (routeFlags == 0) {
@@ -329,7 +314,6 @@
          * @return the same Builder instance
          * @throws IllegalArgumentException
          */
-        @SystemApi
         public Builder setDevice(@NonNull AudioDeviceInfo device) throws IllegalArgumentException {
             if (device == null) {
                 throw new IllegalArgumentException("Illegal null AudioDeviceInfo argument");
@@ -347,7 +331,6 @@
          * @return a new {@link AudioMix} object
          * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
          */
-        @SystemApi
         public AudioMix build() throws IllegalArgumentException {
             if (mRule == null) {
                 throw new IllegalArgumentException("Illegal null AudioMixingRule");
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index fbee62a..6c48cdb 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -54,7 +54,6 @@
      * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of
      * {@link AudioAttributes}.
      */
-    @SystemApi
     public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
     /**
      * A rule requiring the capture preset information of the {@link AudioAttributes} to match.
@@ -62,14 +61,12 @@
      * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of
      * {@link AudioAttributes}.
      */
-    @SystemApi
     public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
     /**
      * A rule requiring the UID of the audio stream to match that specified.
      * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object
      * parameter is an instance of {@link java.lang.Integer}.
      */
-    @SystemApi
     public static final int RULE_MATCH_UID = 0x1 << 2;
 
     private final static int RULE_EXCLUSION_MASK = 0x8000;
@@ -239,7 +236,6 @@
     /**
      * Builder class for {@link AudioMixingRule} objects
      */
-    @SystemApi
     public static class Builder {
         private ArrayList<AudioMixMatchCriterion> mCriteria;
         private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
@@ -247,7 +243,6 @@
         /**
          * Constructs a new Builder with no rules.
          */
-        @SystemApi
         public Builder() {
             mCriteria = new ArrayList<AudioMixMatchCriterion>();
         }
@@ -262,7 +257,6 @@
          * @throws IllegalArgumentException
          * @see #excludeRule(AudioAttributes, int)
          */
-        @SystemApi
         public Builder addRule(AudioAttributes attrToMatch, int rule)
                 throws IllegalArgumentException {
             if (!isValidAttributesSystemApiRule(rule)) {
@@ -291,7 +285,6 @@
          * @throws IllegalArgumentException
          * @see #addRule(AudioAttributes, int)
          */
-        @SystemApi
         public Builder excludeRule(AudioAttributes attrToMatch, int rule)
                 throws IllegalArgumentException {
             if (!isValidAttributesSystemApiRule(rule)) {
@@ -313,7 +306,6 @@
          * @throws IllegalArgumentException
          * @see #excludeMixRule(int, Object)
          */
-        @SystemApi
         public Builder addMixRule(int rule, Object property) throws IllegalArgumentException {
             if (!isValidSystemApiRule(rule)) {
                 throw new IllegalArgumentException("Illegal rule value " + rule);
@@ -343,7 +335,6 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        @SystemApi
         public Builder excludeMixRule(int rule, Object property) throws IllegalArgumentException {
             if (!isValidSystemApiRule(rule)) {
                 throw new IllegalArgumentException("Illegal rule value " + rule);
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 11107e2..6103f557 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -58,12 +58,10 @@
     /**
      * The status of an audio policy that is valid but cannot be used because it is not registered.
      */
-    @SystemApi
     public static final int POLICY_STATUS_UNREGISTERED = 1;
     /**
      * The status of an audio policy that is valid, successfully registered and thus active.
      */
-    @SystemApi
     public static final int POLICY_STATUS_REGISTERED = 2;
 
     private int mStatus;
@@ -75,7 +73,6 @@
      * The behavior of a policy with regards to audio focus where it relies on the application
      * to do the ducking, the is the legacy and default behavior.
      */
-    @SystemApi
     public static final int FOCUS_POLICY_DUCKING_IN_APP = 0;
     public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP;
     /**
@@ -85,7 +82,6 @@
      * <br>Can only be used after having set a listener with
      * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
      */
-    @SystemApi
     public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1;
 
     private AudioPolicyFocusListener mFocusListener;
@@ -133,7 +129,6 @@
      * Builder class for {@link AudioPolicy} objects.
      * By default the policy to be created doesn't govern audio focus decisions.
      */
-    @SystemApi
     public static class Builder {
         private ArrayList<AudioMix> mMixes;
         private Context mContext;
@@ -147,7 +142,6 @@
          * Constructs a new Builder with no audio mixes.
          * @param context the context for the policy
          */
-        @SystemApi
         public Builder(Context context) {
             mMixes = new ArrayList<AudioMix>();
             mContext = context;
@@ -159,7 +153,6 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        @SystemApi
         public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
             if (mix == null) {
                 throw new IllegalArgumentException("Illegal null AudioMix argument");
@@ -174,7 +167,6 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        @SystemApi
         public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
             if (looper == null) {
                 throw new IllegalArgumentException("Illegal null Looper argument");
@@ -187,7 +179,6 @@
          * Sets the audio focus listener for the policy.
          * @param l a {@link AudioPolicy.AudioPolicyFocusListener}
          */
-        @SystemApi
         public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) {
             mFocusListener = l;
         }
@@ -201,7 +192,6 @@
          * @param enforce true if the policy will govern audio focus decisions.
          * @return the same Builder instance.
          */
-        @SystemApi
         public Builder setIsAudioFocusPolicy(boolean isFocusPolicy) {
             mIsFocusPolicy = isFocusPolicy;
             return this;
@@ -211,12 +201,10 @@
          * Sets the audio policy status listener.
          * @param l a {@link AudioPolicy.AudioPolicyStatusListener}
          */
-        @SystemApi
         public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
             mStatusListener = l;
         }
 
-        @SystemApi
         /**
          * Sets the callback to receive all volume key-related events.
          * The callback will only be called if the device is configured to handle volume events
@@ -240,7 +228,6 @@
          *     {@link AudioPolicy.AudioPolicyStatusListener} but the policy was configured
          *     as an audio focus policy with {@link #setIsAudioFocusPolicy(boolean)}.
          */
-        @SystemApi
         public AudioPolicy build() {
             if (mStatusListener != null) {
                 // the AudioPolicy status listener includes updates on each mix activity state
@@ -258,7 +245,6 @@
     }
 
     /**
-     * @hide
      * Update the current configuration of the set of audio mixes by adding new ones, while
      * keeping the policy registered.
      * This method can only be called on a registered policy.
@@ -266,7 +252,6 @@
      * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
      *    otherwise.
      */
-    @SystemApi
     public int attachMixes(@NonNull List<AudioMix> mixes) {
         if (mixes == null) {
             throw new IllegalArgumentException("Illegal null list of AudioMix");
@@ -299,7 +284,6 @@
     }
 
     /**
-     * @hide
      * Update the current configuration of the set of audio mixes by removing some, while
      * keeping the policy registered.
      * This method can only be called on a registered policy.
@@ -307,7 +291,6 @@
      * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
      *    otherwise.
      */
-    @SystemApi
     public int detachMixes(@NonNull List<AudioMix> mixes) {
         if (mixes == null) {
             throw new IllegalArgumentException("Illegal null list of AudioMix");
@@ -405,7 +388,6 @@
      * Returns the current behavior for audio focus-related ducking.
      * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY}
      */
-    @SystemApi
     public int getFocusDuckingBehavior() {
         return mConfig.mDuckingPolicy;
     }
@@ -422,7 +404,6 @@
      * @throws IllegalArgumentException
      * @throws IllegalStateException
      */
-    @SystemApi
     public int setFocusDuckingBehavior(int behavior)
             throws IllegalArgumentException, IllegalStateException {
         if ((behavior != FOCUS_POLICY_DUCKING_IN_APP)
@@ -466,7 +447,6 @@
      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
      * @throws IllegalArgumentException
      */
-    @SystemApi
     public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
         if (!policyReadyToUse()) {
             Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
@@ -506,7 +486,6 @@
      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
      * @throws IllegalArgumentException
      */
-    @SystemApi
     public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
         if (!policyReadyToUse()) {
             Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
@@ -528,18 +507,15 @@
         return at;
     }
 
-    @SystemApi
     public int getStatus() {
         return mStatus;
     }
 
-    @SystemApi
     public static abstract class AudioPolicyStatusListener {
         public void onStatusChange() {}
         public void onMixStateUpdate(AudioMix mix) {}
     }
 
-    @SystemApi
     public static abstract class AudioPolicyFocusListener {
         public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {}
         public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {}
@@ -563,7 +539,6 @@
         public void onAudioFocusAbandon(AudioFocusInfo afi) {}
     }
 
-    @SystemApi
     /**
      * Callback class to receive volume change-related events.
      * See {@link #Builder.setAudioPolicyVolumeCallback(AudioPolicyCallback)} to configure the
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index ec2d9ba..e8f188c 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -1332,7 +1332,6 @@
      *
      * @return the list of content ratings blocked by the user.
      */
-    @SystemApi
     public List<TvContentRating> getBlockedRatings() {
         try {
             List<TvContentRating> ratings = new ArrayList<>();
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 5037209..2578608 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -752,6 +752,13 @@
     }
 }
 
+void JMediaCodec::selectAudioPresentation(const int32_t presentationId, const int32_t programId) {
+    sp<AMessage> msg = new AMessage;
+    msg->setInt32("audio-presentation-presentation-id", presentationId);
+    msg->setInt32("audio-presentation-program-id", programId);
+    (void)mCodec->setParameters(msg);
+}
+
 static jthrowable createCodecException(
         JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) {
     ScopedLocalRef<jclass> clazz(
@@ -1874,6 +1881,18 @@
     codec->setVideoScalingMode(mode);
 }
 
+static void android_media_MediaCodec_setAudioPresentation(
+        JNIEnv *env, jobject thiz, jint presentationId, jint programId) {
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        return;
+    }
+
+    codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId);
+}
+
 static void android_media_MediaCodec_native_init(JNIEnv *env) {
     ScopedLocalRef<jclass> clazz(
             env, env->FindClass("android/media/MediaCodec"));
@@ -2183,6 +2202,9 @@
     { "setVideoScalingMode", "(I)V",
       (void *)android_media_MediaCodec_setVideoScalingMode },
 
+    { "native_setAudioPresentation", "(II)V",
+      (void *)android_media_MediaCodec_setAudioPresentation },
+
     { "native_init", "()V", (void *)android_media_MediaCodec_native_init },
 
     { "native_setup", "(Ljava/lang/String;ZZ)V",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 985f55a..0a53f1a 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -128,6 +128,8 @@
 
     void setVideoScalingMode(int mode);
 
+    void selectAudioPresentation(const int32_t presentationId, const int32_t programId);
+
 protected:
     virtual ~JMediaCodec();
 
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 693bd8b..747d4c01 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include "android_media_AudioEffect.h"
 
 #include <stdio.h>
 
@@ -29,6 +28,10 @@
 
 #include <nativehelper/ScopedUtfChars.h>
 
+#include "android_media_AudioEffect.h"
+#include "android_media_AudioEffectDescriptor.h"
+#include "android_media_AudioErrors.h"
+
 using namespace android;
 
 #define AUDIOEFFECT_SUCCESS                      0
@@ -49,8 +52,6 @@
     jmethodID midPostNativeEvent;   // event post callback method
     jfieldID  fidNativeAudioEffect; // stores in Java the native AudioEffect object
     jfieldID  fidJniData;           // stores in Java additional resources used by the native AudioEffect
-    jclass    clazzDesc;            // AudioEffect.Descriptor class
-    jmethodID midDescCstor;         // AudioEffect.Descriptor class constructor
 };
 static fields_t fields;
 
@@ -226,7 +227,6 @@
     ALOGV("android_media_AudioEffect_native_init");
 
     fields.clazzEffect = NULL;
-    fields.clazzDesc = NULL;
 
     // Get the AudioEffect class
     jclass clazz = env->FindClass(kClassPathName);
@@ -263,23 +263,6 @@
         ALOGE("Can't find AudioEffect.%s", "mJniData");
         return;
     }
-
-    clazz = env->FindClass("android/media/audiofx/AudioEffect$Descriptor");
-    if (clazz == NULL) {
-        ALOGE("Can't find android/media/audiofx/AudioEffect$Descriptor class");
-        return;
-    }
-    fields.clazzDesc = (jclass)env->NewGlobalRef(clazz);
-
-    fields.midDescCstor
-            = env->GetMethodID(
-                    fields.clazzDesc,
-                    "<init>",
-                    "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
-    if (fields.midDescCstor == NULL) {
-        ALOGE("Can't find android/media/audiofx/AudioEffect$Descriptor class constructor");
-        return;
-    }
 }
 
 
@@ -297,12 +280,6 @@
     const char *uuidStr = NULL;
     effect_descriptor_t desc;
     jobject jdesc;
-    char str[EFFECT_STRING_LEN_MAX];
-    jstring jdescType;
-    jstring jdescUuid;
-    jstring jdescConnect;
-    jstring jdescName;
-    jstring jdescImplementor;
 
     ScopedUtfChars opPackageNameStr(env, opPackageName);
 
@@ -394,41 +371,12 @@
     // get the effect descriptor
     desc = lpAudioEffect->descriptor();
 
-    AudioEffect::guidToString(&desc.type, str, EFFECT_STRING_LEN_MAX);
-    jdescType = env->NewStringUTF(str);
-
-    AudioEffect::guidToString(&desc.uuid, str, EFFECT_STRING_LEN_MAX);
-    jdescUuid = env->NewStringUTF(str);
-
-    if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
-        jdescConnect = env->NewStringUTF("Auxiliary");
-    } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) {
-        jdescConnect = env->NewStringUTF("Pre Processing");
-    } else {
-        jdescConnect = env->NewStringUTF("Insert");
-    }
-
-    jdescName = env->NewStringUTF(desc.name);
-    jdescImplementor = env->NewStringUTF(desc.implementor);
-
-    jdesc = env->NewObject(fields.clazzDesc,
-                           fields.midDescCstor,
-                           jdescType,
-                           jdescUuid,
-                           jdescConnect,
-                           jdescName,
-                           jdescImplementor);
-    env->DeleteLocalRef(jdescType);
-    env->DeleteLocalRef(jdescUuid);
-    env->DeleteLocalRef(jdescConnect);
-    env->DeleteLocalRef(jdescName);
-    env->DeleteLocalRef(jdescImplementor);
-    if (jdesc == NULL) {
-        ALOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)");
+    if (convertAudioEffectDescriptorFromNative(env, &jdesc, &desc) != AUDIO_JAVA_SUCCESS) {
         goto setup_failure;
     }
 
     env->SetObjectArrayElement(javadesc, 0, jdesc);
+    env->DeleteLocalRef(jdesc);
 
     setAudioEffect(env, thiz, lpAudioEffect);
 
@@ -729,23 +677,16 @@
 android_media_AudioEffect_native_queryEffects(JNIEnv *env, jclass clazz __unused)
 {
     effect_descriptor_t desc;
-    char str[EFFECT_STRING_LEN_MAX];
     uint32_t totalEffectsCount = 0;
     uint32_t returnedEffectsCount = 0;
     uint32_t i = 0;
-    jstring jdescType;
-    jstring jdescUuid;
-    jstring jdescConnect;
-    jstring jdescName;
-    jstring jdescImplementor;
-    jobject jdesc;
     jobjectArray ret;
 
     if (AudioEffect::queryNumberEffects(&totalEffectsCount) != NO_ERROR) {
         return NULL;
     }
 
-    jobjectArray temp = env->NewObjectArray(totalEffectsCount, fields.clazzDesc, NULL);
+    jobjectArray temp = env->NewObjectArray(totalEffectsCount, audioEffectDescriptorClass(), NULL);
     if (temp == NULL) {
         return temp;
     }
@@ -757,49 +698,18 @@
             goto queryEffects_failure;
         }
 
-        if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
-            jdescConnect = env->NewStringUTF("Auxiliary");
-        } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_INSERT) {
-            jdescConnect = env->NewStringUTF("Insert");
-        } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) {
-            jdescConnect = env->NewStringUTF("Pre Processing");
-        } else {
+        jobject jdesc;
+        if (convertAudioEffectDescriptorFromNative(env, &jdesc, &desc) != AUDIO_JAVA_SUCCESS) {
             continue;
         }
-
-        AudioEffect::guidToString(&desc.type, str, EFFECT_STRING_LEN_MAX);
-        jdescType = env->NewStringUTF(str);
-
-        AudioEffect::guidToString(&desc.uuid, str, EFFECT_STRING_LEN_MAX);
-        jdescUuid = env->NewStringUTF(str);
-
-        jdescName = env->NewStringUTF(desc.name);
-        jdescImplementor = env->NewStringUTF(desc.implementor);
-
-        jdesc = env->NewObject(fields.clazzDesc,
-                               fields.midDescCstor,
-                               jdescType,
-                               jdescUuid,
-                               jdescConnect,
-                               jdescName,
-                               jdescImplementor);
-        env->DeleteLocalRef(jdescType);
-        env->DeleteLocalRef(jdescUuid);
-        env->DeleteLocalRef(jdescConnect);
-        env->DeleteLocalRef(jdescName);
-        env->DeleteLocalRef(jdescImplementor);
-        if (jdesc == NULL) {
-            ALOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)");
-            goto queryEffects_failure;
-        }
-
         env->SetObjectArrayElement(temp, returnedEffectsCount++, jdesc);
-   }
+        env->DeleteLocalRef(jdesc);
+    }
 
     if (returnedEffectsCount == 0) {
         goto queryEffects_failure;
     }
-    ret = env->NewObjectArray(returnedEffectsCount, fields.clazzDesc, NULL);
+    ret = env->NewObjectArray(returnedEffectsCount, audioEffectDescriptorClass(), NULL);
     if (ret == NULL) {
         goto queryEffects_failure;
     }
@@ -835,51 +745,11 @@
     }
     ALOGV("queryDefaultPreProcessing() got %d effects", numEffects);
 
-    jobjectArray ret = env->NewObjectArray(numEffects, fields.clazzDesc, NULL);
-    if (ret == NULL) {
-        return ret;
-    }
+    std::vector<effect_descriptor_t> descVector(descriptors.get(), descriptors.get() + numEffects);
 
-    char str[EFFECT_STRING_LEN_MAX];
-    jstring jdescType;
-    jstring jdescUuid;
-    jstring jdescConnect;
-    jstring jdescName;
-    jstring jdescImplementor;
-    jobject jdesc;
-
-    for (uint32_t i = 0; i < numEffects; i++) {
-
-        AudioEffect::guidToString(&descriptors[i].type, str, EFFECT_STRING_LEN_MAX);
-        jdescType = env->NewStringUTF(str);
-        AudioEffect::guidToString(&descriptors[i].uuid, str, EFFECT_STRING_LEN_MAX);
-        jdescUuid = env->NewStringUTF(str);
-        jdescConnect = env->NewStringUTF("Pre Processing");
-        jdescName = env->NewStringUTF(descriptors[i].name);
-        jdescImplementor = env->NewStringUTF(descriptors[i].implementor);
-
-        jdesc = env->NewObject(fields.clazzDesc,
-                               fields.midDescCstor,
-                               jdescType,
-                               jdescUuid,
-                               jdescConnect,
-                               jdescName,
-                               jdescImplementor);
-        env->DeleteLocalRef(jdescType);
-        env->DeleteLocalRef(jdescUuid);
-        env->DeleteLocalRef(jdescConnect);
-        env->DeleteLocalRef(jdescName);
-        env->DeleteLocalRef(jdescImplementor);
-        if (jdesc == NULL) {
-            ALOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)");
-            env->DeleteLocalRef(ret);
-            return NULL;
-        }
-
-        env->SetObjectArrayElement(ret, i, jdesc);
-   }
-
-   return ret;
+    jobjectArray ret;
+    convertAudioEffectDescriptorVectorFromNative(env, &ret, descVector);
+    return ret;
 }
 
 // ----------------------------------------------------------------------------
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index b7d7b03..45de36e 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -440,6 +440,7 @@
         if (lpVisualizer == 0) {
             return;
         }
+        lpVisualizer->release();
     }
     // delete the JNI data
     VisualizerJniStorage* lpJniStorage =
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 96113d6..537aed4 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -235,6 +235,10 @@
     android_getaddrinfofornetwork; # introduced=23
     android_setprocnetwork; # introduced=23
     android_setsocknetwork; # introduced=23
+    android_res_cancel; # introduced=29
+    android_res_nquery; # introduced=29
+    android_res_nresult; # introduced=29
+    android_res_nsend; # introduced=29
   local:
     *;
 };
diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt
index 9b5a5a1..be3531d 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -1,10 +1,15 @@
-# These functions have been part of the NDK since API 24.
 # They are also all available to vendor code.
 LIBANDROID_NET {
   global:
+    # These functions have been part of the NDK since API 24.
+    android_getaddrinfofornetwork; # vndk
     android_setsocknetwork; # vndk
     android_setprocnetwork; # vndk
-    android_getaddrinfofornetwork; # vndk
+    # These functions have been part of the NDK since API 29.
+    android_res_cancel; # vndk
+    android_res_nquery; # vndk
+    android_res_nresult; # vndk
+    android_res_nsend; # vndk
   local:
     *;
 };
diff --git a/native/android/net.c b/native/android/net.c
index 60296a7..e32b787 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -83,3 +83,31 @@
 
     return android_getaddrinfofornet(node, service, hints, netid, 0, res);
 }
+
+int android_res_nquery(net_handle_t network,
+        const char *dname, int ns_class, int ns_type) {
+    unsigned netid;
+    if (!getnetidfromhandle(network, &netid)) {
+        return -ENONET;
+    }
+
+    return resNetworkQuery(netid, dname, ns_class, ns_type);
+}
+
+int android_res_nresult(int fd, int *rcode, unsigned char *answer, int anslen) {
+    return resNetworkResult(fd, rcode, answer, anslen);
+}
+
+int android_res_nsend(net_handle_t network,
+        const unsigned char *msg, int msglen) {
+    unsigned netid;
+    if (!getnetidfromhandle(network, &netid)) {
+        return -ENONET;
+    }
+
+    return resNetworkSend(netid, msg, msglen);
+}
+
+void android_res_cancel(int nsend_fd) {
+    resNetworkCancel(nsend_fd);
+}
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/Android.bp b/packages/CarSystemUI/Android.bp
index f244f9f..74d6605 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -26,6 +26,7 @@
     ],
 
     static_libs: [
+        "CarNotificationLib",
         "SystemUI-core",
         "SystemUIPluginLib",
         "SystemUISharedLib",
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index 452d61d..572737f 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -28,4 +28,31 @@
     <bool name="config_enableRightNavigationBar">false</bool>
     <bool name="config_enableBottomNavigationBar">true</bool>
 
+    <!-- SystemUI Services: The classes of the stuff to start. This is duplicated from core
+         SystemUi b/c it can't be overlayed at this level for now
+    -->
+    <string-array name="config_systemUIServiceComponents" translatable="false">
+        <item>com.android.systemui.Dependency</item>
+        <item>com.android.systemui.util.NotificationChannels</item>
+        <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
+        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
+        <item>com.android.systemui.recents.Recents</item>
+        <item>com.android.systemui.volume.VolumeUI</item>
+        <item>com.android.systemui.stackdivider.Divider</item>
+        <item>com.android.systemui.SystemBars</item>
+        <item>com.android.systemui.usb.StorageNotification</item>
+        <item>com.android.systemui.power.PowerUI</item>
+        <item>com.android.systemui.media.RingtonePlayer</item>
+        <item>com.android.systemui.keyboard.KeyboardUI</item>
+        <item>com.android.systemui.pip.PipUI</item>
+        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
+        <item>@string/config_systemUIVendorServiceComponent</item>
+        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
+        <item>com.android.systemui.LatencyTester</item>
+        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
+        <item>com.android.systemui.ScreenDecorations</item>
+        <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
+        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
+        <item>com.android.systemui.notifications.NotificationsUI</item>
+    </string-array>
 </resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
new file mode 100644
index 0000000..5e63b90
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
@@ -0,0 +1,176 @@
+/*
+ * 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.notifications;
+
+import android.app.ActivityManager;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.ComponentName;
+import android.content.Context;
+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;
+import android.view.WindowManager;
+
+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;
+
+/**
+ * Standalone SystemUI for displaying Notifications that have been designed to be used in the car
+ */
+public class NotificationsUI extends SystemUI {
+
+    private static final String TAG = "NotificationsUI";
+    private CarNotificationListener mCarNotificationListener;
+    private CarUxRestrictionsManager mCarUxRestrictionsManager;
+    private NotificationClickHandlerFactory mClickHandlerFactory;
+    private Car mCar;
+    private ViewGroup mCarNotificationWindow;
+    private NotificationViewController mNotificationViewController;
+    private boolean mIsShowing;
+    private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper =
+            new CarUxRestrictionManagerWrapper();
+
+    /**
+     * Inits the window that hosts the notifications and establishes the connections
+     * to the car related services.
+     */
+    @Override
+    public void start() {
+        WindowManager windowManager =
+                (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        mCarNotificationListener = new CarNotificationListener();
+        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();
+
+
+        mCarNotificationWindow = (ViewGroup) View.inflate(mContext,
+                R.layout.navigation_bar_window, null);
+        View.inflate(mContext,
+                com.android.car.notification.R.layout.notification_center_activity,
+                mCarNotificationWindow);
+        mCarNotificationWindow.findViewById(
+                com.android.car.notification.R.id.exit_button_container)
+                .setOnClickListener(v -> toggleShowingCarNotifications());
+
+        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+                PixelFormat.TRANSLUCENT);
+        layoutParams.setTitle("Car Notification Window");
+        // start in the hidden state
+        mCarNotificationWindow.setVisibility(View.GONE);
+        windowManager.addView(mCarNotificationWindow, layoutParams);
+        mNotificationViewController = new NotificationViewController(
+                mCarNotificationWindow
+                        .findViewById(com.android.car.notification.R.id.notification_view),
+                PreprocessingManager.getInstance(mContext),
+                mCarNotificationListener,
+                mCarUxRestrictionManagerWrapper
+        );
+        // Add to the SystemUI component registry
+        putComponent(NotificationsUI.class, this);
+    }
+
+    /**
+     * Connection callback to establish UX Restrictions
+     */
+    private ServiceConnection mCarConnectionListener = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            try {
+                mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager(
+                        Car.CAR_UX_RESTRICTION_SERVICE);
+                mCarUxRestrictionManagerWrapper
+                        .setCarUxRestrictionsManager(mCarUxRestrictionsManager);
+                PreprocessingManager preprocessingManager = PreprocessingManager.getInstance(
+                        mContext);
+                preprocessingManager
+                        .setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper);
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Car not connected in CarConnectionListener", e);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.e(TAG, "Car service disconnected unexpectedly");
+        }
+    };
+
+    /**
+     * Toggles the visiblity of the notifications
+     */
+    public void toggleShowingCarNotifications() {
+        if (mCarNotificationWindow.getVisibility() == View.VISIBLE) {
+            closeCarNotifications();
+            return;
+        }
+        openCarNotifications();
+    }
+
+    /**
+     * Hides the notifications
+     */
+    public void closeCarNotifications() {
+        mCarNotificationWindow.setVisibility(View.GONE);
+        mNotificationViewController.disable();
+        mIsShowing = false;
+    }
+
+    /**
+     * Sets the notifications to visible
+     */
+    public void openCarNotifications() {
+        mCarNotificationWindow.setVisibility(View.VISIBLE);
+        mNotificationViewController.enable();
+        mIsShowing = true;
+    }
+
+    /**
+     * Returns {@code true} if notifications are currently on the screen
+     */
+    public boolean isShowing() {
+        return mIsShowing;
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index 81f7846..0cba351 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -21,7 +21,6 @@
 import android.view.View;
 import android.widget.LinearLayout;
 
-import com.android.keyguard.AlphaOptimizedImageButton;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -34,7 +33,7 @@
  */
 class CarNavigationBarView extends LinearLayout {
     private View mNavButtons;
-    private AlphaOptimizedImageButton mNotificationsButton;
+    private CarFacetButton mNotificationsButton;
     private CarStatusBar mCarStatusBar;
     private Context mContext;
     private View mLockScreenButtons;
@@ -71,7 +70,7 @@
     }
 
     protected void onNotificationsClick(View v) {
-        mCarStatusBar.togglePanel();
+        mCarStatusBar.toggleCarNotifications();
     }
 
     /**
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 2d90f8f..5da236c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -35,6 +35,7 @@
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.notifications.NotificationsUI;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.car.CarQSFragment;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -587,4 +588,9 @@
     private Drawable getDefaultWallpaper() {
         return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper);
     }
+
+    public void toggleCarNotifications() {
+        getComponent(NotificationsUI.class).toggleShowingCarNotifications();
+    }
+
 }
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/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index ff70e97..010a810 100644
--- a/packages/ExtServices/AndroidManifest.xml
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -63,6 +63,13 @@
                 android:resource="@array/autofill_field_classification_available_algorithms" />
         </service>
 
+        <service android:name=".sms.FinancialSmsServiceImpl"
+                 android:permission="android.permission.BIND_FINANCIAL_SMS_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.sms.action.FINANCIAL_SERVICE_INTENT" />
+            </intent-filter>
+        </service>
+
         <library android:name="android.ext.services"/>
     </application>
 
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 6f2b6c9..38df9b0 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
+import android.app.Person;
 import android.app.RemoteAction;
 import android.content.Context;
 import android.os.Bundle;
@@ -31,8 +32,14 @@
 import android.view.textclassifier.TextClassifier;
 import android.view.textclassifier.TextLinks;
 
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -50,6 +57,8 @@
     private static final int MAX_ACTIONS_PER_LINK = 1;
     private static final int MAX_SMART_ACTIONS = 3;
     private static final int MAX_SUGGESTED_REPLIES = 3;
+    // TODO: Make this configurable.
+    private static final int MAX_MESSAGES_TO_EXTRACT = 5;
 
     private static final ConversationActions.TypeConfig TYPE_CONFIG =
             new ConversationActions.TypeConfig.Builder().setIncludedTypes(
@@ -64,9 +73,6 @@
 
     /**
      * Adds action adjustments based on the notification contents.
-     *
-     * TODO: Once we have a API in {@link TextClassificationManager} to predict smart actions
-     * from notification text / message, we can replace most of the code here by consuming that API.
      */
     @NonNull
     ArrayList<Notification.Action> suggestActions(@Nullable Context context,
@@ -84,9 +90,13 @@
         if (tcm == null) {
             return EMPTY_ACTION_LIST;
         }
+        List<ConversationActions.Message> messages = extractMessages(entry.getNotification());
+        if (messages.isEmpty()) {
+            return EMPTY_ACTION_LIST;
+        }
+        // TODO: Move to TextClassifier.suggestConversationActions once it is ready.
         return suggestActionsFromText(
-                tcm,
-                getMostSalientActionText(entry.getNotification()), MAX_SMART_ACTIONS);
+                tcm, messages.get(messages.size() - 1).getText(), MAX_SMART_ACTIONS);
     }
 
     ArrayList<CharSequence> suggestReplies(@Nullable Context context,
@@ -104,14 +114,12 @@
         if (tcm == null) {
             return EMPTY_REPLY_LIST;
         }
-        CharSequence text = getMostSalientActionText(entry.getNotification());
-        ConversationActions.Message message =
-                new ConversationActions.Message.Builder()
-                        .setText(text)
-                        .build();
-
+        List<ConversationActions.Message> messages = extractMessages(entry.getNotification());
+        if (messages.isEmpty()) {
+            return EMPTY_REPLY_LIST;
+        }
         ConversationActions.Request request =
-                new ConversationActions.Request.Builder(Collections.singletonList(message))
+                new ConversationActions.Request.Builder(messages)
                         .setMaxSuggestions(MAX_SUGGESTED_REPLIES)
                         .setHints(HINTS)
                         .setTypeConfig(TYPE_CONFIG)
@@ -140,10 +148,6 @@
         if (!Process.myUserHandle().equals(entry.getSbn().getUser())) {
             return false;
         }
-        if (notification.actions != null
-                && notification.actions.length >= Notification.MAX_ACTION_BUTTONS) {
-            return false;
-        }
         if ((notification.flags & FLAG_MASK_INELGIBILE_FOR_ACTIONS) != 0) {
             return false;
         }
@@ -176,21 +180,41 @@
 
     /** Returns the text most salient for action extraction in a notification. */
     @Nullable
-    private CharSequence getMostSalientActionText(@NonNull Notification notification) {
-        /* If it's messaging style, use the most recent message. */
-        // TODO: Use the last few X messages instead and take the Person object into consideration.
+    private List<ConversationActions.Message> extractMessages(@NonNull Notification notification) {
         Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
-        if (messages != null && messages.length != 0) {
-            Bundle lastMessage = (Bundle) messages[messages.length - 1];
-            CharSequence lastMessageText =
-                    lastMessage.getCharSequence(Notification.MessagingStyle.Message.KEY_TEXT);
-            if (!TextUtils.isEmpty(lastMessageText)) {
-                return lastMessageText;
+        if (messages == null || messages.length == 0) {
+            return Arrays.asList(new ConversationActions.Message.Builder(
+                    ConversationActions.Message.PERSON_USER_REMOTE)
+                    .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT))
+                    .build());
+        }
+        Person localUser = notification.extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
+        Deque<ConversationActions.Message> extractMessages = new ArrayDeque<>();
+        for (int i = messages.length - 1; i >= 0; i--) {
+            Notification.MessagingStyle.Message message =
+                    Notification.MessagingStyle.Message.getMessageFromBundle((Bundle) messages[i]);
+            if (message == null) {
+                continue;
+            }
+            Person senderPerson = message.getSenderPerson();
+            // Skip encoding once the sender is missing as it is important to distinguish
+            // local user and remote user when generating replies.
+            if (senderPerson == null) {
+                break;
+            }
+            Person author = localUser != null && localUser.equals(senderPerson)
+                    ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson;
+            extractMessages.push(new ConversationActions.Message.Builder(author)
+                    .setText(message.getText())
+                    .setReferenceTime(
+                            ZonedDateTime.ofInstant(Instant.ofEpochMilli(message.getTimestamp()),
+                                    ZoneOffset.systemDefault()))
+                    .build());
+            if (extractMessages.size() >= MAX_MESSAGES_TO_EXTRACT) {
+                break;
             }
         }
-
-        // Fall back to using the normal text.
-        return notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+        return new ArrayList<>(extractMessages);
     }
 
     /** Returns a list of actions to act on entities in a given piece of text. */
diff --git a/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java
new file mode 100644
index 0000000..ab718021
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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.ext.services.sms;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.net.Uri;
+import android.os.Bundle;
+import android.service.sms.FinancialSmsService;
+import android.util.Log;
+
+import java.util.ArrayList;
+/**
+ * Service to provide financial apps access to sms messages.
+ */
+public class FinancialSmsServiceImpl extends FinancialSmsService {
+
+    private static final String TAG = "FinancialSmsServiceImpl";
+    private static final String KEY_COLUMN_NAMES = "column_names";
+
+    @Nullable
+    @Override
+    public CursorWindow onGetSmsMessages(@NonNull Bundle params) {
+        ArrayList<String> columnNames = params.getStringArrayList(KEY_COLUMN_NAMES);
+        if (columnNames == null || columnNames.size() <= 0) {
+            return null;
+        }
+
+        Uri inbox = Uri.parse("content://sms/inbox");
+
+        try (Cursor cursor = getContentResolver().query(inbox, null, null, null, null);
+                CursorWindow window = new CursorWindow("FinancialSmsMessages")) {
+            int messageCount = cursor.getCount();
+            if (messageCount > 0 && cursor.moveToFirst()) {
+                window.setNumColumns(columnNames.size());
+                for (int row = 0; row < messageCount; row++) {
+                    if (!window.allocRow()) {
+                        Log.e(TAG, "CursorWindow ran out of memory.");
+                        return null;
+                    }
+                    for (int col = 0; col < columnNames.size(); col++) {
+                        String columnName = columnNames.get(col);
+                        int inboxColumnIndex = cursor.getColumnIndexOrThrow(columnName);
+                        String inboxColumnValue = cursor.getString(inboxColumnIndex);
+                        boolean addedToCursorWindow = window.putString(inboxColumnValue, row, col);
+                        if (!addedToCursorWindow) {
+                            Log.e(TAG, "Failed to add:"
+                                    + inboxColumnValue
+                                    + ";column:"
+                                    + columnName);
+                            return null;
+                        }
+                    }
+                    cursor.moveToNext();
+                }
+            } else {
+                Log.w(TAG, "No sms messages.");
+            }
+            return window;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to get sms messages.");
+            return null;
+        }
+    }
+}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
new file mode 100644
index 0000000..0352ebc
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
@@ -0,0 +1,236 @@
+/**
+ * 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.ext.services.notification;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.Person;
+import android.content.Context;
+import android.os.Process;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nullable;
+
+@RunWith(AndroidJUnit4.class)
+public class SmartActionHelperTest {
+
+    private SmartActionsHelper mSmartActionsHelper = new SmartActionsHelper();
+    private Context mContext;
+    @Mock private TextClassifier mTextClassifier;
+    @Mock private NotificationEntry mNotificationEntry;
+    @Mock private StatusBarNotification mStatusBarNotification;
+    private Notification.Builder mNotificationBuilder;
+    private AssistantSettings mSettings;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getTargetContext();
+
+        mContext.getSystemService(TextClassificationManager.class)
+                .setTextClassifier(mTextClassifier);
+        when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class)))
+                .thenReturn(new ConversationActions(Collections.emptyList(), null));
+
+        when(mNotificationEntry.getSbn()).thenReturn(mStatusBarNotification);
+        // The notification is eligible to have smart suggestions.
+        when(mNotificationEntry.hasInlineReply()).thenReturn(true);
+        when(mNotificationEntry.isMessaging()).thenReturn(true);
+        when(mStatusBarNotification.getPackageName()).thenReturn("random.app");
+        when(mStatusBarNotification.getUser()).thenReturn(Process.myUserHandle());
+        mNotificationBuilder = new Notification.Builder(mContext, "channel");
+        mSettings = AssistantSettings.createForTesting(
+                null, null, Process.myUserHandle().getIdentifier(), null);
+        mSettings.mGenerateActions = true;
+        mSettings.mGenerateReplies = true;
+    }
+
+    @Test
+    public void testSuggestReplies_notMessagingApp() {
+        when(mNotificationEntry.isMessaging()).thenReturn(false);
+        ArrayList<CharSequence> textReplies =
+                mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings);
+        assertThat(textReplies).isEmpty();
+    }
+
+    @Test
+    public void testSuggestReplies_noInlineReply() {
+        when(mNotificationEntry.hasInlineReply()).thenReturn(false);
+        ArrayList<CharSequence> textReplies =
+                mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings);
+        assertThat(textReplies).isEmpty();
+    }
+
+    @Test
+    public void testSuggestReplies_nonMessageStyle() {
+        Notification notification = mNotificationBuilder.setContentText("Where are you?").build();
+        when(mNotificationEntry.getNotification()).thenReturn(notification);
+
+        List<ConversationActions.Message> messages = getMessagesInRequest();
+        assertThat(messages).hasSize(1);
+        MessageSubject.assertThat(messages.get(0)).hasText("Where are you?");
+    }
+
+    @Test
+    public void testSuggestReplies_messageStyle() {
+        Person me = new Person.Builder().setName("Me").build();
+        Person userA = new Person.Builder().setName("A").build();
+        Person userB = new Person.Builder().setName("B").build();
+        Notification.MessagingStyle style =
+                new Notification.MessagingStyle(me)
+                        .addMessage("firstMessage", 1000, (Person) null)
+                        .addMessage("secondMessage", 2000, me)
+                        .addMessage("thirdMessage", 3000, userA)
+                        .addMessage("fourthMessage", 4000, userB);
+        Notification notification =
+                mNotificationBuilder
+                        .setContentText("You have three new messages")
+                        .setStyle(style)
+                        .build();
+        when(mNotificationEntry.getNotification()).thenReturn(notification);
+
+        List<ConversationActions.Message> messages = getMessagesInRequest();
+        assertThat(messages).hasSize(3);
+
+        ConversationActions.Message secondMessage = messages.get(0);
+        MessageSubject.assertThat(secondMessage).hasText("secondMessage");
+        MessageSubject.assertThat(secondMessage)
+                .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL);
+        MessageSubject.assertThat(secondMessage)
+                .hasReferenceTime(createZonedDateTimeFromMsUtc(2000));
+
+        ConversationActions.Message thirdMessage = messages.get(1);
+        MessageSubject.assertThat(thirdMessage).hasText("thirdMessage");
+        MessageSubject.assertThat(thirdMessage).hasPerson(userA);
+        MessageSubject.assertThat(thirdMessage)
+                .hasReferenceTime(createZonedDateTimeFromMsUtc(3000));
+
+        ConversationActions.Message fourthMessage = messages.get(2);
+        MessageSubject.assertThat(fourthMessage).hasText("fourthMessage");
+        MessageSubject.assertThat(fourthMessage).hasPerson(userB);
+        MessageSubject.assertThat(fourthMessage)
+                .hasReferenceTime(createZonedDateTimeFromMsUtc(4000));
+    }
+
+    @Test
+    public void testSuggestReplies_messageStyle_noPerson() {
+        Person me = new Person.Builder().setName("Me").build();
+        Notification.MessagingStyle style =
+                new Notification.MessagingStyle(me).addMessage("message", 1000, (Person) null);
+        Notification notification =
+                mNotificationBuilder
+                        .setContentText("You have one new message")
+                        .setStyle(style)
+                        .build();
+        when(mNotificationEntry.getNotification()).thenReturn(notification);
+
+        mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings);
+
+        verify(mTextClassifier, never())
+                .suggestConversationActions(any(ConversationActions.Request.class));
+    }
+
+    private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) {
+        return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneOffset.systemDefault());
+    }
+
+    private List<ConversationActions.Message> getMessagesInRequest() {
+        mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings);
+
+        ArgumentCaptor<ConversationActions.Request> argumentCaptor =
+                ArgumentCaptor.forClass(ConversationActions.Request.class);
+        verify(mTextClassifier).suggestConversationActions(argumentCaptor.capture());
+        ConversationActions.Request request = argumentCaptor.getValue();
+        return request.getConversation();
+    }
+
+    private static final class MessageSubject
+            extends Subject<MessageSubject, ConversationActions.Message> {
+
+        private static final SubjectFactory<MessageSubject, ConversationActions.Message> FACTORY =
+                new SubjectFactory<MessageSubject, ConversationActions.Message>() {
+                    @Override
+                    public MessageSubject getSubject(
+                            @NonNull FailureStrategy failureStrategy,
+                            @NonNull ConversationActions.Message subject) {
+                        return new MessageSubject(failureStrategy, subject);
+                    }
+                };
+
+        private MessageSubject(
+                FailureStrategy failureStrategy, @Nullable ConversationActions.Message subject) {
+            super(failureStrategy, subject);
+        }
+
+        private void hasText(String text) {
+            if (!Objects.equals(text, getSubject().getText().toString())) {
+                failWithBadResults("has text", text, "has", getSubject().getText());
+            }
+        }
+
+        private void hasPerson(Person person) {
+            if (!Objects.equals(person, getSubject().getAuthor())) {
+                failWithBadResults("has author", person, "has", getSubject().getAuthor());
+            }
+        }
+
+        private void hasReferenceTime(ZonedDateTime referenceTime) {
+            if (!Objects.equals(referenceTime, getSubject().getReferenceTime())) {
+                failWithBadResults(
+                        "has reference time",
+                        referenceTime,
+                        "has",
+                        getSubject().getReferenceTime());
+            }
+        }
+
+        private static MessageSubject assertThat(ConversationActions.Message message) {
+            return assertAbout(FACTORY).that(message);
+        }
+    }
+}
diff --git a/packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java
new file mode 100644
index 0000000..12575a6
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/sms/FinancialSmsServiceImplTest.java
@@ -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.ext.services.sms;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+
+import org.junit.Test;
+
+/**
+ * Contains the base tests for FinancialSmsServiceImpl.
+ */
+public class FinancialSmsServiceImplTest {
+
+    private final FinancialSmsServiceImpl mService = new FinancialSmsServiceImpl();
+
+    @Test
+    public void testOnGetSmsMessages_nullWithNoParamData() {
+        assertThat(mService.onGetSmsMessages(new Bundle())).isNull();
+    }
+}
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 18b8662..591cf70 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -16,6 +16,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
     <uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" />
 
diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
index 5ecb614..2f8966c 100644
--- a/packages/PackageInstaller/res/layout/uninstall_content_view.xml
+++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
@@ -36,12 +36,23 @@
         style="@android:style/TextAppearance.Material.Subhead" />
 
     <CheckBox
-        android:id="@+id/checkbox"
+        android:id="@+id/clearContributedFiles"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="8dp"
         android:layout_marginStart="-8dp"
         android:paddingLeft="8sp"
+        android:visibility="gone"
+        style="@android:style/TextAppearance.Material.Subhead" />
+
+    <CheckBox
+        android:id="@+id/keepData"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginStart="-8dp"
+        android:paddingLeft="8sp"
+        android:visibility="gone"
         style="@android:style/TextAppearance.Material.Subhead" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 1d8747a1..1e0ff50 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -121,6 +121,8 @@
     <string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string>
     <!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] -->
     <string name="uninstall_remove_contributed_files">Also remove <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of associated media files.</string>
+    <!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] -->
+    <string name="uninstall_keep_data">Keep <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of app data.</string>
 
     <!-- Label for the notification channel containing notifications for current uninstall operations [CHAR LIMIT=40] -->
     <string name="uninstalling_notification_channel">Running uninstalls</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index d13bb65..63d8c5a8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -52,6 +52,7 @@
     static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
     static final String EXTRA_CLEAR_CONTRIBUTED_FILES =
             "com.android.packageinstaller.extra.CLEAR_CONTRIBUTED_FILES";
+    static final String EXTRA_KEEP_DATA = "com.android.packageinstaller.extra.KEEP_DATA";
 
     private int mUninstallId;
     private ApplicationInfo mAppInfo;
@@ -76,6 +77,7 @@
                         false);
                 boolean clearContributedFiles = getIntent().getBooleanExtra(
                         EXTRA_CLEAR_CONTRIBUTED_FILES, false);
+                boolean keepData = getIntent().getBooleanExtra(EXTRA_KEEP_DATA, false);
                 UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER);
 
                 // Show dialog, which is the whole UI
@@ -101,6 +103,7 @@
 
                 int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
                 flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0;
+                flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
 
                 try {
                     ActivityThread.getPackageManager().getPackageInstaller().uninstall(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 0fa8c9a..5419449 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -285,7 +285,7 @@
         fragment.show(ft, "dialog");
     }
 
-    public void startUninstallProgress(boolean clearContributedFiles) {
+    public void startUninstallProgress(boolean clearContributedFiles, boolean keepData) {
         boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
         CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager());
 
@@ -312,6 +312,7 @@
             newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label);
             newIntent.putExtra(UninstallUninstalling.EXTRA_CLEAR_CONTRIBUTED_FILES,
                     clearContributedFiles);
+            newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData);
             newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
 
             if (returnResult) {
@@ -362,6 +363,7 @@
 
                 int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
                 flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0;
+                flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
 
                 ActivityThread.getPackageManager().getPackageInstaller().uninstall(
                         new VersionedPackage(mDialogInfo.appInfo.packageName,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index e4e1275..499da75 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -16,21 +16,30 @@
 
 package com.android.packageinstaller.handheld;
 
+import static android.os.storage.StorageManager.convert;
 import static android.text.format.Formatter.formatFileSize;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
+import android.app.usage.StorageStats;
+import android.app.usage.StorageStatsManager;
 import android.content.DialogInterface;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
 import android.provider.MediaStore;
+import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CheckBox;
 import android.widget.TextView;
@@ -43,25 +52,120 @@
 
 public class UninstallAlertDialogFragment extends DialogFragment implements
         DialogInterface.OnClickListener {
+    private static final String LOG_TAG = UninstallAlertDialogFragment.class.getSimpleName();
 
-    private CheckBox mClearContributedFiles;
+    private @Nullable CheckBox mClearContributedFiles;
+    private @Nullable CheckBox mKeepData;
 
     /**
-     * Get number of bytes of the combined files contributed by the package.
+     * Get number of bytes of the files contributed by the package.
      *
-     * @param pkg The package that might have contibuted files.
+     * @param pkg The package that might have contributed files.
      * @param user The user the package belongs to.
      *
      * @return The number of bytes.
      */
-    private long getContributedMediaSize(@NonNull String pkg, @NonNull UserHandle user) {
+    private long getContributedMediaSizeForUser(@NonNull String pkg, @NonNull UserHandle user) {
         try {
             return MediaStore.getContributedMediaSize(getContext(), pkg, user);
         } catch (IOException e) {
+            Log.e(LOG_TAG, "Cannot determine amount of contributes files for " + pkg
+                    + " (user " + user + ")", e);
             return 0;
         }
     }
 
+    /**
+     * Get number of bytes of the files contributed by the package.
+     *
+     * @param pkg The package that might have contributed files.
+     * @param user The user the package belongs to or {@code null} if files of all users should be
+     *             counted.
+     *
+     * @return The number of bytes.
+     */
+    private long getContributedMediaSize(@NonNull String pkg, @Nullable UserHandle user) {
+        UserManager userManager = getContext().getSystemService(UserManager.class);
+
+        long contributedFileSize = 0;
+
+        if (user == null) {
+            List<UserInfo> users = userManager.getUsers();
+
+            int numUsers = users.size();
+            for (int i = 0; i < numUsers; i++) {
+                contributedFileSize += getContributedMediaSizeForUser(pkg,
+                        UserHandle.of(users.get(i).id));
+            }
+        } else {
+            contributedFileSize = getContributedMediaSizeForUser(pkg, user);
+        }
+
+        return contributedFileSize;
+    }
+
+    /**
+     * Get number of bytes of the app data of the package.
+     *
+     * @param pkg The package that might have app data.
+     * @param user The user the package belongs to
+     *
+     * @return The number of bytes.
+     */
+    private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) {
+        StorageManager storageManager = getContext().getSystemService(StorageManager.class);
+        StorageStatsManager storageStatsManager =
+                getContext().getSystemService(StorageStatsManager.class);
+
+        List<StorageVolume> volumes = storageManager.getStorageVolumes();
+        long appDataSize = 0;
+
+        int numVolumes = volumes.size();
+        for (int i = 0; i < numVolumes; i++) {
+            StorageStats stats;
+            try {
+                stats = storageStatsManager.queryStatsForPackage(convert(volumes.get(i).getUuid()),
+                        pkg, user);
+            } catch (PackageManager.NameNotFoundException | IOException e) {
+                Log.e(LOG_TAG, "Cannot determine amount of app data for " + pkg + " on "
+                        + volumes.get(i) + " (user " + user + ")", e);
+                continue;
+            }
+
+            appDataSize += stats.getDataBytes();
+        }
+
+        return appDataSize;
+    }
+
+    /**
+     * Get number of bytes of the app data of the package.
+     *
+     * @param pkg The package that might have app data.
+     * @param user The user the package belongs to or {@code null} if files of all users should be
+     *             counted.
+     *
+     * @return The number of bytes.
+     */
+    private long getAppDataSize(@NonNull String pkg, @Nullable UserHandle user) {
+        UserManager userManager = getContext().getSystemService(UserManager.class);
+
+        long appDataSize = 0;
+
+        if (user == null) {
+            List<UserInfo> users = userManager.getUsers();
+
+            int numUsers = users.size();
+            for (int i = 0; i < numUsers; i++) {
+                appDataSize += getAppDataSizeForUser(pkg, UserHandle.of(users.get(i).id));
+            }
+        } else {
+            appDataSize = getAppDataSizeForUser(pkg, user);
+        }
+
+        return appDataSize;
+    }
+
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         final PackageManager pm = getActivity().getPackageManager();
@@ -108,30 +212,46 @@
         dialogBuilder.setNegativeButton(android.R.string.cancel, this);
 
         String pkg = dialogInfo.appInfo.packageName;
-        long contributedFileSize = 0;
-        if (dialogInfo.allUsers) {
-            List<UserInfo> users = userManager.getUsers();
+        long contributedFileSize = getContributedMediaSize(pkg,
+                dialogInfo.allUsers ? null : dialogInfo.user);
 
-            int numUsers = users.size();
-            for (int i = 0; i < numUsers; i++) {
-                UserHandle user = UserHandle.of(users.get(i).id);
+        boolean suggestToKeepAppData;
+        try {
+            PackageInfo pkgInfo = pm.getPackageInfo(pkg, 0);
 
-                contributedFileSize += getContributedMediaSize(pkg, user);
-            }
-        } else {
-            contributedFileSize = getContributedMediaSize(pkg, dialogInfo.user);
+            suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData();
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(LOG_TAG, "Cannot check hasFragileUserData for " + pkg, e);
+            suggestToKeepAppData = false;
         }
 
-        if (contributedFileSize == 0) {
+        long appDataSize = 0;
+        if (suggestToKeepAppData) {
+            appDataSize = getAppDataSize(pkg, dialogInfo.allUsers ? null : dialogInfo.user);
+        }
+
+        if (contributedFileSize == 0 && appDataSize == 0) {
             dialogBuilder.setMessage(messageBuilder.toString());
         } else {
             LayoutInflater inflater = getContext().getSystemService(LayoutInflater.class);
             ViewGroup content = (ViewGroup) inflater.inflate(R.layout.uninstall_content_view, null);
 
             ((TextView) content.requireViewById(R.id.message)).setText(messageBuilder.toString());
-            mClearContributedFiles = content.requireViewById(R.id.checkbox);
-            mClearContributedFiles.setText(getString(R.string.uninstall_remove_contributed_files,
-                            formatFileSize(getContext(), contributedFileSize)));
+
+            if (contributedFileSize != 0) {
+                mClearContributedFiles = content.requireViewById(R.id.clearContributedFiles);
+                mClearContributedFiles.setVisibility(View.VISIBLE);
+                mClearContributedFiles.setText(
+                        getString(R.string.uninstall_remove_contributed_files,
+                                formatFileSize(getContext(), contributedFileSize)));
+            }
+
+            if (appDataSize != 0) {
+                mKeepData = content.requireViewById(R.id.keepData);
+                mKeepData.setVisibility(View.VISIBLE);
+                mKeepData.setText(getString(R.string.uninstall_keep_data,
+                        formatFileSize(getContext(), appDataSize)));
+            }
 
             dialogBuilder.setView(content);
         }
@@ -143,7 +263,8 @@
     public void onClick(DialogInterface dialog, int which) {
         if (which == Dialog.BUTTON_POSITIVE) {
             ((UninstallerActivity) getActivity()).startUninstallProgress(
-                    mClearContributedFiles != null && mClearContributedFiles.isChecked());
+                    mClearContributedFiles != null && mClearContributedFiles.isChecked(),
+                    mKeepData != null && mKeepData.isChecked());
         } else {
             ((UninstallerActivity) getActivity()).dispatchAborted();
         }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 21d25f5..ac5fd76 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -99,7 +99,7 @@
     public void onGuidedActionClicked(GuidedAction action) {
         if (isAdded()) {
             if (action.getId() == GuidedAction.ACTION_ID_OK) {
-                ((UninstallerActivity) getActivity()).startUninstallProgress(false);
+                ((UninstallerActivity) getActivity()).startUninstallProgress(false, false);
                 getActivity().finish();
             } else {
                 ((UninstallerActivity) getActivity()).dispatchAborted();
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/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 8ebb182..5b09b29 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -184,7 +184,7 @@
     <string name="category_work" msgid="8699184680584175622">"कार्य"</string>
     <string name="development_settings_title" msgid="215179176067683667">"डेव्हलपर पर्याय"</string>
     <string name="development_settings_enable" msgid="542530994778109538">"डेव्हलपर पर्याय सुरू करा"</string>
-    <string name="development_settings_summary" msgid="1815795401632854041">"अॅप विकासासाठी पर्याय सेट करा"</string>
+    <string name="development_settings_summary" msgid="1815795401632854041">"अ‍ॅप विकासासाठी पर्याय सेट करा"</string>
     <string name="development_settings_not_available" msgid="4308569041701535607">"या वापरकर्त्यासाठी डेव्हलपर पर्याय उपलब्ध नाहीत"</string>
     <string name="vpn_settings_not_available" msgid="956841430176985598">"या वापरकर्त्यासाठी VPN सेटिंग्ज उपलब्ध नाहीत"</string>
     <string name="tethering_settings_not_available" msgid="6765770438438291012">"या वापरकर्त्यासाठी टेदरिंग सेटिंग्ज उपलब्ध नाहीत"</string>
@@ -202,9 +202,9 @@
     <string name="oem_unlock_enable_summary" msgid="4720281828891618376">"बूटलोडर अनलॉक करण्यासाठी अनुमती द्या"</string>
     <string name="confirm_enable_oem_unlock_title" msgid="4802157344812385674">"OEM अनलॉक करण्यास अनुमती द्यायची?"</string>
     <string name="confirm_enable_oem_unlock_text" msgid="5517144575601647022">"चेतावणी: हे सेटिंग चालू असताना या डिव्हाइस वर डिव्हाइस संरक्षण वैशिष्ट्ये काम करणार नाहीत."</string>
-    <string name="mock_location_app" msgid="7966220972812881854">"बनावट स्थान अॅप निवडा"</string>
-    <string name="mock_location_app_not_set" msgid="809543285495344223">"कोणताही बनावट स्थान अॅप सेट केला नाही"</string>
-    <string name="mock_location_app_set" msgid="8966420655295102685">"बनावट स्थान अॅप: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="mock_location_app" msgid="7966220972812881854">"बनावट स्थान अ‍ॅप निवडा"</string>
+    <string name="mock_location_app_not_set" msgid="809543285495344223">"कोणताही बनावट स्थान अ‍ॅप सेट केला नाही"</string>
+    <string name="mock_location_app_set" msgid="8966420655295102685">"बनावट स्थान अ‍ॅप: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="debug_networking_category" msgid="7044075693643009662">"नेटवर्किंग"</string>
     <string name="wifi_display_certification" msgid="8611569543791307533">"वायरलेस डिस्प्ले प्रमाणीकरण"</string>
     <string name="wifi_verbose_logging" msgid="4203729756047242344">"वाय-फाय व्हर्बोझ लॉगिंग सुरू करा"</string>
@@ -261,11 +261,11 @@
     <string name="bluetooth_show_devices_without_names_summary" msgid="2351196058115755520">"नावांशिवाय ब्‍लूटूथ डीव्‍हाइस (फक्‍त MAC पत्‍ते) दाखवले जातील"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="6031284410786545957">"रिमोट डिव्हाइसमध्ये सहन न होणारा मोठा आवाज किंवा नियंत्रणाचा अभाव यासारखी आवाजाची समस्या असल्यास ब्लूटूथ संपूर्ण आवाज वैशिष्ट्य बंद करते."</string>
     <string name="enable_terminal_title" msgid="95572094356054120">"स्थानिक टर्मिनल"</string>
-    <string name="enable_terminal_summary" msgid="67667852659359206">"स्थानिक शेल प्रवेश देणारा टर्मिनल अॅप सुरू करा"</string>
+    <string name="enable_terminal_summary" msgid="67667852659359206">"स्थानिक शेल प्रवेश देणारा टर्मिनल अ‍ॅप सुरू करा"</string>
     <string name="hdcp_checking_title" msgid="8605478913544273282">"HDCP तपासणी"</string>
     <string name="hdcp_checking_dialog_title" msgid="5141305530923283">"HDCP तपासणी वर्तन सेट करा"</string>
     <string name="debug_debugging_category" msgid="6781250159513471316">"डीबग करणे"</string>
-    <string name="debug_app" msgid="8349591734751384446">"डीबग अॅप निवडा"</string>
+    <string name="debug_app" msgid="8349591734751384446">"डीबग अ‍ॅप निवडा"</string>
     <string name="debug_app_not_set" msgid="718752499586403499">"कोणतेही डीबग अॅप्लिकेशन सेट नाही"</string>
     <string name="debug_app_set" msgid="2063077997870280017">"अॅप्लिकेशन डीबग करत आहे: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="select_application" msgid="5156029161289091703">"अॅप्लिकेशन निवडा"</string>
@@ -315,7 +315,7 @@
     <string name="immediately_destroy_activities_summary" msgid="3592221124808773368">"वापरकर्त्याने प्रत्येक अॅक्टिव्हिटी सोडताच ती नष्ट करा"</string>
     <string name="app_process_limit_title" msgid="4280600650253107163">"पार्श्वभूमी प्रक्रिया मर्यादा"</string>
     <string name="show_all_anrs" msgid="4924885492787069007">"बॅकग्राउंड ANR दाखवा"</string>
-    <string name="show_all_anrs_summary" msgid="6636514318275139826">"बॅकग्राउंड अॅप्ससाठी अॅप प्रतिसाद देत नाही दाखवते"</string>
+    <string name="show_all_anrs_summary" msgid="6636514318275139826">"बॅकग्राउंड अ‍ॅप्ससाठी अ‍ॅप प्रतिसाद देत नाही दाखवते"</string>
     <string name="show_notification_channel_warnings" msgid="1399948193466922683">"सूचना चॅनेल चेतावण्या दाखवा"</string>
     <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"एखादे अ‍ॅप वैध चॅनेलशिवाय सूचना पोस्ट करते तेव्हा स्क्रीनवर चेतावणी देते"</string>
     <string name="force_allow_on_external" msgid="3215759785081916381">"बाह्यवर अॅप्सना अनुमती देण्याची सक्ती करा"</string>
@@ -343,7 +343,7 @@
     <string name="inactive_apps_title" msgid="9042996804461901648">"स्टँडबाय अॅप्स"</string>
     <string name="inactive_app_inactive_summary" msgid="5091363706699855725">"निष्क्रिय. टॉगल करण्यासाठी टॅप करा."</string>
     <string name="inactive_app_active_summary" msgid="4174921824958516106">"सक्रिय. टॉगल करण्यासाठी टॅप करा."</string>
-    <string name="standby_bucket_summary" msgid="6567835350910684727">"अॅप स्टँडबाय स्थिती: <xliff:g id="BUCKET"> %s</xliff:g>"</string>
+    <string name="standby_bucket_summary" msgid="6567835350910684727">"अ‍ॅप स्टँडबाय स्थिती: <xliff:g id="BUCKET"> %s</xliff:g>"</string>
     <string name="runningservices_settings_title" msgid="8097287939865165213">"चालू सेवा"</string>
     <string name="runningservices_settings_summary" msgid="854608995821032748">"सध्या चालत असलेल्या सेवा पहा आणि नियंत्रित करा"</string>
     <string name="select_webview_provider_title" msgid="4628592979751918907">"वेबदृश्य अंमलबजावणी"</string>
@@ -422,7 +422,7 @@
     <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"सिस्टम भाषा वापरा"</string>
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> साठी सेटिंग्ज उघडण्यात अयशस्वी"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"ही इनपुट पद्धत पासवर्ड आणि क्रेडिट कार्ड नंबर यासह, तुम्ही टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अॅपवरून येते. ही इनपुट पद्धत वापरायची?"</string>
-    <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"टीप: रीबूट केल्यानंतर, तुम्ही तुमचा फोन अनलॉक करे पर्यंत हे अॅप सुरू होऊ शकत नाही"</string>
+    <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"टीप: रीबूट केल्यानंतर, तुम्ही तुमचा फोन अनलॉक करे पर्यंत हे अ‍ॅप सुरू होऊ शकत नाही"</string>
     <string name="ims_reg_title" msgid="7609782759207241443">"IMS नोंदणी स्थिती"</string>
     <string name="ims_reg_status_registered" msgid="933003316932739188">"नोंदवलेले"</string>
     <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"नोंदवलेले नाही"</string>
@@ -449,6 +449,6 @@
     <string name="zen_mode_duration_always_prompt_title" msgid="6478923750878945501">"प्रत्येक वेळी विचारा"</string>
     <string name="zen_mode_forever" msgid="2704305038191592967">"तुम्ही बंद करेपर्यंत"</string>
     <string name="time_unit_just_now" msgid="6363336622778342422">"आत्ताच"</string>
-    <string name="updated_gfx_driver_dev_opt_in_app_summary" msgid="5309913444094165199">"अपडेट केलेले ग्राफिक ड्राइव्हर डेव्हलमेंटमध्ये वापरण्यासाठी अॅप निवडा"</string>
+    <string name="updated_gfx_driver_dev_opt_in_app_summary" msgid="5309913444094165199">"अपडेट केलेले ग्राफिक ड्राइव्हर डेव्हलमेंटमध्ये वापरण्यासाठी अ‍ॅप निवडा"</string>
     <string name="media_transfer_phone_device_name" msgid="1003823744105758574">"फोनचा स्पीकर"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index ae9c5f2..e810ba2 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -40,7 +40,7 @@
     <string name="connected_via_passpoint" msgid="2826205693803088747">"Tilkoblet via %1$s"</string>
     <string name="available_via_passpoint" msgid="1617440946846329613">"Tilgjengelig via %1$s"</string>
     <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Tilkoblet – ingen Internett-tilgang"</string>
-    <string name="wifi_status_no_internet" msgid="5784710974669608361">"Ingen Internett-tilkobling"</string>
+    <string name="wifi_status_no_internet" msgid="5784710974669608361">"Ingen internettilkobling"</string>
     <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Pålogging kreves"</string>
     <string name="wifi_ap_unable_to_handle_new_sta" msgid="5348824313514404541">"Tilgangspunktet er midlertidig fullt"</string>
     <string name="connected_via_carrier" msgid="7583780074526041912">"Tilkoblet via %1$s"</string>
@@ -75,7 +75,7 @@
     <string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internett-tilgang"</string>
     <string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktdeling"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Bruk til kontaktdeling"</string>
-    <string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Deling av Internett-tilkobling"</string>
+    <string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Deling av internettilkobling"</string>
     <string name="bluetooth_profile_map" msgid="1019763341565580450">"Tekstmeldinger"</string>
     <string name="bluetooth_profile_sap" msgid="5764222021851283125">"Tilgang til SIM-kortet"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="5444517801472820055">"HD-lyd: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
@@ -90,7 +90,7 @@
     <string name="bluetooth_opp_profile_summary_not_connected" msgid="1267091356089086285">"Ikke koblet til tjener for filoverføring"</string>
     <string name="bluetooth_hid_profile_summary_connected" msgid="3381760054215168689">"Koblet til inndataenhet"</string>
     <string name="bluetooth_pan_user_profile_summary_connected" msgid="6436258151814414028">"Koblet til enhet for Internett-tilgang"</string>
-    <string name="bluetooth_pan_nap_profile_summary_connected" msgid="1322694224800769308">"Deler lokal Internett-tilkobling med enhet"</string>
+    <string name="bluetooth_pan_nap_profile_summary_connected" msgid="1322694224800769308">"Deler lokal internettilkobling med enhet"</string>
     <string name="bluetooth_pan_profile_summary_use_for" msgid="5736111170225304239">"Bruk for Internett-tilgang"</string>
     <string name="bluetooth_map_profile_summary_use_for" msgid="5154200119919927434">"Bruk for kart"</string>
     <string name="bluetooth_sap_profile_summary_use_for" msgid="7085362712786907993">"Bruk for tilgang til SIM-kortet"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2823149..842779d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -831,6 +831,10 @@
     <!-- Toast message shown when setting a new local backup password fails due to the user not supplying the correct existing password. The phrasing here is deliberately quite general. [CHAR LIMIT=80] -->
     <string name="local_backup_password_toast_validation_failure">Failure setting backup password</string>
 
+    <!-- [CHAR LIMIT=30] Location mode screen, temporary summary text to show as the status of a location
+      setting injected by an external app while the app is being queried for the actual value -->
+    <string name="loading_injected_setting_summary">Loading\u2026</string>
+
     <!-- Name of each color mode for the display. [CHAR LIMIT=40] -->
     <string-array name="color_mode_names">
         <item>Vibrant (default)</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index 780fcba..74057be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -37,6 +37,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.IconDrawableFactory;
 import android.util.Log;
@@ -44,11 +45,16 @@
 
 import androidx.preference.Preference;
 
+import com.android.settingslib.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Deque;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -102,7 +108,7 @@
     public SettingsInjector(Context context) {
         mContext = context;
         mSettings = new HashSet<Setting>();
-        mHandler = new StatusLoadingHandler();
+        mHandler = new StatusLoadingHandler(mSettings);
     }
 
     /**
@@ -165,7 +171,7 @@
             Log.e(TAG, "Can't get ApplicationInfo for " + setting.packageName, e);
         }
         preference.setTitle(setting.title);
-        preference.setSummary(null);
+        preference.setSummary(R.string.loading_injected_setting_summary);
         preference.setIcon(appIcon);
         preference.setOnPreferenceClickListener(new ServiceSettingClickedListener(setting));
     }
@@ -180,6 +186,7 @@
         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         final List<UserHandle> profiles = um.getUserProfiles();
         ArrayList<Preference> prefs = new ArrayList<>();
+        mSettings.clear();
         for (UserHandle userHandle : profiles) {
             if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
                 Iterable<InjectedSetting> settings = getSettings(userHandle);
@@ -363,31 +370,28 @@
      * SettingInjectorService}, so to reduce memory pressure we don't want to load too many at
      * once.
      */
-    private final class StatusLoadingHandler extends Handler {
+    private static final class StatusLoadingHandler extends Handler {
+        /**
+         * References all the injected settings.
+         */
+        WeakReference<Set<Setting>> mAllSettings;
 
         /**
          * Settings whose status values need to be loaded. A set is used to prevent redundant loads.
          */
-        private Set<Setting> mSettingsToLoad = new HashSet<Setting>();
+        private Deque<Setting> mSettingsToLoad = new ArrayDeque<Setting>();
 
         /**
          * Settings that are being loaded now and haven't timed out. In practice this should have
          * zero or one elements.
          */
-        private Set<Setting> mSettingsBeingLoaded = new HashSet<Setting>();
+        private Set<Setting> mSettingsBeingLoaded = new ArraySet<Setting>();
 
-        /**
-         * Settings that are being loaded but have timed out. If only one setting has timed out, we
-         * will go ahead and start loading the next setting so that one slow load won't delay the
-         * load of the other settings.
-         */
-        private Set<Setting> mTimedOutSettings = new HashSet<Setting>();
-
-        private boolean mReloadRequested;
-
-        private StatusLoadingHandler() {
+        public StatusLoadingHandler(Set<Setting> allSettings) {
             super(Looper.getMainLooper());
+            mAllSettings = new WeakReference<>(allSettings);
         }
+
         @Override
         public void handleMessage(Message msg) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -396,20 +400,24 @@
 
             // Update state in response to message
             switch (msg.what) {
-                case WHAT_RELOAD:
-                    mReloadRequested = true;
+                case WHAT_RELOAD: {
+                    final Set<Setting> allSettings = mAllSettings.get();
+                    if (allSettings != null) {
+                        // Reload requested, so must reload all settings
+                        mSettingsToLoad.clear();
+                        mSettingsToLoad.addAll(allSettings);
+                    }
                     break;
+                }
                 case WHAT_RECEIVED_STATUS:
                     final Setting receivedSetting = (Setting) msg.obj;
                     receivedSetting.maybeLogElapsedTime();
                     mSettingsBeingLoaded.remove(receivedSetting);
-                    mTimedOutSettings.remove(receivedSetting);
                     removeMessages(WHAT_TIMEOUT, receivedSetting);
                     break;
                 case WHAT_TIMEOUT:
                     final Setting timedOutSetting = (Setting) msg.obj;
                     mSettingsBeingLoaded.remove(timedOutSetting);
-                    mTimedOutSettings.add(timedOutSetting);
                     if (Log.isLoggable(TAG, Log.WARN)) {
                         Log.w(TAG, "Timed out after " + timedOutSetting.getElapsedTime()
                                 + " millis trying to get status for: " + timedOutSetting);
@@ -421,37 +429,22 @@
 
             // Decide whether to load additional settings based on the new state. Start by seeing
             // if we have headroom to load another setting.
-            if (mSettingsBeingLoaded.size() > 0 || mTimedOutSettings.size() > 1) {
+            if (mSettingsBeingLoaded.size() > 0) {
                 // Don't load any more settings until one of the pending settings has completed.
-                // To reduce memory pressure, we want to be loading at most one setting (plus at
-                // most one timed-out setting) at a time. This means we'll be responsible for
-                // bringing in at most two services.
+                // To reduce memory pressure, we want to be loading at most one setting.
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Log.v(TAG, "too many services already live for " + msg + ", " + this);
                 }
                 return;
             }
 
-            if (mReloadRequested && mSettingsToLoad.isEmpty() && mSettingsBeingLoaded.isEmpty()
-                    && mTimedOutSettings.isEmpty()) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "reloading because idle and reload requesteed " + msg + ", " + this);
-                }
-                // Reload requested, so must reload all settings
-                mSettingsToLoad.addAll(mSettings);
-                mReloadRequested = false;
-            }
-
-            // Remove the next setting to load from the queue, if any
-            Iterator<Setting> iter = mSettingsToLoad.iterator();
-            if (!iter.hasNext()) {
+            if (mSettingsToLoad.isEmpty()) {
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Log.v(TAG, "nothing left to do for " + msg + ", " + this);
                 }
                 return;
             }
-            Setting setting = iter.next();
-            iter.remove();
+            Setting setting = mSettingsToLoad.removeFirst();
 
             // Request the status value
             setting.startService();
@@ -473,21 +466,48 @@
             return "StatusLoadingHandler{" +
                     "mSettingsToLoad=" + mSettingsToLoad +
                     ", mSettingsBeingLoaded=" + mSettingsBeingLoaded +
-                    ", mTimedOutSettings=" + mTimedOutSettings +
-                    ", mReloadRequested=" + mReloadRequested +
                     '}';
         }
     }
 
+    private static class MessengerHandler extends Handler {
+        private WeakReference<Setting> mSettingRef;
+        private Handler mHandler;
+
+        public MessengerHandler(Setting setting, Handler handler) {
+            mSettingRef = new WeakReference(setting);
+            mHandler = handler;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final Setting setting = mSettingRef.get();
+            if (setting == null) {
+                return;
+            }
+            final Preference preference = setting.preference;
+            Bundle bundle = msg.getData();
+            boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true);
+            String summary = bundle.getString(SettingInjectorService.SUMMARY_KEY, null);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle);
+            }
+            preference.setSummary(summary);
+            preference.setEnabled(enabled);
+            mHandler.sendMessage(
+                    mHandler.obtainMessage(WHAT_RECEIVED_STATUS, setting));
+        }
+    }
+
     /**
      * Represents an injected setting and the corresponding preference.
      */
     protected final class Setting {
-
         public final InjectedSetting setting;
         public final Preference preference;
         public long startMillis;
 
+
         public Setting(InjectedSetting setting, Preference preference) {
             this.setting = setting;
             this.preference = preference;
@@ -502,20 +522,6 @@
         }
 
         /**
-         * Returns true if they both have the same {@link #setting} value. Ignores mutable
-         * {@link #preference} and {@link #startMillis} so that it's safe to use in sets.
-         */
-        @Override
-        public boolean equals(Object o) {
-            return this == o || o instanceof Setting && setting.equals(((Setting) o).setting);
-        }
-
-        @Override
-        public int hashCode() {
-            return setting.hashCode();
-        }
-
-        /**
          * Starts the service to fetch for the current status for the setting, and updates the
          * preference when the service replies.
          */
@@ -529,20 +535,7 @@
                 }
                 return;
             }
-            Handler handler = new Handler() {
-                @Override
-                public void handleMessage(Message msg) {
-                    Bundle bundle = msg.getData();
-                    boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true);
-                    if (Log.isLoggable(TAG, Log.DEBUG)) {
-                        Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle);
-                    }
-                    preference.setSummary(null);
-                    preference.setEnabled(enabled);
-                    mHandler.sendMessage(
-                            mHandler.obtainMessage(WHAT_RECEIVED_STATUS, Setting.this));
-                }
-            };
+            Handler handler = new MessengerHandler(this, mHandler);
             Messenger messenger = new Messenger(handler);
 
             Intent intent = setting.getServiceIntent();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
index 4a8ef1e..924eb04 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
@@ -36,12 +36,12 @@
     }
 
     @Implementation
-    public static int getCurrentUser() {
+    protected static int getCurrentUser() {
         return sCurrentUserId;
     }
 
     @Implementation
-    public boolean switchUser(int userId) {
+    protected boolean switchUser(int userId) {
         mUserSwitchedTo = userId;
         return true;
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
index cae74c8..906dba4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
@@ -32,7 +32,7 @@
     private BluetoothProfile.ServiceListener mServiceListener;
 
     @Implementation
-    public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
+    protected boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
             int profile) {
         mServiceListener = listener;
         return true;
@@ -43,7 +43,7 @@
     }
 
     @Implementation
-    public List<Integer> getSupportedProfiles() {
+    protected List<Integer> getSupportedProfiles() {
         return mSupportedProfiles;
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
index 3e91641..d8fc861 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
@@ -34,7 +34,7 @@
     }
 
     @Implementation
-    public static String getDefaultDialerApplication(Context context) {
+    protected static String getDefaultDialerApplication(Context context) {
         return sDefaultDialer;
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
index dd7b007..c8c4526 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
@@ -36,7 +36,7 @@
     }
 
     @Implementation
-    public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
+    protected static ComponentName getDefaultSmsApplication(Context context, boolean update) {
         return sDefaultSmsApplication;
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java
index a81e395..c50d646 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java
@@ -21,11 +21,8 @@
 import android.content.pm.UserInfo;
 import android.os.UserManager;
 
-import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.Resetter;
-import org.robolectric.shadow.api.Shadow;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -56,5 +53,4 @@
     protected List<UserInfo> getProfiles(@UserIdInt int userHandle) {
         return getProfiles();
     }
-
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java
deleted file mode 100644
index 3455765..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowXmlUtils.java
+++ /dev/null
@@ -1,43 +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 com.android.settingslib.testutils.shadow;
-
-import static org.robolectric.shadow.api.Shadow.directlyOn;
-
-import com.android.internal.util.XmlUtils;
-
-import org.robolectric.Robolectric;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.util.ReflectionHelpers;
-
-@Implements(XmlUtils.class)
-public class ShadowXmlUtils {
-
-    @Implementation
-    public static final int convertValueToInt(CharSequence charSeq, int defaultValue) {
-        final Class<?> xmlUtilsClass = ReflectionHelpers.loadClass(
-                Robolectric.class.getClassLoader(), "com.android.internal.util.XmlUtils");
-        try {
-            return directlyOn(xmlUtilsClass, "convertValueToInt",
-                    ReflectionHelpers.ClassParameter.from(CharSequence.class, charSeq),
-                    ReflectionHelpers.ClassParameter.from(int.class, new Integer(defaultValue)));
-        } catch (NumberFormatException e) {
-            return defaultValue;
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
index 9a169d2..83cc39a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
@@ -78,8 +78,8 @@
     @Test
     public void getForegroundUserInfo() {
         ShadowActivityManager.setCurrentUser(17);
-        when(mUserManager.getUserInfo(ShadowActivityManager.getCurrentUser()))
-                .thenReturn(createUserInfoForId(ShadowActivityManager.getCurrentUser()));
+        when(mUserManager.getUserInfo(ActivityManager.getCurrentUser()))
+                .thenReturn(createUserInfoForId(ActivityManager.getCurrentUser()));
         assertThat(mHelper.getForegroundUserInfo().id).isEqualTo(17);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java
index 88fef08..97de7ef 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java
@@ -18,9 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 424368d..b071355 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -418,7 +418,7 @@
             case Settings.CALL_METHOD_PUT_CONFIG: {
                 String value = getSettingValue(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
-                insertConfigSetting(name, value, null, makeDefault, requestingUserId, false);
+                insertConfigSetting(name, value, makeDefault);
                 break;
             }
 
@@ -447,7 +447,7 @@
             case Settings.CALL_METHOD_RESET_CONFIG: {
                 final int mode = getResetModeEnforcingPermission(args);
                 String prefix = getSettingPrefix(args);
-                resetConfigSetting(requestingUserId, mode, prefix);
+                resetConfigSetting(mode, prefix);
                 break;
             }
 
@@ -466,7 +466,7 @@
             }
 
             case Settings.CALL_METHOD_DELETE_CONFIG: {
-                int rows  = deleteConfigSetting(name, requestingUserId, false) ? 1 : 0;
+                int rows  = deleteConfigSetting(name) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
@@ -1067,38 +1067,33 @@
         }
     }
 
-    private boolean insertConfigSetting(String name, String value, String tag,
-            boolean makeDefault, int requestingUserId, boolean forceNotify) {
+    private boolean insertConfigSetting(String name, String value, boolean makeDefault) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "insertConfigSetting(" + name + ", " + value  + ", "
-                    + ", " + tag + ", " + makeDefault + ", " + requestingUserId
-                    + ", " + forceNotify + ")");
+                    + makeDefault + ")");
         }
-        return mutateConfigSetting(name, value, tag, makeDefault, requestingUserId,
-                MUTATION_OPERATION_INSERT, forceNotify, 0);
+        return mutateConfigSetting(name, value, null, makeDefault,
+                MUTATION_OPERATION_INSERT, 0);
     }
 
-    private boolean deleteConfigSetting(String name, int requestingUserId, boolean forceNotify) {
+    private boolean deleteConfigSetting(String name) {
         if (DEBUG) {
-            Slog.v(LOG_TAG, "deleteConfigSetting(" + name + ", " + requestingUserId
-                    + ", " + forceNotify + ")");
+            Slog.v(LOG_TAG, "deleteConfigSetting(" + name + ")");
         }
-        return mutateConfigSetting(name, null, null, false, requestingUserId,
-                MUTATION_OPERATION_DELETE, forceNotify, 0);
+        return mutateConfigSetting(name, null, null, false,
+                MUTATION_OPERATION_DELETE, 0);
     }
 
-    private void resetConfigSetting(int requestingUserId, int mode, String prefix) {
+    private void resetConfigSetting(int mode, String prefix) {
         if (DEBUG) {
-            Slog.v(LOG_TAG, "resetConfigSetting(" + requestingUserId + ", "
-                    + mode + ", " + prefix + ")");
+            Slog.v(LOG_TAG, "resetConfigSetting(" + mode + ", " + prefix + ")");
         }
-        mutateConfigSetting(null, null, prefix, false, requestingUserId,
-                MUTATION_OPERATION_RESET, false, mode);
+        mutateConfigSetting(null, null, prefix, false,
+                MUTATION_OPERATION_RESET, mode);
     }
 
     private boolean mutateConfigSetting(String name, String value, String prefix,
-            boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
-            int mode) {
+            boolean makeDefault, int operation, int mode) {
         // TODO(b/117663715): check the new permission when it's added.
         // enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
@@ -1107,13 +1102,13 @@
             switch (operation) {
                 case MUTATION_OPERATION_INSERT: {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG,
-                            UserHandle.USER_SYSTEM, name, value, null, makeDefault,
-                            getCallingPackage(), forceNotify, null);
+                            UserHandle.USER_SYSTEM, name, value, null, makeDefault, true,
+                            getCallingPackage(), false, null);
                 }
 
                 case MUTATION_OPERATION_DELETE: {
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG,
-                            UserHandle.USER_SYSTEM, name, forceNotify, null);
+                            UserHandle.USER_SYSTEM, name, false, null);
                 }
 
                 case MUTATION_OPERATION_RESET: {
@@ -2631,13 +2626,20 @@
         public boolean insertSettingLocked(int type, int userId, String name, String value,
                 String tag, boolean makeDefault, String packageName, boolean forceNotify,
                 Set<String> criticalSettings) {
+            return insertSettingLocked(type, userId, name, value, tag, makeDefault, false,
+                    packageName, forceNotify, criticalSettings);
+        }
+
+        public boolean insertSettingLocked(int type, int userId, String name, String value,
+                String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName,
+                boolean forceNotify, Set<String> criticalSettings) {
             final int key = makeKey(type, userId);
 
             boolean success = false;
             SettingsState settingsState = peekSettingsStateLocked(key);
             if (settingsState != null) {
                 success = settingsState.insertSettingLocked(name, value,
-                        tag, makeDefault, packageName);
+                        tag, makeDefault, forceNonSystemPackage, packageName);
             }
 
             if (success && criticalSettings != null && criticalSettings.contains(name)) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index ae2ca3f..521163f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -377,6 +377,13 @@
     @GuardedBy("mLock")
     public boolean insertSettingLocked(String name, String value, String tag,
             boolean makeDefault, String packageName) {
+        return insertSettingLocked(name, value, tag, makeDefault, false, packageName);
+    }
+
+    // The settings provider must hold its lock when calling here.
+    @GuardedBy("mLock")
+    public boolean insertSettingLocked(String name, String value, String tag,
+            boolean makeDefault, boolean forceNonSystemPackage, String packageName) {
         if (TextUtils.isEmpty(name)) {
             return false;
         }
@@ -387,7 +394,7 @@
         Setting newState;
 
         if (oldState != null) {
-            if (!oldState.update(value, makeDefault, packageName, tag, false)) {
+            if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage)) {
                 return false;
             }
             newState = oldState;
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 59de6a7e..9d0462e 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -17,10 +17,9 @@
 package com.android.providers.settings;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 
-import static org.junit.Assert.assertNotNull;
-
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.os.Bundle;
@@ -147,10 +146,10 @@
     }
 
     @Test
-    public void testReset_setUntrustedDefault() throws Exception {
+    public void testReset() throws Exception {
         String newValue = "value2";
 
-        // make sValue the untrusted default (set by root)
+        // make sValue the default value
         executeShellCommand(
                 "device_config put " + sNamespace + " " + sKey + " " + sValue + " default");
         // make newValue the current value
@@ -159,40 +158,19 @@
         String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
         assertEquals(newValue, result);
 
+        // reset values that were set by untrusted packages
         executeShellCommand("device_config reset untrusted_defaults " + sNamespace);
         result = getFromContentProvider(mContentResolver, sNamespace, sKey);
-        // back to the default
+        // the default value has been restored
         assertEquals(sValue, result);
 
+        // clear values that were set by untrusted packages
         executeShellCommand("device_config reset trusted_defaults " + sNamespace);
         result = getFromContentProvider(mContentResolver, sNamespace, sKey);
-        // not trusted default was set
+        // even the default value is gone now
         assertNull(result);
     }
 
-    @Test
-    public void testReset_setTrustedDefault() throws Exception {
-        String newValue = "value2";
-
-        // make sValue the trusted default (set by system)
-        putWithContentProvider(mContentResolver, sNamespace, sKey, sValue, true);
-        // make newValue the current value
-        executeShellCommand(
-                "device_config put " + sNamespace + " " + sKey + " " + newValue);
-        String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
-        assertEquals(newValue, result);
-
-        executeShellCommand("device_config reset untrusted_defaults " + sNamespace);
-        result = getFromContentProvider(mContentResolver, sNamespace, sKey);
-        // back to the default
-        assertEquals(sValue, result);
-
-        executeShellCommand("device_config reset trusted_defaults " + sNamespace);
-        result = getFromContentProvider(mContentResolver, sNamespace, sKey);
-        // our trusted default is still set
-        assertEquals(sValue, result);
-    }
-
     private static void executeShellCommand(String command) throws IOException {
         InputStream is = new FileInputStream(InstrumentationRegistry.getInstrumentation()
                 .getUiAutomation().executeShellCommand(command).getFileDescriptor());
@@ -212,8 +190,7 @@
         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(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
     }
 
     private static String getFromContentProvider(ContentResolver resolver, String namespace,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 75492f3..b73c706 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"],
@@ -186,8 +178,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/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..d457307 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -598,6 +598,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 +2322,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..87155c4 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -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/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/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/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index ba69f3b..9391737 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -218,88 +218,7 @@
                 return false;
             }
 
-            ViewParent p = view.getParent();
-            RemoteInputView riv = null;
-            while (p != null) {
-                if (p instanceof View) {
-                    View pv = (View) p;
-                    if (pv.isRootNamespace()) {
-                        riv = findRemoteInputView(pv);
-                        break;
-                    }
-                }
-                p = p.getParent();
-            }
-            ExpandableNotificationRow row = null;
-            while (p != null) {
-                if (p instanceof ExpandableNotificationRow) {
-                    row = (ExpandableNotificationRow) p;
-                    break;
-                }
-                p = p.getParent();
-            }
-
-            if (row == null) {
-                return false;
-            }
-
-            row.setUserExpanded(true);
-
-            if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
-                final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
-                if (mLockscreenUserManager.isLockscreenPublicMode(userId)) {
-                    mCallback.onLockedRemoteInput(row, view);
-                    return true;
-                }
-                if (mUserManager.getUserInfo(userId).isManagedProfile()
-                        && mKeyguardManager.isDeviceLocked(userId)) {
-                    mCallback.onLockedWorkRemoteInput(userId, row, view);
-                    return true;
-                }
-            }
-
-            if (riv == null) {
-                riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
-                if (riv == null) {
-                    return false;
-                }
-                if (!row.getPrivateLayout().getExpandedChild().isShown()) {
-                    mCallback.onMakeExpandedVisibleForRemoteInput(row, view);
-                    return true;
-                }
-            }
-
-            int width = view.getWidth();
-            if (view instanceof TextView) {
-                // Center the reveal on the text which might be off-center from the TextView
-                TextView tv = (TextView) view;
-                if (tv.getLayout() != null) {
-                    int innerWidth = (int) tv.getLayout().getLineWidth(0);
-                    innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
-                    width = Math.min(width, innerWidth);
-                }
-            }
-            int cx = view.getLeft() + width / 2;
-            int cy = view.getTop() + view.getHeight() / 2;
-            int w = riv.getWidth();
-            int h = riv.getHeight();
-            int r = Math.max(
-                    Math.max(cx + cy, cx + (h - cy)),
-                    Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
-            riv.setRevealParameters(cx, cy, r);
-            riv.setPendingIntent(pendingIntent);
-            riv.setRemoteInput(inputs, input);
-            riv.focusAnimated();
-
-            return true;
-        }
-
-        private RemoteInputView findRemoteInputView(View v) {
-            if (v == null) {
-                return null;
-            }
-            return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+            return activateRemoteInput(view, inputs, input, pendingIntent);
         }
     };
 
@@ -355,6 +274,102 @@
     }
 
     /**
+     * Activates a given {@link RemoteInput}
+     *
+     * @param view The view of the action button or suggestion chip that was tapped.
+     * @param inputs The remote inputs that need to be sent to the app.
+     * @param input The remote input that needs to be activated.
+     * @param pendingIntent The pending intent to be sent to the app.
+     * @return Whether the {@link RemoteInput} was activated.
+     */
+    public boolean activateRemoteInput(View view, RemoteInput[] inputs, RemoteInput input,
+            PendingIntent pendingIntent) {
+
+        ViewParent p = view.getParent();
+        RemoteInputView riv = null;
+        while (p != null) {
+            if (p instanceof View) {
+                View pv = (View) p;
+                if (pv.isRootNamespace()) {
+                    riv = findRemoteInputView(pv);
+                    break;
+                }
+            }
+            p = p.getParent();
+        }
+        ExpandableNotificationRow row = null;
+        while (p != null) {
+            if (p instanceof ExpandableNotificationRow) {
+                row = (ExpandableNotificationRow) p;
+                break;
+            }
+            p = p.getParent();
+        }
+
+        if (row == null) {
+            return false;
+        }
+
+        row.setUserExpanded(true);
+
+        if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
+            final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
+            if (mLockscreenUserManager.isLockscreenPublicMode(userId)) {
+                mCallback.onLockedRemoteInput(row, view);
+                return true;
+            }
+            if (mUserManager.getUserInfo(userId).isManagedProfile()
+                    && mKeyguardManager.isDeviceLocked(userId)) {
+                mCallback.onLockedWorkRemoteInput(userId, row, view);
+                return true;
+            }
+        }
+
+        if (riv == null) {
+            riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
+            if (riv == null) {
+                return false;
+            }
+            if (!row.getPrivateLayout().getExpandedChild().isShown()) {
+                mCallback.onMakeExpandedVisibleForRemoteInput(row, view);
+                return true;
+            }
+        }
+
+        int width = view.getWidth();
+        if (view instanceof TextView) {
+            // Center the reveal on the text which might be off-center from the TextView
+            TextView tv = (TextView) view;
+            if (tv.getLayout() != null) {
+                int innerWidth = (int) tv.getLayout().getLineWidth(0);
+                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+                width = Math.min(width, innerWidth);
+            }
+        }
+        int cx = view.getLeft() + width / 2;
+        int cy = view.getTop() + view.getHeight() / 2;
+        int w = riv.getWidth();
+        int h = riv.getHeight();
+        int r = Math.max(
+                Math.max(cx + cy, cx + (h - cy)),
+                Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+        riv.setRevealParameters(cx, cy, r);
+        riv.setPendingIntent(pendingIntent);
+        riv.setRemoteInput(inputs, input);
+        riv.focusAnimated();
+
+        return true;
+    }
+
+    private RemoteInputView findRemoteInputView(View v) {
+        if (v == null) {
+            return null;
+        }
+        return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+    }
+
+    /**
      * Adds all the notification lifetime extenders. Each extender represents a reason for the
      * NotificationRemoteInputManager to keep a notification lifetime extended.
      */
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 8214ea6..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). */
@@ -1098,6 +1104,10 @@
         mHeadsUpManager = headsUpManager;
     }
 
+    public HeadsUpManager getHeadsUpManager() {
+        return mHeadsUpManager;
+    }
+
     public void setGutsView(MenuItem item) {
         if (mGuts != null && item.getGutsView() instanceof NotificationGuts.GutsContent) {
             ((NotificationGuts.GutsContent) item.getGutsView()).setGutsParent(mGuts);
@@ -1208,9 +1218,8 @@
         }
     }
 
-    @Override
     public void onUiModeChanged() {
-        super.onUiModeChanged();
+        mUpdateBackgroundOnUpdate = true;
         reInflateViews();
         if (mChildrenContainer != null) {
             for (ExpandableNotificationRow child : mChildrenContainer.getNotificationChildren()) {
@@ -1681,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 689d6d5..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
@@ -91,6 +91,7 @@
 
     private SmartReplyConstants mSmartReplyConstants;
     private SmartReplyView mExpandedSmartReplyView;
+    private SmartReplyView mHeadsUpSmartReplyView;
     private SmartReplyController mSmartReplyController;
 
     private NotificationViewWrapper mContractedWrapper;
@@ -253,6 +254,9 @@
         }
         if (mHeadsUpChild != null) {
             int maxHeight = mHeadsUpHeight;
+            if (mHeadsUpSmartReplyView != null) {
+                maxHeight += mHeadsUpSmartReplyView.getHeightUpperLimit();
+            }
             maxHeight += mHeadsUpWrapper.getExtraMeasureHeight();
             int size = maxHeight;
             ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
@@ -955,6 +959,9 @@
         if (mExpandedSmartReplyView != null) {
             mExpandedSmartReplyView.setBackgroundTintColor(color);
         }
+        if (mHeadsUpSmartReplyView != null) {
+            mHeadsUpSmartReplyView.setBackgroundTintColor(color);
+        }
     }
 
     public int getVisibleType() {
@@ -1472,6 +1479,10 @@
                         entry, smartRepliesAndActions.smartReplies.choices.length);
             }
         }
+        if (mHeadsUpChild != null) {
+            mHeadsUpSmartReplyView =
+                    applySmartReplyView(mHeadsUpChild, smartRepliesAndActions, entry);
+        }
     }
 
     private SmartReplyView applySmartReplyView(View view,
@@ -1520,7 +1531,8 @@
             }
             if (smartRepliesAndActions.smartActions != null) {
                 smartReplyView.addSmartActions(
-                        smartRepliesAndActions.smartActions, mSmartReplyController, entry);
+                        smartRepliesAndActions.smartActions, mSmartReplyController, entry,
+                        mContainingNotification.getHeadsUpManager());
             }
             smartReplyContainer.setVisibility(View.VISIBLE);
         }
@@ -1603,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..67b077e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -259,6 +259,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/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/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index a2a11bb..c7e4d34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -105,10 +105,22 @@
 
     private static final boolean DEBUG = false;
 
-    private static final boolean EXPAND_ON_WAKE_UP = SystemProperties.getBoolean(
+    /**
+     * If passive interrupts expand the NSSL or not
+     */
+    private static final boolean EXPAND_ON_PASSIVE_INTERRUPT = SystemProperties.getBoolean(
             "persist.sysui.expand_shade_on_wake_up", true);
+    /**
+     * If the notification panel should remain collapsed when the phone wakes up, even if the user
+     * presses power.
+     */
+    private static final boolean NEVER_EXPAND_WHEN_WAKING_UP = SystemProperties.getBoolean(
+            "persist.sysui.defer_notifications_on_lock_screen", false);
+    /**
+     * If waking up the phone should take you to SHADE_LOCKED instead of KEYGUARD
+     */
     private static final boolean WAKE_UP_TO_SHADE = SystemProperties.getBoolean(
-            "persist.sysui.go_to_shade_on_wake_up", true);
+            "persist.sysui.go_to_shade_on_wake_up", false);
 
     /**
      * Fling expanding QS.
@@ -2774,10 +2786,12 @@
     }
 
     public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation,
-            boolean passiveInterrupted) {
+            boolean passivelyInterrupted) {
         if (dozing == mDozing) return;
         mDozing = dozing;
-        mSemiAwake = !EXPAND_ON_WAKE_UP && !mDozing && passiveInterrupted;
+        boolean doNotExpand = (!EXPAND_ON_PASSIVE_INTERRUPT && passivelyInterrupted)
+                || NEVER_EXPAND_WHEN_WAKING_UP;
+        mSemiAwake = doNotExpand && !mDozing;
         if (!mSemiAwake) {
             mNotificationStackScroller.setDark(mDozing, animate, wakeUpTouchLocation);
         }
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..a508f1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -628,6 +628,8 @@
         mBubbleController = Dependency.get(BubbleController.class);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
+        mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
+
         mColorExtractor.addOnColorsChangedListener(this);
         mStatusBarStateController.addCallback(this, StatusBarStateController.RANK_STATUS_BAR);
 
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/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/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 2a4336e..4fa8321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -16,6 +16,8 @@
 import android.graphics.drawable.InsetDrawable;
 import android.graphics.drawable.RippleDrawable;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.text.Layout;
 import android.text.TextPaint;
 import android.text.method.TransformationMethod;
@@ -34,6 +36,7 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.NotificationData;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -61,6 +64,8 @@
 
     private final SmartReplyConstants mConstants;
     private final KeyguardDismissUtil mKeyguardDismissUtil;
+    private final NotificationRemoteInputManager mRemoteInputManager;
+    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
 
     /**
      * The upper bound for the height of this view in pixels. Notifications are automatically
@@ -109,6 +114,7 @@
         super(context, attrs);
         mConstants = Dependency.get(SmartReplyConstants.class);
         mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class);
+        mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
 
         mHeightUpperLimit = NotificationUtils.getFontScaledHeight(mContext,
             R.dimen.smart_reply_button_max_height);
@@ -209,13 +215,15 @@
      * notification are shown.
      */
     public void addSmartActions(SmartActions smartActions,
-            SmartReplyController smartReplyController, NotificationData.Entry entry) {
+            SmartReplyController smartReplyController, NotificationData.Entry entry,
+            HeadsUpManager headsUpManager) {
         int numSmartActions = smartActions.actions.size();
         for (int n = 0; n < numSmartActions; n++) {
             Notification.Action action = smartActions.actions.get(n);
             if (action.actionIntent != null) {
                 Button actionButton = inflateActionButton(
-                        getContext(), this, n, smartActions, smartReplyController, entry);
+                        getContext(), this, n, smartActions, smartReplyController, entry,
+                        headsUpManager);
                 addView(actionButton);
             }
         }
@@ -237,12 +245,22 @@
         b.setText(choice);
 
         OnDismissAction action = () -> {
+            // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags.
+            if (smartReplies.remoteInput.getEditChoicesBeforeSending()
+                    == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) {
+                entry.remoteInputText = choice;
+                mRemoteInputManager.activateRemoteInput(b,
+                        new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
+                        smartReplies.pendingIntent);
+                return false;
+            }
+
             smartReplyController.smartReplySent(
                     entry, replyIndex, b.getText(), smartReplies.fromAssistant);
             Bundle results = new Bundle();
             results.putString(smartReplies.remoteInput.getResultKey(), choice.toString());
             Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            RemoteInput.addResultsToIntent(new RemoteInput[]{smartReplies.remoteInput}, intent,
+            RemoteInput.addResultsToIntent(new RemoteInput[] { smartReplies.remoteInput }, intent,
                     results);
             RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
             entry.setHasSentReply();
@@ -274,7 +292,7 @@
     @VisibleForTesting
     Button inflateActionButton(Context context, ViewGroup root, int actionIndex,
             SmartActions smartActions, SmartReplyController smartReplyController,
-            NotificationData.Entry entry) {
+            NotificationData.Entry entry, HeadsUpManager headsUpManager) {
         Notification.Action action = smartActions.actions.get(actionIndex);
         Button button = (Button) LayoutInflater.from(context).inflate(
                 R.layout.smart_action_button, root, false);
@@ -290,8 +308,12 @@
         button.setOnClickListener(view ->
                 getActivityStarter().startPendingIntentDismissingKeyguard(
                         action.actionIntent,
-                        () -> smartReplyController.smartActionClicked(
-                                entry, actionIndex, action, smartActions.fromAssistant)));
+                        () -> {
+                            smartReplyController.smartActionClicked(
+                                    entry, actionIndex, action, smartActions.fromAssistant);
+                            postOnUiThread(() ->
+                                    headsUpManager.removeNotification(entry.key, true));
+                        }));
 
         // TODO(b/119010281): handle accessibility
 
@@ -301,6 +323,10 @@
         return button;
     }
 
+    private void postOnUiThread(Runnable runnable) {
+        mMainThreadHandler.post(runnable);
+    }
+
     @Override
     public LayoutParams generateLayoutParams(AttributeSet attrs) {
         return new LayoutParams(mContext, attrs);
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/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 506fa97..c5bac92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -98,6 +98,7 @@
     private Notification mNotification;
 
     @Mock ActivityStarter mActivityStarter;
+    @Mock HeadsUpManager mHeadsUpManager;
 
     @Before
     public void setUp() {
@@ -434,7 +435,8 @@
         mView.addSmartActions(
                 new SmartReplyView.SmartActions(createActions(actionTitles), false),
                 mLogger,
-                mEntry);
+                mEntry,
+                mHeadsUpManager);
     }
 
     private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
@@ -442,7 +444,8 @@
         mView.addSmartActions(
                 new SmartReplyView.SmartActions(createActions(actionTitles), false),
                 mLogger,
-                mEntry);
+                mEntry,
+                mHeadsUpManager);
     }
 
     private ViewGroup buildExpectedView(CharSequence[] choices, int lineCount) {
@@ -747,7 +750,7 @@
     private Button inflateActionButton(Notification.Action action) {
         return mView.inflateActionButton(getContext(), mView, 0,
                 new SmartReplyView.SmartActions(Collections.singletonList(action), false),
-                mLogger, mEntry);
+                mLogger, mEntry, mHeadsUpManager);
     }
 
     @Test
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/proto/src/system_messages.proto b/proto/src/system_messages.proto
index fba639c..1212676 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -216,6 +216,10 @@
     // Package: android
     NOTE_SOFTAP_CONFIG_CHANGED = 50;
 
+    // Notify the user that connected to app suggested network.
+    // Package: android
+    NOTE_CONNECTED_TO_NETWORK_SUGGESTION = 51;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d5decce..36ca52a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2466,6 +2466,25 @@
     }
 
     /**
+     * AIDL-exposed method. System only.
+     * Gets accessibility window id from window token.
+     *
+     * @param windowToken Window token to get accessibility window id.
+     * @return Accessibility window id for the window token. Returns -1 if no such token is
+     *   registered.
+     */
+    @Override
+    public int getAccessibilityWindowId(IBinder windowToken) {
+        synchronized (mLock) {
+            if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
+                throw new SecurityException("Only SYSTEM can call getAccessibilityWindowId");
+            }
+
+            return findWindowIdLocked(windowToken);
+        }
+    }
+
+    /**
      * Get the recommended timeout of interactive controls and non-interactive controls.
      *
      * @return A long for pair of {@code int}s. First integer for interactive one, and second
diff --git a/services/core/Android.bp b/services/core/Android.bp
index cccacf4..4408db8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -7,7 +7,6 @@
             "frameworks/native/aidl/binder",
             "frameworks/native/cmds/dumpstate/binder",
             "system/core/storaged/binder",
-            "system/netd/server/binder",
             "system/vold/binder",
         ],
     },
@@ -21,7 +20,6 @@
         ":mediaupdateservice_aidl",
         "java/com/android/server/EventLogTags.logtags",
         "java/com/android/server/am/EventLogTags.logtags",
-        ":netd_metrics_aidl",
     ],
 
     libs: [
@@ -52,6 +50,7 @@
         "android.hardware.contexthub-V1.0-java",
         "android.hidl.manager-V1.0-java",
         "netd_aidl_interface-java",
+        "netd_event_listener_interface-java",
     ],
 }
 
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 854c03f..08034f7 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -48,6 +48,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
@@ -1306,8 +1307,12 @@
             // because kernel doesn't keep this after reboot
             setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY));
 
-            // Also sure that we're booting with a halfway sensible current time
-            final long systemBuildTime = Environment.getRootDirectory().lastModified();
+            // Ensure that we're booting with a halfway sensible current time.  Use the
+            // most recent of Build.TIME, the root file system's timestamp, and the
+            // value of the ro.build.date.utc system property (which is in seconds).
+            final long systemBuildTime =  Long.max(
+                    1000L * SystemProperties.getLong("ro.build.date.utc", -1L),
+                    Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
             if (mInjector.getCurrentTimeMillis() < systemBuildTime) {
                 Slog.i(TAG, "Current time only " + mInjector.getCurrentTimeMillis()
                         + ", advancing to build time " + systemBuildTime);
diff --git a/services/core/java/com/android/server/AnyMotionDetector.java b/services/core/java/com/android/server/AnyMotionDetector.java
index d564925..8c5ee7f 100644
--- a/services/core/java/com/android/server/AnyMotionDetector.java
+++ b/services/core/java/com/android/server/AnyMotionDetector.java
@@ -26,8 +26,6 @@
 import android.os.SystemClock;
 import android.util.Slog;
 
-import java.lang.Float;
-
 /**
  * Determines if the device has been set upon a stationary object.
  */
@@ -140,6 +138,13 @@
         }
     }
 
+    /**
+     * If we do not have an accelerometer, we are not going to collect much data.
+     */
+    public boolean hasSensor() {
+        return mAccelSensor != null;
+    }
+
     /*
      * Acquire accel data until we determine AnyMotion status.
      */
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/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index af9d4c8..08cb7a2 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -43,6 +43,7 @@
 import android.net.INetworkPolicyManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
+import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Bundle;
@@ -272,6 +273,7 @@
     private PowerManager mPowerManager;
     private INetworkPolicyManager mNetworkPolicyManager;
     private SensorManager mSensorManager;
+    private final boolean mUseMotionSensor;
     private Sensor mMotionSensor;
     private LocationRequest mLocationRequest;
     private Intent mIdleIntent;
@@ -520,9 +522,10 @@
                     updateConnectivityState(intent);
                 } break;
                 case Intent.ACTION_BATTERY_CHANGED: {
+                    boolean present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
+                    boolean plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
                     synchronized (DeviceIdleController.this) {
-                        int plugged = intent.getIntExtra("plugged", 0);
-                        updateChargingLocked(plugged != 0);
+                        updateChargingLocked(present && plugged);
                     }
                 } break;
                 case Intent.ACTION_PACKAGE_REMOVED: {
@@ -1629,6 +1632,9 @@
         mHandler = mInjector.getHandler(this);
         mAppStateTracker = mInjector.getAppStateTracker(context, FgThread.get().getLooper());
         LocalServices.addService(AppStateTracker.class, mAppStateTracker);
+
+        mUseMotionSensor = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_autoPowerModeUseMotionSensor);
     }
 
     public DeviceIdleController(Context context) {
@@ -1729,20 +1735,23 @@
                         ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
                 mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class);
                 mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
-                int sigMotionSensorId = getContext().getResources().getInteger(
-                        com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);
-                if (sigMotionSensorId > 0) {
-                    mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true);
-                }
-                if (mMotionSensor == null && getContext().getResources().getBoolean(
-                        com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {
-                    mMotionSensor = mSensorManager.getDefaultSensor(
-                            Sensor.TYPE_WRIST_TILT_GESTURE, true);
-                }
-                if (mMotionSensor == null) {
-                    // As a last ditch, fall back to SMD.
-                    mMotionSensor = mSensorManager.getDefaultSensor(
-                            Sensor.TYPE_SIGNIFICANT_MOTION, true);
+
+                if (mUseMotionSensor) {
+                    int sigMotionSensorId = getContext().getResources().getInteger(
+                            com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);
+                    if (sigMotionSensorId > 0) {
+                        mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true);
+                    }
+                    if (mMotionSensor == null && getContext().getResources().getBoolean(
+                            com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {
+                        mMotionSensor = mSensorManager.getDefaultSensor(
+                                Sensor.TYPE_WRIST_TILT_GESTURE, true);
+                    }
+                    if (mMotionSensor == null) {
+                        // As a last ditch, fall back to SMD.
+                        mMotionSensor = mSensorManager.getDefaultSensor(
+                                Sensor.TYPE_SIGNIFICANT_MOTION, true);
+                    }
                 }
 
                 if (getContext().getResources().getBoolean(
@@ -2588,14 +2597,21 @@
                 mState = STATE_SENSING;
                 if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
                 EventLogTags.writeDeviceIdle(mState, reason);
-                scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
                 cancelLocatingLocked();
-                mNotMoving = false;
                 mLocated = false;
                 mLastGenericLocation = null;
                 mLastGpsLocation = null;
-                mAnyMotionDetector.checkForAnyMotion();
-                break;
+
+                // If we have an accelerometer, wait to find out whether we are moving.
+                if (mUseMotionSensor && mAnyMotionDetector.hasSensor()) {
+                    scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
+                    mNotMoving = false;
+                    mAnyMotionDetector.checkForAnyMotion();
+                    break;
+                }
+
+                mNotMoving = true;
+                // Otherwise, fall through and check this off the list of requirements.
             case STATE_SENSING:
                 cancelSensingTimeoutAlarmLocked();
                 mState = STATE_LOCATING;
@@ -2893,9 +2909,12 @@
 
     void scheduleAlarmLocked(long delay, boolean idleUntil) {
         if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")");
-        if (mMotionSensor == null && !(mState == STATE_QUICK_DOZE_DELAY || mState == STATE_IDLE
-                  || mState == STATE_IDLE_MAINTENANCE)) {
-            // If there is no motion sensor on this device, then we won't schedule
+
+        if (mUseMotionSensor && mMotionSensor == null
+                && mState != STATE_QUICK_DOZE_DELAY
+                && mState != STATE_IDLE
+                && mState != STATE_IDLE_MAINTENANCE) {
+            // If there is no motion sensor on this device, but we need one, then we won't schedule
             // alarms, because we can't determine if the device is not moving.  This effectively
             // turns off normal execution of device idling, although it is still possible to
             // manually poke it by pretending like the alarm is going off.
@@ -3765,13 +3784,20 @@
             pw.print("  mLightEnabled="); pw.print(mLightEnabled);
             pw.print("  mDeepEnabled="); pw.println(mDeepEnabled);
             pw.print("  mForceIdle="); pw.println(mForceIdle);
-            pw.print("  mMotionSensor="); pw.println(mMotionSensor);
+            pw.print("  mUseMotionSensor="); pw.print(mUseMotionSensor);
+            if (mUseMotionSensor) {
+                pw.print(" mMotionSensor="); pw.println(mMotionSensor);
+            } else {
+                pw.println();
+            }
             pw.print("  mScreenOn="); pw.println(mScreenOn);
             pw.print("  mScreenLocked="); pw.println(mScreenLocked);
             pw.print("  mNetworkConnected="); pw.println(mNetworkConnected);
             pw.print("  mCharging="); pw.println(mCharging);
             pw.print("  mMotionActive="); pw.println(mMotionListener.active);
-            pw.print("  mNotMoving="); pw.println(mNotMoving);
+            if (mUseMotionSensor) {
+                pw.print("  mNotMoving="); pw.println(mNotMoving);
+            }
             pw.print("  mLocating="); pw.print(mLocating); pw.print(" mHasGps=");
                     pw.print(mHasGps); pw.print(" mHasNetwork=");
                     pw.print(mHasNetworkLocation); pw.print(" mLocated="); pw.println(mLocated);
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 87a42fa..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)}.
@@ -956,23 +954,10 @@
     // INetworkManagementService members
     //
     @Override
-    public INetd getNetdService() throws RemoteException {
-        final CountDownLatch connectedSignal = mConnectedSignal;
-        if (connectedSignal != null) {
-            try {
-                connectedSignal.await();
-            } catch (InterruptedException ignored) {}
-        }
-
-        return mNetdService;
-    }
-
-    @Override
     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);
         }
@@ -1264,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);
         }
@@ -1288,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/OWNERS b/services/core/java/com/android/server/OWNERS
index fe9f1b5..a9c38bc 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -3,3 +3,4 @@
 
 # Vibrator / Threads
 per-file VibratorService.java, DisplayThread.java = michaelwr@google.com
+per-file VibratorService.java, DisplayThread.java = ogunwale@google.com
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 4b092b2..581d435 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -16,7 +16,15 @@
 
 package com.android.server;
 
-import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+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_WRITE;
 import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
 import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT;
@@ -27,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;
@@ -51,7 +61,9 @@
 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;
@@ -116,12 +128,14 @@
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.os.AppFuseMount;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.FuseUnavailableMountException;
 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;
@@ -204,7 +218,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();
@@ -261,6 +277,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";
@@ -311,6 +328,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<>();
@@ -453,6 +474,9 @@
     private UserManagerInternal mUmInternal;
     private ActivityManagerInternal mAmInternal;
 
+    private IPackageManager mIPackageManager;
+    private IAppOpsService mIAppOpsService;
+
     private final Callbacks mCallbacks;
     private final LockPatternUtils mLockPatternUtils;
 
@@ -1565,11 +1589,83 @@
         }
     }
 
+    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);
 
         mSystemReady = true;
+        mIPackageManager = IPackageManager.Stub.asInterface(
+                ServiceManager.getService("package"));
+        mIAppOpsService = IAppOpsService.Stub.asInterface(
+                ServiceManager.getService(Context.APP_OPS_SERVICE));
         mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
     }
 
@@ -1589,6 +1685,7 @@
     private void readSettingsLocked() {
         mRecords.clear();
         mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
+        mLastIsolatedStorage = false;
 
         FileInputStream fis = null;
         try {
@@ -1610,6 +1707,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);
@@ -1640,6 +1739,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);
@@ -2775,11 +2875,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) {
@@ -2906,7 +3002,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);
@@ -3117,7 +3213,8 @@
             throw new SecurityException("Shady looking path " + path);
         }
 
-        if (!mAmInternal.isAppStorageSandboxed(pid, uid)) {
+        final int mountMode = mAmInternal.getStorageMountMode(pid, uid);
+        if (mountMode == Zygote.MOUNT_EXTERNAL_FULL) {
             return path;
         }
 
@@ -3126,6 +3223,11 @@
             final String device = m.group(1);
             final String devicePath = m.group(2);
 
+            if (mountMode == Zygote.MOUNT_EXTERNAL_INSTALLER
+                    && devicePath.startsWith("Android/obb/")) {
+                return path;
+            }
+
             // Does path belong to any packages belonging to this UID? If so,
             // they get to go straight through to legacy paths.
             final String[] pkgs = mContext.getPackageManager().getPackagesForUid(uid);
@@ -3477,6 +3579,31 @@
         }
     }
 
+    private int getMountMode(int uid, String packageName) {
+        try {
+            if (Process.isIsolated(uid)) {
+                return Zygote.MOUNT_EXTERNAL_NONE;
+            }
+            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) {
+                return Zygote.MOUNT_EXTERNAL_INSTALLER;
+            } else {
+                return Zygote.MOUNT_EXTERNAL_WRITE;
+            }
+        } catch (RemoteException e) {
+            // Should not happen
+        }
+        return Zygote.MOUNT_EXTERNAL_NONE;
+    }
+
     private static class Callbacks extends Handler {
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
@@ -3718,6 +3845,9 @@
 
         @Override
         public int getExternalStorageMountMode(int uid, String packageName) {
+            if (ENABLE_ISOLATED_STORAGE) {
+                return getMountMode(uid, packageName);
+            }
             // No locking - CopyOnWriteArrayList
             int mountMode = Integer.MAX_VALUE;
             for (ExternalStorageMountPolicy policy : mPolicies) {
@@ -3754,6 +3884,9 @@
             if (uid == Process.SYSTEM_UID) {
                 return true;
             }
+            if (ENABLE_ISOLATED_STORAGE) {
+                return getMountMode(uid, packageName) != Zygote.MOUNT_EXTERNAL_NONE;
+            }
             // No locking - CopyOnWriteArrayList
             for (ExternalStorageMountPolicy policy : mPolicies) {
                 final boolean policyHasStorage = policy.hasExternalStorage(uid, packageName);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index b04ae17..b6c4921 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1996,6 +1996,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/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index bbb1d13..9f353a8 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -16,7 +16,9 @@
 
 package com.android.server;
 
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.IUidObserver;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -111,6 +113,7 @@
     private final boolean mSupportsAmplitudeControl;
     private final int mDefaultVibrationAmplitude;
     private final SparseArray<VibrationEffect> mFallbackEffects;
+    private final SparseArray<Integer> mProcStatesCache = new SparseArray();
     private final WorkSource mTmpWorkSource = new WorkSource();
     private final Handler mH = new Handler();
     private final Object mLock = new Object();
@@ -147,6 +150,25 @@
     native static void vibratorSetAmplitude(int amplitude);
     native static long vibratorPerformEffect(long effect, long strength);
 
+    private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+        @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+            mProcStatesCache.put(uid, procState);
+        }
+
+        @Override public void onUidGone(int uid, boolean disabled) {
+            mProcStatesCache.delete(uid);
+        }
+
+        @Override public void onUidActive(int uid) {
+        }
+
+        @Override public void onUidIdle(int uid, boolean disabled) {
+        }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
+        }
+    };
+
     private class Vibration implements IBinder.DeathRecipient {
         public final IBinder token;
         // Start time in CLOCK_BOOTTIME base.
@@ -411,6 +433,14 @@
                 }
             }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
 
+            try {
+                ActivityManager.getService().registerUidObserver(mUidObserver,
+                        ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
+                        ActivityManager.PROCESS_STATE_UNKNOWN, null);
+            } catch (RemoteException e) {
+                // ignored; both services live in system_server
+            }
+
             updateVibrators();
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -502,6 +532,12 @@
                 return;
             }
             verifyIncomingUid(uid);
+            if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+                    > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
+                        + uid + " is background");
+                return;
+            }
             if (!verifyVibrationEffect(effect)) {
                 return;
             }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index f7acf7e..8751d24 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2019,6 +2019,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 +2106,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 +2143,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 c9cab98..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 {
@@ -2724,47 +2855,86 @@
         return (ai.flags&ApplicationInfo.FLAG_PERSISTENT) != 0;
     }
 
-    void updateUsageStats(ComponentName activity, int uid, int userId, boolean resumed) {
+    /**
+     * Update battery stats on the activity' usage.
+     * @param activity
+     * @param uid
+     * @param userId
+     * @param resumed
+     */
+    void updateBatteryStats(ComponentName activity, int uid, int userId, boolean resumed) {
         if (DEBUG_SWITCH) {
             Slog.d(TAG_SWITCH,
-                    "updateUsageStats: comp=" + activity + "res=" + resumed);
+                    "updateBatteryStats: comp=" + activity + "res=" + resumed);
         }
         final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
         StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED,
-            uid, activity.getPackageName(),
-            activity.getShortClassName(), resumed ?
-                        StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__FOREGROUND :
+                uid, activity.getPackageName(), activity.getShortClassName(),
+                resumed ? StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__FOREGROUND :
                         StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__BACKGROUND);
-        if (resumed) {
-            if (mUsageStatsService != null) {
-                mUsageStatsService.reportEvent(activity, userId,
-                        UsageEvents.Event.MOVE_TO_FOREGROUND);
-
-            }
-            synchronized (stats) {
+        synchronized (stats) {
+            if (resumed) {
                 stats.noteActivityResumedLocked(uid);
-            }
-        } else {
-            if (mUsageStatsService != null) {
-                mUsageStatsService.reportEvent(activity, userId,
-                        UsageEvents.Event.MOVE_TO_BACKGROUND);
-            }
-            synchronized (stats) {
+            } else {
                 stats.noteActivityPausedLocked(uid);
             }
         }
     }
 
+    /**
+     * Update UsageStas on the activity's usage.
+     * @param activity
+     * @param userId
+     * @param event
+     * @param appToken ActivityRecord's appToken.
+     */
+    public void updateActivityUsageStats(ComponentName activity, int userId, int event,
+            IBinder appToken) {
+        if (DEBUG_SWITCH) {
+            Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp="
+                    + activity + " hash=" + appToken.hashCode() + " event=" + event);
+        }
+        synchronized (this) {
+            if (mUsageStatsService != null) {
+                mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode());
+            }
+        }
+    }
+
+    /**
+     * Update UsageStats on this package's usage.
+     * @param packageName
+     * @param userId
+     * @param event
+     */
+    public void updateActivityUsageStats(String packageName, int userId, int event) {
+        if (DEBUG_SWITCH) {
+            Slog.d(TAG_SWITCH, "updateActivityUsageStats: package="
+                    + packageName + " event=" + event);
+        }
+        synchronized (this) {
+            if (mUsageStatsService != null) {
+                mUsageStatsService.reportEvent(packageName, userId, event);
+            }
+        }
+    }
+
+    /**
+     * Update Usages on this foreground service's usage.
+     * @param service
+     * @param userId
+     * @param started
+     */
     void updateForegroundServiceUsageStats(ComponentName service, int userId, boolean started) {
         if (DEBUG_SWITCH) {
             Slog.d(TAG_SWITCH, "updateForegroundServiceUsageStats: comp="
-                    + service + "started=" + started);
+                    + service + " started=" + started);
         }
         synchronized (this) {
             if (mUsageStatsService != null) {
                 mUsageStatsService.reportEvent(service, userId,
                         started ? UsageEvents.Event.FOREGROUND_SERVICE_START
-                                : UsageEvents.Event.FOREGROUND_SERVICE_STOP);
+                                : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0);
             }
         }
     }
@@ -6225,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) {
@@ -6296,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))
@@ -6325,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))
@@ -6422,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) {
@@ -7608,7 +7808,7 @@
         String extraOptions = null;
         switch (bugreportType) {
             case ActivityManager.BUGREPORT_OPTION_FULL:
-                // Default options.
+                extraOptions = "bugreportfull";
                 break;
             case ActivityManager.BUGREPORT_OPTION_INTERACTIVE:
                 extraOptions = "bugreportplus";
@@ -9168,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) {
@@ -9435,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];
@@ -10785,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;
@@ -16766,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 "
@@ -19068,9 +19338,19 @@
         }
 
         @Override
-        public void updateUsageStats(ComponentName activity, int uid, int userId, boolean resumed) {
+        public void updateBatteryStats(ComponentName activity, int uid, int userId,
+                boolean resumed) {
             synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.updateUsageStats(activity, uid, userId, resumed);
+                ActivityManagerService.this.updateBatteryStats(activity, uid, userId, resumed);
+            }
+        }
+
+        @Override
+        public void updateActivityUsageStats(ComponentName activity, int userId, int event,
+                IBinder appToken) {
+            synchronized (ActivityManagerService.this) {
+                ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
+                        appToken);
             }
         }
 
@@ -19392,16 +19672,13 @@
         }
 
         @Override
-        public boolean isAppStorageSandboxed(int pid, int uid) {
-            if (!StorageManager.hasIsolatedStorage()) {
-                return false;
-            }
+        public int getStorageMountMode(int pid, int uid) {
             if (uid == SHELL_UID || uid == ROOT_UID) {
-                return false;
+                return Zygote.MOUNT_EXTERNAL_FULL;
             }
             synchronized (mPidsSelfLocked) {
                 final ProcessRecord pr = mPidsSelfLocked.get(pid);
-                return pr == null || pr.mountMode != Zygote.MOUNT_EXTERNAL_FULL;
+                return pr == null ? Zygote.MOUNT_EXTERNAL_NONE : pr.mountMode;
             }
         }
     }
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/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/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/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 9649ccd..32219aa 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -282,9 +282,11 @@
 
         public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
                 IBinder token, ServiceListener listener, int userId, int groupId,
-                byte[] cryptoToken, boolean restricted, String owner) {
+                byte[] cryptoToken, boolean restricted, String owner,
+                final int[] disabledFeatures) {
             super(context, getMetrics(), daemon, halDeviceId, token, listener,
-                    userId, groupId, cryptoToken, restricted, owner, getBiometricUtils());
+                    userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(),
+                    disabledFeatures);
         }
 
         @Override
@@ -408,7 +410,8 @@
         int cancel() throws RemoteException;
         int remove(int groupId, int biometricId) throws RemoteException;
         int enumerate() throws RemoteException;
-        int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException;
+        int enroll(byte[] cryptoToken, int groupId, int timeout,
+                ArrayList<Integer> disabledFeatures) throws RemoteException;
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java
index f858ef5..8a0f085 100644
--- a/services/core/java/com/android/server/biometrics/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/EnrollClient.java
@@ -34,15 +34,18 @@
     private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute
     private final byte[] mCryptoToken;
     private final BiometricUtils mBiometricUtils;
+    private final int[] mDisabledFeatures;
 
     public EnrollClient(Context context, Metrics metrics,
             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
             BiometricServiceBase.ServiceListener listener, int userId, int groupId,
-            byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils) {
+            byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils,
+            final int[] disabledFeatures) {
         super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
                 owner, 0 /* cookie */);
         mBiometricUtils = utils;
         mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
+        mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
     }
 
     @Override
@@ -74,7 +77,13 @@
     public int start() {
         final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
         try {
-            final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout);
+            final ArrayList<Integer> disabledFeatures = new ArrayList<>();
+            for (int i = 0; i < mDisabledFeatures.length; i++) {
+                disabledFeatures.add(mDisabledFeatures[i]);
+            }
+
+            final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout,
+                    disabledFeatures);
             if (result != 0) {
                 Slog.w(getLogTag(), "startEnroll failed, result=" + result);
                 mMetricsLogger.histogram(mMetrics.tagEnrollStartError(), result);
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 557af04..72f73f6 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -113,17 +113,17 @@
         }
 
         @Override // Binder call
-        public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
-                final IFaceServiceReceiver receiver, final int flags,
-                final String opPackageName) {
+        public void enroll(final IBinder token, final byte[] cryptoToken,
+                final IFaceServiceReceiver receiver, final String opPackageName,
+                final int[] disabledFeatures) {
             checkPermission(MANAGE_BIOMETRIC);
 
             final boolean restricted = isRestricted();
             final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId,
-                    0 /* groupId */, cryptoToken, restricted, opPackageName);
+                    0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures);
 
-            enrollInternal(client, userId);
+            enrollInternal(client, UserHandle.getCallingUserId());
         }
 
         @Override // Binder call
@@ -333,7 +333,7 @@
         }
 
         @Override
-        public int setRequireAttention(boolean requireAttention, final byte[] token) {
+        public int setFeature(int feature, boolean enabled, final byte[] token) {
             checkPermission(MANAGE_BIOMETRIC);
 
             final ArrayList<Byte> byteToken = new ArrayList<>();
@@ -343,10 +343,11 @@
 
             int result;
             try {
-                result = mDaemon != null ? mDaemon.setRequireAttention(requireAttention, byteToken)
+                result = mDaemon != null ? mDaemon.setFeature(feature, enabled, byteToken)
                         : Status.INTERNAL_ERROR;
             } catch (RemoteException e) {
-                Slog.e(getTag(), "Unable to setRequireAttention to " + requireAttention, e);
+                Slog.e(getTag(), "Unable to set feature: " + feature + " to enabled:" + enabled,
+                        e);
                 result = Status.INTERNAL_ERROR;
             }
 
@@ -354,17 +355,12 @@
         }
 
         @Override
-        public boolean getRequireAttention(final byte[] token) {
+        public boolean getFeature(int feature) {
             checkPermission(MANAGE_BIOMETRIC);
 
-            final ArrayList<Byte> byteToken = new ArrayList<>();
-            for (int i = 0; i < token.length; i++) {
-                byteToken.add(token[i]);
-            }
-
             boolean result = true;
             try {
-                result = mDaemon != null ? mDaemon.getRequireAttention(byteToken).value : true;
+                result = mDaemon != null ? mDaemon.getFeature(feature) : true;
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Unable to getRequireAttention", e);
             }
@@ -612,7 +608,8 @@
         }
 
         @Override
-        public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException {
+        public int enroll(byte[] cryptoToken, int groupId, int timeout,
+                ArrayList<Integer> disabledFeatures) throws RemoteException {
             IBiometricsFace daemon = getFaceDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "enroll(): no face HAL!");
@@ -623,7 +620,7 @@
                 token.add(cryptoToken[i]);
             }
             // TODO: plumb requireAttention down from framework
-            return daemon.enroll(token, timeout, true /* requireAttention */);
+            return daemon.enroll(token, timeout, disabledFeatures);
         }
     };
 
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 6a5bc61..3895ef7 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -144,7 +144,7 @@
             final int groupId = userId; // default group for fingerprint enrollment
             final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId,
-                    cryptoToken, restricted, opPackageName);
+                    cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */);
 
             enrollInternal(client, userId);
         }
@@ -716,7 +716,8 @@
         }
 
         @Override
-        public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException {
+        public int enroll(byte[] cryptoToken, int groupId, int timeout,
+                ArrayList<Integer> disabledFeatures) throws RemoteException {
             IBiometricsFingerprint daemon = getFingerprintDaemon();
             if (daemon == null) {
                 Slog.w(TAG, "enroll(): no fingerprint HAL!");
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/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 360a7d1..cf8d21b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -46,6 +46,8 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
 import android.hardware.display.DisplayViewport;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
 import android.hardware.display.IDisplayManager;
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
@@ -1241,6 +1243,29 @@
         }
     }
 
+    @VisibleForTesting
+    DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributesInternal(
+            int displayId) {
+        IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId);
+        return SurfaceControl.getDisplayedContentSamplingAttributes(displayToken);
+    }
+
+    @VisibleForTesting
+    boolean setDisplayedContentSamplingEnabledInternal(
+            int displayId, boolean enable, int componentMask, int maxFrames) {
+        IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId);
+        return SurfaceControl.setDisplayedContentSamplingEnabled(
+                displayToken, enable, componentMask, maxFrames);
+    }
+
+    @VisibleForTesting
+    DisplayedContentSample getDisplayedContentSampleInternal(int displayId,
+            long maxFrames, long timestamp) {
+        IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId);
+        return SurfaceControl.getDisplayedContentSample(
+            displayToken, maxFrames, timestamp);
+    }
+
     private void clearViewportsLocked() {
         mViewports.clear();
     }
@@ -2331,5 +2356,25 @@
                 }
             }
         }
+
+        @Override
+        public DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes(
+                int displayId) {
+            return getDisplayedContentSamplingAttributesInternal(displayId);
+        }
+
+        @Override
+        public boolean setDisplayedContentSamplingEnabled(
+                int displayId, boolean enable, int componentMask, int maxFrames) {
+            return setDisplayedContentSamplingEnabledInternal(
+                    displayId, enable, componentMask, maxFrames);
+        }
+
+        @Override
+        public DisplayedContentSample getDisplayedContentSample(int displayId,
+                long maxFrames, long timestamp) {
+            return getDisplayedContentSampleInternal(displayId, maxFrames, timestamp);
+        }
+
     }
 }
diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
index 98e3299..0d64dbd 100644
--- a/services/core/java/com/android/server/display/OWNERS
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -1,4 +1,5 @@
 michaelwr@google.com
+dangittik@google.com
 hackbod@google.com
 ogunwale@google.com
 
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 611c8b7..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);
@@ -839,6 +877,15 @@
                                 break;
                             }
                         }
+                        if (DEBUG) {
+                            Slog.d(TAG, "Something in " + pkgName
+                                    + " changed. Reevaluating controller states.");
+                        }
+                        synchronized (mLock) {
+                            for (int c = mControllers.size() - 1; c >= 0; --c) {
+                                mControllers.get(c).reevaluateStateLocked(pkgUid);
+                            }
+                        }
                     }
                 } else {
                     Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
@@ -852,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);
@@ -859,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?
@@ -933,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();
     }
@@ -948,7 +1011,9 @@
 
     @Override
     public void onStopUser(int userHandle) {
-        mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
+        synchronized (mLock) {
+            mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
+        }
     }
 
     /**
@@ -1042,6 +1107,8 @@
                 mJobPackageTracker.notePending(jobStatus);
                 addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
                 maybeRunPendingJobsLocked();
+            } else {
+                evaluateControllerStatesLocked(jobStatus);
             }
         }
         return JobScheduler.RESULT_SUCCESS;
@@ -1884,6 +1951,8 @@
                     newReadyJobs = new ArrayList<JobStatus>();
                 }
                 newReadyJobs.add(job);
+            } else {
+                evaluateControllerStatesLocked(job);
             }
         }
 
@@ -1957,6 +2026,8 @@
                     runnableJobs = new ArrayList<>();
                 }
                 runnableJobs.add(job);
+            } else {
+                evaluateControllerStatesLocked(job);
             }
         }
 
@@ -2087,6 +2158,15 @@
                 HEARTBEAT_TAG, mHeartbeatAlarm, mHandler);
     }
 
+    /** Returns true if both the calling and source users for the job are started. */
+    private boolean areUsersStartedLocked(final JobStatus job) {
+        boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
+        if (job.getUserId() == job.getSourceUserId()) {
+            return sourceStarted;
+        }
+        return sourceStarted && ArrayUtils.contains(mStartedUsers, job.getUserId());
+    }
+
     /**
      * Criteria for moving a job into the pending queue:
      *      - It's ready.
@@ -2116,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()
@@ -2205,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();
         }
@@ -2219,6 +2298,61 @@
         return componentPresent;
     }
 
+    private void evaluateControllerStatesLocked(final JobStatus job) {
+        for (int c = mControllers.size() - 1; c >= 0; --c) {
+            final StateController sc = mControllers.get(c);
+            sc.evaluateStateLocked(job);
+        }
+    }
+
+    /**
+     * Returns true if non-job constraint components are in place -- if job.isReady() returns true
+     * and this method returns true, then the job is ready to be executed.
+     */
+    public boolean areComponentsInPlaceLocked(JobStatus job) {
+        // This code is very similar to the code in isReadyToBeExecutedLocked --- it uses the same
+        // conditions.
+
+        final boolean jobExists = mJobs.containsJob(job);
+        final boolean userStarted = areUsersStartedLocked(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
+                    + " exists=" + jobExists + " userStarted=" + userStarted);
+        }
+
+        // These are also fairly cheap to check, though they typically will not
+        // be conditions we fail.
+        if (!jobExists || !userStarted) {
+            return false;
+        }
+
+        // Job pending/active doesn't affect the readiness of a job.
+
+        // Skipping the hearbeat check as this will only come into play when using the rolling
+        // window quota management system.
+
+        // The expensive check last: validate that the defined package+service is
+        // still present & viable.
+        final boolean componentPresent;
+        try {
+            // TODO: cache result until we're notified that something in the package changed.
+            componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
+                    job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+                    job.getUserId()) != null);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+
+        if (DEBUG) {
+            Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
+                    + " componentPresent=" + componentPresent);
+        }
+
+        // Everything else checked out so far, so this is the final yes/no check
+        return componentPresent;
+    }
+
     /**
      * Reconcile jobs in the pending queue against available execution contexts.
      * A controller can force a job into the pending queue even if it's already running, but
@@ -2963,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(" ");
@@ -3114,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=");
@@ -3284,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 6989c33..aca02bf 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -41,10 +41,12 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobSchedulerService.Constants;
 import com.android.server.job.JobServiceContext;
 import com.android.server.job.StateControllerProto;
+import com.android.server.net.NetworkPolicyManagerInternal;
 
 import java.util.Objects;
 import java.util.function.Predicate;
@@ -66,16 +68,29 @@
 
     private final ConnectivityManager mConnManager;
     private final NetworkPolicyManager mNetPolicyManager;
+    private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
 
     /** List of tracked jobs keyed by source UID. */
     @GuardedBy("mLock")
     private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();
 
+    /**
+     * Keep track of all the UID's jobs that the controller has requested that NetworkPolicyManager
+     * grant an exception to in the app standby chain.
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<ArraySet<JobStatus>> mRequestedWhitelistJobs = new SparseArray<>();
+
+    /** List of currently available networks. */
+    @GuardedBy("mLock")
+    private final ArraySet<Network> mAvailableNetworks = new ArraySet<>();
+
     public ConnectivityController(JobSchedulerService service) {
         super(service);
 
         mConnManager = mContext.getSystemService(ConnectivityManager.class);
         mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
+        mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
 
         // We're interested in all network changes; internally we match these
         // network changes against the active network for each UID with jobs.
@@ -109,9 +124,184 @@
             if (jobs != null) {
                 jobs.remove(jobStatus);
             }
+            maybeRevokeStandbyExceptionLocked(jobStatus);
         }
     }
 
+    @GuardedBy("mLock")
+    @Override
+    public void onConstantsUpdatedLocked() {
+        if (mConstants.USE_HEARTBEATS) {
+            // App idle exceptions are only requested for the rolling quota system.
+            if (DEBUG) Slog.i(TAG, "Revoking all standby exceptions");
+            for (int i = 0; i < mRequestedWhitelistJobs.size(); ++i) {
+                int uid = mRequestedWhitelistJobs.keyAt(i);
+                mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
+            }
+            mRequestedWhitelistJobs.clear();
+        }
+    }
+
+    /**
+     * Returns true if the job's requested network is available. This DOES NOT necesarilly mean
+     * that the UID has been granted access to the network.
+     */
+    public boolean isNetworkAvailable(JobStatus job) {
+        synchronized (mLock) {
+            for (int i = 0; i < mAvailableNetworks.size(); ++i) {
+                final Network network = mAvailableNetworks.valueAt(i);
+                final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(
+                        network);
+                final boolean satisfied = isSatisfied(job, network, capabilities, mConstants);
+                if (DEBUG) {
+                    Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network
+                            + " and capabilities " + capabilities + ". Satisfied=" + satisfied);
+                }
+                if (satisfied) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Request that NetworkPolicyManager grant an exception to the uid from its standby policy
+     * chain.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void requestStandbyExceptionLocked(JobStatus job) {
+        final int uid = job.getSourceUid();
+        // Need to call this before adding the job.
+        final boolean isExceptionRequested = isStandbyExceptionRequestedLocked(uid);
+        ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
+        if (jobs == null) {
+            jobs = new ArraySet<JobStatus>();
+            mRequestedWhitelistJobs.put(uid, jobs);
+        }
+        if (!jobs.add(job) || isExceptionRequested) {
+            if (DEBUG) {
+                Slog.i(TAG, "requestStandbyExceptionLocked found exception already requested.");
+            }
+            return;
+        }
+        if (DEBUG) Slog.i(TAG, "Requesting standby exception for UID: " + uid);
+        mNetPolicyManagerInternal.setAppIdleWhitelist(uid, true);
+    }
+
+    /** Returns whether a standby exception has been requested for the UID. */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    boolean isStandbyExceptionRequestedLocked(final int uid) {
+        ArraySet jobs = mRequestedWhitelistJobs.get(uid);
+        return jobs != null && jobs.size() > 0;
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    boolean wouldBeReadyWithConnectivityLocked(JobStatus jobStatus) {
+        final boolean networkAvailable = isNetworkAvailable(jobStatus);
+        if (DEBUG) {
+            Slog.v(TAG, "wouldBeReadyWithConnectivityLocked: " + jobStatus.toShortString()
+                    + " networkAvailable=" + networkAvailable);
+        }
+        // If the network isn't available, then requesting an exception won't help.
+
+        return networkAvailable && wouldBeReadyWithConstraintLocked(jobStatus,
+                JobStatus.CONSTRAINT_CONNECTIVITY);
+    }
+
+    /**
+     * Tell NetworkPolicyManager not to block a UID's network connection if that's the only
+     * thing stopping a job from running.
+     */
+    @GuardedBy("mLock")
+    @Override
+    public void evaluateStateLocked(JobStatus jobStatus) {
+        if (mConstants.USE_HEARTBEATS) {
+            // This should only be used for the rolling quota system.
+            return;
+        }
+
+        if (!jobStatus.hasConnectivityConstraint()) {
+            return;
+        }
+
+        // Always check the full job readiness stat in case the component has been disabled.
+        if (wouldBeReadyWithConnectivityLocked(jobStatus)) {
+            if (DEBUG) {
+                Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready.");
+            }
+            requestStandbyExceptionLocked(jobStatus);
+        } else {
+            if (DEBUG) {
+                Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would not be ready.");
+            }
+            maybeRevokeStandbyExceptionLocked(jobStatus);
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    public void reevaluateStateLocked(final int uid) {
+        if (mConstants.USE_HEARTBEATS) {
+            return;
+        }
+        // Check if we still need a connectivity exception in case the JobService was disabled.
+        ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
+        if (jobs == null) {
+            return;
+        }
+        for (int i = jobs.size() - 1; i >= 0; i--) {
+            evaluateStateLocked(jobs.valueAt(i));
+        }
+    }
+
+    /** Cancel the requested standby exception if none of the jobs would be ready to run anyway. */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void maybeRevokeStandbyExceptionLocked(final JobStatus job) {
+        final int uid = job.getSourceUid();
+        if (!isStandbyExceptionRequestedLocked(uid)) {
+            return;
+        }
+        ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
+        if (jobs == null) {
+            Slog.wtf(TAG,
+                    "maybeRevokeStandbyExceptionLocked found null jobs array even though a "
+                            + "standby exception has been requested.");
+            return;
+        }
+        if (!jobs.remove(job) || jobs.size() > 0) {
+            if (DEBUG) {
+                Slog.i(TAG,
+                        "maybeRevokeStandbyExceptionLocked not revoking because there are still "
+                                + jobs.size() + " jobs left.");
+            }
+            return;
+        }
+        // No more jobs that need an exception.
+        revokeStandbyExceptionLocked(uid);
+    }
+
+    /**
+     * Tell NetworkPolicyManager to revoke any exception it granted from its standby policy chain
+     * for the uid.
+     */
+    @GuardedBy("mLock")
+    private void revokeStandbyExceptionLocked(final int uid) {
+        if (DEBUG) Slog.i(TAG, "Revoking standby exception for UID: " + uid);
+        mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
+        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>
@@ -326,6 +516,14 @@
 
     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
         @Override
+        public void onAvailable(Network network) {
+            if (DEBUG) Slog.v(TAG, "onAvailable: " + network);
+            synchronized (mLock) {
+                mAvailableNetworks.add(network);
+            }
+        }
+
+        @Override
         public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
             if (DEBUG) {
                 Slog.v(TAG, "onCapabilitiesChanged: " + network);
@@ -338,6 +536,9 @@
             if (DEBUG) {
                 Slog.v(TAG, "onLost: " + network);
             }
+            synchronized (mLock) {
+                mAvailableNetworks.remove(network);
+            }
             updateTrackedJobs(-1, network);
         }
     };
@@ -356,6 +557,27 @@
     @Override
     public void dumpControllerStateLocked(IndentingPrintWriter pw,
             Predicate<JobStatus> predicate) {
+        if (mRequestedWhitelistJobs.size() > 0) {
+            pw.print("Requested standby exceptions:");
+            for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
+                pw.print(" ");
+                pw.print(mRequestedWhitelistJobs.keyAt(i));
+                pw.print(" (");
+                pw.print(mRequestedWhitelistJobs.valueAt(i).size());
+                pw.print(" jobs)");
+            }
+            pw.println();
+        }
+        if (mAvailableNetworks.size() > 0) {
+            pw.println("Available networks:");
+            pw.increaseIndent();
+            for (int i = 0; i < mAvailableNetworks.size(); i++) {
+                pw.println(mAvailableNetworks.valueAt(i));
+            }
+            pw.decreaseIndent();
+        } else {
+            pw.println("No available networks");
+        }
         for (int i = 0; i < mTrackedJobs.size(); i++) {
             final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
             for (int j = 0; j < jobs.size(); j++) {
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 4341589..82bfa51 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -1007,6 +1007,18 @@
      * @return Whether or not this job is ready to run, based on its requirements.
      */
     public boolean isReady() {
+        return isReady(mSatisfiedConstraintsOfInterest);
+    }
+
+    /**
+     * @return Whether or not this job would be ready to run if it had the specified constraint
+     * granted, based on its requirements.
+     */
+    public boolean wouldBeReadyWithConstraint(int constraint) {
+        return isReady(mSatisfiedConstraintsOfInterest | constraint);
+    }
+
+    private boolean isReady(int satisfiedConstraints) {
         // Quota constraints trumps all other constraints.
         if (!mReadyWithinQuota) {
             return false;
@@ -1017,7 +1029,7 @@
         // DeviceNotDozing implicit constraint must be satisfied
         // NotRestrictedInBackground implicit constraint must be satisfied
         return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
-                || isConstraintsSatisfied());
+                || isConstraintsSatisfied(satisfiedConstraints));
     }
 
     static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
@@ -1033,12 +1045,16 @@
      * @return Whether the constraints set on this job are satisfied.
      */
     public boolean isConstraintsSatisfied() {
+        return isConstraintsSatisfied(mSatisfiedConstraintsOfInterest);
+    }
+
+    private boolean isConstraintsSatisfied(int satisfiedConstraints) {
         if (overrideState == OVERRIDE_FULL) {
             // force override: the job is always runnable
             return true;
         }
 
-        int sat = mSatisfiedConstraintsOfInterest;
+        int sat = satisfiedConstraints;
         if (overrideState == OVERRIDE_SOFT) {
             // override: pretend all 'soft' requirements are satisfied
             sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS);
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 f73ffac..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);
@@ -151,8 +163,7 @@
         return "<" + userId + ">" + packageName;
     }
 
-    @VisibleForTesting
-    static final class Package {
+    private static final class Package {
         public final String packageName;
         public final int userId;
 
@@ -191,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<>();
 
@@ -206,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;
@@ -223,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() {
@@ -251,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;
@@ -329,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,
@@ -361,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.
@@ -372,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.
@@ -387,14 +537,14 @@
 
     private boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
         final int standbyBucket = getEffectiveStandbyBucket(jobStatus);
-        return isWithinQuotaLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
-                standbyBucket);
+        // Jobs for the active app should always be able to run.
+        return jobStatus.uidActive || isWithinQuotaLocked(
+                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
     }
 
     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();
         }
@@ -579,7 +837,10 @@
         boolean changed = false;
         for (int i = jobs.size() - 1; i >= 0; --i) {
             final JobStatus js = jobs.valueAt(i);
-            if (realStandbyBucket == getEffectiveStandbyBucket(js)) {
+            if (js.uidActive) {
+                // Jobs for the active app should always be able to run.
+                changed |= js.setQuotaConstraintSatisfied(true);
+            } else if (realStandbyBucket == getEffectiveStandbyBucket(js)) {
                 changed |= js.setQuotaConstraintSatisfied(realInQuota);
             } else {
                 // This job is somehow exempted. Need to determine its own quota status.
@@ -609,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
@@ -699,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);
         }
     }
 
@@ -765,18 +984,26 @@
         public final long startTimeElapsed;
         // End timestamp in elapsed realtime timebase.
         public final long endTimeElapsed;
-        // How many jobs ran during this session.
-        public final int jobCount;
+        // 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.jobCount = jobCount;
+            this.bgJobCount = bgJobCount;
+
+            int hashCode = 0;
+            hashCode = 31 * hashCode + hashLong(startTimeElapsed);
+            hashCode = 31 * hashCode + hashLong(endTimeElapsed);
+            hashCode = 31 * hashCode + bgJobCount;
+            mHashCode = hashCode;
         }
 
         @Override
         public String toString() {
-            return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + jobCount
+            return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount
                     + "}";
         }
 
@@ -786,7 +1013,7 @@
                 TimingSession other = (TimingSession) obj;
                 return startTimeElapsed == other.startTimeElapsed
                         && endTimeElapsed == other.endTimeElapsed
-                        && jobCount == other.jobCount;
+                        && bgJobCount == other.bgJobCount;
             } else {
                 return false;
             }
@@ -794,7 +1021,7 @@
 
         @Override
         public int hashCode() {
-            return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, jobCount});
+            return mHashCode;
         }
 
         public void dump(IndentingPrintWriter pw) {
@@ -804,8 +1031,8 @@
             pw.print(" (");
             pw.print(endTimeElapsed - startTimeElapsed);
             pw.print("), ");
-            pw.print(jobCount);
-            pw.print(" jobs.");
+            pw.print(bgJobCount);
+            pw.print(" bg jobs.");
             pw.println();
         }
 
@@ -816,7 +1043,8 @@
                     startTimeElapsed);
             proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED,
                     endTimeElapsed);
-            proto.write(StateControllerProto.QuotaController.TimingSession.JOB_COUNT, jobCount);
+            proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT,
+                    bgJobCount);
 
             proto.end(token);
         }
@@ -825,25 +1053,37 @@
     private final class Timer {
         private final Package mPkg;
 
-        // List of jobs currently running for this package.
-        private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
+        // List of jobs currently running for this app that started when the app wasn't in the
+        // foreground.
+        private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
         private long mStartTimeElapsed;
-        private int mJobCount;
+        private int mBgJobCount;
 
         Timer(int userId, String packageName) {
             mPkg = new Package(userId, packageName);
         }
 
         void startTrackingJob(@NonNull JobStatus jobStatus) {
+            if (jobStatus.uidActive) {
+                // We intentionally don't pay attention to fg state changes after a job has started.
+                if (DEBUG) {
+                    Slog.v(TAG,
+                            "Timer ignoring " + jobStatus.toShortString() + " because uidActive");
+                }
+                return;
+            }
             if (DEBUG) Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
             synchronized (mLock) {
                 // Always track jobs, even when charging.
-                mRunningJobs.add(jobStatus);
+                mRunningBgJobs.add(jobStatus);
                 if (!mChargeTracker.isCharging()) {
-                    mJobCount++;
-                    if (mRunningJobs.size() == 1) {
+                    mBgJobCount++;
+                    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();
                     }
                 }
@@ -853,7 +1093,7 @@
         void stopTrackingJob(@NonNull JobStatus jobStatus) {
             if (DEBUG) Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString());
             synchronized (mLock) {
-                if (mRunningJobs.size() == 0) {
+                if (mRunningBgJobs.size() == 0) {
                     // maybeStopTrackingJobLocked can be called when an app cancels a job, so a
                     // timer may not be running when it's asked to stop tracking a job.
                     if (DEBUG) {
@@ -861,22 +1101,31 @@
                     }
                     return;
                 }
-                mRunningJobs.remove(jobStatus);
-                if (!mChargeTracker.isCharging() && mRunningJobs.size() == 0) {
+                if (mRunningBgJobs.remove(jobStatus)
+                        && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) {
                     emitSessionLocked(sElapsedRealtimeClock.millis());
                     cancelCutoff();
                 }
             }
         }
 
+        /**
+         * 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 (mJobCount <= 0) {
+            if (mBgJobCount <= 0) {
                 // Nothing to emit.
                 return;
             }
-            TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mJobCount);
+            TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount);
             saveTimingSession(mPkg.userId, mPkg.packageName, ts);
-            mJobCount = 0;
+            mBgJobCount = 0;
             // Don't reset the tracked jobs list as we need to keep tracking the current number
             // of jobs.
             // However, cancel the currently scheduled cutoff since it's not currently useful.
@@ -889,7 +1138,7 @@
          */
         public boolean isActive() {
             synchronized (mLock) {
-                return mJobCount > 0;
+                return mBgJobCount > 0;
             }
         }
 
@@ -899,18 +1148,27 @@
             }
         }
 
+        int getBgJobCount() {
+            synchronized (mLock) {
+                return mBgJobCount;
+            }
+        }
+
         void onChargingChanged(long nowElapsed, boolean isCharging) {
             synchronized (mLock) {
                 if (isCharging) {
                     emitSessionLocked(nowElapsed);
                 } else {
                     // Start timing from unplug.
-                    if (mRunningJobs.size() > 0) {
+                    if (mRunningBgJobs.size() > 0) {
                         mStartTimeElapsed = nowElapsed;
                         // NOTE: this does have the unfortunate consequence that if the device is
                         // repeatedly plugged in and unplugged, the job count for a package may be
                         // artificially high.
-                        mJobCount = mRunningJobs.size();
+                        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();
                     }
@@ -958,12 +1216,12 @@
                 pw.print("NOT active");
             }
             pw.print(", ");
-            pw.print(mJobCount);
-            pw.print(" running jobs");
+            pw.print(mBgJobCount);
+            pw.print(" running bg jobs");
             pw.println();
             pw.increaseIndent();
-            for (int i = 0; i < mRunningJobs.size(); i++) {
-                JobStatus js = mRunningJobs.valueAt(i);
+            for (int i = 0; i < mRunningBgJobs.size(); i++) {
+                JobStatus js = mRunningBgJobs.valueAt(i);
                 if (predicate.test(js)) {
                     pw.println(js.toShortString());
                 }
@@ -979,9 +1237,9 @@
             proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive());
             proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED,
                     mStartTimeElapsed);
-            proto.write(StateControllerProto.QuotaController.Timer.JOB_COUNT, mJobCount);
-            for (int i = 0; i < mRunningJobs.size(); i++) {
-                JobStatus js = mRunningJobs.valueAt(i);
+            proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount);
+            for (int i = 0; i < mRunningBgJobs.size(); i++) {
+                JobStatus js = mRunningBgJobs.valueAt(i);
                 if (predicate.test(js)) {
                     js.writeToShortProto(proto,
                             StateControllerProto.QuotaController.Timer.RUNNING_JOBS);
@@ -1172,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 b439c0d..97c3bac 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -16,7 +16,10 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.DEBUG;
+
 import android.content.Context;
+import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.IndentingPrintWriter;
@@ -32,6 +35,8 @@
  * are ready to run, or whether they must be stopped.
  */
 public abstract class StateController {
+    private static final String TAG = "JobScheduler.SC";
+
     protected final JobSchedulerService mService;
     protected final StateChangedListener mStateChangedListener;
     protected final Context mContext;
@@ -78,6 +83,45 @@
     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);
+        if (DEBUG) {
+            Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString()
+                    + " readyWithConstraint=" + jobWouldBeReady);
+        }
+        if (!jobWouldBeReady) {
+            // If the job wouldn't be ready, nothing to do here.
+            return false;
+        }
+
+        // This is potentially more expensive since JSS may have to query component
+        // presence.
+        return mService.areComponentsInPlaceLocked(jobStatus);
+    }
+
     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/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS
index 7e7335d..c7c6d56 100644
--- a/services/core/java/com/android/server/lights/OWNERS
+++ b/services/core/java/com/android/server/lights/OWNERS
@@ -1 +1,2 @@
 michaelwr@google.com
+dangittik@google.com
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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fc364c0..3d87634 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();
@@ -3357,14 +3350,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 +3374,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 +6689,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 +6721,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 +6734,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/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
new file mode 100644
index 0000000..2ae424d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -0,0 +1,115 @@
+/*
+ * 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 com.android.server.pm;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.server.pm.dex.DexLogger;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and
+ * charging. The actual logging is performed by {@link DexLogger}.
+ * {@hide}
+ */
+public class DynamicCodeLoggingService extends JobService {
+    private static final String TAG = DynamicCodeLoggingService.class.getName();
+
+    private static final int JOB_ID = 2030028;
+    private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
+
+    private volatile boolean mStopRequested = false;
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * Schedule our job with the {@link JobScheduler}.
+     */
+    public static void schedule(Context context) {
+        ComponentName serviceName = new ComponentName(
+                "android", DynamicCodeLoggingService.class.getName());
+
+        JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        js.schedule(new JobInfo.Builder(JOB_ID, serviceName)
+                .setRequiresDeviceIdle(true)
+                .setRequiresCharging(true)
+                .setPeriodic(PERIOD_MILLIS)
+                .build());
+        if (DEBUG) {
+            Log.d(TAG, "Job scheduled");
+        }
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        if (DEBUG) {
+            Log.d(TAG, "onStartJob");
+        }
+        mStopRequested = false;
+        new IdleLoggingThread(params).start();
+        return true;  // Job is running on another thread
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        if (DEBUG) {
+            Log.d(TAG, "onStopJob");
+        }
+        mStopRequested = true;
+        return true;  // Requests job be re-scheduled.
+    }
+
+    private class IdleLoggingThread extends Thread {
+        private final JobParameters mParams;
+
+        IdleLoggingThread(JobParameters params) {
+            super("DynamicCodeLoggingService_IdleLoggingJob");
+            mParams = params;
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG) {
+                Log.d(TAG, "Starting logging run");
+            }
+
+            PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package");
+            DexLogger dexLogger = pm.getDexManager().getDexLogger();
+            for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) {
+                if (mStopRequested) {
+                    Log.w(TAG, "Stopping logging run at scheduler request");
+                    return;
+                }
+
+                dexLogger.logDynamicCodeLoading(packageName);
+            }
+
+            jobFinished(mParams, /* reschedule */ false);
+            if (DEBUG) {
+                Log.d(TAG, "Finished logging run");
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2e9a71a..a95e730 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -27,7 +27,6 @@
 import android.app.NotificationManager;
 import android.app.PackageDeleteObserver;
 import android.app.PackageInstallObserver;
-import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
 import android.content.Intent;
@@ -164,6 +163,11 @@
     @GuardedBy("mSessions")
     private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
 
+    // STOPSHIP: This is a temporary mock implementation of staged sessions. This variable
+    //           shouldn't be needed at all.
+    @GuardedBy("mStagedSessions")
+    private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
+
     /** Historical sessions kept around for debugging purposes */
     @GuardedBy("mSessions")
     private final List<String> mHistoricalSessions = new ArrayList<>();
@@ -481,10 +485,12 @@
                 if (!PackageHelper.fitsOnInternal(mContext, params)) {
                     throw new IOException("No suitable internal storage available");
                 }
-            } else {
+            } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
                 // For now, installs to adopted media are treated as internal from
                 // an install flag point-of-view.
                 params.installFlags |= PackageManager.INSTALL_INTERNAL;
+            } else {
+                params.installFlags |= PackageManager.INSTALL_INTERNAL;
 
                 // Resolve best location for install, based on combination of
                 // requested install flags, delta size, and manifest settings.
@@ -536,6 +542,11 @@
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
         }
+        if (params.isStaged) {
+            synchronized (mStagedSessions) {
+                mStagedSessions.put(sessionId, session);
+            }
+        }
 
         mCallbacks.notifySessionCreated(session.sessionId, session.userId);
         writeSessionsAsync();
@@ -666,6 +677,18 @@
     }
 
     @Override
+    public ParceledListSlice<SessionInfo> getStagedSessions() {
+        final List<SessionInfo> result = new ArrayList<>();
+        synchronized (mStagedSessions) {
+            for (int i = 0; i < mStagedSessions.size(); i++) {
+                final PackageInstallerSession session = mStagedSessions.valueAt(i);
+                result.add(session.generateInfo(false));
+            }
+        }
+        return new ParceledListSlice<>(result);
+    }
+
+    @Override
     public ParceledListSlice<SessionInfo> getAllSessions(int userId) {
         mPermissionManager.enforceCrossUserPermission(
                 Binder.getCallingUid(), userId, true, false, "getAllSessions");
@@ -714,22 +737,19 @@
 
         // Check whether the caller is device owner or affiliated profile owner, in which case we do
         // it silently.
-        final int callingUserId = UserHandle.getUserId(callingUid);
         DevicePolicyManagerInternal dpmi =
                 LocalServices.getService(DevicePolicyManagerInternal.class);
-        final boolean isDeviceOwnerOrAffiliatedProfileOwner =
-                dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
-                        DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)
-                        && dpmi.isUserAffiliatedWithDevice(callingUserId);
+        final boolean canSilentlyInstallPackage =
+                dpmi != null && dpmi.canSilentlyInstallPackage(callerPackageName, callingUid);
 
         final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
                 statusReceiver, versionedPackage.getPackageName(),
-                isDeviceOwnerOrAffiliatedProfileOwner, userId);
+                canSilentlyInstallPackage, userId);
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
                     == PackageManager.PERMISSION_GRANTED) {
             // Sweet, call straight through!
             mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
-        } else if (isDeviceOwnerOrAffiliatedProfileOwner) {
+        } else if (canSilentlyInstallPackage) {
             // Allow the device owner and affiliated profile owner to silently delete packages
             // Need to clear the calling identity to get DELETE_PACKAGES permission
             long ident = Binder.clearCallingIdentity();
@@ -1110,6 +1130,18 @@
             mInstallHandler.post(new Runnable() {
                 @Override
                 public void run() {
+                    // TODO: remove this mock implementation.
+                    if (session.isStaged()) {
+                        // If the session is aborted, don't keep it in memory. Only store
+                        // sessions successfully staged.
+                        if (!success) {
+                            synchronized (mStagedSessions) {
+                                mStagedSessions.remove(session.sessionId);
+                            }
+                        } else {
+                            return;
+                        }
+                    }
                     synchronized (mSessions) {
                         mSessions.remove(session.sessionId);
                         addHistoricalSessionLocked(session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ea190a7..206a88b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -44,7 +44,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.apex.IApexService;
-import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -125,7 +124,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 public class PackageInstallerSession extends IPackageInstallerSession.Stub {
-    private static final String TAG = "PackageInstaller";
+    private static final String TAG = "PackageInstallerSession";
     private static final boolean LOGD = true;
     private static final String REMOVE_SPLIT_MARKER_EXTENSION = ".removed";
 
@@ -147,6 +146,7 @@
     private static final String ATTR_SEALED = "sealed";
     private static final String ATTR_MULTI_PACKAGE = "multiPackage";
     private static final String ATTR_PARENT_SESSION_ID = "parentSessionId";
+    private static final String ATTR_STAGED_SESSION = "stagedSession";
     private static final String ATTR_MODE = "mode";
     private static final String ATTR_INSTALL_FLAGS = "installFlags";
     private static final String ATTR_INSTALL_LOCATION = "installLocation";
@@ -193,7 +193,7 @@
 
     /** Package of the owner of the installer session */
     @GuardedBy("mLock")
-    private String mInstallerPackageName;
+    private @Nullable String mInstallerPackageName;
 
     /** Uid of the owner of the installer session */
     @GuardedBy("mLock")
@@ -339,11 +339,12 @@
      */
     @GuardedBy("mLock")
     private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked() {
+        if (userId != UserHandle.getUserId(mInstallerUid)) {
+            return false;
+        }
         DevicePolicyManagerInternal dpmi =
                 LocalServices.getService(DevicePolicyManagerInternal.class);
-        return dpmi != null && dpmi.isActiveAdminWithPolicy(mInstallerUid,
-                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) && dpmi.isUserAffiliatedWithDevice(
-                userId);
+        return dpmi != null && dpmi.canSilentlyInstallPackage(mInstallerPackageName, mInstallerUid);
     }
 
     /**
@@ -463,6 +464,7 @@
             info.grantedRuntimePermissions = params.grantedRuntimePermissions;
             info.installFlags = params.installFlags;
             info.isMultiPackage = params.isMultiPackage;
+            info.isStaged = params.isStaged;
             info.parentSessionId = mParentSessionId;
             info.childSessionIds = mChildSessionIds.copyKeys();
             if (info.childSessionIds == null) {
@@ -1047,6 +1049,11 @@
         if (committingSession == null) {
             return;
         }
+        if (isStaged()) {
+            // STOPSHIP: implement staged sessions
+            dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null);
+            return;
+        }
         if (isMultiPackage()) {
             final int[] childSessionIds = getChildSessionIds();
             List<PackageManagerService.ActiveInstallSession> childSessions =
@@ -1816,6 +1823,11 @@
     }
 
     @Override
+    public boolean isStaged() {
+        return params.isStaged;
+    }
+
+    @Override
     public int[] getChildSessionIds() {
         final int[] childSessionIds = mChildSessionIds.copyKeys();
         if (childSessionIds != null) {
@@ -1838,6 +1850,8 @@
                 return;
             }
             session.setParentSessionId(this.sessionId);
+            // TODO: sanity check, if parent session is staged then child session should be
+            //       marked as staged.
             addChildSessionIdInternal(sessionId);
         }
     }
@@ -1975,6 +1989,7 @@
         pw.printPair("mFinalStatus", mFinalStatus);
         pw.printPair("mFinalMessage", mFinalMessage);
         pw.printPair("params.isMultiPackage", params.isMultiPackage);
+        pw.printPair("params.isStaged", params.isStaged);
         pw.println();
 
         pw.decreaseIndent();
@@ -2026,6 +2041,7 @@
             writeBooleanAttribute(out, ATTR_SEALED, isSealed());
 
             writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage);
+            writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged);
             // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
             //                       we've read all sessions.
             writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId);
@@ -2140,6 +2156,7 @@
         final SessionParams params = new SessionParams(
                 SessionParams.MODE_INVALID);
         params.isMultiPackage = readBooleanAttribute(in, ATTR_MULTI_PACKAGE, false);
+        params.isStaged = readBooleanAttribute(in, ATTR_STAGED_SESSION, false);
         params.mode = readIntAttribute(in, ATTR_MODE);
         params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS);
         params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index edab94c..ae29180 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24,7 +24,6 @@
 import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
 import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_DEFAULT;
 import static android.content.Intent.CATEGORY_HOME;
@@ -304,7 +303,6 @@
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
 import com.android.server.pm.dex.ArtManagerService;
-import com.android.server.pm.dex.DexLogger;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.PackageDexUsage;
@@ -1066,9 +1064,6 @@
                         + verificationId + " packageName:" + packageName);
                 return;
             }
-            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
-                    "Updating IntentFilterVerificationInfo for package " + packageName
-                            +" verificationId:" + verificationId);
 
             synchronized (mPackages) {
                 if (verified) {
@@ -1086,19 +1081,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;
@@ -1114,6 +1137,8 @@
                                 packageName, updatedStatus, userId);
                         scheduleWritePackageRestrictionsLocked(userId);
                     }
+                } else {
+                    Slog.i(TAG, "autoVerify ignored when installing for all users");
                 }
             }
         }
@@ -2168,10 +2193,7 @@
 
         mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                 "*dexopt*");
-        DexManager.Listener dexManagerListener = DexLogger.getListener(this,
-                installer, mInstallLock);
-        mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock,
-                dexManagerListener);
+        mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock);
         mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock);
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
@@ -9215,7 +9237,7 @@
 
     /**
      * Reconcile the information we have about the secondary dex files belonging to
-     * {@code packagName} and the actual dex files. For all dex files that were
+     * {@code packageName} and the actual dex files. For all dex files that were
      * deleted, update the internal records and delete the generated oat files.
      */
     @Override
@@ -15269,9 +15291,12 @@
             final DeletePackageAction deletePackageAction;
             // we only want to try to delete for non system apps
             if (prepareResult.replace && !prepareResult.system) {
+                final boolean killApp = (scanResult.request.scanFlags & SCAN_DONT_KILL_APP) == 0;
+                final int deleteFlags = PackageManager.DELETE_KEEP_DATA
+                        | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
                 deletePackageAction = mayDeletePackageLocked(res.removedInfo,
                         prepareResult.originalPs, prepareResult.disabledPs,
-                        prepareResult.childPackageSettings);
+                        prepareResult.childPackageSettings, deleteFlags, null /* all users */);
                 if (deletePackageAction == null) {
                     throw new ReconcileFailure(
                             PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
@@ -15353,12 +15378,9 @@
                         }
                     }
                 } else {
-                    final boolean killApp = (scanRequest.scanFlags & SCAN_DONT_KILL_APP) == 0;
-                    final int deleteFlags = PackageManager.DELETE_KEEP_DATA
-                            | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
                     try {
                         executeDeletePackageLIF(reconciledPkg.deletePackageAction, packageName,
-                                null, true, request.mAllUsers, deleteFlags, true, pkg);
+                                true, request.mAllUsers, true, pkg);
                     } catch (SystemDeleteException e) {
                         if (Build.IS_ENG) {
                             throw new RuntimeException("Unexpected failure", e);
@@ -16713,6 +16735,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;
 
@@ -17361,28 +17384,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;
@@ -17391,8 +17408,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);
@@ -17452,10 +17469,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);
                     }
                 }
             }
@@ -17465,7 +17482,7 @@
                 mSettings.writeLPr();
             }
             if (installedStateChanged) {
-                mSettings.writeKernelMappingLPr(ps);
+                mSettings.writeKernelMappingLPr(deletedPs);
             }
         }
         if (removedAppId != -1) {
@@ -17535,12 +17552,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
@@ -17818,12 +17835,23 @@
         public final PackageSetting deletingPs;
         public final PackageSetting disabledPs;
         public final PackageRemovedInfo outInfo;
+        public final int flags;
+        public final UserHandle user;
+        /**
+         * True if this package is an unupdated system app that may be deleted by the system.
+         * When true, disabledPs will be null.
+         */
+        public final boolean mayDeleteUnupdatedSystemApp;
 
         private DeletePackageAction(PackageSetting deletingPs, PackageSetting disabledPs,
-                PackageRemovedInfo outInfo) {
+                PackageRemovedInfo outInfo, int flags, UserHandle user,
+                boolean mayDeleteUnupdatedSystemApp) {
             this.deletingPs = deletingPs;
             this.disabledPs = disabledPs;
             this.outInfo = outInfo;
+            this.flags = flags;
+            this.user = user;
+            this.mayDeleteUnupdatedSystemApp = mayDeleteUnupdatedSystemApp;
         }
     }
 
@@ -17835,23 +17863,26 @@
     @GuardedBy("mPackages")
     private static DeletePackageAction mayDeletePackageLocked(
             PackageRemovedInfo outInfo, PackageSetting ps, @Nullable PackageSetting disabledPs,
-            @Nullable PackageSetting[] children) {
+            @Nullable PackageSetting[] children, int flags, UserHandle user) {
         if (ps == null) {
             return null;
         }
+        boolean mayDeleteUnupdatedSystemApp = false;
         if (isSystemApp(ps)) {
             if (ps.parentPackageName != null) {
                 Slog.w(TAG, "Attempt to delete child system package " + ps.pkg.packageName);
                 return null;
             }
 
-            // 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
-            // reader
-            if (disabledPs == null) {
-                Slog.w(TAG,
-                        "Attempt to delete unknown system package " + ps.pkg.packageName);
+            if (((flags & PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
+                    && user.getIdentifier() != UserHandle.USER_ALL) {
+                mayDeleteUnupdatedSystemApp = true;
+            } else if (disabledPs == null) {
+                // Confirmed 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
+                // reader
+                Slog.w(TAG, "Attempt to delete unknown system package " + ps.pkg.packageName);
                 return null;
             }
         }
@@ -17868,7 +17899,8 @@
                 }
             }
         }
-        return new DeletePackageAction(ps, disabledPs, outInfo);
+        return new DeletePackageAction(ps, disabledPs, outInfo, flags, user,
+                mayDeleteUnupdatedSystemApp);
     }
 
     /*
@@ -17883,18 +17915,20 @@
             final PackageSetting ps = mSettings.mPackages.get(packageName);
             final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps);
             PackageSetting[] children = mSettings.getChildSettingsLPr(ps);
-            action = mayDeletePackageLocked(outInfo, ps, disabledPs, children);
+            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, user, deleteCodeAndResources,
-                    allUserHandles, flags, writeSettings, replacingPackage);
+            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;
@@ -17910,11 +17944,13 @@
 
     /** Deletes a package. Only throws when install of a disabled package fails. */
     private void executeDeletePackageLIF(DeletePackageAction action,
-            String packageName, UserHandle user, boolean deleteCodeAndResources,
-            int[] allUserHandles, int flags, boolean writeSettings,
+            String packageName, boolean deleteCodeAndResources,
+            int[] allUserHandles, boolean writeSettings,
             PackageParser.Package replacingPackage) throws SystemDeleteException {
         final PackageSetting ps = action.deletingPs;
         final PackageRemovedInfo outInfo = action.outInfo;
+        final UserHandle user = action.user;
+        final int flags = action.flags;
         final boolean systemApp = isSystemApp(ps);
         synchronized (mPackages) {
 
@@ -17939,43 +17975,48 @@
             unsuspendForSuspendingPackage(packageName, userId);
         }
 
-
-        if (((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
-                && user.getIdentifier() != UserHandle.USER_ALL)) {
+        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;
             }
         }
 
@@ -18004,8 +18045,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,
@@ -20169,11 +20209,6 @@
                 if (Process.isIsolated(uid)) {
                     return Zygote.MOUNT_EXTERNAL_NONE;
                 }
-                if (StorageManager.hasIsolatedStorage()) {
-                    return checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED
-                            ? Zygote.MOUNT_EXTERNAL_FULL
-                            : Zygote.MOUNT_EXTERNAL_WRITE;
-                }
                 if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
                     return Zygote.MOUNT_EXTERNAL_DEFAULT;
                 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 77f8c3a..37a35a2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -536,6 +536,7 @@
         boolean listInstaller = false;
         boolean showUid = false;
         boolean showVersionCode = false;
+        boolean listApexOnly = false;
         int uid = -1;
         int userId = UserHandle.USER_SYSTEM;
         try {
@@ -576,6 +577,10 @@
                     case "--show-versioncode":
                         showVersionCode = true;
                         break;
+                    case "--apex-only":
+                        getFlags |= PackageManager.MATCH_APEX;
+                        listApexOnly = true;
+                        break;
                     case "--user":
                         userId = UserHandle.parseUserArg(getNextArgRequired());
                         break;
@@ -606,30 +611,34 @@
             if (filter != null && !info.packageName.contains(filter)) {
                 continue;
             }
-            if (uid != -1 && info.applicationInfo.uid != uid) {
+            final boolean isApex = info.isApex;
+            if (uid != -1 && !isApex && info.applicationInfo.uid != uid) {
                 continue;
             }
-            final boolean isSystem =
+
+            final boolean isSystem = !isApex &&
                     (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
-            if ((!listDisabled || !info.applicationInfo.enabled) &&
-                    (!listEnabled || info.applicationInfo.enabled) &&
+            final boolean isEnabled = !isApex && info.applicationInfo.enabled;
+            if ((!listDisabled || !isEnabled) &&
+                    (!listEnabled || isEnabled) &&
                     (!listSystem || isSystem) &&
-                    (!listThirdParty || !isSystem)) {
+                    (!listThirdParty || !isSystem) &&
+                    (!listApexOnly || isApex)) {
                 pw.print("package:");
-                if (showSourceDir) {
+                if (showSourceDir && !isApex) {
                     pw.print(info.applicationInfo.sourceDir);
                     pw.print("=");
                 }
                 pw.print(info.packageName);
-                if (showVersionCode) {
+                if (showVersionCode && !isApex) {
                     pw.print(" versionCode:");
                     pw.print(info.applicationInfo.versionCode);
                 }
-                if (listInstaller) {
+                if (listInstaller && !isApex) {
                     pw.print("  installer=");
                     pw.print(mInterface.getInstallerPackageName(info.packageName));
                 }
-                if (showUid) {
+                if (showUid && !isApex) {
                     pw.print(" uid:");
                     pw.print(info.applicationInfo.uid);
                 }
@@ -2284,6 +2293,9 @@
                 case "--multi-package":
                     sessionParams.setMultiPackage();
                     break;
+                case "--staged":
+                    sessionParams.setStaged();
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
@@ -2767,11 +2779,11 @@
         pw.println("    Prints all system libraries.");
         pw.println("");
         pw.println("  list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] ");
-        pw.println("      [--uid UID] [--user USER_ID] [FILTER]");
+        pw.println("      [--apex-only] [--uid UID] [--user USER_ID] [FILTER]");
         pw.println("    Prints all packages; optionally only those whose name contains");
         pw.println("    the text in FILTER.  Options are:");
         pw.println("      -f: see their associated file");
-        pw.println("      -a: all known packages");
+        pw.println("      -a: all known packages (but excluding APEXes)");
         pw.println("      -d: filter to only show disabled packages");
         pw.println("      -e: filter to only show enabled packages");
         pw.println("      -s: filter to only show system packages");
@@ -2780,6 +2792,7 @@
         pw.println("      -l: ignored (used for compatibility with older releases)");
         pw.println("      -U: also show the package UID");
         pw.println("      -u: also include uninstalled packages");
+        pw.println("      --apex-only: only show APEX packages");
         pw.println("      --uid UID: filter to only show packages with the given UID");
         pw.println("      --user USER_ID: only list packages belonging to the given user");
         pw.println("");
@@ -2853,7 +2866,7 @@
         pw.println("       [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
         pw.println("       [--preload] [--instantapp] [--full] [--dont-kill]");
         pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]");
-        pw.println("       [--multi-package]");
+        pw.println("       [--multi-package] [--staged]");
         pw.println("    Like \"install\", but starts an install session.  Use \"install-write\"");
         pw.println("    to push data into the session, and \"install-commit\" to finish.");
         pw.println("");
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
index 88d9e52..68a755b 100644
--- a/services/core/java/com/android/server/pm/dex/DexLogger.java
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -18,29 +18,32 @@
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.os.FileUtils;
 import android.os.RemoteException;
-
-import android.util.ArraySet;
+import android.os.storage.StorageManager;
 import android.util.ByteStringUtils;
 import android.util.EventLog;
 import android.util.PackageUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile;
+import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
 
 import java.io.File;
+import java.util.Map;
 import java.util.Set;
 
-import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
-
 /**
  * This class is responsible for logging data about secondary dex files.
  * The data logged includes hashes of the name and content of each file.
  */
-public class DexLogger implements DexManager.Listener {
+public class DexLogger {
     private static final String TAG = "DexLogger";
 
     // Event log tag & subtag used for SafetyNet logging of dynamic
@@ -49,75 +52,172 @@
     private static final String DCL_SUBTAG = "dcl";
 
     private final IPackageManager mPackageManager;
+    private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
     private final Object mInstallLock;
     @GuardedBy("mInstallLock")
     private final Installer mInstaller;
 
-    public static DexManager.Listener getListener(IPackageManager pms,
-            Installer installer, Object installLock) {
-        return new DexLogger(pms, installer, installLock);
+    public DexLogger(IPackageManager pms, Installer installer, Object installLock) {
+        this(pms, installer, installLock, new PackageDynamicCodeLoading());
     }
 
     @VisibleForTesting
-    /*package*/ DexLogger(IPackageManager pms, Installer installer, Object installLock) {
+    DexLogger(IPackageManager pms, Installer installer, Object installLock,
+            PackageDynamicCodeLoading packageDynamicCodeLoading) {
         mPackageManager = pms;
+        mPackageDynamicCodeLoading = packageDynamicCodeLoading;
         mInstaller = installer;
         mInstallLock = installLock;
     }
 
-    /**
-     * Compute and log hashes of the name and content of a secondary dex file.
-     */
-    @Override
-    public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
-            String dexPath, int storageFlags) {
-        int ownerUid = appInfo.uid;
+    public Set<String> getAllPackagesWithDynamicCodeLoading() {
+        return mPackageDynamicCodeLoading.getAllPackagesWithDynamicCodeLoading();
+    }
 
-        byte[] hash = null;
-        synchronized(mInstallLock) {
-            try {
-                hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName,
-                        ownerUid, appInfo.volumeUuid, storageFlags);
-            } catch (InstallerException e) {
-                Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath +
-                        " : " + e.getMessage());
-            }
-        }
-        if (hash == null) {
+    /**
+     * Write information about code dynamically loaded by {@code packageName} to the event log.
+     */
+    public void logDynamicCodeLoading(String packageName) {
+        PackageDynamicCode info = getPackageDynamicCodeInfo(packageName);
+        if (info == null) {
             return;
         }
 
-        String dexFileName = new File(dexPath).getName();
-        String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
-        // Valid SHA256 will be 256 bits, 32 bytes.
-        if (hash.length == 32) {
-            message = message + ' ' + ByteStringUtils.toHexString(hash);
-        }
+        SparseArray<ApplicationInfo> appInfoByUser = new SparseArray<>();
+        boolean needWrite = false;
 
-        writeDclEvent(ownerUid, message);
+        for (Map.Entry<String, DynamicCodeFile> fileEntry : info.mFileUsageMap.entrySet()) {
+            String filePath = fileEntry.getKey();
+            DynamicCodeFile fileInfo = fileEntry.getValue();
+            int userId = fileInfo.mUserId;
 
-        if (dexUseInfo.isUsedByOtherApps()) {
-            Set<String> otherPackages = dexUseInfo.getLoadingPackages();
-            Set<Integer> otherUids = new ArraySet<>(otherPackages.size());
-            for (String otherPackageName : otherPackages) {
+            int index = appInfoByUser.indexOfKey(userId);
+            ApplicationInfo appInfo;
+            if (index >= 0) {
+                appInfo = appInfoByUser.get(userId);
+            } else {
+                appInfo = null;
+
                 try {
-                    int otherUid = mPackageManager.getPackageUid(
-                        otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId());
-                    if (otherUid != -1 && otherUid != ownerUid) {
-                        otherUids.add(otherUid);
-                    }
-                } catch (RemoteException ignore) {
+                    PackageInfo ownerInfo =
+                            mPackageManager.getPackageInfo(packageName, /*flags*/ 0, userId);
+                    appInfo = ownerInfo == null ? null : ownerInfo.applicationInfo;
+                } catch (RemoteException ignored) {
                     // Can't happen, we're local.
                 }
+                appInfoByUser.put(userId, appInfo);
+                if (appInfo == null) {
+                    Slog.d(TAG, "Could not find package " + packageName + " for user " + userId);
+                    // Package has probably been uninstalled for user.
+                    needWrite |= mPackageDynamicCodeLoading.removeUserPackage(packageName, userId);
+                }
             }
-            for (int otherUid : otherUids) {
-                writeDclEvent(otherUid, message);
+
+            if (appInfo == null) {
+                continue;
             }
+
+            int storageFlags;
+            if (appInfo.deviceProtectedDataDir != null
+                    && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) {
+                storageFlags = StorageManager.FLAG_STORAGE_DE;
+            } else if (appInfo.credentialProtectedDataDir != null
+                    && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) {
+                storageFlags = StorageManager.FLAG_STORAGE_CE;
+            } else {
+                Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath);
+                needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId);
+                continue;
+            }
+
+            byte[] hash = null;
+            synchronized (mInstallLock) {
+                try {
+                    hash = mInstaller.hashSecondaryDexFile(filePath, packageName, appInfo.uid,
+                            appInfo.volumeUuid, storageFlags);
+                } catch (InstallerException e) {
+                    Slog.e(TAG, "Got InstallerException when hashing file " + filePath
+                            + ": " + e.getMessage());
+                }
+            }
+
+            String fileName = new File(filePath).getName();
+            String message = PackageUtils.computeSha256Digest(fileName.getBytes());
+
+            // Valid SHA256 will be 256 bits, 32 bytes.
+            if (hash != null && hash.length == 32) {
+                message = message + ' ' + ByteStringUtils.toHexString(hash);
+            } else {
+                Slog.d(TAG, "Got no hash for " + filePath);
+                // File has probably been deleted.
+                needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId);
+            }
+
+            for (String loadingPackageName : fileInfo.mLoadingPackages) {
+                int loadingUid = -1;
+                if (loadingPackageName.equals(packageName)) {
+                    loadingUid = appInfo.uid;
+                } else {
+                    try {
+                        loadingUid = mPackageManager.getPackageUid(loadingPackageName, /*flags*/ 0,
+                                userId);
+                    } catch (RemoteException ignored) {
+                        // Can't happen, we're local.
+                    }
+                }
+
+                if (loadingUid != -1) {
+                    writeDclEvent(loadingUid, message);
+                }
+            }
+        }
+
+        if (needWrite) {
+            mPackageDynamicCodeLoading.maybeWriteAsync();
         }
     }
 
     @VisibleForTesting
-    /*package*/ void writeDclEvent(int uid, String message) {
+    PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
+        return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
+    }
+
+    @VisibleForTesting
+    void writeDclEvent(int uid, String message) {
         EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message);
     }
+
+    void record(int loaderUserId, String dexPath,
+            String owningPackageName, String loadingPackageName) {
+        if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath,
+                PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
+                loadingPackageName)) {
+            mPackageDynamicCodeLoading.maybeWriteAsync();
+        }
+    }
+
+    void clear() {
+        mPackageDynamicCodeLoading.clear();
+    }
+
+    void removePackage(String packageName) {
+        if (mPackageDynamicCodeLoading.removePackage(packageName)) {
+            mPackageDynamicCodeLoading.maybeWriteAsync();
+        }
+    }
+
+    void removeUserPackage(String packageName, int userId) {
+        if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) {
+            mPackageDynamicCodeLoading.maybeWriteAsync();
+        }
+    }
+
+    void readAndSync(Map<String, Set<Integer>> packageToUsersMap) {
+        mPackageDynamicCodeLoading.read();
+        mPackageDynamicCodeLoading.syncData(packageToUsersMap);
+    }
+
+    void writeNow() {
+        mPackageDynamicCodeLoading.writeNow();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 36b7269..25ef767 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -19,7 +19,6 @@
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
-import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -90,18 +89,17 @@
     // encode and save the dex usage data.
     private final PackageDexUsage mPackageDexUsage;
 
-    // PackageDynamicCodeLoading handles recording of dynamic code loading -
-    // which is similar to PackageDexUsage but records a different aspect of the data.
+    // DexLogger handles recording of dynamic code loading - which is similar to PackageDexUsage
+    // but records a different aspect of the data.
     // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't
     // record class loaders or ISAs.)
-    private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
+    private final DexLogger mDexLogger;
 
     private final IPackageManager mPackageManager;
     private final PackageDexOptimizer mPackageDexOptimizer;
     private final Object mInstallLock;
     @GuardedBy("mInstallLock")
     private final Installer mInstaller;
-    private final Listener mListener;
 
     // Possible outcomes of a dex search.
     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
@@ -122,26 +120,20 @@
      */
     private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
 
-    public interface Listener {
-        /**
-         * Invoked just before the secondary dex file {@code dexPath} for the specified application
-         * is reconciled.
-         */
-        void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
-                String dexPath, int storageFlags);
-    }
-
     public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
-            Installer installer, Object installLock, Listener listener) {
+            Installer installer, Object installLock) {
         mContext = context;
         mPackageCodeLocationsCache = new HashMap<>();
         mPackageDexUsage = new PackageDexUsage();
-        mPackageDynamicCodeLoading = new PackageDynamicCodeLoading();
         mPackageManager = pms;
         mPackageDexOptimizer = pdo;
         mInstaller = installer;
         mInstallLock = installLock;
-        mListener = listener;
+        mDexLogger = new DexLogger(pms, installer, installLock);
+    }
+
+    public DexLogger getDexLogger() {
+        return mDexLogger;
     }
 
     public void systemReady() {
@@ -243,11 +235,8 @@
                     continue;
                 }
 
-                if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath,
-                        PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
-                        loadingAppInfo.packageName)) {
-                    mPackageDynamicCodeLoading.maybeWriteAsync();
-                }
+                mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName,
+                        loadingAppInfo.packageName);
 
                 if (classLoaderContexts != null) {
 
@@ -284,7 +273,7 @@
             loadInternal(existingPackages);
         } catch (Exception e) {
             mPackageDexUsage.clear();
-            mPackageDynamicCodeLoading.clear();
+            mDexLogger.clear();
             Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e);
         }
     }
@@ -335,16 +324,12 @@
             if (mPackageDexUsage.removePackage(packageName)) {
                 mPackageDexUsage.maybeWriteAsync();
             }
-            if (mPackageDynamicCodeLoading.removePackage(packageName)) {
-                mPackageDynamicCodeLoading.maybeWriteAsync();
-            }
+            mDexLogger.removePackage(packageName);
         } else {
             if (mPackageDexUsage.removeUserPackage(packageName, userId)) {
                 mPackageDexUsage.maybeWriteAsync();
             }
-            if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) {
-                mPackageDynamicCodeLoading.maybeWriteAsync();
-            }
+            mDexLogger.removeUserPackage(packageName, userId);
         }
     }
 
@@ -423,10 +408,9 @@
         }
 
         try {
-            mPackageDynamicCodeLoading.read();
-            mPackageDynamicCodeLoading.syncData(packageToUsersMap);
+            mDexLogger.readAndSync(packageToUsersMap);
         } catch (Exception e) {
-            mPackageDynamicCodeLoading.clear();
+            mDexLogger.clear();
             Slog.w(TAG, "Exception while loading package dynamic code usage. "
                     + "Starting with a fresh state.", e);
         }
@@ -460,11 +444,6 @@
         return mPackageDexUsage.getPackageUseInfo(packageName) != null;
     }
 
-    @VisibleForTesting
-    /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
-        return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
-    }
-
     /**
      * Perform dexopt on with the given {@code options} on the secondary dex files.
      * @return true if all secondary dex files were processed successfully (compiled or skipped
@@ -574,10 +553,6 @@
                 continue;
             }
 
-            if (mListener != null) {
-                mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
-            }
-
             boolean dexStillExists = true;
             synchronized(mInstallLock) {
                 try {
@@ -721,7 +696,7 @@
      */
     public void writePackageDexUsageNow() {
         mPackageDexUsage.writeNow();
-        mPackageDynamicCodeLoading.writeNow();
+        mDexLogger.writeNow();
     }
 
     private void registerSettingObserver() {
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/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index c5139b5..cedb548 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -111,7 +111,7 @@
             false, /* enableAdjustBrightness */
             false, /* enableDataSaver */
             true,  /* enableFirewall */
-            false, /* enableQuickDoze */
+            true, /* enableQuickDoze */
             new ArrayMap<>(), /* filesForInteractive */
             new ArrayMap<>(), /* filesForNoninteractive */
             true, /* forceAllAppsStandby */
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index 20e4985..244ccb6 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -1,4 +1,5 @@
 michaelwr@google.com
+santoscordon@google.com
 
 per-file BatterySaverPolicy.java=omakoto@google.com
 per-file ShutdownThread.java=fkupolov@google.com
diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
new file mode 100644
index 0000000..944db84
--- /dev/null
+++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
@@ -0,0 +1,109 @@
+/*
+ * 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.signedconfig;
+
+import android.os.Build;
+import android.util.Slog;
+
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+/**
+ * Helper class for verifying config signatures.
+ */
+public class SignatureVerifier {
+
+    private static final String TAG = "SignedConfig";
+    private static final boolean DBG = false;
+
+    private static final String DEBUG_KEY =
+            "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60"
+            + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==";
+
+    private final PublicKey mDebugKey;
+
+    public SignatureVerifier() {
+        mDebugKey = createKey(DEBUG_KEY);
+    }
+
+    private static PublicKey createKey(String base64) {
+        EncodedKeySpec keySpec;
+        try {
+            byte[] key = Base64.getDecoder().decode(base64);
+            keySpec = new X509EncodedKeySpec(key);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Failed to base64 decode public key", e);
+            return null;
+        }
+        try {
+            KeyFactory factory = KeyFactory.getInstance("EC");
+            return factory.generatePublic(keySpec);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+            Slog.e(TAG, "Failed to construct public key", e);
+            return null;
+        }
+    }
+
+    /**
+     * Verify a signature for signed config.
+     *
+     * @param config Config as read from APK meta-data.
+     * @param base64Signature Signature as read from APK meta-data.
+     * @return {@code true} iff the signature was successfully verified.
+     */
+    public boolean verifySignature(String config, String base64Signature)
+            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
+        byte[] signature;
+        try {
+            signature = Base64.getDecoder().decode(base64Signature);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Failed to base64 decode signature");
+            return false;
+        }
+        byte[] data = config.getBytes(StandardCharsets.UTF_8);
+        if (DBG) Slog.i(TAG, "Data: " + Base64.getEncoder().encodeToString(data));
+
+        if (Build.IS_DEBUGGABLE) {
+            if (mDebugKey != null) {
+                if (DBG) Slog.w(TAG, "Trying to verify signature using debug key");
+                Signature verifier = Signature.getInstance("SHA256withECDSA");
+                verifier.initVerify(mDebugKey);
+                verifier.update(data);
+                if (verifier.verify(signature)) {
+                    Slog.i(TAG, "Verified config using debug key");
+                    return true;
+                } else {
+                    if (DBG) Slog.i(TAG, "Config verification failed using debug key");
+                }
+            } else {
+                Slog.w(TAG, "Debuggable build, but have no debug key");
+            }
+        }
+        // TODO verify production key.
+        Slog.w(TAG, "NO PRODUCTION KEY YET, FAILING VERIFICATION");
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/signedconfig/SignedConfig.java b/services/core/java/com/android/server/signedconfig/SignedConfig.java
index e6bb800..560a1e1 100644
--- a/services/core/java/com/android/server/signedconfig/SignedConfig.java
+++ b/services/core/java/com/android/server/signedconfig/SignedConfig.java
@@ -33,19 +33,34 @@
  * Represents signed configuration.
  *
  * <p>This configuration should only be used if the signature has already been verified.
+ *
+ * This class also parses signed config from JSON. The format expected is:
+ * <pre>
+ * {
+ *   "version": 1
+ *   "config": [
+ *     {
+ *       "min_sdk": 28,
+ *       "max_sdk": 29,
+ *       "values": {
+ *         "key": "value",
+ *         "key2": "value2"
+ *         ...
+ *       }
+ *     },
+ *     ...
+ *   ],
+ * }
+ * </pre>
  */
 public class SignedConfig {
 
     private static final String KEY_VERSION = "version";
     private static final String KEY_CONFIG = "config";
 
-    private static final String CONFIG_KEY_MIN_SDK = "minSdk";
-    private static final String CONFIG_KEY_MAX_SDK = "maxSdk";
+    private static final String CONFIG_KEY_MIN_SDK = "min_sdk";
+    private static final String CONFIG_KEY_MAX_SDK = "max_sdk";
     private static final String CONFIG_KEY_VALUES = "values";
-    // TODO it may be better to use regular key/value pairs in a JSON object, rather than an array
-    // of objects with the 2 keys below.
-    private static final String CONFIG_KEY_KEY = "key";
-    private static final String CONFIG_KEY_VALUE = "value";
 
     /**
      * Represents config values targeting an SDK range.
@@ -141,14 +156,10 @@
             throws JSONException, InvalidConfigException {
         int minSdk = json.getInt(CONFIG_KEY_MIN_SDK);
         int maxSdk = json.getInt(CONFIG_KEY_MAX_SDK);
-        JSONArray valueArray = json.getJSONArray(CONFIG_KEY_VALUES);
+        JSONObject valuesJson = json.getJSONObject(CONFIG_KEY_VALUES);
         Map<String, String> values = new HashMap<>();
-        for (int i = 0; i < valueArray.length(); ++i) {
-            JSONObject keyValuePair = valueArray.getJSONObject(i);
-            String key = keyValuePair.getString(CONFIG_KEY_KEY);
-            Object valueObject = keyValuePair.has(CONFIG_KEY_VALUE)
-                    ? keyValuePair.get(CONFIG_KEY_VALUE)
-                    : null;
+        for (String key : valuesJson.keySet()) {
+            Object valueObject = valuesJson.get(key);
             String value = valueObject == JSONObject.NULL || valueObject == null
                             ? null
                             : valueObject.toString();
diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java b/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java
index e4d799a..4908964 100644
--- a/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java
+++ b/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java
@@ -24,6 +24,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import java.security.GeneralSecurityException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Map;
@@ -65,15 +66,21 @@
 
     private final Context mContext;
     private final String mSourcePackage;
+    private final SignatureVerifier mVerifier;
 
     SignedConfigApplicator(Context context, String sourcePackage) {
         mContext = context;
         mSourcePackage = sourcePackage;
+        mVerifier = new SignatureVerifier();
     }
 
     private boolean checkSignature(String data, String signature) {
-        Slog.w(TAG, "SIGNATURE CHECK NOT IMPLEMENTED YET!");
-        return false;
+        try {
+            return mVerifier.verifySignature(data, signature);
+        } catch (GeneralSecurityException e) {
+            Slog.e(TAG, "Failed to verify signature", e);
+            return false;
+        }
     }
 
     private int getCurrentConfigVersion() {
diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigService.java b/services/core/java/com/android/server/signedconfig/SignedConfigService.java
index be1d41d..84ce93f 100644
--- a/services/core/java/com/android/server/signedconfig/SignedConfigService.java
+++ b/services/core/java/com/android/server/signedconfig/SignedConfigService.java
@@ -29,6 +29,9 @@
 
 import com.android.server.LocalServices;
 
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
 /**
  * Signed config service. This is not an Android Service, but just owns a broadcast receiver for
  * receiving package install and update notifications from the package manager.
@@ -81,6 +84,13 @@
                 && metaData.containsKey(KEY_CONFIG_SIGNATURE)) {
             String config = metaData.getString(KEY_CONFIG);
             String signature = metaData.getString(KEY_CONFIG_SIGNATURE);
+            try {
+                // Base64 encoding is standard (not URL safe) encoding: RFC4648
+                config = new String(Base64.getDecoder().decode(config), StandardCharsets.UTF_8);
+            } catch (IllegalArgumentException iae) {
+                Slog.e(TAG, "Failed to base64 decode config from " + packageName);
+                return;
+            }
             if (DBG) {
                 Slog.d(TAG, "Got signed config: " + config);
                 Slog.d(TAG, "Got config signature: " + signature);
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/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 4e9c5ab..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;
@@ -96,6 +97,7 @@
 import static com.android.server.am.ActivityRecordProto.VISIBLE;
 import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY;
 import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY;
+import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED;
 import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
@@ -122,8 +124,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.ActivityTaskManagerService
-        .RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -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;
@@ -157,6 +159,7 @@
 import android.app.servertransaction.PipModeChangeItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.WindowVisibilityItem;
+import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -581,6 +584,9 @@
             if (info.maxAspectRatio != 0) {
                 pw.println(prefix + "maxAspectRatio=" + info.maxAspectRatio);
             }
+            if (info.minAspectRatio != 0) {
+                pw.println(prefix + "minAspectRatio=" + info.minAspectRatio);
+            }
         }
     }
 
@@ -716,8 +722,12 @@
             // to forcing the update of the picture-in-picture mode as a part of the PiP animation.
             mLastReportedPictureInPictureMode = inPictureInPictureMode;
             mLastReportedMultiWindowMode = inPictureInPictureMode;
-            final Configuration newConfig = task.computeNewOverrideConfigurationForBounds(
-                    targetStackBounds, null);
+            final Configuration newConfig = new Configuration();
+            if (targetStackBounds != null && !targetStackBounds.isEmpty()) {
+                task.computeResolvedOverrideConfiguration(newConfig,
+                        task.getParent().getConfiguration(),
+                        task.getRequestedOverrideConfiguration());
+            }
             schedulePictureInPictureModeChanged(newConfig);
             scheduleMultiWindowModeChanged(newConfig);
         }
@@ -1035,8 +1045,6 @@
 
         inHistory = true;
 
-        final TaskWindowContainerController taskController = task.getWindowContainerController();
-
         // TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration()
         task.updateOverrideConfigurationFromLaunchBounds();
         // Make sure override configuration is up-to-date before using to create window controller.
@@ -1048,10 +1056,9 @@
             // TODO: Should this throw an exception instead?
             Slog.w(TAG, "Attempted to add existing app token: " + appToken);
         } else {
-            final Task container = taskController.mContainer;
+            final Task container = task.getTask();
             if (container == null) {
-                throw new IllegalArgumentException("AppWindowContainerController: invalid "
-                        + " controller=" + taskController);
+                throw new IllegalArgumentException("createAppWindowToken: invalid task =" + task);
             }
             mAppWindowToken = createAppWindow(mAtmService.mWindowManager, appToken,
                     task.voiceSession != null, container.getDisplayContent(),
@@ -1062,7 +1069,7 @@
                     mLaunchTaskBehind, isAlwaysFocusable());
             if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) {
                 Slog.v(TAG, "addAppToken: "
-                        + mAppWindowToken + " controller=" + taskController + " at "
+                        + mAppWindowToken + " task=" + container + " at "
                         + Integer.MAX_VALUE);
             }
             container.addChild(mAppWindowToken, Integer.MAX_VALUE /* add on top */);
@@ -1091,6 +1098,12 @@
             Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + appToken);
             return false;
         }
+        if (mAppWindowToken.getTask() == null) {
+            // Can be removed after unification of Task and TaskRecord.
+            Slog.w(TAG_WM, "Attempted to start a window to an app token not having attached to any"
+                    + " task: " + appToken);
+            return false;
+        }
         return mAppWindowToken.addStartingWindow(pkg, theme, compatInfo, nonLocalizedLabel,
                 labelRes, icon, logo, windowFlags, transferFrom, newTask, taskSwitch,
                 processRunning, allowTaskSnapshot, activityCreated, fromRecents);
@@ -1148,7 +1161,7 @@
                     + " r=" + this + " (" + prevTask.getStackId() + ")");
         }
 
-        mAppWindowToken.reparent(newTask.getWindowContainerController(), position);
+        mAppWindowToken.reparent(newTask.getTask(), position);
 
         // Reparenting prevents informing the parent stack of activity removal in the case that
         // the new stack has the same parent. we must manually signal here if this is not the case.
@@ -1801,6 +1814,18 @@
             }
             mAppWindowToken.detachChildren();
         }
+
+        if (state == RESUMED) {
+            mAtmService.updateBatteryStats(this, true);
+            mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_RESUMED);
+        } else if (state == PAUSED) {
+            mAtmService.updateBatteryStats(this, false);
+            mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_PAUSED);
+        } else if (state == STOPPED) {
+            mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
+        } else if (state == DESTROYED) {
+            mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_DESTROYED);
+        }
     }
 
     ActivityState getState() {
@@ -1996,10 +2021,7 @@
         stopped = false;
 
         if (isActivityTypeHome()) {
-            WindowProcessController app = task.mActivities.get(0).app;
-            if (hasProcess() && app != mAtmService.mHomeProcess) {
-                mAtmService.mHomeProcess = app;
-            }
+            mStackSupervisor.updateHomeProcess(task.mActivities.get(0).app);
         }
 
         if (nowVisible) {
@@ -2163,7 +2185,7 @@
                 .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
         if (info != null) {
             mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
-                    info.windowsFullyDrawnDelayMs);
+                    info.windowsFullyDrawnDelayMs, info.getLaunchState());
         }
     }
 
@@ -2187,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) {
@@ -2542,12 +2565,10 @@
 
         setBounds(mTmpBounds);
 
-        final Rect updatedBounds = getRequestedOverrideBounds();
-
         // Bounds changed...update configuration to match.
         if (!matchParentBounds()) {
-            task.computeOverrideConfiguration(mTmpConfig, updatedBounds,
-                    false /* overrideWidth */, false /* overrideHeight */);
+            task.computeResolvedOverrideConfiguration(mTmpConfig,
+                    task.getParent().getConfiguration(), getRequestedOverrideConfiguration());
         }
 
         onRequestedOverrideConfigurationChanged(mTmpConfig);
@@ -2576,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
@@ -2591,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
@@ -2619,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;
         }
     }
 
@@ -2774,7 +2822,7 @@
             final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
             if (hasResizeChange) {
                 final boolean isDragResizing =
-                        getTaskRecord().getWindowContainerController().isDragResizing();
+                        getTaskRecord().getTask().isDragResizing();
                 mRelaunchReason = isDragResizing ? RELAUNCH_REASON_FREE_RESIZE
                         : RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
             } else {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index aca9702..2663d99 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -294,13 +294,6 @@
     // stack and the new stack will be on top of all stacks.
     static final int REMOVE_TASK_MODE_MOVING_TO_TOP = 2;
 
-    // The height/width divide used when fitting a task within a bounds with method
-    // {@link #fitWithinBounds}.
-    // We always want the task to to be visible in the bounds without affecting its size when
-    // fitting. To make sure this is the case, we don't adjust the task left or top side pass
-    // the input bounds right or bottom side minus the width or height divided by this value.
-    private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
-
     final ActivityTaskManagerService mService;
     private final WindowManagerService mWindowManager;
     T mWindowContainerController;
@@ -365,9 +358,9 @@
 
     private boolean mUpdateBoundsDeferred;
     private boolean mUpdateBoundsDeferredCalled;
+    private boolean mUpdateDisplayedBoundsDeferredCalled;
     private final Rect mDeferredBounds = new Rect();
-    private final Rect mDeferredTaskBounds = new Rect();
-    private final Rect mDeferredTaskInsetBounds = new Rect();
+    private final Rect mDeferredDisplayedBounds = new Rect();
 
     int mCurrentUser;
 
@@ -605,7 +598,9 @@
             if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 getStackDockedModeBounds(null, null, mTmpRect2, mTmpRect3);
                 // immediately resize so docked bounds are available in onSplitScreenModeActivated
-                resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
+                setTaskDisplayedBounds(null);
+                setTaskBounds(mTmpRect2);
+                setBounds(mTmpRect2);
             } else if (
                     getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
                 Rect dockedBounds = display.getSplitScreenPrimaryStack().getBounds();
@@ -911,7 +906,7 @@
     }
 
     void positionChildWindowContainerAtTop(TaskRecord child) {
-        mWindowContainerController.positionChildAtTop(child.getWindowContainerController(),
+        mWindowContainerController.positionChildAtTop(child.getTask(),
                 true /* includingParents */);
     }
 
@@ -921,7 +916,7 @@
         // task to bottom, the next focusable stack on the same display should be focused.
         final ActivityStack nextFocusableStack = getDisplay().getNextFocusableStack(
                 child.getStack(), true /* ignoreCurrent */);
-        mWindowContainerController.positionChildAtBottom(child.getWindowContainerController(),
+        mWindowContainerController.positionChildAtBottom(child.getTask(),
                 nextFocusableStack == null /* includingParents */);
     }
 
@@ -949,17 +944,19 @@
      * be resized to that bounds.
      */
     void continueUpdateBounds() {
-        final boolean wasDeferred = mUpdateBoundsDeferred;
-        mUpdateBoundsDeferred = false;
-        if (wasDeferred && mUpdateBoundsDeferredCalled) {
-            resize(mDeferredBounds.isEmpty() ? null : mDeferredBounds,
-                    mDeferredTaskBounds.isEmpty() ? null : mDeferredTaskBounds,
-                    mDeferredTaskInsetBounds.isEmpty() ? null : mDeferredTaskInsetBounds);
+        if (mUpdateBoundsDeferred) {
+            mUpdateBoundsDeferred = false;
+            if (mUpdateBoundsDeferredCalled) {
+                setTaskBounds(mDeferredBounds);
+                setBounds(mDeferredBounds);
+            }
+            if (mUpdateDisplayedBoundsDeferredCalled) {
+                setTaskDisplayedBounds(mDeferredDisplayedBounds);
+            }
         }
     }
 
-    boolean updateBoundsAllowed(Rect bounds, Rect tempTaskBounds,
-            Rect tempTaskInsetBounds) {
+    boolean updateBoundsAllowed(Rect bounds) {
         if (!mUpdateBoundsDeferred) {
             return true;
         }
@@ -968,20 +965,23 @@
         } else {
             mDeferredBounds.setEmpty();
         }
-        if (tempTaskBounds != null) {
-            mDeferredTaskBounds.set(tempTaskBounds);
-        } else {
-            mDeferredTaskBounds.setEmpty();
-        }
-        if (tempTaskInsetBounds != null) {
-            mDeferredTaskInsetBounds.set(tempTaskInsetBounds);
-        } else {
-            mDeferredTaskInsetBounds.setEmpty();
-        }
         mUpdateBoundsDeferredCalled = true;
         return false;
     }
 
+    boolean updateDisplayedBoundsAllowed(Rect bounds) {
+        if (!mUpdateBoundsDeferred) {
+            return true;
+        }
+        if (bounds != null) {
+            mDeferredDisplayedBounds.set(bounds);
+        } else {
+            mDeferredDisplayedBounds.setEmpty();
+        }
+        mUpdateDisplayedBoundsDeferredCalled = true;
+        return false;
+    }
+
     @Override
     public int setBounds(Rect bounds) {
         return super.setBounds(!inMultiWindowMode() ? null : bounds);
@@ -1617,7 +1617,6 @@
             try {
                 EventLogTags.writeAmPauseActivity(prev.mUserId, System.identityHashCode(prev),
                         prev.shortComponentName, "userLeaving=" + userLeaving);
-                mService.updateUsageStats(prev, false);
 
                 mService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
                         prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
@@ -2984,7 +2983,7 @@
         position = getAdjustedPositionForTask(task, position, null /* starting */);
         mTaskHistory.remove(task);
         mTaskHistory.add(position, task);
-        mWindowContainerController.positionChildAt(task.getWindowContainerController(), position);
+        mWindowContainerController.positionChildAt(task.getTask(), position);
         updateTaskMovement(task, true);
     }
 
@@ -4649,9 +4648,6 @@
                                     r.mUserId, System.identityHashCode(r),
                                     r.getTaskRecord().taskId, r.shortComponentName,
                                     "proc died without state saved");
-                            if (r.getState() == RESUMED) {
-                                mService.updateUsageStats(r, false);
-                            }
                         }
                     } else {
                         // We have the current state for this activity, so
@@ -4912,7 +4908,7 @@
     // TODO: Can only be called from special methods in ActivityStackSupervisor.
     // Need to consolidate those calls points into this resize method so anyone can call directly.
     void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
-        if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
+        if (!updateBoundsAllowed(bounds)) {
             return;
         }
 
@@ -4926,20 +4922,7 @@
         for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
             final TaskRecord task = mTaskHistory.get(i);
             if (task.isResizeable()) {
-                if (inFreeformWindowingMode()) {
-                    // TODO(b/71028874): Can be removed since each freeform task is its own
-                    //                   stack.
-                    // For freeform stack we don't adjust the size of the tasks to match that
-                    // of the stack, but we do try to make sure the tasks are still contained
-                    // with the bounds of the stack.
-                    if (task.getRequestedOverrideBounds() != null) {
-                        mTmpRect2.set(task.getRequestedOverrideBounds());
-                        fitWithinBounds(mTmpRect2, bounds);
-                        task.updateOverrideConfiguration(mTmpRect2);
-                    }
-                } else {
-                    task.updateOverrideConfiguration(taskBounds, insetBounds);
-                }
+                task.updateOverrideConfiguration(taskBounds, insetBounds);
             }
 
             if (task.hasDisplayedBounds()) {
@@ -4951,7 +4934,6 @@
             }
         }
 
-        mWindowContainerController.resize(bounds, mTmpBounds, mTmpInsetBounds);
         setBounds(bounds);
     }
 
@@ -4961,41 +4943,37 @@
 
 
     /**
-     * Adjust bounds to stay within stack bounds.
-     *
-     * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way
-     * that keep them unchanged, but be contained within the stack bounds.
-     *
-     * @param bounds Bounds to be adjusted.
-     * @param stackBounds Bounds within which the other bounds should remain.
+     * Until we can break this "set task bounds to same as stack bounds" behavior, this
+     * basically resizes both stack and task bounds to the same bounds.
      */
-    private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
-        if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) {
+    void setTaskBounds(Rect bounds) {
+        if (!updateBoundsAllowed(bounds)) {
             return;
         }
 
-        if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) {
-            final int maxRight = stackBounds.right
-                    - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER);
-            int horizontalDiff = stackBounds.left - bounds.left;
-            if ((horizontalDiff < 0 && bounds.left >= maxRight)
-                    || (bounds.left + horizontalDiff >= maxRight)) {
-                horizontalDiff = maxRight - bounds.left;
+        for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
+            final TaskRecord task = mTaskHistory.get(i);
+            if (task.isResizeable()) {
+                task.setBounds(bounds);
+            } else {
+                task.setBounds(null);
             }
-            bounds.left += horizontalDiff;
-            bounds.right += horizontalDiff;
+        }
+    }
+
+    /** Helper to setDisplayedBounds on all child tasks */
+    void setTaskDisplayedBounds(Rect bounds) {
+        if (!updateDisplayedBoundsAllowed(bounds)) {
+            return;
         }
 
-        if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) {
-            final int maxBottom = stackBounds.bottom
-                    - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER);
-            int verticalDiff = stackBounds.top - bounds.top;
-            if ((verticalDiff < 0 && bounds.top >= maxBottom)
-                    || (bounds.top + verticalDiff >= maxBottom)) {
-                verticalDiff = maxBottom - bounds.top;
+        for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
+            final TaskRecord task = mTaskHistory.get(i);
+            if (bounds == null || bounds.isEmpty()) {
+                task.setDisplayedBounds(null);
+            } else if (task.isResizeable()) {
+                task.setDisplayedBounds(bounds);
             }
-            bounds.top += verticalDiff;
-            bounds.bottom += verticalDiff;
         }
     }
 
@@ -5349,7 +5327,7 @@
                 && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
             task.updateOverrideConfiguration(getRequestedOverrideBounds());
         }
-        task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
+        task.createTask(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
         return task;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index e761ad8..f58b83d 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -179,6 +179,7 @@
     static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
     static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14;
     static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15;
+    static final int REPORT_HOME_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 16;
 
     // Used to indicate that windows of activities should be preserved during the resize.
     static final boolean PRESERVE_WINDOWS = true;
@@ -598,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);
@@ -609,6 +611,7 @@
                     w.who = new ComponentName(r.info.packageName, r.info.name);
                 }
                 w.totalTime = totalTime;
+                w.launchState = launchState;
                 // Do not modify w.result.
             }
         }
@@ -793,7 +796,7 @@
                         System.identityHashCode(r), task.taskId, r.shortComponentName);
                 if (r.isActivityTypeHome()) {
                     // Home process is the root process of the task.
-                    mService.mHomeProcess = task.mActivities.get(0).app;
+                    updateHomeProcess(task.mActivities.get(0).app);
                 }
                 mService.getPackageManagerInternalLocked().notifyPackageUse(
                         r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
@@ -915,6 +918,15 @@
         return true;
     }
 
+    void updateHomeProcess(WindowProcessController app) {
+        if (app != null && mService.mHomeProcess != app) {
+            if (!mHandler.hasMessages(REPORT_HOME_CHANGED_MSG)) {
+                mHandler.sendEmptyMessage(REPORT_HOME_CHANGED_MSG);
+            }
+            mService.mHomeProcess = app;
+        }
+    }
+
     private void logIfTransactionTooLarge(Intent intent, Bundle icicle) {
         int extrasSize = 0;
         if (intent != null) {
@@ -1242,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
@@ -1868,7 +1881,7 @@
 
         stack.addTask(task, onTop, "restoreRecentTask");
         // TODO: move call for creation here and other place into Stack.addTask()
-        task.createWindowContainer(onTop, true /* showForAllUsers */);
+        task.createTask(onTop, true /* showForAllUsers */);
         if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
                 "Added restored task=" + task + " to stack=" + stack);
         final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -2040,9 +2053,6 @@
         mStoppingActivities.remove(r);
 
         final ActivityStack stack = r.getActivityStack();
-        if (mRootActivityContainer.isTopDisplayFocusedStack(stack)) {
-            mService.updateUsageStats(r, true);
-        }
         if (stack.getDisplay().allResumedActivitiesComplete()) {
             mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
             // Make sure activity & window visibility should be identical
@@ -2543,7 +2553,15 @@
                         }
                     }
                 } break;
+                case REPORT_HOME_CHANGED_MSG: {
+                    synchronized (mService.mGlobalLock) {
+                        mHandler.removeMessages(REPORT_HOME_CHANGED_MSG);
 
+                        // Start home activities on displays with no activities.
+                        mRootActivityContainer.startHomeOnEmptyDisplays("homeChanged");
+                    }
+                }
+                break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 182d1a0..9861157 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3799,7 +3799,7 @@
         // changed, so we should reflect that check here as well.
         final PinnedActivityStack stack = r.getActivityStack();
         final PinnedStackWindowController windowController = stack.getWindowContainerController();
-        return !windowController.isAnimatingBoundsToFullscreen();
+        return !windowController.mContainer.isAnimatingBoundsToFullscreen();
     }
 
     @Override
@@ -5234,13 +5234,20 @@
         mH.post(mAmInternal::updateCpuStats);
     }
 
-    void updateUsageStats(ActivityRecord component, boolean resumed) {
-        final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::updateUsageStats,
+    void updateBatteryStats(ActivityRecord component, boolean resumed) {
+        final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::updateBatteryStats,
                 mAmInternal, component.mActivityComponent, component.app.mUid, component.mUserId,
                 resumed);
         mH.sendMessage(m);
     }
 
+    void updateActivityUsageStats(ActivityRecord activity, int event) {
+        final Message m = PooledLambda.obtainMessage(
+                ActivityManagerInternal::updateActivityUsageStats, mAmInternal,
+                activity.mActivityComponent, activity.mUserId, event, activity.appToken);
+        mH.sendMessage(m);
+    }
+
     void setBooting(boolean booting) {
         mAmInternal.setBooting(booting);
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5476e7a..8d49bf3 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -758,6 +758,9 @@
     @Override
     void removeImmediately() {
         onRemovedFromDisplay();
+        if (mActivityRecord != null) {
+            mActivityRecord.unregisterConfigurationChangeListener(this);
+        }
         super.removeImmediately();
     }
 
@@ -1187,21 +1190,14 @@
         }
     }
 
-    void reparent(TaskWindowContainerController taskController, int position) {
+    void reparent(Task task, int position) {
         if (DEBUG_ADD_REMOVE) {
             Slog.i(TAG_WM, "reparent: moving app token=" + this
-                    + " to task=" + taskController + " at " + position);
+                    + " to task=" + task.mTaskId + " at " + position);
         }
-        final Task task = taskController.mContainer;
         if (task == null) {
-            throw new IllegalArgumentException("reparent: could not find task="
-                    + taskController);
+            throw new IllegalArgumentException("reparent: could not find task");
         }
-        reparent(task, position);
-        getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
-    }
-
-    void reparent(Task task, int position) {
         final Task currentTask = getTask();
         if (task == currentTask) {
             throw new IllegalArgumentException(
@@ -1232,6 +1228,7 @@
             onDisplayChanged(displayContent);
             prevDisplayContent.setLayoutNeeded();
         }
+        getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
     }
 
     @Override
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/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/PinnedActivityStack.java b/services/core/java/com/android/server/wm/PinnedActivityStack.java
index 1c7ebd6..2a05af4 100644
--- a/services/core/java/com/android/server/wm/PinnedActivityStack.java
+++ b/services/core/java/com/android/server/wm/PinnedActivityStack.java
@@ -77,7 +77,7 @@
     }
 
     boolean isAnimatingBoundsToFullscreen() {
-        return getWindowContainerController().isAnimatingBoundsToFullscreen();
+        return getWindowContainerController().mContainer.isAnimatingBoundsToFullscreen();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index bbdcc62..518e39b 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
 import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
@@ -180,15 +181,6 @@
     }
 
     /**
-     * @return whether the bounds are currently animating to fullscreen.
-     */
-    public boolean isAnimatingBoundsToFullscreen() {
-        synchronized (mGlobalLock) {
-            return mContainer.isAnimatingBoundsToFullscreen();
-        }
-    }
-
-    /**
      * @return whether the stack can be resized from the bounds animation.
      */
     public boolean pinnedStackResizeDisallowed() {
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index c5b42f9..84a32fc 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -336,6 +336,15 @@
         return homeStarted;
     }
 
+    void startHomeOnEmptyDisplays(String reason) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
+            final ActivityDisplay display = mActivityDisplays.get(i);
+            if (display.topRunningActivity() == null) {
+                startHomeOnDisplay(mCurrentUser, reason, display.mDisplayId);
+            }
+        }
+    }
+
     /**
      * This starts home activity on displays that can have system decorations and only if the
      * home activity can have multiple instances.
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index 8f18aa5..ada807b 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -21,7 +21,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
-import android.app.WindowConfiguration;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -29,8 +28,6 @@
 import android.os.Message;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.DisplayCutout;
-import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -49,11 +46,7 @@
 
     private final H mHandler;
 
-    // Temp bounds only used in adjustConfigurationForBounds()
-    private final Rect mTmpRect = new Rect();
-    private final Rect mTmpStableInsets = new Rect();
-    private final Rect mTmpNonDecorInsets = new Rect();
-    private final Rect mTmpDisplayBounds = new Rect();
+    final Rect mTmpBounds = new Rect();
 
     public StackWindowController(int stackId, StackWindowListener listener, int displayId,
             boolean onTop, Rect outBounds) {
@@ -67,107 +60,87 @@
         mStackId = stackId;
         mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
 
-        synchronized (mGlobalLock) {
-            final DisplayContent dc = mRoot.getDisplayContent(displayId);
-            if (dc == null) {
-                throw new IllegalArgumentException("Trying to add stackId=" + stackId
-                        + " to unknown displayId=" + displayId);
-            }
-
-            dc.createStack(stackId, onTop, this);
-            getRawBounds(outBounds);
+        final DisplayContent dc = mRoot.getDisplayContent(displayId);
+        if (dc == null) {
+            throw new IllegalArgumentException("Trying to add stackId=" + stackId
+                    + " to unknown displayId=" + displayId);
         }
+
+        dc.createStack(stackId, onTop, this);
+        getRawBounds(outBounds);
     }
 
     @Override
     public void removeContainer() {
-        synchronized (mGlobalLock) {
-            if (mContainer != null) {
-                mContainer.removeIfPossible();
-                super.removeContainer();
-            }
+        if (mContainer != null) {
+            mContainer.removeIfPossible();
+            super.removeContainer();
         }
     }
 
-    public void reparent(int displayId, Rect outStackBounds, boolean onTop) {
-        synchronized (mGlobalLock) {
-            if (mContainer == null) {
-                throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
-                        + " to displayId=" + displayId);
-            }
-
-            final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
-            if (targetDc == null) {
-                throw new IllegalArgumentException("Trying to move stackId=" + mStackId
-                        + " to unknown displayId=" + displayId);
-            }
-
-            targetDc.moveStackToDisplay(mContainer, onTop);
-            getRawBounds(outStackBounds);
+    void reparent(int displayId, Rect outStackBounds, boolean onTop) {
+        if (mContainer == null) {
+            throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
+                    + " to displayId=" + displayId);
         }
+
+        final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
+        if (targetDc == null) {
+            throw new IllegalArgumentException("Trying to move stackId=" + mStackId
+                    + " to unknown displayId=" + displayId);
+        }
+
+        targetDc.moveStackToDisplay(mContainer, onTop);
+        getRawBounds(outStackBounds);
     }
 
-    public void positionChildAt(TaskWindowContainerController child, int position) {
-        synchronized (mGlobalLock) {
-            if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
-                    + " at " + position);
-            if (child.mContainer == null) {
-                if (DEBUG_STACK) Slog.i(TAG_WM,
-                        "positionChildAt: could not find task=" + this);
-                return;
-            }
-            if (mContainer == null) {
-                if (DEBUG_STACK) Slog.i(TAG_WM,
-                        "positionChildAt: could not find stack for task=" + mContainer);
-                return;
-            }
-            child.mContainer.positionAt(position);
-            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+    void positionChildAt(Task child, int position) {
+        if (DEBUG_STACK) {
+            Slog.i(TAG_WM, "positionChildAt: positioning task=" + child + " at " + position);
         }
+        if (child == null) {
+            if (DEBUG_STACK) {
+                Slog.i(TAG_WM, "positionChildAt: could not find task=" + this);
+            }
+            return;
+        }
+        if (mContainer == null) {
+            if (DEBUG_STACK) {
+                Slog.i(TAG_WM, "positionChildAt: could not find stack for task=" + mContainer);
+            }
+            return;
+        }
+        child.positionAt(position);
+        mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
     }
 
-    public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) {
+    void positionChildAtTop(Task child, boolean includingParents) {
         if (child == null) {
             // TODO: Fix the call-points that cause this to happen.
             return;
         }
 
-        synchronized (mGlobalLock) {
-            final Task childTask = child.mContainer;
-            if (childTask == null) {
-                Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found");
-                return;
-            }
-            mContainer.positionChildAt(POSITION_TOP, childTask, includingParents);
+        mContainer.positionChildAt(POSITION_TOP, child, includingParents);
 
-            final DisplayContent displayContent = mContainer.getDisplayContent();
-            if (displayContent.mAppTransition.isTransitionSet()) {
-                childTask.setSendingToBottom(false);
-            }
-            displayContent.layoutAndAssignWindowLayersIfNeeded();
+        final DisplayContent displayContent = mContainer.getDisplayContent();
+        if (displayContent.mAppTransition.isTransitionSet()) {
+            child.setSendingToBottom(false);
         }
+        displayContent.layoutAndAssignWindowLayersIfNeeded();
     }
 
-    public void positionChildAtBottom(TaskWindowContainerController child,
-            boolean includingParents) {
+    void positionChildAtBottom(Task child, boolean includingParents) {
         if (child == null) {
             // TODO: Fix the call-points that cause this to happen.
             return;
         }
 
-        synchronized (mGlobalLock) {
-            final Task childTask = child.mContainer;
-            if (childTask == null) {
-                Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
-                return;
-            }
-            mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents);
+        mContainer.positionChildAt(POSITION_BOTTOM, child, includingParents);
 
-            if (mContainer.getDisplayContent().mAppTransition.isTransitionSet()) {
-                childTask.setSendingToBottom(true);
-            }
-            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        if (mContainer.getDisplayContent().mAppTransition.isTransitionSet()) {
+            child.setSendingToBottom(true);
         }
+        mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
     }
 
     /**
@@ -179,24 +152,20 @@
      */
     public void resize(Rect bounds, SparseArray<Rect> taskBounds,
             SparseArray<Rect> taskTempInsetBounds) {
-        synchronized (mGlobalLock) {
-            if (mContainer == null) {
-                throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
-            }
-            // We might trigger a configuration change. Save the current task bounds for freezing.
-            mContainer.prepareFreezingTaskBounds();
-            if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds)
-                    && mContainer.isVisible()) {
-                mContainer.getDisplayContent().setLayoutNeeded();
-                mService.mWindowPlacerLocked.performSurfacePlacement();
-            }
+        if (mContainer == null) {
+            throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
+        }
+        // We might trigger a configuration change. Save the current task bounds for freezing.
+        mContainer.prepareFreezingTaskBounds();
+        if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds)
+                && mContainer.isVisible()) {
+            mContainer.getDisplayContent().setLayoutNeeded();
+            mService.mWindowPlacerLocked.performSurfacePlacement();
         }
     }
 
     public void onPipAnimationEndResize() {
-        synchronized (mService.mGlobalLock) {
-            mContainer.onPipAnimationEndResize();
-        }
+        mContainer.onPipAnimationEndResize();
     }
 
     /**
@@ -205,167 +174,37 @@
     public void getStackDockedModeBounds(Configuration parentConfig, Rect dockedBounds,
             Rect currentTempTaskBounds,
             Rect outStackBounds, Rect outTempTaskBounds) {
-        synchronized (mGlobalLock) {
-            if (mContainer != null) {
-                mContainer.getStackDockedModeBoundsLocked(parentConfig, dockedBounds,
-                        currentTempTaskBounds, outStackBounds, outTempTaskBounds);
-                return;
-            }
-            outStackBounds.setEmpty();
-            outTempTaskBounds.setEmpty();
+        if (mContainer != null) {
+            mContainer.getStackDockedModeBoundsLocked(parentConfig, dockedBounds,
+                    currentTempTaskBounds, outStackBounds, outTempTaskBounds);
+            return;
         }
+        outStackBounds.setEmpty();
+        outTempTaskBounds.setEmpty();
     }
 
     public void prepareFreezingTaskBounds() {
-        synchronized (mGlobalLock) {
-            if (mContainer == null) {
-                throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
-                        + " not found.");
-            }
-            mContainer.prepareFreezingTaskBounds();
+        if (mContainer == null) {
+            throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
+                    + " not found.");
         }
+        mContainer.prepareFreezingTaskBounds();
     }
 
     public void getRawBounds(Rect outBounds) {
-        synchronized (mGlobalLock) {
-            if (mContainer.matchParentBounds()) {
-                outBounds.setEmpty();
-            } else {
-                mContainer.getRawBounds(outBounds);
-            }
+        if (mContainer.matchParentBounds()) {
+            outBounds.setEmpty();
+        } else {
+            mContainer.getRawBounds(outBounds);
         }
     }
 
     public void getBounds(Rect outBounds) {
-        synchronized (mGlobalLock) {
-            if (mContainer != null) {
-                mContainer.getBounds(outBounds);
-                return;
-            }
-            outBounds.setEmpty();
+        if (mContainer != null) {
+            mContainer.getBounds(outBounds);
+            return;
         }
-    }
-
-    /**
-     * Adjusts the screen size in dp's for the {@param config} for the given params. The provided
-     * params represent the desired state of a configuration change. Since this utility is used
-     * before mContainer has been updated, any relevant properties (like {@param windowingMode})
-     * need to be passed in.
-     */
-    public void adjustConfigurationForBounds(Rect bounds,
-            Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth,
-            boolean overrideHeight, float density, Configuration config,
-            Configuration parentConfig, int windowingMode) {
-        synchronized (mGlobalLock) {
-            final TaskStack stack = mContainer;
-            final DisplayContent displayContent = stack.getDisplayContent();
-            final DisplayInfo di = displayContent.getDisplayInfo();
-            final DisplayCutout displayCutout = di.displayCutout;
-            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
-
-            // Get the insets and display bounds
-            displayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
-                    displayCutout, mTmpStableInsets);
-            displayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
-                    displayCutout, mTmpNonDecorInsets);
-            mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight);
-
-            int width;
-            int height;
-
-            final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
-
-            config.windowConfiguration.setBounds(bounds);
-            config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null);
-            boolean intersectParentBounds = false;
-
-            if (WindowConfiguration.isFloating(windowingMode)) {
-                // Floating tasks should not be resized to the screen's bounds.
-
-                if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED
-                        && bounds.width() == mTmpDisplayBounds.width()
-                        && bounds.height() == mTmpDisplayBounds.height()) {
-                    // If the bounds we are animating is the same as the fullscreen stack
-                    // dimensions, then apply the same inset calculations that we normally do for
-                    // the fullscreen stack, without intersecting it with the display bounds
-                    stableBounds.inset(mTmpStableInsets);
-                    nonDecorBounds.inset(mTmpNonDecorInsets);
-                    // Move app bounds to zero to apply intersection with parent correctly. They are
-                    // used only for evaluating width and height, so it's OK to move them around.
-                    config.windowConfiguration.getAppBounds().offsetTo(0, 0);
-                    intersectParentBounds = true;
-                }
-                width = (int) (stableBounds.width() / density);
-                height = (int) (stableBounds.height() / density);
-            } else {
-                // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
-                // area, i.e. the screen area without the system bars.
-                // Additionally task dimensions should not be bigger than its parents dimensions.
-                // The non decor inset are areas that could never be removed in Honeycomb. See
-                // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
-                intersectDisplayBoundsExcludeInsets(nonDecorBounds, bounds, mTmpNonDecorInsets,
-                        mTmpDisplayBounds, overrideWidth, overrideHeight);
-                intersectDisplayBoundsExcludeInsets(stableBounds, bounds, mTmpStableInsets,
-                        mTmpDisplayBounds, overrideWidth, overrideHeight);
-                width = Math.min((int) (stableBounds.width() / density),
-                        parentConfig.screenWidthDp);
-                height = Math.min((int) (stableBounds.height() / density),
-                        parentConfig.screenHeightDp);
-                intersectParentBounds = true;
-            }
-
-            if (intersectParentBounds && config.windowConfiguration.getAppBounds() != null) {
-                config.windowConfiguration.getAppBounds().intersect(parentAppBounds);
-            }
-
-            config.screenWidthDp = width;
-            config.screenHeightDp = height;
-            config.smallestScreenWidthDp = getSmallestWidthForTaskBounds(
-                    bounds, density, windowingMode);
-        }
-    }
-
-    /**
-     * Intersects the specified {@code inOutBounds} with the display frame that excludes the stable
-     * inset areas.
-     *
-     * @param inOutBounds The inOutBounds to subtract the stable inset areas from.
-     */
-    private void intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds,
-            Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight) {
-        mTmpRect.set(inInsetBounds);
-        mService.intersectDisplayInsetBounds(displayBounds, stableInsets, mTmpRect);
-        int leftInset = mTmpRect.left - inInsetBounds.left;
-        int topInset = mTmpRect.top - inInsetBounds.top;
-        int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect.right;
-        int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect.bottom;
-        inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
-    }
-
-    /**
-     * Calculates the smallest width for a task given the target {@param bounds} and
-     * {@param windowingMode}. Avoid using values from mContainer since they can be out-of-date.
-     *
-     * @return the smallest width to be used in the Configuration, in dips
-     */
-    private int getSmallestWidthForTaskBounds(Rect bounds, float density, int windowingMode) {
-        final DisplayContent displayContent = mContainer.getDisplayContent();
-        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-
-        if (bounds == null || (bounds.width() == displayInfo.logicalWidth &&
-                bounds.height() == displayInfo.logicalHeight)) {
-            // If the bounds are fullscreen, return the value of the fullscreen configuration
-            return displayContent.getConfiguration().smallestScreenWidthDp;
-        } else if (WindowConfiguration.isFloating(windowingMode)) {
-            // For floating tasks, calculate the smallest width from the bounds of the task
-            return (int) (Math.min(bounds.width(), bounds.height()) / density);
-        } else {
-            // Iterating across all screen orientations, and return the minimum of the task
-            // width taking into account that the bounds might change because the snap algorithm
-            // snaps to a different value
-            return displayContent.getDockedDividerController()
-                    .getSmallestWidthDpForBounds(bounds);
-        }
+        outBounds.setEmpty();
     }
 
     void requestResize(Rect bounds) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 67657d0..b10fd31 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -24,6 +24,7 @@
 import static android.content.res.Configuration.EMPTY;
 
 import static com.android.server.EventLogTags.WM_TASK_REMOVED;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS;
 import static com.android.server.wm.TaskProto.BOUNDS;
 import static com.android.server.wm.TaskProto.DEFER_REMOVAL;
@@ -38,6 +39,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.CallSuper;
+import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -54,7 +56,7 @@
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
-class Task extends WindowContainer<AppWindowToken> {
+class Task extends WindowContainer<AppWindowToken> implements ConfigurationContainerListener{
     static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
 
     // TODO: Track parent marks like this in WindowContainer.
@@ -109,16 +111,24 @@
     /** @see #setCanAffectSystemUiFlags */
     private boolean mCanAffectSystemUiFlags = true;
 
+    // TODO: remove after unification
+    TaskRecord mTaskRecord;
+
     Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
             boolean supportsPictureInPicture, TaskDescription taskDescription,
-            TaskWindowContainerController controller) {
+            TaskRecord taskRecord) {
         super(service);
         mTaskId = taskId;
         mStack = stack;
         mUserId = userId;
         mResizeMode = resizeMode;
         mSupportsPictureInPicture = supportsPictureInPicture;
-        setController(controller);
+        mTaskRecord = taskRecord;
+        if (mTaskRecord != null) {
+            // This can be null when we call createTaskInStack in WindowTestUtils. Remove this after
+            // unification.
+            mTaskRecord.registerConfigurationChangeListener(this);
+        }
         setBounds(getRequestedOverrideBounds());
         mTaskDescription = taskDescription;
 
@@ -191,10 +201,28 @@
         if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
         EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask");
         mDeferRemoval = false;
+        if (mTaskRecord != null) {
+            mTaskRecord.unregisterConfigurationChangeListener(this);
+        }
 
         super.removeImmediately();
     }
 
+    void reparent(StackWindowController stackController, int position, boolean moveParents) {
+        if (DEBUG_STACK) {
+            Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId
+                    + " to stack=" + stackController + " at " + position);
+        }
+        final TaskStack stack = stackController.mContainer;
+        if (stack == null) {
+            throw new IllegalArgumentException("reparent: could not find stack="
+                    + stackController);
+        }
+        reparent(stack, position, moveParents);
+        getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+    }
+
+
     void reparent(TaskStack stack, int position, boolean moveParents) {
         if (stack == mStack) {
             throw new IllegalArgumentException(
@@ -300,6 +328,12 @@
         return boundsChange;
     }
 
+    void resize(boolean relayout, boolean forced) {
+        if (setBounds(getRequestedOverrideBounds(), forced) != BOUNDS_CHANGE_NONE && relayout) {
+            getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
     @Override
     void onDisplayChanged(DisplayContent dc) {
         adjustBoundsForDisplayChangeIfNeeded(dc);
@@ -515,6 +549,15 @@
         return mDragResizeMode;
     }
 
+    /**
+     * Puts this task into docked drag resizing mode. See {@link DragResizeMode}.
+     *
+     * @param resizing Whether to put the task into drag resize mode.
+     */
+    public void setTaskDockedResizing(boolean resizing) {
+        setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+    }
+
     private void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) {
         if (displayContent == null) {
             return;
@@ -556,9 +599,8 @@
 
         displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) {
-            final TaskWindowContainerController controller = getController();
-            if (controller != null) {
-                controller.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
+            if (mTaskRecord != null) {
+                mTaskRecord.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
             }
         }
     }
@@ -631,6 +673,20 @@
         return null;
     }
 
+    void positionChildAtTop(AppWindowToken aToken) {
+        positionChildAt(aToken, POSITION_TOP);
+    }
+
+    void positionChildAt(AppWindowToken aToken, int position) {
+        if (aToken == null) {
+            Slog.w(TAG_WM,
+                    "Attempted to position of non-existing app");
+            return;
+        }
+
+        positionChildAt(position, aToken, false /* includeParents */);
+    }
+
     boolean isFullscreen() {
         if (useCurrentBounds()) {
             return matchParentBounds();
@@ -656,6 +712,10 @@
         mTaskDescription = taskDescription;
     }
 
+    void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) {
+        mTaskRecord.onSnapshotChanged(snapshot);
+    }
+
     TaskDescription getTaskDescription() {
         return mTaskDescription;
     }
@@ -666,11 +726,6 @@
     }
 
     @Override
-    TaskWindowContainerController getController() {
-        return (TaskWindowContainerController) super.getController();
-    }
-
-    @Override
     void forAllTasks(Consumer<Task> callback) {
         callback.accept(this);
     }
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index b6a6009..5bb6440 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -27,6 +27,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
@@ -47,6 +48,7 @@
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.server.EventLogTags.WM_TASK_CREATED;
 import static com.android.server.am.TaskRecordProto.ACTIVITIES;
 import static com.android.server.am.TaskRecordProto.ACTIVITY_TYPE;
 import static com.android.server.am.TaskRecordProto.BOUNDS;
@@ -76,10 +78,15 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
 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.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import static java.lang.Integer.MAX_VALUE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -106,8 +113,10 @@
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.DisplayMetrics;
+import android.util.EventLog;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
@@ -125,8 +134,7 @@
 import java.util.ArrayList;
 import java.util.Objects;
 
-// TODO: Make package private again once move to WM package is complete.
-public class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener {
+class TaskRecord extends ConfigurationContainer {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_ATM;
     private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
     private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
@@ -190,6 +198,13 @@
     // Do not move the stack as a part of reparenting
     static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
 
+    // The height/width divide used when fitting a task within a bounds with method
+    // {@link #fitWithinBounds}.
+    // We always want the task to to be visible in the bounds without affecting its size when
+    // fitting. To make sure this is the case, we don't adjust the task left or top side pass
+    // the input bounds right or bottom side minus the width or height divided by this value.
+    private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
+
     /**
      * The factory used to create {@link TaskRecord}. This allows OEM subclass {@link TaskRecord}.
      */
@@ -295,7 +310,8 @@
 
     private final Rect mTmpStableBounds = new Rect();
     private final Rect mTmpNonDecorBounds = new Rect();
-    private final Rect mTmpRect = new Rect();
+    private final Rect mTmpBounds = new Rect();
+    private final Rect mTmpInsets = new Rect();
 
     // Last non-fullscreen bounds the task was launched in or resized to.
     // The information is persisted and used to determine the appropriate stack to launch the
@@ -318,7 +334,8 @@
     /** Helper object used for updating override configuration. */
     private Configuration mTmpConfig = new Configuration();
 
-    private TaskWindowContainerController mWindowContainerController;
+    // TODO: remove after unification
+    Task mTask;
 
     /**
      * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
@@ -424,43 +441,54 @@
         mService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity);
     }
 
-    TaskWindowContainerController getWindowContainerController() {
-        return mWindowContainerController;
+    Task getTask() {
+        return mTask;
     }
 
-    void createWindowContainer(boolean onTop, boolean showForAllUsers) {
-        if (mWindowContainerController != null) {
-            throw new IllegalArgumentException("Window container=" + mWindowContainerController
+    void createTask(boolean onTop, boolean showForAllUsers) {
+        if (mTask != null) {
+            throw new IllegalArgumentException("mTask=" + mTask
                     + " already created for task=" + this);
         }
 
         final Rect bounds = updateOverrideConfigurationFromLaunchBounds();
-        setWindowContainerController(new TaskWindowContainerController(taskId, this,
-                getStack().getWindowContainerController(), userId, bounds,
-                mResizeMode, mSupportsPictureInPicture, onTop,
-                showForAllUsers, lastTaskDescription));
+        final StackWindowController stackController = getStack().getWindowContainerController();
+
+        if (DEBUG_STACK) {
+            Slog.i(TAG_WM, "TaskRecord: taskId=" + taskId
+                    + " stack=" + stackController + " bounds=" + bounds);
+        }
+
+        final TaskStack stack = stackController.mContainer;
+        if (stack == null) {
+            throw new IllegalArgumentException("TaskRecord: invalid stack="
+                    + stackController);
+        }
+        EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
+        mTask = new Task(taskId, stack, userId, mService.mWindowManager, mResizeMode,
+                mSupportsPictureInPicture, lastTaskDescription, this);
+        final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
+
+        if (!mDisplayedBounds.isEmpty()) {
+            mTask.setOverrideDisplayedBounds(mDisplayedBounds);
+        }
+        // We only want to move the parents to the parents if we are creating this task at the
+        // top of its stack.
+        stack.addTask(mTask, position, showForAllUsers, onTop /* moveParents */);
     }
 
-    /**
-     * Should only be invoked from {@link #createWindowContainer(boolean, boolean)}.
-     */
-    @VisibleForTesting
-    protected void setWindowContainerController(TaskWindowContainerController controller) {
-        if (mWindowContainerController != null) {
-            throw new IllegalArgumentException("Window container=" + mWindowContainerController
-                    + " already created for task=" + this);
-        }
-
-        mWindowContainerController = controller;
-        if (!mDisplayedBounds.isEmpty() && controller.mContainer != null) {
-            controller.mContainer.setOverrideDisplayedBounds(mDisplayedBounds);
-        }
+    void setTask(Task task) {
+        mTask = task;
     }
 
     void removeWindowContainer() {
         mService.getLockTaskController().clearLockedTask(this);
-        mWindowContainerController.removeContainer();
-        mWindowContainerController = null;
+        if (mTask == null) {
+            if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + taskId);
+            return;
+        }
+        mTask.removeIfPossible();
+        mTask = null;
         if (!getWindowConfiguration().persistTaskBounds()) {
             // Reset current bounds for task whose bounds shouldn't be persisted so it uses
             // default configuration the next time it launches.
@@ -469,7 +497,6 @@
         mService.getTaskChangeNotificationController().notifyTaskRemoved(taskId);
     }
 
-    @Override
     public void onSnapshotChanged(TaskSnapshot snapshot) {
         mService.getTaskChangeNotificationController().notifyTaskSnapshotChanged(taskId, snapshot);
     }
@@ -479,17 +506,20 @@
             return;
         }
         mResizeMode = resizeMode;
-        mWindowContainerController.setResizeable(resizeMode);
+        mTask.setResizeable(resizeMode);
         mService.mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
         mService.mRootActivityContainer.resumeFocusedStacksTopActivities();
     }
 
     void setTaskDockedResizing(boolean resizing) {
-        mWindowContainerController.setTaskDockedResizing(resizing);
+        if (mTask == null) {
+            Slog.w(TAG_WM, "setTaskDockedResizing: taskId " + taskId + " not found.");
+            return;
+        }
+        mTask.setTaskDockedResizing(resizing);
     }
 
     // TODO: Consolidate this with the resize() method below.
-    @Override
     public void requestResize(Rect bounds, int resizeMode) {
         mService.resizeTask(taskId, bounds, resizeMode);
     }
@@ -511,7 +541,7 @@
                 return true;
             }
 
-            if (mWindowContainerController == null) {
+            if (mTask == null) {
                 // Task doesn't exist in window manager yet (e.g. was restored from recents).
                 // All we can do for now is update the bounds so it can be used when the task is
                 // added to window manager.
@@ -558,7 +588,7 @@
                     }
                 }
             }
-            mWindowContainerController.resize(kept, forced);
+            mTask.resize(kept, forced);
 
             saveLaunchingStateIfNeeded();
 
@@ -571,11 +601,15 @@
 
     // TODO: Investigate combining with the resize() method above.
     void resizeWindowContainer() {
-        mWindowContainerController.resize(false /* relayout */, false /* forced */);
+        mTask.resize(false /* relayout */, false /* forced */);
     }
 
     void getWindowContainerBounds(Rect bounds) {
-        mWindowContainerController.getBounds(bounds);
+        if (mTask != null) {
+            mTask.getBounds(bounds);
+        } else {
+            bounds.setEmpty();
+        }
     }
 
     /**
@@ -679,7 +713,7 @@
 
             // Must reparent first in window manager to avoid a situation where AM can delete the
             // we are coming from in WM before we reparent because it became empty.
-            mWindowContainerController.reparent(toStack.getWindowContainerController(), position,
+            mTask.reparent(toStack.getWindowContainerController(), position,
                     moveStackMode == REPARENT_MOVE_STACK_TO_FRONT);
 
             final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
@@ -779,7 +813,11 @@
     }
 
     void cancelWindowTransition() {
-        mWindowContainerController.cancelWindowTransition();
+        if (mTask == null) {
+            Slog.w(TAG_WM, "cancelWindowTransition: taskId " + taskId + " not found.");
+            return;
+        }
+        mTask.cancelTaskWindowTransition();
     }
 
     /**
@@ -1190,7 +1228,7 @@
         mActivities.add(newTop);
 
         // Make sure window manager is aware of the position change.
-        mWindowContainerController.positionChildAtTop(newTop.mAppWindowToken);
+        mTask.positionChildAtTop(newTop.mAppWindowToken);
         updateEffectiveIntent();
 
         setFrontOfTask();
@@ -1275,7 +1313,7 @@
         if (r.mAppWindowToken != null) {
             // Only attempt to move in WM if the child has a controller. It is possible we haven't
             // created controller for the activity we are starting yet.
-            mWindowContainerController.positionChildAt(r.mAppWindowToken, index);
+            mTask.positionChildAt(r.mAppWindowToken, index);
         }
 
         // Make sure the list of display UID whitelists is updated
@@ -1643,8 +1681,8 @@
             }
             lastTaskDescription = new TaskDescription(label, null, iconResource, iconFilename,
                     colorPrimary, colorBackground, statusBarColor, navigationBarColor);
-            if (mWindowContainerController != null) {
-                mWindowContainerController.setTaskDescription(lastTaskDescription);
+            if (mTask != null) {
+                mTask.setTaskDescription(lastTaskDescription);
             }
             // Update the task affiliation color if we are the parent of the group
             if (taskId == mAffiliatedTaskId) {
@@ -1687,7 +1725,7 @@
         // If the task has no requested minimal size, we'd like to enforce a minimal size
         // so that the user can not render the task too small to manipulate. We don't need
         // to do this for the pinned stack as the bounds are controlled by the system.
-        if (!inPinnedWindowingMode()) {
+        if (!inPinnedWindowingMode() && mStack != null) {
             final int defaultMinSizeDp =
                     mService.mRootActivityContainer.mDefaultMinSizeOfResizeableTaskDp;
             final ActivityDisplay display =
@@ -1731,31 +1769,6 @@
     }
 
     /**
-     * @return a new Configuration for this Task, given the provided {@param bounds} and
-     *         {@param insetBounds}.
-     */
-    Configuration computeNewOverrideConfigurationForBounds(Rect bounds, Rect insetBounds) {
-        // Compute a new override configuration for the given bounds, if fullscreen bounds
-        // (bounds == null), then leave the override config unset
-        final Configuration newOverrideConfig = new Configuration();
-        if (bounds != null) {
-            newOverrideConfig.setTo(getRequestedOverrideConfiguration());
-            if (insetBounds != null && !insetBounds.isEmpty()) {
-                mTmpRect.set(insetBounds);
-                setDisplayedBounds(bounds);
-            } else {
-                mTmpRect.set(bounds);
-                setDisplayedBounds(null);
-            }
-            adjustForMinimalTaskDimensions(mTmpRect);
-            computeOverrideConfiguration(newOverrideConfig, mTmpRect,
-                    mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
-        }
-
-        return newOverrideConfig;
-    }
-
-    /**
      * Update task's override configuration based on the bounds.
      * @param bounds The bounds of the task.
      * @return True if the override configuration was updated.
@@ -1781,42 +1794,21 @@
      * @return True if the override configuration was updated.
      */
     boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) {
-        if (equivalentRequestedOverrideBounds(bounds)) {
+        final boolean hasSetDisplayedBounds = (insetBounds != null && !insetBounds.isEmpty());
+        if (hasSetDisplayedBounds) {
+            setDisplayedBounds(bounds);
+        } else {
+            setDisplayedBounds(null);
+        }
+        // "steady" bounds do not include any temporary offsets from animation or interaction.
+        Rect steadyBounds = hasSetDisplayedBounds ? insetBounds : bounds;
+        if (equivalentRequestedOverrideBounds(steadyBounds)) {
             return false;
         }
-        final Rect currentBounds = getRequestedOverrideBounds();
 
-        mTmpConfig.setTo(getRequestedOverrideConfiguration());
-        final Configuration newConfig = getRequestedOverrideConfiguration();
-
-        final boolean matchParentBounds = bounds == null || bounds.isEmpty();
-        final boolean persistBounds = getWindowConfiguration().persistTaskBounds();
-        if (matchParentBounds) {
-            if (!currentBounds.isEmpty() && persistBounds) {
-                setLastNonFullscreenBounds(currentBounds);
-            }
-            setBounds(null);
-            setDisplayedBounds(null);
-            newConfig.unset();
-        } else {
-            if (insetBounds != null && !insetBounds.isEmpty()) {
-                mTmpRect.set(insetBounds);
-                setDisplayedBounds(bounds);
-            } else {
-                mTmpRect.set(bounds);
-                setDisplayedBounds(null);
-            }
-            adjustForMinimalTaskDimensions(mTmpRect);
-            setBounds(mTmpRect);
-
-            if (mStack == null || persistBounds) {
-                setLastNonFullscreenBounds(getRequestedOverrideBounds());
-            }
-            computeOverrideConfiguration(newConfig, mTmpRect,
-                    mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
-        }
-        onRequestedOverrideConfigurationChanged(newConfig);
-        return !mTmpConfig.equals(newConfig);
+        mTmpConfig.setTo(getResolvedOverrideConfiguration());
+        setBounds(steadyBounds);
+        return !mTmpConfig.equals(getResolvedOverrideConfiguration());
     }
 
     /**
@@ -1842,6 +1834,12 @@
         if (wasInMultiWindowMode != inMultiWindowMode()) {
             mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
         }
+        if (getWindowConfiguration().persistTaskBounds()) {
+            final Rect currentBounds = getRequestedOverrideBounds();
+            if (!currentBounds.isEmpty()) {
+                setLastNonFullscreenBounds(currentBounds);
+            }
+        }
         // TODO: Should also take care of Pip mode changes here.
 
         saveLaunchingStateIfNeeded();
@@ -1869,6 +1867,45 @@
     }
 
     /**
+     * Adjust bounds to stay within stack bounds.
+     *
+     * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way
+     * that keep them unchanged, but be contained within the stack bounds.
+     *
+     * @param bounds Bounds to be adjusted.
+     * @param stackBounds Bounds within which the other bounds should remain.
+     */
+    private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
+        if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) {
+            return;
+        }
+
+        if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) {
+            final int maxRight = stackBounds.right
+                    - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER);
+            int horizontalDiff = stackBounds.left - bounds.left;
+            if ((horizontalDiff < 0 && bounds.left >= maxRight)
+                    || (bounds.left + horizontalDiff >= maxRight)) {
+                horizontalDiff = maxRight - bounds.left;
+            }
+            bounds.left += horizontalDiff;
+            bounds.right += horizontalDiff;
+        }
+
+        if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) {
+            final int maxBottom = stackBounds.bottom
+                    - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER);
+            int verticalDiff = stackBounds.top - bounds.top;
+            if ((verticalDiff < 0 && bounds.top >= maxBottom)
+                    || (bounds.top + verticalDiff >= maxBottom)) {
+                verticalDiff = maxBottom - bounds.top;
+            }
+            bounds.top += verticalDiff;
+            bounds.bottom += verticalDiff;
+        }
+    }
+
+    /**
      * Displayed bounds are used to set where the task is drawn at any given time. This is
      * separate from its actual bounds so that the app doesn't see any meaningful configuration
      * changes during transitionary periods.
@@ -1879,9 +1916,8 @@
         } else {
             mDisplayedBounds.set(bounds);
         }
-        final TaskWindowContainerController controller = getWindowContainerController();
-        if (controller != null && controller.mContainer != null) {
-            controller.mContainer.setOverrideDisplayedBounds(
+        if (mTask != null) {
+            mTask.setOverrideDisplayedBounds(
                     mDisplayedBounds.isEmpty() ? null : mDisplayedBounds);
         }
     }
@@ -1901,46 +1937,205 @@
         return !mDisplayedBounds.isEmpty();
     }
 
-    /** Clears passed config and fills it with new override values. */
-    // TODO(b/36505427): TaskRecord.computeOverrideConfiguration() is a utility method that doesn't
-    // depend on task or stacks, but uses those object to get the display to base the calculation
-    // on. Probably best to centralize calculations like this in ConfigurationContainer.
-    void computeOverrideConfiguration(Configuration config, Rect bounds,
-            boolean overrideWidth, boolean overrideHeight) {
-        mTmpNonDecorBounds.set(bounds);
-        mTmpStableBounds.set(bounds);
+    /**
+     * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than
+     * intersectBounds on a side, then the respective side will not be intersected.
+     *
+     * The assumption is that if inOutBounds is initially larger than intersectBounds, then the
+     * inset on that side is no-longer applicable. This scenario happens when a task's minimal
+     * bounds are larger than the provided parent/display bounds.
+     *
+     * @param inOutBounds the bounds to intersect.
+     * @param intersectBounds the bounds to intersect with.
+     * @param intersectInsets insets to apply to intersectBounds before intersecting.
+     */
+    private static void intersectWithInsetsIfFits(
+            Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) {
+        if (inOutBounds.right <= intersectBounds.right) {
+            inOutBounds.right =
+                    Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right);
+        }
+        if (inOutBounds.bottom <= intersectBounds.bottom) {
+            inOutBounds.bottom =
+                    Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom);
+        }
+        if (inOutBounds.left >= intersectBounds.left) {
+            inOutBounds.left =
+                    Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left);
+        }
+        if (inOutBounds.top >= intersectBounds.top) {
+            inOutBounds.top =
+                    Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top);
+        }
+    }
 
-        config.unset();
-        final Configuration parentConfig = getParent().getConfiguration();
+    /**
+     * Gets bounds with non-decor and stable insets applied respectively.
+     *
+     * If bounds overhangs the display, those edges will not get insets. See
+     * {@link #intersectWithInsetsIfFits}
+     *
+     * @param outNonDecorBounds where to place bounds with non-decor insets applied.
+     * @param outStableBounds where to place bounds with stable insets applied.
+     * @param bounds the bounds to inset.
+     */
+    private void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
+            DisplayInfo displayInfo) {
+        outNonDecorBounds.set(bounds);
+        outStableBounds.set(bounds);
+        if (getStack() == null || getStack().getDisplay() == null) {
+            return;
+        }
+        DisplayPolicy policy = getStack().getDisplay().mDisplayContent.getDisplayPolicy();
+        if (policy == null) {
+            return;
+        }
+        mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
 
-        final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        policy.getStableInsetsLw(displayInfo.rotation,
+                displayInfo.logicalWidth, displayInfo.logicalHeight, displayInfo.displayCutout,
+                mTmpInsets);
+        intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
 
-        if (mStack != null) {
-            final StackWindowController stackController = mStack.getWindowContainerController();
-            stackController.adjustConfigurationForBounds(bounds,
-                    mTmpNonDecorBounds, mTmpStableBounds, overrideWidth, overrideHeight, density,
-                    config, parentConfig, getWindowingMode());
-        } else {
-            throw new IllegalArgumentException("Expected stack when calculating override config");
+        policy.getNonDecorInsetsLw(displayInfo.rotation,
+                displayInfo.logicalWidth, displayInfo.logicalHeight, displayInfo.displayCutout,
+                mTmpInsets);
+        intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
+    }
+
+    /**
+     * Asks docked-divider controller for the smallestwidthdp given bounds.
+     * @param bounds bounds to calculate smallestwidthdp for.
+     */
+    private int getSmallestScreenWidthDpForDockedBounds(Rect bounds) {
+        DisplayContent dc = mStack.getDisplay().mDisplayContent;
+        if (dc != null) {
+            return dc.getDockedDividerController().getSmallestWidthDpForBounds(bounds);
+        }
+        return Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+    }
+
+    /**
+     * Calculates configuration values used by the client to get resources. This should be run
+     * using app-facing bounds (bounds unmodified by animations or transient interactions).
+     *
+     * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely
+     * configuring an "inherit-bounds" window which means that all configuration settings would
+     * just be inherited from the parent configuration.
+     **/
+    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Rect bounds,
+            @NonNull Configuration parentConfig) {
+        int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            windowingMode = parentConfig.windowConfiguration.getWindowingMode();
         }
 
-        config.orientation = (config.screenWidthDp <= config.screenHeightDp)
-                ? Configuration.ORIENTATION_PORTRAIT
-                : Configuration.ORIENTATION_LANDSCAPE;
+        float density = inOutConfig.densityDpi;
+        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+            density = parentConfig.densityDpi;
+        }
+        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
 
-        // For calculating screen layout, we need to use the non-decor inset screen area for the
-        // calculation for compatibility reasons, i.e. screen area without system bars that could
-        // never go away in Honeycomb.
-        final int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
-        final int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
-        // We're only overriding LONG, SIZE and COMPAT parts of screenLayout, so we start override
-        // calculation with partial default.
-        // Reducing the screen layout starting from its parent config.
-        final int sl = parentConfig.screenLayout &
-                (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
-        final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
-        final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
-        config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
+        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+        if (outAppBounds == null || outAppBounds.isEmpty()) {
+            inOutConfig.windowConfiguration.setAppBounds(bounds);
+            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+        }
+        if (windowingMode != WINDOWING_MODE_FREEFORM) {
+            final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+            if (parentAppBounds != null && !parentAppBounds.isEmpty()) {
+                outAppBounds.intersect(parentAppBounds);
+            }
+        }
+
+        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
+                || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+            if (mStack != null) {
+                final DisplayInfo di = new DisplayInfo();
+                mStack.getDisplay().mDisplay.getDisplayInfo(di);
+
+                // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
+                // area, i.e. the screen area without the system bars.
+                // The non decor inset are areas that could never be removed in Honeycomb. See
+                // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
+                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, bounds, di);
+            } else {
+                mTmpNonDecorBounds.set(bounds);
+                mTmpStableBounds.set(bounds);
+            }
+
+            if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+                inOutConfig.screenWidthDp = Math.min((int) (mTmpStableBounds.width() / density),
+                        parentConfig.screenWidthDp);
+            }
+            if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+                inOutConfig.screenHeightDp = Math.min((int) (mTmpStableBounds.height() / density),
+                        parentConfig.screenHeightDp);
+            }
+
+            if (inOutConfig.smallestScreenWidthDp
+                    == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+                if (WindowConfiguration.isFloating(windowingMode)) {
+                    // For floating tasks, calculate the smallest width from the bounds of the task
+                    inOutConfig.smallestScreenWidthDp = (int) (
+                            Math.min(bounds.width(), bounds.height()) / density);
+                } else if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
+                    // Iterating across all screen orientations, and return the minimum of the task
+                    // width taking into account that the bounds might change because the snap
+                    // algorithm snaps to a different value
+                    getSmallestScreenWidthDpForDockedBounds(bounds);
+                }
+                // otherwise, it will just inherit
+            }
+        }
+
+        if (inOutConfig.orientation == Configuration.ORIENTATION_UNDEFINED) {
+            inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+                    ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
+        }
+        if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
+            // For calculating screen layout, we need to use the non-decor inset screen area for the
+            // calculation for compatibility reasons, i.e. screen area without system bars that
+            // could never go away in Honeycomb.
+            final int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
+            final int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
+            // We're only overriding LONG, SIZE and COMPAT parts of screenLayout, so we start
+            // override calculation with partial default.
+            // Reducing the screen layout starting from its parent config.
+            final int sl = parentConfig.screenLayout
+                    & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
+            final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
+            final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
+            inOutConfig.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
+        }
+    }
+
+    // TODO(b/113900640): remove this once ActivityRecord is changed to not need it anymore.
+    void computeResolvedOverrideConfiguration(Configuration inOutConfig, Configuration parentConfig,
+            Configuration overrideConfig) {
+        inOutConfig.setTo(overrideConfig);
+
+        Rect outOverrideBounds = inOutConfig.windowConfiguration.getBounds();
+        if (outOverrideBounds != null && !outOverrideBounds.isEmpty()) {
+            adjustForMinimalTaskDimensions(outOverrideBounds);
+
+            int windowingMode = overrideConfig.windowConfiguration.getWindowingMode();
+            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+                windowingMode = parentConfig.windowConfiguration.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_FREEFORM) {
+                // by policy, make sure the window remains within parent
+                fitWithinBounds(outOverrideBounds, parentConfig.windowConfiguration.getBounds());
+            }
+
+            computeConfigResourceOverrides(inOutConfig, outOverrideBounds, parentConfig);
+        }
+    }
+
+    @Override
+    void resolveOverrideConfiguration(Configuration newParentConfig) {
+        computeResolvedOverrideConfiguration(getResolvedOverrideConfiguration(), newParentConfig,
+                getRequestedOverrideConfiguration());
     }
 
     Rect updateOverrideConfigurationFromLaunchBounds() {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 7ab4d08..01a5622 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -191,9 +191,7 @@
                 } else {
                     mCache.putSnapshot(task, snapshot);
                     mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
-                    if (task.getController() != null) {
-                        task.getController().reportSnapshotChanged(snapshot);
-                    }
+                    task.onSnapshotChanged(snapshot);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
deleted file mode 100644
index b87b65e..0000000
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static com.android.server.EventLogTags.WM_TASK_CREATED;
-import static com.android.server.wm.ConfigurationContainer.BOUNDS_CHANGE_NONE;
-import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
-import static com.android.server.wm.WindowContainer.POSITION_TOP;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.app.ActivityManager.TaskDescription;
-import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.EventLog;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Controller for the task container. This is created by activity manager to link task records to
- * the task container they use in window manager.
- *
- * Test class: {@link TaskWindowContainerControllerTests}
- */
-public class TaskWindowContainerController
-        extends WindowContainerController<Task, TaskWindowContainerListener> {
-
-    private final int mTaskId;
-    private final H mHandler;
-
-    public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
-            StackWindowController stackController, int userId, Rect bounds, int resizeMode,
-            boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers,
-            TaskDescription taskDescription) {
-        this(taskId, listener, stackController, userId, bounds, resizeMode,
-                supportsPictureInPicture, toTop, showForAllUsers, taskDescription,
-                WindowManagerService.getInstance());
-    }
-
-    public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
-            StackWindowController stackController, int userId, Rect bounds, int resizeMode,
-            boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers,
-            TaskDescription taskDescription, WindowManagerService service) {
-        super(listener, service);
-        mTaskId = taskId;
-        mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
-
-        synchronized (mGlobalLock) {
-            if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId
-                    + " stack=" + stackController + " bounds=" + bounds);
-
-            final TaskStack stack = stackController.mContainer;
-            if (stack == null) {
-                throw new IllegalArgumentException("TaskWindowContainerController: invalid stack="
-                        + stackController);
-            }
-            EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
-            final Task task = createTask(taskId, stack, userId, resizeMode,
-                    supportsPictureInPicture, taskDescription);
-            final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
-            // We only want to move the parents to the parents if we are creating this task at the
-            // top of its stack.
-            stack.addTask(task, position, showForAllUsers, toTop /* moveParents */);
-        }
-    }
-
-    @VisibleForTesting
-    Task createTask(int taskId, TaskStack stack, int userId, int resizeMode,
-            boolean supportsPictureInPicture, TaskDescription taskDescription) {
-        return new Task(taskId, stack, userId, mService, resizeMode, supportsPictureInPicture,
-                taskDescription, this);
-    }
-
-    @Override
-    public void removeContainer() {
-        synchronized (mGlobalLock) {
-            if (mContainer == null) {
-                if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + mTaskId);
-                return;
-            }
-            mContainer.removeIfPossible();
-            super.removeContainer();
-        }
-    }
-
-    void positionChildAtTop(AppWindowToken aToken) {
-        positionChildAt(aToken, POSITION_TOP);
-    }
-
-    void positionChildAt(AppWindowToken aToken, int position) {
-        synchronized (mService.mGlobalLock) {
-            if (aToken == null) {
-                Slog.w(TAG_WM,
-                        "Attempted to position of non-existing app");
-                return;
-            }
-
-            final Task task = mContainer;
-            if (task == null) {
-                throw new IllegalArgumentException("positionChildAt: invalid task=" + this);
-            }
-            task.positionChildAt(position, aToken, false /* includeParents */);
-        }
-    }
-
-    public void reparent(StackWindowController stackController, int position, boolean moveParents) {
-        synchronized (mGlobalLock) {
-            if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId
-                    + " to stack=" + stackController + " at " + position);
-            if (mContainer == null) {
-                if (DEBUG_STACK) Slog.i(TAG_WM,
-                        "reparent: could not find taskId=" + mTaskId);
-                return;
-            }
-            final TaskStack stack = stackController.mContainer;
-            if (stack == null) {
-                throw new IllegalArgumentException("reparent: could not find stack="
-                        + stackController);
-            }
-            mContainer.reparent(stack, position, moveParents);
-            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
-        }
-    }
-
-    public void setResizeable(int resizeMode) {
-        synchronized (mGlobalLock) {
-            if (mContainer != null) {
-                mContainer.setResizeable(resizeMode);
-            }
-        }
-    }
-
-    public void resize(boolean relayout, boolean forced) {
-        synchronized (mGlobalLock) {
-            if (mContainer == null) {
-                throw new IllegalArgumentException("resizeTask: taskId " + mTaskId + " not found.");
-            }
-
-            if (mContainer.setBounds(
-                    mContainer.getRequestedOverrideBounds(), forced) != BOUNDS_CHANGE_NONE
-                    && relayout) {
-                mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
-            }
-        }
-    }
-
-    public void getBounds(Rect bounds) {
-        synchronized (mGlobalLock) {
-            if (mContainer != null) {
-                mContainer.getBounds(bounds);
-                return;
-            }
-            bounds.setEmpty();
-        }
-    }
-
-    /**
-     * Puts this task into docked drag resizing mode. See {@link DragResizeMode}.
-     *
-     * @param resizing Whether to put the task into drag resize mode.
-     */
-    public void setTaskDockedResizing(boolean resizing) {
-        synchronized (mGlobalLock) {
-            if (mContainer == null) {
-                Slog.w(TAG_WM, "setTaskDockedResizing: taskId " + mTaskId + " not found.");
-                return;
-            }
-            mContainer.setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
-        }
-    }
-
-    public void cancelWindowTransition() {
-        synchronized (mGlobalLock) {
-            if (mContainer == null) {
-                Slog.w(TAG_WM, "cancelWindowTransition: taskId " + mTaskId + " not found.");
-                return;
-            }
-            mContainer.cancelTaskWindowTransition();
-        }
-    }
-
-    public void setTaskDescription(TaskDescription taskDescription) {
-        synchronized (mGlobalLock) {
-            if (mContainer == null) {
-                Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found.");
-                return;
-            }
-            mContainer.setTaskDescription(taskDescription);
-        }
-    }
-
-    public boolean isDragResizing() {
-        synchronized (mGlobalLock) {
-            return mContainer.isDragResizing();
-        }
-    }
-
-    void reportSnapshotChanged(TaskSnapshot snapshot) {
-        mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
-    }
-
-    void requestResize(Rect bounds, int resizeMode) {
-        mHandler.obtainMessage(H.REQUEST_RESIZE, resizeMode, 0, bounds).sendToTarget();
-    }
-
-    @Override
-    public String toString() {
-        return "{TaskWindowContainerController taskId=" + mTaskId + "}";
-    }
-
-    private static final class H extends Handler {
-
-        static final int REPORT_SNAPSHOT_CHANGED = 0;
-        static final int REQUEST_RESIZE = 1;
-
-        private final WeakReference<TaskWindowContainerController> mController;
-
-        H(WeakReference<TaskWindowContainerController> controller, Looper looper) {
-            super(looper);
-            mController = controller;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            final TaskWindowContainerController controller = mController.get();
-            final TaskWindowContainerListener listener = (controller != null)
-                    ? controller.mListener : null;
-            if (listener == null) {
-                return;
-            }
-            switch (msg.what) {
-                case REPORT_SNAPSHOT_CHANGED:
-                    listener.onSnapshotChanged((TaskSnapshot) msg.obj);
-                    break;
-                case REQUEST_RESIZE:
-                    listener.requestResize((Rect) msg.obj, msg.arg1);
-                    break;
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
deleted file mode 100644
index af67de3..0000000
--- a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.Rect;
-
-/**
- * Interface used by the creator of {@link TaskWindowContainerController} to listen to changes with
- * the task container.
- */
-public interface TaskWindowContainerListener extends WindowContainerListener {
-
-    /** Called when the snapshot of this task has changed. */
-    void onSnapshotChanged(TaskSnapshot snapshot);
-
-    /** Called when the task container would like its controller to resize. */
-    void requestResize(Rect bounds, int resizeMode);
-}
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 50bc247..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;
             }
         }
@@ -966,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);
@@ -1045,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);
 
@@ -2822,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));
     }
 
     /**
@@ -3144,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();
@@ -7113,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/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/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 649f1a5..4d4a7b4 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -34,7 +34,6 @@
 #include "netdbpf/BpfNetworkStats.h"
 
 using android::bpf::Stats;
-using android::bpf::hasBpfSupport;
 using android::bpf::bpfGetUidStats;
 using android::bpf::bpfGetIfaceStats;
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 240b820..d8225b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -99,6 +99,11 @@
     public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { }
 
     @Override
+    public int getPasswordComplexity() {
+        return DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+    }
+
+    @Override
     public void installUpdateFromFile(ComponentName admin,
             ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {}
 
@@ -136,4 +141,10 @@
     public boolean isUnattendedManagedKiosk() {
         return false;
     }
+
+    @Override
+    public boolean startViewCalendarEventInManagedProfile(String packageName, long eventId,
+            long start, long end, boolean allDay, int flags) {
+        return false;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bc550dc..b3afacbf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.devicepolicy;
 
 import static android.Manifest.permission.BIND_DEVICE_ADMIN;
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
@@ -40,10 +41,13 @@
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION;
 import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
 import static android.app.admin.DevicePolicyManager.DELEGATION_INSTALL_EXISTING_PACKAGE;
 import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
+import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING;
 import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_INSTALLATION;
 import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
@@ -53,6 +57,7 @@
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF;
@@ -113,6 +118,7 @@
 import android.app.admin.DevicePolicyCache;
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.NetworkEvent;
 import android.app.admin.PasswordMetrics;
@@ -124,6 +130,7 @@
 import android.app.backup.IBackupManager;
 import android.app.trust.TrustManager;
 import android.app.usage.UsageStatsManagerInternal;
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -157,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;
@@ -184,6 +190,7 @@
 import android.os.UserManagerInternal;
 import android.os.UserManagerInternal.UserRestrictionsListener;
 import android.os.storage.StorageManager;
+import android.provider.CalendarContract;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
 import android.provider.Settings;
@@ -361,9 +368,24 @@
         DELEGATION_PACKAGE_ACCESS,
         DELEGATION_PERMISSION_GRANT,
         DELEGATION_INSTALL_EXISTING_PACKAGE,
-        DELEGATION_KEEP_UNINSTALLED_PACKAGES
+        DELEGATION_KEEP_UNINSTALLED_PACKAGES,
+        DELEGATION_NETWORK_LOGGING,
+        DELEGATION_CERT_SELECTION,
+        DELEGATION_PACKAGE_INSTALLATION
     };
 
+    // Subset of delegations that can only be delegated by Device Owner.
+    private static final List<String> DEVICE_OWNER_DELEGATIONS = Arrays.asList(new String[] {
+            DELEGATION_NETWORK_LOGGING,
+            DELEGATION_PACKAGE_INSTALLATION
+    });
+
+    // Subset of delegations that only one single package within a given user can hold
+    private static final List<String> EXCLUSIVE_DELEGATIONS = Arrays.asList(new String[] {
+            DELEGATION_NETWORK_LOGGING,
+            DELEGATION_CERT_SELECTION,
+    });
+
     /**
      *  System property whose value is either "true" or "false", indicating whether
      *  device owner is present.
@@ -455,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";
@@ -4305,6 +4328,11 @@
             }
         }
 
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_WIDGET_PROVIDER)
+                .setAdmin(admin)
+                .write();
+
         if (changedProviders != null) {
             mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
             return true;
@@ -4332,6 +4360,11 @@
             }
         }
 
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_WIDGET_PROVIDER)
+                .setAdmin(admin)
+                .write();
+
         if (changedProviders != null) {
             mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
             return true;
@@ -4714,6 +4747,22 @@
     }
 
     @Override
+    @PasswordComplexity
+    public int getPasswordComplexity() {
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
+        enforceUserUnlocked(callingUserId);
+        mContext.enforceCallingOrSelfPermission(
+                GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY,
+                "Must have " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY + " permission.");
+
+        synchronized (getLockObject()) {
+            int targetUserId = getCredentialOwner(callingUserId, /* parent= */ false);
+            PasswordMetrics metrics = getUserPasswordMetricsLocked(targetUserId);
+            return metrics == null ? PASSWORD_COMPLEXITY_NONE : metrics.determineComplexity();
+        }
+    }
+
+    @Override
     public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) {
         enforceFullCrossUsersPermission(userHandle);
         synchronized (getLockObject()) {
@@ -5346,7 +5395,8 @@
     @Override
     public void enforceCanManageCaCerts(ComponentName who, String callerPackage) {
         if (who == null) {
-            if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
+            if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
+                    DELEGATION_CERT_INSTALL)) {
                 mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
             }
         } else {
@@ -5451,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);
         }
@@ -5516,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 {
@@ -5700,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) {
@@ -5728,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);
@@ -5765,13 +5842,22 @@
         }
 
         Intent intent = new Intent(DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS);
-        intent.setComponent(aliasChooser);
         intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, uid);
         intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI, uri);
         intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS, alias);
         intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE, response);
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
 
+        final ComponentName delegateReceiver;
+        delegateReceiver = resolveDelegateReceiver(DELEGATION_CERT_SELECTION,
+                DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS, caller.getIdentifier());
+
+        if (delegateReceiver != null) {
+            intent.setComponent(delegateReceiver);
+        } else {
+            intent.setComponent(aliasChooser);
+        }
+
         final long id = mInjector.binderClearCallingIdentity();
         try {
             mContext.sendOrderedBroadcastAsUser(intent, caller, null, new BroadcastReceiver() {
@@ -5781,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);
         }
@@ -5831,22 +5923,26 @@
      */
     @Override
     public void setDelegatedScopes(ComponentName who, String delegatePackage,
-            List<String> scopes) throws SecurityException {
+            List<String> scopeList) throws SecurityException {
         Preconditions.checkNotNull(who, "ComponentName is null");
         Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty");
-        Preconditions.checkCollectionElementsNotNull(scopes, "Scopes");
+        Preconditions.checkCollectionElementsNotNull(scopeList, "Scopes");
         // Remove possible duplicates.
-        scopes = new ArrayList(new ArraySet(scopes));
+        final ArrayList<String> scopes = new ArrayList(new ArraySet(scopeList));
         // Ensure given scopes are valid.
         if (scopes.retainAll(Arrays.asList(DELEGATIONS))) {
             throw new IllegalArgumentException("Unexpected delegation scopes");
         }
-
+        final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
         // Retrieve the user ID of the calling process.
         final int userId = mInjector.userHandleGetCallingUserId();
         synchronized (getLockObject()) {
             // Ensure calling process is device/profile owner.
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (hasDoDelegation) {
+                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            } else {
+                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            }
             // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N).
             if (shouldCheckIfDelegatePackageIsInstalled(delegatePackage,
                         getTargetSdk(who.getPackageName(), userId), scopes)) {
@@ -5859,31 +5955,57 @@
 
             // Set the new delegate in user policies.
             final DevicePolicyData policy = getUserData(userId);
+            List<String> exclusiveScopes = null;
             if (!scopes.isEmpty()) {
                 policy.mDelegationMap.put(delegatePackage, new ArrayList<>(scopes));
+                exclusiveScopes = new ArrayList<>(scopes);
+                exclusiveScopes.retainAll(EXCLUSIVE_DELEGATIONS);
             } else {
                 // Remove any delegation info if the given scopes list is empty.
                 policy.mDelegationMap.remove(delegatePackage);
             }
+            sendDelegationChangedBroadcast(delegatePackage, scopes, userId);
 
-            // Notify delegate package of updates.
-            final Intent intent = new Intent(
-                    DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED);
-            // Only call receivers registered with Context#registerReceiver (don’t wake delegate).
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            // Limit components this intent resolves to to the delegate package.
-            intent.setPackage(delegatePackage);
-            // Include the list of delegated scopes as an extra.
-            intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES,
-                    (ArrayList<String>) scopes);
-            // Send the broadcast.
-            mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+            // 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();
 
+                    if (!currentPackage.equals(delegatePackage)) {
+                        // Iterate through all other delegates
+                        if (currentScopes.removeAll(exclusiveScopes)) {
+                            // 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);
+                            }
+                            sendDelegationChangedBroadcast(currentPackage,
+                                    new ArrayList<>(currentScopes), userId);
+                        }
+                    }
+                }
+            }
             // Persist updates.
             saveSettingsLocked(userId);
         }
     }
 
+    private void sendDelegationChangedBroadcast(String delegatePackage, ArrayList<String> scopes,
+            int userId) {
+        // Notify delegate package of updates.
+        final Intent intent = new Intent(
+                DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED);
+        // Only call receivers registered with Context#registerReceiver (don’t wake delegate).
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        // Limit components this intent resolves to to the delegate package.
+        intent.setPackage(delegatePackage);
+        // Include the list of delegated scopes as an extra.
+        intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes);
+        // Send the broadcast.
+        mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+    }
+
     /**
      * Get the delegation scopes given to a delegate package by a device owner or profile owner.
      *
@@ -5951,17 +6073,59 @@
         synchronized (getLockObject()) {
             // Ensure calling process is device/profile owner.
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            final DevicePolicyData policy = getUserData(userId);
+            return getDelegatePackagesInternalLocked(scope, userId);
+        }
+    }
 
-            // Create a list to hold the resulting delegate packages.
-            final List<String> delegatePackagesWithScope = new ArrayList<>();
-            // Add all delegations containing scope to the result list.
-            for (int i = 0; i < policy.mDelegationMap.size(); i++) {
-                if (policy.mDelegationMap.valueAt(i).contains(scope)) {
-                    delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i));
-                }
+    private List<String> getDelegatePackagesInternalLocked(String scope, int userId) {
+        final DevicePolicyData policy = getUserData(userId);
+
+        // Create a list to hold the resulting delegate packages.
+        final List<String> delegatePackagesWithScope = new ArrayList<>();
+        // Add all delegations containing scope to the result list.
+        for (int i = 0; i < policy.mDelegationMap.size(); i++) {
+            if (policy.mDelegationMap.valueAt(i).contains(scope)) {
+                delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i));
             }
-            return delegatePackagesWithScope;
+        }
+        return delegatePackagesWithScope;
+    }
+
+    /**
+     * Return the ComponentName of the receiver that handles the given broadcast action, from
+     * the app that holds the given delegation capability. If the app defines multiple receivers
+     * with the same intent action filter, will return any one of them nondeterministically.
+     *
+     * @return ComponentName of the receiver or {@null} if none exists.
+     */
+    private ComponentName resolveDelegateReceiver(String scope, String action, int userId) {
+
+        final List<String> delegates;
+        synchronized (getLockObject()) {
+            delegates = getDelegatePackagesInternalLocked(scope, userId);
+        }
+        if (delegates.size() != 1) {
+            Slog.wtf(LOG_TAG, "More than one delegate holds " + scope);
+            return null;
+        }
+        final String pkg = delegates.get(0);
+        Intent intent = new Intent(action);
+        intent.setPackage(pkg);
+        final List<ResolveInfo> receivers;
+        try {
+            receivers = mIPackageManager.queryIntentReceivers(
+                    intent, null, 0, userId).getList();
+        } catch (RemoteException e) {
+            return null;
+        }
+        final int count = receivers.size();
+        if (count >= 1) {
+            if (count > 1) {
+                Slog.w(LOG_TAG, pkg + " defines more than one delegate receiver for " + action);
+            }
+            return receivers.get(0).activityInfo.getComponentName();
+        } else {
+            return null;
         }
     }
 
@@ -5978,15 +6142,14 @@
      * @param scope the delegation scope to be checked.
      * @return {@code true} if the calling process is a delegate of {@code scope}.
      */
-    private boolean isCallerDelegate(String callerPackage, String scope) {
+    private boolean isCallerDelegate(String callerPackage, int callerUid, String scope) {
         Preconditions.checkNotNull(callerPackage, "callerPackage is null");
         if (!Arrays.asList(DELEGATIONS).contains(scope)) {
             throw new IllegalArgumentException("Unexpected delegation scope: " + scope);
         }
 
         // Retrieve the UID and user ID of the calling process.
-        final int callingUid = mInjector.binderGetCallingUid();
-        final int userId = UserHandle.getUserId(callingUid);
+        final int userId = UserHandle.getUserId(callerUid);
         synchronized (getLockObject()) {
             // Retrieve user policy data.
             final DevicePolicyData policy = getUserData(userId);
@@ -5999,7 +6162,7 @@
                     final int uid = mInjector.getPackageManager()
                             .getPackageUidAsUser(callerPackage, userId);
                     // Return true if the caller is actually callerPackage.
-                    return uid == callingUid;
+                    return uid == callerUid;
                 } catch (NameNotFoundException e) {
                     // Ignore.
                 }
@@ -6024,15 +6187,34 @@
      */
     private void enforceCanManageScope(ComponentName who, String callerPackage, int reqPolicy,
             String scope) {
+        enforceCanManageScopeOrCheckPermission(who, callerPackage, reqPolicy, scope, null);
+    }
+
+    /**
+     * Throw a security exception if a ComponentName is given and it is not a device/profile owner
+     * OR if the calling process is not a delegate of the given scope and does not hold the
+     * required permission.
+     */
+    private void enforceCanManageScopeOrCheckPermission(@Nullable ComponentName who,
+            @NonNull String callerPackage, int reqPolicy, @NonNull String scope,
+            @Nullable String permission) {
         // If a ComponentName is given ensure it is a device or profile owner according to policy.
         if (who != null) {
             synchronized (getLockObject()) {
                 getActiveAdminForCallerLocked(who, reqPolicy);
             }
-        // If no ComponentName is given ensure calling process has scope delegation.
-        } else if (!isCallerDelegate(callerPackage, scope)) {
-            throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid()
-                    + " is not a delegate of scope " + scope + ".");
+        } else {
+            // If no ComponentName is given ensure calling process has scope delegation or required
+            // permission
+            if (isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), scope)) {
+                return;
+            }
+            if (permission == null) {
+                throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid()
+                        + " is not a delegate of scope " + scope + ".");
+            } else {
+                mContext.enforceCallingOrSelfPermission(permission, null);
+            }
         }
     }
 
@@ -6080,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
@@ -6111,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);
         }
@@ -6826,6 +7020,11 @@
                 updateScreenCaptureDisabled(userHandle, disabled);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_SCREEN_CAPTURE_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
     }
 
     /**
@@ -6897,6 +7096,11 @@
                 mInjector.binderRestoreCallingIdentity(ident);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_AUTO_TIME_REQUIRED)
+                .setAdmin(who)
+                .setBoolean(required)
+                .write();
     }
 
     /**
@@ -6971,9 +7175,16 @@
         }
     }
 
-    private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) throws SecurityException {
+    private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who)
+            throws SecurityException {
         synchronized (getLockObject()) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        }
+        ensureAllUsersAffiliated();
+    }
+
+    private void ensureAllUsersAffiliated() throws SecurityException {
+        synchronized (getLockObject()) {
             if (!areAllUsersAffiliatedWithDeviceLocked()) {
                 throw new SecurityException("Not all users are affiliated.");
             }
@@ -7021,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
@@ -7032,14 +7247,22 @@
     }
 
     void sendDeviceOwnerCommand(String action, Bundle extras) {
-        int deviceOwnerUserId;
-        ComponentName deviceOwnerComponent;
+        final int deviceOwnerUserId;
         synchronized (getLockObject()) {
             deviceOwnerUserId = mOwners.getDeviceOwnerUserId();
-            deviceOwnerComponent = mOwners.getDeviceOwnerComponent();
         }
-        sendActiveAdminCommand(action, extras, deviceOwnerUserId,
-                deviceOwnerComponent);
+
+        ComponentName receiverComponent = null;
+        if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) {
+            receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action,
+                    deviceOwnerUserId);
+        }
+        if (receiverComponent == null) {
+            synchronized (getLockObject()) {
+                receiverComponent = mOwners.getDeviceOwnerComponent();
+            }
+        }
+        sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent);
     }
 
     private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) {
@@ -7223,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();
     }
 
     /**
@@ -7374,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
@@ -7431,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);
@@ -7700,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);
@@ -7787,6 +8032,10 @@
                 mInjector.binderRestoreCallingIdentity(token);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_DEVICE_OWNER_LOCK_SCREEN_INFO)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -7973,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);
         }
@@ -8447,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
@@ -8496,7 +8756,8 @@
 
     @Override
     public boolean isCallerApplicationRestrictionsManagingPackage(String callerPackage) {
-        return isCallerDelegate(callerPackage, DELEGATION_APP_RESTRICTIONS);
+        return isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
+                DELEGATION_APP_RESTRICTIONS);
     }
 
     @Override
@@ -8509,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);
         }
@@ -8640,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
@@ -8759,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;
     }
 
@@ -8933,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;
     }
 
@@ -9473,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,
@@ -9480,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.
@@ -9488,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
@@ -9626,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,
@@ -9633,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
@@ -9707,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,
@@ -9732,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) {
@@ -9748,7 +10075,6 @@
                         }
                     }
                 }
-                return numberOfAppsInstalled;
             } catch (RemoteException e) {
                 // shouldn't happen
                 Slog.wtf(LOG_TAG, "Failed to resolve intent for: " + intent);
@@ -9757,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)
@@ -9773,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,
@@ -9791,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) {
@@ -9801,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
@@ -9864,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
@@ -9905,6 +10258,11 @@
                 saveSettingsLocked(mInjector.userHandleGetCallingUserId());
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALLER_ID_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
     }
 
     @Override
@@ -9943,6 +10301,11 @@
                 saveSettingsLocked(mInjector.userHandleGetCallingUserId());
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
     }
 
     @Override
@@ -10042,6 +10405,11 @@
                 saveSettingsLocked(UserHandle.getCallingUserId());
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_BLUETOOTH_CONTACT_SHARING_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
     }
 
     @Override
@@ -10201,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();
                 }
             }
         }
@@ -10369,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();
         }
     }
 
@@ -10398,6 +10777,10 @@
                 mInjector.binderRestoreCallingIdentity(id);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_USER_ICON)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -10423,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 {
@@ -10461,6 +10849,11 @@
                 saveSettingsLocked(userId);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_STATUS_BAR_DISABLED)
+                .setAdmin(who)
+                .setBoolean(disabled)
+                .write();
         return true;
     }
 
@@ -10715,6 +11108,24 @@
         }
 
         @Override
+        public boolean canSilentlyInstallPackage(String callerPackage, int callerUid) {
+            if (callerPackage == null) {
+                return false;
+            }
+            if (isUserAffiliatedWithDevice(UserHandle.getUserId(callerUid))
+                    && isActiveAdminWithPolicy(callerUid,
+                            DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)) {
+                // device owner or a profile owner affiliated with the device owner
+                return true;
+            }
+            if (DevicePolicyManagerService.this.isCallerDelegate(callerPackage, callerUid,
+                    DELEGATION_PACKAGE_INSTALLATION)) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
         public void reportSeparateProfileChallengeChanged(@UserIdInt int userId) {
             synchronized (getLockObject()) {
                 updateMaximumTimeToLockLocked(userId);
@@ -10863,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
@@ -11411,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);
         }
@@ -11461,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);
@@ -11482,6 +11906,10 @@
                 saveSettingsLocked(userHandle);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_SHORT_SUPPORT_MESSAGE)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -11512,6 +11940,10 @@
                 saveSettingsLocked(userHandle);
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_LONG_SUPPORT_MESSAGE)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -11577,6 +12009,10 @@
             admin.organizationColor = color;
             saveSettingsLocked(userHandle);
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_ORGANIZATION_COLOR)
+                .setAdmin(who)
+                .write();
     }
 
     @Override
@@ -12507,13 +12943,14 @@
     }
 
     @Override
-    public void setNetworkLoggingEnabled(ComponentName admin, boolean enabled) {
+    public void setNetworkLoggingEnabled(@Nullable ComponentName admin,
+            @NonNull String packageName, boolean enabled) {
         if (!mHasFeature) {
             return;
         }
         synchronized (getLockObject()) {
-            Preconditions.checkNotNull(admin);
-            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+                    DELEGATION_NETWORK_LOGGING);
 
             if (enabled == isNetworkLoggingEnabledInternalLocked()) {
                 // already in the requested state
@@ -12614,12 +13051,15 @@
     }
 
     @Override
-    public boolean isNetworkLoggingEnabled(ComponentName admin) {
+    public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin,
+            @NonNull String packageName) {
         if (!mHasFeature) {
             return false;
         }
         synchronized (getLockObject()) {
-            enforceDeviceOwnerOrManageUsers();
+            enforceCanManageScopeOrCheckPermission(admin, packageName,
+                    DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, DELEGATION_NETWORK_LOGGING,
+                    android.Manifest.permission.MANAGE_USERS);
             return isNetworkLoggingEnabledInternalLocked();
         }
     }
@@ -12637,12 +13077,14 @@
      * @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH
      */
     @Override
-    public List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, long batchToken) {
+    public List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin,
+            @NonNull String packageName, long batchToken) {
         if (!mHasFeature) {
             return null;
         }
-        Preconditions.checkNotNull(admin);
-        ensureDeviceOwnerAndAllUsersAffiliated(admin);
+        enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+                DELEGATION_NETWORK_LOGGING);
+        ensureAllUsersAffiliated();
 
         synchronized (getLockObject()) {
             if (mNetworkLogger == null
@@ -12974,6 +13416,7 @@
         }
 
         final long id = mInjector.binderClearCallingIdentity();
+        String ownerType = null;
         try {
             synchronized (getLockObject()) {
                 /*
@@ -12994,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);
@@ -13004,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);
@@ -13015,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,
@@ -13490,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);
         }
@@ -13515,6 +13970,11 @@
                 saveSettingsLocked(mInjector.userHandleGetCallingUserId());
             }
         }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE)
+                .setAdmin(who)
+                .setStrings(packageName)
+                .write();
     }
 
     @Override
@@ -13534,6 +13994,13 @@
                 saveSettingsLocked(mInjector.userHandleGetCallingUserId());
             }
         }
+        if (isRemoved) {
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE)
+                    .setAdmin(who)
+                    .setStrings(packageName)
+                    .write();
+        }
         return isRemoved;
     }
 
@@ -13658,4 +14125,59 @@
     private PowerManagerInternal getPowerManagerInternal() {
         return mInjector.getPowerManagerInternal();
     }
+
+    @Override
+    public boolean startViewCalendarEventInManagedProfile(String packageName, long eventId,
+            long start, long end, boolean allDay, int flags) {
+        if (!mHasFeature) {
+            return false;
+        }
+        Preconditions.checkStringNotEmpty(packageName, "Package name is empty");
+
+        final int callingUid = mInjector.binderGetCallingUid();
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
+        if (!isCallingFromPackage(packageName, callingUid)) {
+            throw new SecurityException("Input package name doesn't align with actual "
+                    + "calling package.");
+        }
+        final long identity = mInjector.binderClearCallingIdentity();
+        try {
+            final int workProfileUserId = getManagedUserId(callingUserId);
+            if (workProfileUserId < 0) {
+                return false;
+            }
+            if (!isPackageAllowedToAccessCalendarForUser(packageName, workProfileUserId)) {
+                Log.d(LOG_TAG, String.format("Package %s is not allowed to access cross-profile"
+                        + "calendar APIs", packageName));
+                return false;
+            }
+            final Intent intent = new Intent(CalendarContract.ACTION_VIEW_WORK_CALENDAR_EVENT);
+            intent.setPackage(packageName);
+            intent.putExtra(CalendarContract.EXTRA_EVENT_ID, eventId);
+            intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, start);
+            intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end);
+            intent.putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, allDay);
+            intent.setFlags(flags);
+            try {
+                mContext.startActivityAsUser(intent, UserHandle.of(workProfileUserId));
+            } catch (ActivityNotFoundException e) {
+                Log.e(LOG_TAG, "View event activity not found", e);
+                return false;
+            }
+        } finally {
+            mInjector.binderRestoreCallingIdentity(identity);
+        }
+        return true;
+    }
+
+    private boolean isCallingFromPackage(String packageName, int callingUid) {
+        try {
+            final int packageUid = mInjector.getPackageManager().getPackageUidAsUser(
+                    packageName, UserHandle.getUserId(callingUid));
+            return packageUid == callingUid;
+        } catch (NameNotFoundException e) {
+            Log.d(LOG_TAG, "Calling package not found", e);
+            return false;
+        }
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
index 7910598..d8a875d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java
@@ -16,6 +16,7 @@
 
 package com.android.server.devicepolicy;
 
+import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.StartInstallingUpdateCallback;
 import android.content.Context;
@@ -26,6 +27,7 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.stats.devicepolicy.DevicePolicyEnums;
 import android.util.Log;
 
 import java.io.File;
@@ -132,6 +134,10 @@
 
     protected void notifyCallbackOnError(int errorCode, String errorMessage) {
         cleanupUpdateFile();
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE_ERROR)
+                .setInt(errorCode)
+                .write();
         try {
             mCallback.onStartInstallingUpdateError(errorCode, errorMessage);
         } catch (RemoteException e) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 88f645d..cf03d61 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -109,6 +109,7 @@
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.CrossProfileAppsService;
+import com.android.server.pm.DynamicCodeLoggingService;
 import com.android.server.pm.Installer;
 import com.android.server.pm.LauncherAppsService;
 import com.android.server.pm.OtaDexoptService;
@@ -723,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
@@ -1667,6 +1672,18 @@
             traceEnd();
 
             if (!isWatch) {
+                // We don't run this on watches as there are no plans to use the data logged
+                // on watch devices.
+                traceBeginAndSlog("StartDynamicCodeLoggingService");
+                try {
+                    DynamicCodeLoggingService.schedule(context);
+                } catch (Throwable e) {
+                    reportWtf("starting DynamicCodeLoggingService", e);
+                }
+                traceEnd();
+            }
+
+            if (!isWatch) {
                 traceBeginAndSlog("StartPruneInstantAppsJobService");
                 try {
                     PruneInstantAppsJobService.schedule(context);
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index a7209a0..f037905 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -111,7 +111,7 @@
      * the last writable 32bit word.
      */
     @VisibleForTesting
-    private static enum Counter {
+    public static enum Counter {
         RESERVED_OOB,  // Points to offset 0 from the end of the buffer (out-of-bounds)
         TOTAL_PACKETS,
         PASSED_ARP,
@@ -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/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
new file mode 100644
index 0000000..8e78a56
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -0,0 +1,581 @@
+/*
+ * 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 android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+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.spy;
+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.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkPolicyManager;
+import android.os.Build;
+import android.os.SystemClock;
+import android.util.DataUnit;
+
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobSchedulerService.Constants;
+import com.android.server.net.NetworkPolicyManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ConnectivityControllerTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private ConnectivityManager mConnManager;
+    @Mock
+    private NetworkPolicyManager mNetPolicyManager;
+    @Mock
+    private NetworkPolicyManagerInternal mNetPolicyManagerInternal;
+    @Mock
+    private JobSchedulerService mService;
+
+    private Constants mConstants;
+
+    private static final int UID_RED = 10001;
+    private static final int UID_BLUE = 10002;
+
+    @Before
+    public void setUp() throws Exception {
+        // Assume all packages are current SDK
+        final PackageManagerInternal pm = mock(PackageManagerInternal.class);
+        when(pm.getPackageTargetSdkVersion(anyString()))
+                .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, pm);
+
+        LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
+        LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
+
+        // 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);
+
+        // Assume default constants for now
+        mConstants = new Constants();
+
+        // Get our mocks ready
+        when(mContext.getSystemServiceName(ConnectivityManager.class))
+                .thenReturn(Context.CONNECTIVITY_SERVICE);
+        when(mContext.getSystemService(ConnectivityManager.class))
+                .thenReturn(mConnManager);
+        when(mContext.getSystemServiceName(NetworkPolicyManager.class))
+                .thenReturn(Context.NETWORK_POLICY_SERVICE);
+        when(mContext.getSystemService(NetworkPolicyManager.class))
+                .thenReturn(mNetPolicyManager);
+        when(mService.getTestableContext()).thenReturn(mContext);
+        when(mService.getLock()).thenReturn(mService);
+        when(mService.getConstants()).thenReturn(mConstants);
+    }
+
+    @Test
+    public void testInsane() throws Exception {
+        final Network net = new Network(101);
+        final JobInfo.Builder job = createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+
+        // Slow network is too slow
+        assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net,
+                createCapabilities().setLinkUpstreamBandwidthKbps(1)
+                        .setLinkDownstreamBandwidthKbps(1), mConstants));
+        // Fast network looks great
+        assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), net,
+                createCapabilities().setLinkUpstreamBandwidthKbps(1024)
+                        .setLinkDownstreamBandwidthKbps(1024), mConstants));
+    }
+
+    @Test
+    public void testCongestion() throws Exception {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final JobInfo.Builder job = createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+        final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
+        final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
+
+        // Uncongested network is whenever
+        {
+            final Network net = new Network(101);
+            final NetworkCapabilities caps = createCapabilities()
+                    .addCapability(NET_CAPABILITY_NOT_CONGESTED);
+            assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+        }
+
+        // Congested network is more selective
+        {
+            final Network net = new Network(101);
+            final NetworkCapabilities caps = createCapabilities();
+            assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+        }
+    }
+
+    @Test
+    public void testRelaxed() throws Exception {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final JobInfo.Builder job = createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+        final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
+        final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
+
+        job.setIsPrefetch(true);
+        final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000);
+        final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000);
+
+        // Unmetered network is whenever
+        {
+            final Network net = new Network(101);
+            final NetworkCapabilities caps = createCapabilities()
+                    .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+                    .addCapability(NET_CAPABILITY_NOT_METERED);
+            assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
+        }
+
+        // Metered network is only when prefetching and late
+        {
+            final Network net = new Network(101);
+            final NetworkCapabilities caps = createCapabilities()
+                    .addCapability(NET_CAPABILITY_NOT_CONGESTED);
+            assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
+            assertFalse(ConnectivityController.isSatisfied(late, net, caps, mConstants));
+            assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
+            assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
+        }
+    }
+
+    @Test
+    public void testUpdates() throws Exception {
+        final ArgumentCaptor<NetworkCallback> callback = ArgumentCaptor
+                .forClass(NetworkCallback.class);
+        doNothing().when(mConnManager).registerNetworkCallback(any(), callback.capture());
+
+        final ConnectivityController controller = new ConnectivityController(mService);
+
+        final Network meteredNet = new Network(101);
+        final NetworkCapabilities meteredCaps = createCapabilities();
+        final Network unmeteredNet = new Network(202);
+        final NetworkCapabilities unmeteredCaps = createCapabilities()
+                .addCapability(NET_CAPABILITY_NOT_METERED);
+
+        final JobStatus red = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+        final JobStatus blue = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+        // Pretend we're offline when job is added
+        {
+            reset(mConnManager);
+            answerNetwork(UID_RED, null, null);
+            answerNetwork(UID_BLUE, null, null);
+
+            controller.maybeStartTrackingJobLocked(red, null);
+            controller.maybeStartTrackingJobLocked(blue, null);
+
+            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        }
+
+        // Metered network
+        {
+            reset(mConnManager);
+            answerNetwork(UID_RED, meteredNet, meteredCaps);
+            answerNetwork(UID_BLUE, meteredNet, meteredCaps);
+
+            callback.getValue().onCapabilitiesChanged(meteredNet, meteredCaps);
+
+            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        }
+
+        // Unmetered network background
+        {
+            reset(mConnManager);
+            answerNetwork(UID_RED, meteredNet, meteredCaps);
+            answerNetwork(UID_BLUE, meteredNet, meteredCaps);
+
+            callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
+
+            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        }
+
+        // Lost metered network
+        {
+            reset(mConnManager);
+            answerNetwork(UID_RED, unmeteredNet, unmeteredCaps);
+            answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
+
+            callback.getValue().onLost(meteredNet);
+
+            assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        }
+
+        // Specific UID was blocked
+        {
+            reset(mConnManager);
+            answerNetwork(UID_RED, null, null);
+            answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
+
+            callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
+
+            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+        }
+    }
+
+    @Test
+    public void testRequestStandbyExceptionLocked() {
+        final ConnectivityController controller = new ConnectivityController(mService);
+        final JobStatus red = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+        final JobStatus blue = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+        InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+
+        controller.requestStandbyExceptionLocked(red);
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_RED), eq(true));
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+        // Whitelisting doesn't need to be requested again.
+        controller.requestStandbyExceptionLocked(red);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+
+        controller.requestStandbyExceptionLocked(blue);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_BLUE), eq(true));
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+    }
+
+    @Test
+    public void testWouldBeReadyWithConnectivityLocked() {
+        final ConnectivityController controller = spy(new ConnectivityController(mService));
+        final JobStatus red = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+
+        doReturn(false).when(controller).isNetworkAvailable(any());
+        assertFalse(controller.wouldBeReadyWithConnectivityLocked(red));
+
+        doReturn(true).when(controller).isNetworkAvailable(any());
+        doReturn(false).when(controller).wouldBeReadyWithConstraintLocked(any(),
+                eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+        assertFalse(controller.wouldBeReadyWithConnectivityLocked(red));
+
+        doReturn(true).when(controller).isNetworkAvailable(any());
+        doReturn(true).when(controller).wouldBeReadyWithConstraintLocked(any(),
+                eq(JobStatus.CONSTRAINT_CONNECTIVITY));
+        assertTrue(controller.wouldBeReadyWithConnectivityLocked(red));
+    }
+
+    @Test
+    public void testEvaluateStateLocked_HeartbeatsOn() {
+        mConstants.USE_HEARTBEATS = true;
+        final ConnectivityController controller = new ConnectivityController(mService);
+        final JobStatus red = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+
+        controller.evaluateStateLocked(red);
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+    }
+
+    @Test
+    public void testEvaluateStateLocked_JobWithoutConnectivity() {
+        mConstants.USE_HEARTBEATS = false;
+        final ConnectivityController controller = new ConnectivityController(mService);
+        final JobStatus red = createJobStatus(createJob().setMinimumLatency(1));
+
+        controller.evaluateStateLocked(red);
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+    }
+
+    @Test
+    public void testEvaluateStateLocked_JobWouldBeReady() {
+        mConstants.USE_HEARTBEATS = false;
+        final ConnectivityController controller = spy(new ConnectivityController(mService));
+        doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any());
+        final JobStatus red = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+        final JobStatus blue = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+        InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+
+        controller.evaluateStateLocked(red);
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_RED), eq(true));
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+        // Whitelisting doesn't need to be requested again.
+        controller.evaluateStateLocked(red);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+
+        controller.evaluateStateLocked(blue);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_BLUE), eq(true));
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+    }
+
+    @Test
+    public void testEvaluateStateLocked_JobWouldNotBeReady() {
+        mConstants.USE_HEARTBEATS = false;
+        final ConnectivityController controller = spy(new ConnectivityController(mService));
+        doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any());
+        final JobStatus red = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+        final JobStatus blue = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+        InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+
+        controller.evaluateStateLocked(red);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+
+        // Test that a currently whitelisted uid is now removed.
+        controller.requestStandbyExceptionLocked(blue);
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_BLUE), eq(true));
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+        controller.evaluateStateLocked(blue);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_BLUE), eq(false));
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+    }
+
+    @Test
+    public void testReevaluateStateLocked() {
+        mConstants.USE_HEARTBEATS = false;
+        final ConnectivityController controller = spy(new ConnectivityController(mService));
+        final JobStatus redOne = createJobStatus(createJob(1)
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+        final JobStatus redTwo = createJobStatus(createJob(2)
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+        final JobStatus blue = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+        controller.maybeStartTrackingJobLocked(redOne, null);
+        controller.maybeStartTrackingJobLocked(redTwo, null);
+        controller.maybeStartTrackingJobLocked(blue, null);
+
+        InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+        controller.requestStandbyExceptionLocked(redOne);
+        controller.requestStandbyExceptionLocked(redTwo);
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_RED), eq(true));
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+
+        // Make sure nothing happens if an exception hasn't been requested.
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_BLUE));
+        controller.reevaluateStateLocked(UID_BLUE);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_BLUE), anyBoolean());
+
+        // Make sure a job that isn't being tracked doesn't cause issues.
+        assertFalse(controller.isStandbyExceptionRequestedLocked(12345));
+        controller.reevaluateStateLocked(12345);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(12345), anyBoolean());
+
+        // Both jobs would still be ready. Exception should not be revoked.
+        doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any());
+        controller.reevaluateStateLocked(UID_RED);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+
+        // One job is still ready. Exception should not be revoked.
+        doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(eq(redOne));
+        doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(eq(redTwo));
+        controller.reevaluateStateLocked(UID_RED);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(eq(UID_RED), anyBoolean());
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+
+        // Both jobs are not ready. Exception should be revoked.
+        doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any());
+        controller.reevaluateStateLocked(UID_RED);
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_RED), eq(false));
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+    }
+
+    @Test
+    public void testMaybeRevokeStandbyExceptionLocked() {
+        final ConnectivityController controller = new ConnectivityController(mService);
+        final JobStatus red = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
+        final JobStatus blue = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+
+        InOrder inOrder = inOrder(mNetPolicyManagerInternal);
+        controller.requestStandbyExceptionLocked(red);
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_RED), eq(true));
+
+        // Try revoking for blue instead of red. Red should still have an exception requested.
+        controller.maybeRevokeStandbyExceptionLocked(blue);
+        inOrder.verify(mNetPolicyManagerInternal, never())
+                .setAppIdleWhitelist(anyInt(), anyBoolean());
+        assertTrue(controller.isStandbyExceptionRequestedLocked(UID_RED));
+
+        // Now revoke for red.
+        controller.maybeRevokeStandbyExceptionLocked(red);
+        inOrder.verify(mNetPolicyManagerInternal, times(1))
+                .setAppIdleWhitelist(eq(UID_RED), eq(false));
+        assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
+    }
+
+    private void answerNetwork(int uid, Network net, NetworkCapabilities caps) {
+        when(mConnManager.getActiveNetworkForUid(eq(uid))).thenReturn(net);
+        when(mConnManager.getNetworkCapabilities(eq(net))).thenReturn(caps);
+        if (net != null) {
+            final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, null, null);
+            ni.setDetailedState(DetailedState.CONNECTED, null, null);
+            when(mConnManager.getNetworkInfoForUid(eq(net), eq(uid), anyBoolean())).thenReturn(ni);
+        }
+    }
+
+    private static NetworkCapabilities createCapabilities() {
+        return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_VALIDATED);
+    }
+
+    private static JobInfo.Builder createJob() {
+        return createJob(101);
+    }
+
+    private static JobInfo.Builder createJob(int jobId) {
+        return new JobInfo.Builder(jobId, new ComponentName("foo", "bar"));
+    }
+
+    private static JobStatus createJobStatus(JobInfo.Builder job) {
+        return createJobStatus(job, android.os.Process.NOBODY_UID, 0, Long.MAX_VALUE);
+    }
+
+    private static JobStatus createJobStatus(JobInfo.Builder job, int uid) {
+        return createJobStatus(job, uid, 0, Long.MAX_VALUE);
+    }
+
+    private static JobStatus createJobStatus(JobInfo.Builder job,
+            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
+        return createJobStatus(job, android.os.Process.NOBODY_UID,
+                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis);
+    }
+
+    private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
+            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
+        return new JobStatus(job.build(), uid, null, -1, 0, 0, null,
+                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0);
+    }
+}
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 b2ec835..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;
@@ -87,7 +89,6 @@
     private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
     private static final String TAG_CLEANUP = "*job.cleanup*";
     private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
-    private static final long IN_QUOTA_BUFFER_MILLIS = 30 * SECOND_IN_MILLIS;
     private static final int CALLING_UID = 1000;
     private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
     private static final int SOURCE_USER_ID = 0;
@@ -132,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.
@@ -271,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",
@@ -287,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
@@ -341,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.
@@ -365,7 +747,8 @@
         final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
         // Counting backwards, the quota will come back one minute before the end.
         final long expectedAlarmTime =
-                end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS;
+                end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS
+                        + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
         mQuotaController.saveTimingSession(0, "com.android.test",
                 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1));
         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
@@ -415,7 +798,8 @@
 
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
-        final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS;
+        final long expectedAlarmTime =
+                start + 8 * HOUR_IN_MILLIS + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
         mQuotaController.saveTimingSession(0, "com.android.test",
                 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
@@ -468,7 +852,8 @@
         // Counting backwards, the first minute in the session is over the allowed time, so it
         // needs to be excluded.
         final long expectedAlarmTime =
-                start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS;
+                start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
+                        + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
         mQuotaController.saveTimingSession(0, "com.android.test",
                 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
@@ -523,25 +908,28 @@
 
         // 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.
         final long expectedWorkingAlarmTime =
-                outOfQuotaTime + (2 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS;
+                outOfQuotaTime + (2 * HOUR_IN_MILLIS)
+                        + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
         inOrder.verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
 
         final long expectedFrequentAlarmTime =
-                outOfQuotaTime + (8 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS;
+                outOfQuotaTime + (8 * HOUR_IN_MILLIS)
+                        + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
         inOrder.verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
 
         final long expectedRareAlarmTime =
-                outOfQuotaTime + (24 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS;
+                outOfQuotaTime + (24 * HOUR_IN_MILLIS)
+                        + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
         inOrder.verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
@@ -561,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 {
@@ -593,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();
 
@@ -603,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
@@ -614,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();
 
@@ -623,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;
@@ -631,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();
 
@@ -640,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. */
@@ -775,6 +1287,139 @@
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
 
+    /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
+    @Test
+    public void testTimerTracking_AllBackground() {
+        setDischarging();
+
+        JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
+        mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+
+        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        List<TimingSession> expected = new ArrayList<>();
+
+        // Test single job.
+        long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.prepareForExecutionLocked(jobStatus);
+        advanceElapsedClock(5 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+        expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
+        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        // Test overlapping jobs.
+        JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
+        mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
+
+        JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
+        mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
+
+        advanceElapsedClock(SECOND_IN_MILLIS);
+
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+        mQuotaController.prepareForExecutionLocked(jobStatus);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.prepareForExecutionLocked(jobStatus2);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.prepareForExecutionLocked(jobStatus3);
+        advanceElapsedClock(20 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+        expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
+        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+    }
+
+    /** Tests that Timers don't count foreground jobs. */
+    @Test
+    public void testTimerTracking_AllForeground() {
+        setDischarging();
+
+        JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
+        jobStatus.uidActive = true;
+        mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+
+        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        mQuotaController.prepareForExecutionLocked(jobStatus);
+        advanceElapsedClock(5 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+    }
+
+    /**
+     * Tests that Timers properly track overlapping foreground and background jobs.
+     */
+    @Test
+    public void testTimerTracking_ForegroundAndBackground() {
+        setDischarging();
+
+        JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
+        JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
+        JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
+        jobFg3.uidActive = true;
+        mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
+        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+        List<TimingSession> expected = new ArrayList<>();
+
+        // UID starts out inactive.
+        long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.prepareForExecutionLocked(jobBg1);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        advanceElapsedClock(SECOND_IN_MILLIS);
+
+        // Bg job starts while inactive, spans an entire active session, and ends after the
+        // active session.
+        // Fg job starts after the bg job and ends before the bg job.
+        // Entire bg job duration should be counted since it started before active session. However,
+        // count should only be 1 since Timer shouldn't count fg jobs.
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+        mQuotaController.prepareForExecutionLocked(jobBg2);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.prepareForExecutionLocked(jobFg3);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+        expected.add(createTimingSession(start, 30 * SECOND_IN_MILLIS, 1));
+        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+        advanceElapsedClock(SECOND_IN_MILLIS);
+
+        // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
+        // "inactive" and then bg job 2 starts. Then fg job ends.
+        // This should result in two TimingSessions with a count of one each.
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
+        mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
+        mQuotaController.prepareForExecutionLocked(jobBg1);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.prepareForExecutionLocked(jobFg3);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+        expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
+        advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
+        start = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.prepareForExecutionLocked(jobBg2);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
+        advanceElapsedClock(10 * SECOND_IN_MILLIS);
+        mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+        expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
+        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+    }
+
     /**
      * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
      * its quota.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
new file mode 100644
index 0000000..db69242
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.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.when;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+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 android.util.proto.ProtoOutputStream;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.IndentingPrintWriter;
+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.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.util.function.Predicate;
+
+@RunWith(AndroidJUnit4.class)
+public class StateControllerTest {
+    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 int CALLING_UID = 1000;
+    private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
+    private static final int SOURCE_USER_ID = 0;
+
+    private Constants mConstants;
+    private StateController mStateController;
+
+    private MockitoSession mMockingSession;
+    @Mock
+    private AlarmManager mAlarmManager;
+    @Mock
+    private Context mContext;
+    @Mock
+    private JobSchedulerService mJobSchedulerService;
+
+    private class TestStateController extends StateController {
+        TestStateController(JobSchedulerService service) {
+            super(service);
+        }
+
+        public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+        }
+
+        public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
+                boolean forUpdate) {
+        }
+
+        public void dumpControllerStateLocked(IndentingPrintWriter pw,
+                Predicate<JobStatus> predicate) {
+        }
+
+        public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
+                Predicate<JobStatus> predicate) {
+        }
+    }
+
+    @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 QuotaController constructor.
+        // 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.
+        mStateController = new TestStateController(mJobSchedulerService);
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private JobStatus createJobStatus(String testTag, int jobId) {
+        JobInfo jobInfo = new JobInfo.Builder(jobId,
+                new ComponentName(mContext, "TestQuotaJobService"))
+                .setMinimumLatency(Math.abs(jobId) + 1)
+                .build();
+        return JobStatus.createFromJobInfo(
+                jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+    }
+
+    @Test
+    public void testWouldBeReadyWithConstraintLocked() {
+        JobStatus job = spy(createJobStatus("testWouldBeReadyWithConstraintLocked", 1));
+
+        when(job.wouldBeReadyWithConstraint(anyInt())).thenReturn(false);
+        assertFalse(mStateController.wouldBeReadyWithConstraintLocked(job, 1));
+
+        when(job.wouldBeReadyWithConstraint(anyInt())).thenReturn(true);
+        when(mJobSchedulerService.areComponentsInPlaceLocked(job)).thenReturn(false);
+        assertFalse(mStateController.wouldBeReadyWithConstraintLocked(job, 1));
+
+        when(job.wouldBeReadyWithConstraint(anyInt())).thenReturn(true);
+        when(mJobSchedulerService.areComponentsInPlaceLocked(job)).thenReturn(true);
+        assertTrue(mStateController.wouldBeReadyWithConstraintLocked(job, 1));
+    }
+}
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/src/com/android/server/StorageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
index e6b328a..ec5d93e 100644
--- a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
@@ -29,8 +29,7 @@
 import android.os.UserManagerInternal;
 import android.os.storage.StorageManagerInternal;
 
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.os.Zygote;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -38,6 +37,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class StorageManagerServiceTest {
@@ -97,15 +99,15 @@
         when(mPm.getPackagesForUid(eq(UID_GREY))).thenReturn(new String[] { PKG_GREY });
         when(mPm.getPackagesForUid(eq(UID_COLORS))).thenReturn(new String[] { PKG_RED, PKG_BLUE });
 
-        setIsAppStorageSandboxed(PID_BLUE, UID_COLORS, true);
-        setIsAppStorageSandboxed(PID_GREY, UID_GREY, true);
-        setIsAppStorageSandboxed(PID_RED, UID_COLORS, true);
+        setStorageMountMode(PID_BLUE, UID_COLORS, Zygote.MOUNT_EXTERNAL_WRITE);
+        setStorageMountMode(PID_GREY, UID_GREY, Zygote.MOUNT_EXTERNAL_WRITE);
+        setStorageMountMode(PID_RED, UID_COLORS, Zygote.MOUNT_EXTERNAL_WRITE);
 
         mService = new StorageManagerService(mContext);
     }
 
-    private void setIsAppStorageSandboxed(int pid, int uid, boolean sandboxed) {
-        when(mAmi.isAppStorageSandboxed(pid, uid)).thenReturn(sandboxed);
+    private void setStorageMountMode(int pid, int uid, int mountMode) {
+        when(mAmi.getStorageMountMode(pid, uid)).thenReturn(mountMode);
     }
 
     @Test
@@ -210,7 +212,7 @@
 
     @Test
     public void testPackageNotSandboxed() throws Exception {
-        setIsAppStorageSandboxed(PID_RED, UID_COLORS, false);
+        setStorageMountMode(PID_RED, UID_COLORS, Zygote.MOUNT_EXTERNAL_FULL);
 
         // Both app and system have the same view
         assertTranslation(
@@ -224,6 +226,29 @@
                 PID_RED, UID_COLORS);
     }
 
+    @Test
+    public void testInstallerPackage() throws Exception {
+        setStorageMountMode(PID_GREY, UID_GREY, Zygote.MOUNT_EXTERNAL_INSTALLER);
+
+        assertTranslation(
+                "/storage/emulated/0/Android/obb/com.grey/foo.jpg",
+                "/storage/emulated/0/Android/obb/com.grey/foo.jpg",
+                PID_GREY, UID_GREY);
+        assertTranslation(
+                "/storage/emulated/0/Android/obb/com.blue/bar.jpg",
+                "/storage/emulated/0/Android/obb/com.blue/bar.jpg",
+                PID_GREY, UID_GREY);
+
+        assertTranslation(
+                "/storage/emulated/0/Android/data/com.grey/foo.jpg",
+                "/storage/emulated/0/Android/data/com.grey/foo.jpg",
+                PID_GREY, UID_GREY);
+        assertTranslation(
+                "/storage/emulated/0/Android/sandbox/com.grey/Android/data/com.blue/bar.jpg",
+                "/storage/emulated/0/Android/data/com.blue/bar.jpg",
+                PID_GREY, UID_GREY);
+    }
+
     private void assertTranslation(String system, String sandbox,
             int pid, int uid) throws Exception {
         assertEquals(system,
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 c3a0dda..38e8ac2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -21,6 +21,9 @@
 import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
 import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
@@ -48,6 +51,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+import static org.testng.Assert.assertThrows;
 
 import android.Manifest.permission;
 import android.annotation.RawRes;
@@ -68,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;
@@ -1985,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));
     }
 
@@ -5133,6 +5135,71 @@
         });
     }
 
+    public void testGetPasswordComplexity_securityExceptionIfParentInstance() {
+        assertThrows(SecurityException.class,
+                () -> new DevicePolicyManagerTestable(
+                        mServiceContext,
+                        dpms,
+                        /* parentInstance= */ true)
+                        .getPasswordComplexity());
+    }
+
+    public void testGetPasswordComplexity_illegalStateExceptionIfLocked() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(false);
+        assertThrows(IllegalStateException.class, () -> dpm.getPasswordComplexity());
+    }
+
+    public void testGetPasswordComplexity_securityExceptionWithoutPermissions() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(true);
+        assertThrows(SecurityException.class, () -> dpm.getPasswordComplexity());
+    }
+
+
+    public void testGetPasswordComplexity_currentUserNoPassword() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(true);
+        mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+        when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(DpmMockContext.CALLER_USER_HANDLE);
+
+        assertEquals(PASSWORD_COMPLEXITY_NONE, dpm.getPasswordComplexity());
+    }
+
+    public void testGetPasswordComplexity_currentUserHasPassword() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(true);
+        mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+        when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(DpmMockContext.CALLER_USER_HANDLE);
+        dpms.mUserPasswordMetrics.put(
+                DpmMockContext.CALLER_USER_HANDLE,
+                PasswordMetrics.computeForPassword("asdf"));
+
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity());
+    }
+
+    public void testGetPasswordComplexity_unifiedChallengeReturnsParentUserPassword() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(true);
+        mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+
+        UserInfo parentUser = new UserInfo();
+        parentUser.id = DpmMockContext.CALLER_USER_HANDLE + 10;
+        when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(parentUser.id);
+
+        dpms.mUserPasswordMetrics.put(
+                DpmMockContext.CALLER_USER_HANDLE,
+                PasswordMetrics.computeForPassword("asdf"));
+        dpms.mUserPasswordMetrics.put(
+                parentUser.id,
+                PasswordMetrics.computeForPassword("parentUser"));
+
+        assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
+    }
+
     private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
         final long ident = mServiceContext.binder.clearCallingIdentity();
         mServiceContext.binder.callingUid =
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
index 3da61d6..4982d6e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
@@ -26,7 +26,12 @@
 
     public DevicePolicyManagerTestable(DpmMockContext context,
             DevicePolicyManagerServiceTestable dpms) {
-        super(context, dpms, /* parentInstance = */ false);
+        this(context, dpms, /* parentInstance= */ false);
+    }
+
+    public DevicePolicyManagerTestable(DpmMockContext context,
+            DevicePolicyManagerServiceTestable dpms, boolean parentInstance) {
+        super(context, dpms, parentInstance);
         this.dpms = dpms;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index b421280..e9bfa8f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -18,11 +18,21 @@
 
 import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.Curve;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayViewport;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.input.InputManagerInternal;
 import android.os.Handler;
@@ -31,9 +41,9 @@
 import android.view.DisplayInfo;
 import android.view.SurfaceControl;
 
-import androidx.test.runner.AndroidJUnit4;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -49,17 +59,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
 import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.mock;
+import java.util.stream.LongStream;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -397,6 +398,43 @@
         displayManager.validateBrightnessConfiguration(null);
     }
 
+    /**
+     * Tests that collection of display color sampling results are sensible.
+     */
+    @Test
+    public void testDisplayedContentSampling() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(0);
+        assertNotNull(ddi);
+
+        DisplayedContentSamplingAttributes attr =
+                displayManager.getDisplayedContentSamplingAttributesInternal(0);
+        if (attr == null) return; //sampling not supported on device, skip remainder of test.
+
+        boolean enabled = displayManager.setDisplayedContentSamplingEnabledInternal(0, true, 0, 0);
+        assertTrue(!enabled);
+
+        displayManager.setDisplayedContentSamplingEnabledInternal(0, false, 0, 0);
+        DisplayedContentSample sample = displayManager.getDisplayedContentSampleInternal(0, 0, 0);
+        assertNotNull(sample);
+
+        long numPixels = ddi.width * ddi.height * sample.getNumFrames();
+        long[] samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL0);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL1);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL3);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+    }
+
     private void registerDefaultDisplays(DisplayManagerService displayManager) {
         Handler handler = displayManager.getDisplayHandler();
         // Would prefer to call displayManager.onStart() directly here but it performs binderService
diff --git a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
deleted file mode 100644
index 5b59e60..0000000
--- a/services/tests/servicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ /dev/null
@@ -1,313 +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 com.android.server.job.controllers;
-
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.when;
-
-import android.app.job.JobInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManagerInternal;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkPolicyManager;
-import android.os.Build;
-import android.os.SystemClock;
-import android.util.DataUnit;
-
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobSchedulerService.Constants;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.time.Clock;
-import java.time.ZoneOffset;
-
-@RunWith(MockitoJUnitRunner.class)
-public class ConnectivityControllerTest {
-
-    @Mock private Context mContext;
-    @Mock private ConnectivityManager mConnManager;
-    @Mock private NetworkPolicyManager mNetPolicyManager;
-    @Mock private JobSchedulerService mService;
-
-    private Constants mConstants;
-
-    private static final int UID_RED = 10001;
-    private static final int UID_BLUE = 10002;
-
-    @Before
-    public void setUp() throws Exception {
-        // Assume all packages are current SDK
-        final PackageManagerInternal pm = mock(PackageManagerInternal.class);
-        when(pm.getPackageTargetSdkVersion(anyString()))
-                .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT);
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.addService(PackageManagerInternal.class, pm);
-
-        // 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);
-
-        // Assume default constants for now
-        mConstants = new Constants();
-
-        // Get our mocks ready
-        when(mContext.getSystemServiceName(ConnectivityManager.class))
-                .thenReturn(Context.CONNECTIVITY_SERVICE);
-        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
-                .thenReturn(mConnManager);
-        when(mContext.getSystemServiceName(NetworkPolicyManager.class))
-                .thenReturn(Context.NETWORK_POLICY_SERVICE);
-        when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE))
-                .thenReturn(mNetPolicyManager);
-        when(mService.getTestableContext()).thenReturn(mContext);
-        when(mService.getLock()).thenReturn(mService);
-        when(mService.getConstants()).thenReturn(mConstants);
-    }
-
-    @Test
-    public void testInsane() throws Exception {
-        final Network net = new Network(101);
-        final JobInfo.Builder job = createJob()
-                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
-                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
-
-        // Slow network is too slow
-        assertFalse(ConnectivityController.isSatisfied(createJobStatus(job), net,
-                createCapabilities().setLinkUpstreamBandwidthKbps(1)
-                        .setLinkDownstreamBandwidthKbps(1), mConstants));
-        // Fast network looks great
-        assertTrue(ConnectivityController.isSatisfied(createJobStatus(job), net,
-                createCapabilities().setLinkUpstreamBandwidthKbps(1024)
-                        .setLinkDownstreamBandwidthKbps(1024), mConstants));
-    }
-
-    @Test
-    public void testCongestion() throws Exception {
-        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        final JobInfo.Builder job = createJob()
-                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
-                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
-        final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
-        final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
-
-        // Uncongested network is whenever
-        {
-            final Network net = new Network(101);
-            final NetworkCapabilities caps = createCapabilities()
-                    .addCapability(NET_CAPABILITY_NOT_CONGESTED);
-            assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
-            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
-        }
-
-        // Congested network is more selective
-        {
-            final Network net = new Network(101);
-            final NetworkCapabilities caps = createCapabilities();
-            assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
-            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
-        }
-    }
-
-    @Test
-    public void testRelaxed() throws Exception {
-        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        final JobInfo.Builder job = createJob()
-                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1))
-                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
-        final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
-        final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
-
-        job.setIsPrefetch(true);
-        final JobStatus earlyPrefetch = createJobStatus(job, now - 1000, now + 2000);
-        final JobStatus latePrefetch = createJobStatus(job, now - 2000, now + 1000);
-
-        // Unmetered network is whenever
-        {
-            final Network net = new Network(101);
-            final NetworkCapabilities caps = createCapabilities()
-                    .addCapability(NET_CAPABILITY_NOT_CONGESTED)
-                    .addCapability(NET_CAPABILITY_NOT_METERED);
-            assertTrue(ConnectivityController.isSatisfied(early, net, caps, mConstants));
-            assertTrue(ConnectivityController.isSatisfied(late, net, caps, mConstants));
-            assertTrue(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
-            assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
-        }
-
-        // Metered network is only when prefetching and late
-        {
-            final Network net = new Network(101);
-            final NetworkCapabilities caps = createCapabilities()
-                    .addCapability(NET_CAPABILITY_NOT_CONGESTED);
-            assertFalse(ConnectivityController.isSatisfied(early, net, caps, mConstants));
-            assertFalse(ConnectivityController.isSatisfied(late, net, caps, mConstants));
-            assertFalse(ConnectivityController.isSatisfied(earlyPrefetch, net, caps, mConstants));
-            assertTrue(ConnectivityController.isSatisfied(latePrefetch, net, caps, mConstants));
-        }
-    }
-
-    @Test
-    public void testUpdates() throws Exception {
-        final ArgumentCaptor<NetworkCallback> callback = ArgumentCaptor
-                .forClass(NetworkCallback.class);
-        doNothing().when(mConnManager).registerNetworkCallback(any(), callback.capture());
-
-        final ConnectivityController controller = new ConnectivityController(mService);
-
-        final Network meteredNet = new Network(101);
-        final NetworkCapabilities meteredCaps = createCapabilities();
-        final Network unmeteredNet = new Network(202);
-        final NetworkCapabilities unmeteredCaps = createCapabilities()
-                .addCapability(NET_CAPABILITY_NOT_METERED);
-
-        final JobStatus red = createJobStatus(createJob()
-                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
-                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
-        final JobStatus blue = createJobStatus(createJob()
-                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
-                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
-
-        // Pretend we're offline when job is added
-        {
-            reset(mConnManager);
-            answerNetwork(UID_RED, null, null);
-            answerNetwork(UID_BLUE, null, null);
-
-            controller.maybeStartTrackingJobLocked(red, null);
-            controller.maybeStartTrackingJobLocked(blue, null);
-
-            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-        }
-
-        // Metered network
-        {
-            reset(mConnManager);
-            answerNetwork(UID_RED, meteredNet, meteredCaps);
-            answerNetwork(UID_BLUE, meteredNet, meteredCaps);
-
-            callback.getValue().onCapabilitiesChanged(meteredNet, meteredCaps);
-
-            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-        }
-
-        // Unmetered network background
-        {
-            reset(mConnManager);
-            answerNetwork(UID_RED, meteredNet, meteredCaps);
-            answerNetwork(UID_BLUE, meteredNet, meteredCaps);
-
-            callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
-
-            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-        }
-
-        // Lost metered network
-        {
-            reset(mConnManager);
-            answerNetwork(UID_RED, unmeteredNet, unmeteredCaps);
-            answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
-
-            callback.getValue().onLost(meteredNet);
-
-            assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-        }
-
-        // Specific UID was blocked
-        {
-            reset(mConnManager);
-            answerNetwork(UID_RED, null, null);
-            answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
-
-            callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
-
-            assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-            assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
-        }
-    }
-
-    private void answerNetwork(int uid, Network net, NetworkCapabilities caps) {
-        when(mConnManager.getActiveNetworkForUid(eq(uid))).thenReturn(net);
-        when(mConnManager.getNetworkCapabilities(eq(net))).thenReturn(caps);
-        if (net != null) {
-            final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, null, null);
-            ni.setDetailedState(DetailedState.CONNECTED, null, null);
-            when(mConnManager.getNetworkInfoForUid(eq(net), eq(uid), anyBoolean())).thenReturn(ni);
-        }
-    }
-
-    private static NetworkCapabilities createCapabilities() {
-        return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET)
-                .addCapability(NET_CAPABILITY_VALIDATED);
-    }
-
-    private static JobInfo.Builder createJob() {
-        return new JobInfo.Builder(101, new ComponentName("foo", "bar"));
-    }
-
-    private static JobStatus createJobStatus(JobInfo.Builder job) {
-        return createJobStatus(job, android.os.Process.NOBODY_UID, 0, Long.MAX_VALUE);
-    }
-
-    private static JobStatus createJobStatus(JobInfo.Builder job, int uid) {
-        return createJobStatus(job, uid, 0, Long.MAX_VALUE);
-    }
-
-    private static JobStatus createJobStatus(JobInfo.Builder job,
-            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
-        return createJobStatus(job, android.os.Process.NOBODY_UID,
-                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis);
-    }
-
-    private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
-            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
-        return new JobStatus(job.build(), uid, null, -1, 0, 0, null,
-                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
index 87c3cd2..3b6b48b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java
@@ -16,14 +16,20 @@
 
 package com.android.server.pm.dex;
 
-import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
 import android.os.storage.StorageManager;
 
 import androidx.test.filters.SmallTest;
@@ -43,13 +49,12 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.mockito.quality.Strictness;
-
-import java.util.Arrays;
+import org.mockito.stubbing.Stubber;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DexLoggerTests {
-    private static final String PACKAGE_NAME = "package.name";
+    private static final String OWNING_PACKAGE_NAME = "package.name";
     private static final String VOLUME_UUID = "volUuid";
     private static final String DEX_PATH = "/bar/foo.jar";
     private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE;
@@ -66,6 +71,7 @@
     };
     private static final String CONTENT_HASH =
             "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20";
+    private static final byte[] EMPTY_BYTES = {};
 
     @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
 
@@ -73,92 +79,191 @@
     @Mock Installer mInstaller;
     private final Object mInstallLock = new Object();
 
-    private DexManager.Listener mListener;
+    private PackageDynamicCodeLoading mPackageDynamicCodeLoading;
+    private DexLogger mDexLogger;
 
     private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create();
+    private boolean mWriteTriggered = false;
+    private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH =
+            DEX_FILENAME_HASH + " " + CONTENT_HASH;
 
     @Before
-    public void setup() {
+    public void setup() throws Exception {
+        // Disable actually attempting to do file writes.
+        mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() {
+            @Override
+            void maybeWriteAsync() {
+                mWriteTriggered = true;
+            }
+
+            @Override
+            protected void writeNow(Void data) {
+                throw new AssertionError("These tests should never call this method.");
+            }
+        };
+
         // For test purposes capture log messages as well as sending to the event log.
-        mListener = new DexLogger(mPM, mInstaller, mInstallLock) {
-                @Override
+        mDexLogger = new DexLogger(mPM, mInstaller, mInstallLock, mPackageDynamicCodeLoading) {
+            @Override
                 void writeDclEvent(int uid, String message) {
                     super.writeDclEvent(uid, message);
                     mMessagesForUid.put(uid, message);
                 }
             };
+
+        // Make the owning package exist in our mock PackageManager.
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.deviceProtectedDataDir = "/bar";
+        appInfo.uid = OWNER_UID;
+        appInfo.volumeUuid = VOLUME_UUID;
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.applicationInfo = appInfo;
+
+        doReturn(packageInfo).when(mPM)
+                .getPackageInfo(OWNING_PACKAGE_NAME, /*flags*/ 0, OWNER_USER_ID);
     }
 
     @Test
-    public void testSingleAppWithFileHash() throws Exception {
-        doReturn(CONTENT_HASH_BYTES).when(mInstaller).hashSecondaryDexFile(
-            DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+    public void testOneLoader_ownFile_withFileHash() throws Exception {
+        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
 
-        runOnReconcile();
+        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
-        assertThat(mMessagesForUid.keySet()).containsExactly(OWNER_UID);
-        String expectedMessage = DEX_FILENAME_HASH + " " + CONTENT_HASH;
-        assertThat(mMessagesForUid).containsEntry(OWNER_UID, expectedMessage);
+        assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
+        assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+
+        assertThat(mWriteTriggered).isFalse();
+        assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading())
+                .containsExactly(OWNING_PACKAGE_NAME);
     }
 
     @Test
-    public void testSingleAppNoFileHash() throws Exception {
-        doReturn(new byte[] { }).when(mInstaller).hashSecondaryDexFile(
-            DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+    public void testOneLoader_ownFile_noFileHash() throws Exception {
+        whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES));
 
-        runOnReconcile();
+        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
-        assertThat(mMessagesForUid.keySet()).containsExactly(OWNER_UID);
+        assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
         assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+
+        // File should be removed from the DCL list, since we can't hash it.
+        assertThat(mWriteTriggered).isTrue();
+        assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty();
     }
 
     @Test
-    public void testSingleAppHashFails() throws Exception {
-        doThrow(new InstallerException("Testing failure")).when(mInstaller).hashSecondaryDexFile(
-            DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+    public void testOneLoader_ownFile_hashingFails() throws Exception {
+        whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test")));
 
-        runOnReconcile();
+        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+        assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID);
+        assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH);
+
+        // File should be removed from the DCL list, since we can't hash it.
+        assertThat(mWriteTriggered).isTrue();
+        assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty();
+    }
+
+    @Test
+    public void testOneLoader_ownFile_unknownPath() {
+        recordLoad(OWNING_PACKAGE_NAME, "other/path");
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
         assertThat(mMessagesForUid).isEmpty();
+        assertThat(mWriteTriggered).isTrue();
+        assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty();
     }
 
     @Test
-    public void testOtherApps() throws Exception {
-        doReturn(CONTENT_HASH_BYTES).when(mInstaller).hashSecondaryDexFile(
-            DEX_PATH, PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+    public void testOneLoader_differentOwner() throws Exception {
+        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        setPackageUid("other.package.name", 1001);
 
-        // Simulate three packages from two different UIDs
-        String packageName1 = "other1.package.name";
-        String packageName2 = "other2.package.name";
-        String packageName3 = "other3.package.name";
-        int uid1 = 1001;
-        int uid2 = 1002;
+        recordLoad("other.package.name", DEX_PATH);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
-        doReturn(uid1).when(mPM).getPackageUid(packageName1, 0, OWNER_USER_ID);
-        doReturn(uid2).when(mPM).getPackageUid(packageName2, 0, OWNER_USER_ID);
-        doReturn(uid1).when(mPM).getPackageUid(packageName3, 0, OWNER_USER_ID);
-
-        runOnReconcile(packageName1, packageName2, packageName3);
-
-        assertThat(mMessagesForUid.keySet()).containsExactly(OWNER_UID, uid1, uid2);
-
-        String expectedMessage = DEX_FILENAME_HASH + " " + CONTENT_HASH;
-        assertThat(mMessagesForUid).containsEntry(OWNER_UID, expectedMessage);
-        assertThat(mMessagesForUid).containsEntry(uid1, expectedMessage);
-        assertThat(mMessagesForUid).containsEntry(uid2, expectedMessage);
+        assertThat(mMessagesForUid.keys()).containsExactly(1001);
+        assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+        assertThat(mWriteTriggered).isFalse();
     }
 
-    private void runOnReconcile(String... otherPackageNames) {
-        ApplicationInfo appInfo = new ApplicationInfo();
-        appInfo.packageName = PACKAGE_NAME;
-        appInfo.volumeUuid = VOLUME_UUID;
-        appInfo.uid = OWNER_UID;
+    @Test
+    public void testOneLoader_differentOwner_uninstalled() throws Exception {
+        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        setPackageUid("other.package.name", -1);
 
-        boolean isUsedByOtherApps = otherPackageNames.length > 0;
-        DexUseInfo dexUseInfo = new DexUseInfo(
-            isUsedByOtherApps, OWNER_USER_ID, /* classLoaderContext */ null, /* loaderIsa */ null);
-        dexUseInfo.getLoadingPackages().addAll(Arrays.asList(otherPackageNames));
+        recordLoad("other.package.name", DEX_PATH);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
 
-        mListener.onReconcileSecondaryDexFile(appInfo, dexUseInfo, DEX_PATH, STORAGE_FLAGS);
+        assertThat(mMessagesForUid).isEmpty();
+        assertThat(mWriteTriggered).isFalse();
+    }
+
+    @Test
+    public void testMultipleLoadersAndFiles() throws Exception {
+        String otherDexPath = "/bar/nosuchdir/foo.jar";
+        whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES));
+        whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES));
+        setPackageUid("other.package.name1", 1001);
+        setPackageUid("other.package.name2", 1002);
+
+        recordLoad("other.package.name1", DEX_PATH);
+        recordLoad("other.package.name1", otherDexPath);
+        recordLoad("other.package.name2", DEX_PATH);
+        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+        assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID);
+        assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+        assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH);
+        assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+        assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH);
+
+        assertThat(mWriteTriggered).isTrue();
+        assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading())
+                .containsExactly(OWNING_PACKAGE_NAME);
+
+        // Check the DexLogger caching is working
+        verify(mPM, atMost(1)).getPackageInfo(OWNING_PACKAGE_NAME, /*flags*/ 0, OWNER_USER_ID);
+    }
+
+    @Test
+    public void testUnknownOwner() {
+        reset(mPM);
+        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        mDexLogger.logDynamicCodeLoading("other.package.name");
+
+        assertThat(mMessagesForUid).isEmpty();
+        assertThat(mWriteTriggered).isFalse();
+        verifyZeroInteractions(mPM);
+    }
+
+    @Test
+    public void testUninstalledPackage() {
+        reset(mPM);
+        recordLoad(OWNING_PACKAGE_NAME, DEX_PATH);
+        mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME);
+
+        assertThat(mMessagesForUid).isEmpty();
+        assertThat(mWriteTriggered).isTrue();
+        assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()).isEmpty();
+    }
+
+    private void setPackageUid(String packageName, int uid) throws Exception {
+        doReturn(uid).when(mPM).getPackageUid(packageName, /*flags*/ 0, OWNER_USER_ID);
+    }
+
+    private void whenFileIsHashed(String dexPath, Stubber stubber) throws Exception {
+        stubber.when(mInstaller).hashSecondaryDexFile(
+                dexPath, OWNING_PACKAGE_NAME, OWNER_UID, VOLUME_UUID, STORAGE_FLAGS);
+    }
+
+    private void recordLoad(String loadingPackageName, String dexPath) {
+        mPackageDynamicCodeLoading.record(
+                OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index fd07cb0..7cd8cedd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -27,12 +27,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
@@ -78,7 +72,6 @@
     @Mock Installer mInstaller;
     @Mock IPackageManager mPM;
     private final Object mInstallLock = new Object();
-    @Mock DexManager.Listener mListener;
 
     private DexManager mDexManager;
 
@@ -114,9 +107,8 @@
         mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0,
                 DELEGATE_LAST_CLASS_LOADER_NAME);
 
-        mDexManager = new DexManager(
-            /*Context*/ null, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock,
-            mListener);
+        mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null,
+                mInstaller, mInstallLock);
 
         // Foo and Bar are available to user0.
         // Only Bar is available to user1;
@@ -415,9 +407,10 @@
         String frameworkDex = "/system/framework/com.android.location.provider.jar";
         // Load a dex file from framework.
         notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0);
-        // The dex file should not be recognized as a package.
-        assertFalse(mDexManager.hasInfoOnPackage(frameworkDex));
-        assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex));
+        // The dex file should not be recognized as owned by the package.
+        assertFalse(mDexManager.hasInfoOnPackage(mFooUser0.getPackageName()));
+
+        assertNull(getPackageDynamicCodeInfo(mFooUser0));
     }
 
     @Test
@@ -510,21 +503,6 @@
         assertHasDclInfo(mBarUser0, mBarUser0, secondaries);
     }
 
-    @Test
-    public void testReconcileSecondaryDexFiles_invokesListener() throws Exception {
-        List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs();
-        notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
-
-        when(mPM.getPackageInfo(mFooUser0.getPackageName(), 0, 0))
-                .thenReturn(mFooUser0.mPackageInfo);
-
-        mDexManager.reconcileSecondaryDexFiles(mFooUser0.getPackageName());
-
-        verify(mListener, times(fooSecondaries.size()))
-                .onReconcileSecondaryDexFile(any(ApplicationInfo.class),
-                        any(DexUseInfo.class), anyString(), anyInt());
-    }
-
     private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
             List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
             String[] expectedContexts) {
@@ -585,6 +563,10 @@
         return pui;
     }
 
+    private PackageDynamicCode getPackageDynamicCodeInfo(TestData testData) {
+        return mDexManager.getDexLogger().getPackageDynamicCodeInfo(testData.getPackageName());
+    }
+
     private void assertNoUseInfo(TestData testData) {
         assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName()));
     }
@@ -600,11 +582,11 @@
     }
 
     private void assertNoDclInfo(TestData testData) {
-        assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()));
+        assertNull(getPackageDynamicCodeInfo(testData));
     }
 
     private void assertNoDclInfo(TestData testData, int userId) {
-        PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName());
+        PackageDynamicCode info = getPackageDynamicCodeInfo(testData);
         if (info == null) {
             return;
         }
@@ -615,7 +597,7 @@
     }
 
     private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) {
-        PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName());
+        PackageDynamicCode info = getPackageDynamicCodeInfo(owner);
         assertNotNull("No DCL data for owner " + owner.getPackageName(), info);
         for (String path : paths) {
             DynamicCodeFile fileInfo = info.mFileUsageMap.get(path);
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/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
index 77f6a23..acf511e 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
@@ -20,7 +20,6 @@
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
-import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerSaveState;
@@ -175,7 +174,7 @@
 
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyQuickDoze_DefaultValueCorrect() {
-        testServiceDefaultValue_Off(ServiceType.QUICK_DOZE);
+        testServiceDefaultValue_On(ServiceType.QUICK_DOZE);
     }
 
     @SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java
index 70fadd1..00a62a3 100644
--- a/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java
@@ -66,7 +66,7 @@
 
     @Test
     public void testParsePerSdkConfigSdkMinMax() throws JSONException, InvalidConfigException {
-        JSONObject json = new JSONObject("{\"minSdk\":2, \"maxSdk\": 3, \"values\": []}");
+        JSONObject json = new JSONObject("{\"min_sdk\":2, \"max_sdk\": 3, \"values\": {}}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, emptySet(),
                 emptyMap());
         assertThat(config.minSdk).isEqualTo(2);
@@ -75,7 +75,7 @@
 
     @Test
     public void testParsePerSdkConfigNoMinSdk() throws JSONException {
-        JSONObject json = new JSONObject("{\"maxSdk\": 3, \"values\": []}");
+        JSONObject json = new JSONObject("{\"max_sdk\": 3, \"values\": {}}");
         try {
             SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
@@ -86,7 +86,7 @@
 
     @Test
     public void testParsePerSdkConfigNoMaxSdk() throws JSONException {
-        JSONObject json = new JSONObject("{\"minSdk\": 1, \"values\": []}");
+        JSONObject json = new JSONObject("{\"min_sdk\": 1, \"values\": {}}");
         try {
             SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
@@ -97,7 +97,7 @@
 
     @Test
     public void testParsePerSdkConfigNoValues() throws JSONException {
-        JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3}");
+        JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3}");
         try {
             SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
@@ -108,7 +108,7 @@
 
     @Test
     public void testParsePerSdkConfigSdkNullMinSdk() throws JSONException {
-        JSONObject json = new JSONObject("{\"minSdk\":null, \"maxSdk\": 3, \"values\": []}");
+        JSONObject json = new JSONObject("{\"min_sdk\":null, \"max_sdk\": 3, \"values\": {}}");
         try {
             SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
@@ -119,7 +119,7 @@
 
     @Test
     public void testParsePerSdkConfigSdkNullMaxSdk() throws JSONException {
-        JSONObject json = new JSONObject("{\"minSdk\":1, \"maxSdk\": null, \"values\": []}");
+        JSONObject json = new JSONObject("{\"min_sdk\":1, \"max_sdk\": null, \"values\": {}}");
         try {
             SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
@@ -130,7 +130,7 @@
 
     @Test
     public void testParsePerSdkConfigNullValues() throws JSONException {
-        JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": null}");
+        JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3, \"values\": null}");
         try {
             SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
@@ -142,7 +142,7 @@
     @Test
     public void testParsePerSdkConfigZeroValues()
             throws JSONException, InvalidConfigException {
-        JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": []}");
+        JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3, \"values\": {}}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
                 emptyMap());
         assertThat(config.values).hasSize(0);
@@ -152,7 +152,7 @@
     public void testParsePerSdkConfigSingleKey()
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+                "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
                 emptyMap());
         assertThat(config.values).containsExactly("a", "1");
@@ -162,7 +162,7 @@
     public void testParsePerSdkConfigSingleKeyNullValue()
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": null}]}");
+                "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": null}}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
                 emptyMap());
         assertThat(config.values.keySet()).containsExactly("a");
@@ -173,8 +173,7 @@
     public void testParsePerSdkConfigMultiKeys()
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}, "
-                        + "{\"key\":\"c\", \"value\": \"2\"}]}");
+                "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\", \"c\": \"2\"}}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(
                 json, setOf("a", "b", "c"), emptyMap());
         assertThat(config.values).containsExactly("a", "1", "c", "2");
@@ -183,7 +182,7 @@
     @Test
     public void testParsePerSdkConfigSingleKeyNotAllowed() throws JSONException {
         JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+                "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}");
         try {
             SignedConfig.parsePerSdkConfig(json, setOf("b"), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
@@ -196,7 +195,7 @@
     public void testParsePerSdkConfigSingleKeyWithMap()
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+                "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"),
                 mapOf("a", mapOf("1", "one")));
         assertThat(config.values).containsExactly("a", "one");
@@ -205,7 +204,7 @@
     @Test
     public void testParsePerSdkConfigSingleKeyWithMapInvalidValue() throws JSONException {
         JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"2\"}]}");
+                "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"2\"}}");
         try {
             SignedConfig.parsePerSdkConfig(json, setOf("b"), mapOf("a", mapOf("1", "one")));
             fail("Expected InvalidConfigException");
@@ -218,8 +217,7 @@
     public void testParsePerSdkConfigMultiKeysWithMap()
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"},"
-                + "{\"key\":\"b\", \"value\": \"1\"}]}");
+                "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\", \"b\": \"1\"}}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
                 mapOf("a", mapOf("1", "one")));
         assertThat(config.values).containsExactly("a", "one", "b", "1");
@@ -229,7 +227,7 @@
     public void testParsePerSdkConfigSingleKeyWithMapToNull()
             throws JSONException, InvalidConfigException {
         JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}");
+                "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"),
                 mapOf("a", mapOf("1", null)));
         assertThat(config.values).containsExactly("a", null);
@@ -241,25 +239,15 @@
         assertThat(mapOf(null, "allitnil")).containsExactly(null, "allitnil");
         assertThat(mapOf(null, "allitnil").containsKey(null)).isTrue();
         JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": null}]}");
+                "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": null}}");
         SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"),
                 mapOf("a", mapOf(null, "allitnil")));
         assertThat(config.values).containsExactly("a", "allitnil");
     }
 
     @Test
-    public void testParsePerSdkConfigSingleKeyNoValue()
-            throws JSONException, InvalidConfigException {
-        JSONObject json = new JSONObject(
-                "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\"}]}");
-        SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"),
-                emptyMap());
-        assertThat(config.values).containsExactly("a", null);
-    }
-
-    @Test
     public void testParsePerSdkConfigValuesInvalid() throws JSONException  {
-        JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1,  \"values\": \"foo\"}");
+        JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 1,  \"values\": \"foo\"}");
         try {
             SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
@@ -270,18 +258,7 @@
 
     @Test
     public void testParsePerSdkConfigConfigEntryInvalid() throws JSONException {
-        JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1,  \"values\": [1, 2]}");
-        try {
-            SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
-            fail("Expected InvalidConfigException or JSONException");
-        } catch (JSONException | InvalidConfigException e) {
-            // expected
-        }
-    }
-
-    @Test
-    public void testParsePerSdkConfigConfigEntryNull() throws JSONException {
-        JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1,  \"values\": [null]}");
+        JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 1,  \"values\": [1, 2]}");
         try {
             SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap());
             fail("Expected InvalidConfigException or JSONException");
@@ -361,7 +338,7 @@
     @Test
     public void testParseSdkConfigSingle() throws InvalidConfigException {
         SignedConfig config = SignedConfig.parse(
-                "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}]}",
+                "{\"version\": 1, \"config\":[{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {}}]}",
                 emptySet(), emptyMap());
         assertThat(config.perSdkConfig).hasSize(1);
     }
@@ -369,8 +346,8 @@
     @Test
     public void testParseSdkConfigMultiple() throws InvalidConfigException {
         SignedConfig config = SignedConfig.parse(
-                "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}, "
-                        + "{\"minSdk\": 2, \"maxSdk\": 2, \"values\": []}]}", emptySet(),
+                "{\"version\": 1, \"config\":[{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {}}, "
+                        + "{\"min_sdk\": 2, \"max_sdk\": 2, \"values\": {}}]}", emptySet(),
                 emptyMap());
         assertThat(config.perSdkConfig).hasSize(2);
     }
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index bf7f53d..860656b 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE;
+
 import static junit.framework.TestCase.fail;
 
 import static org.testng.Assert.assertEquals;
@@ -136,9 +138,11 @@
                 event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP;
             }
 
-            event.mClass = ".fake.class.name" + i % 11;
+            final int instanceId = i % 11;
+            event.mClass = ".fake.class.name" + instanceId;
             event.mTimeStamp = time;
-            event.mEventType = i % 19; //"random" event type
+            event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
+            event.mInstanceId = instanceId;
 
             switch (event.mEventType) {
                 case Event.CONFIGURATION_CHANGE:
@@ -160,7 +164,8 @@
             }
 
             mIntervalStats.events.insert(event);
-            mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType);
+            mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
+                    event.mInstanceId);
 
             time += timeProgression; // Arbitrary progression of time
         }
@@ -219,7 +224,9 @@
         // mBeginTimeStamp is based on the enclosing IntervalStats, don't bother checking
         // mEndTimeStamp is based on the enclosing IntervalStats, don't bother checking
         assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed);
+        assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible);
         assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground);
+        assertEquals(us1.mTotalTimeVisible, us2.mTotalTimeVisible);
         assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed);
         assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed);
         // mLaunchCount not persisted, so skipped
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/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index f6871b3..85410f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -82,17 +82,17 @@
 
     @Test
     public void testEmptyTaskCleanupOnRemove() {
-        assertNotNull(mTask.getWindowContainerController());
+        assertNotNull(mTask.getTask());
         mStack.removeTask(mTask, "testEmptyTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING);
-        assertNull(mTask.getWindowContainerController());
+        assertNull(mTask.getTask());
     }
 
     @Test
     public void testOccupiedTaskCleanupOnRemove() {
         final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
-        assertNotNull(mTask.getWindowContainerController());
+        assertNotNull(mTask.getTask());
         mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING);
-        assertNotNull(mTask.getWindowContainerController());
+        assertNotNull(mTask.getTask());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index f553c35..569c6d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -345,7 +345,7 @@
                 mStack.moveToFront("test");
                 mStack.addTask(task, true, "creating test task");
                 task.setStack(mStack);
-                task.setWindowContainerController();
+                task.setTask();
             }
 
             task.touchActiveTime();
@@ -361,12 +361,12 @@
             }
 
             @Override
-            void createWindowContainer(boolean onTop, boolean showForAllUsers) {
-                setWindowContainerController();
+            void createTask(boolean onTop, boolean showForAllUsers) {
+                setTask();
             }
 
-            private void setWindowContainerController() {
-                setWindowContainerController(mock(TaskWindowContainerController.class));
+            private void setTask() {
+                setTask(mock(Task.class));
             }
         }
     }
@@ -447,7 +447,11 @@
         }
 
         @Override
-        void updateUsageStats(ActivityRecord component, boolean resumed) {
+        void updateBatteryStats(ActivityRecord component, boolean resumed) {
+        }
+
+        @Override
+        void updateActivityUsageStats(ActivityRecord activity, int event) {
         }
 
         @Override
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/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/StackWindowControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java
index ce5b13c..5690b58 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java
@@ -41,17 +41,14 @@
     public void testRemoveContainer() {
         final StackWindowController stackController =
                 createStackControllerOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTaskWindowContainerController taskController =
-                new WindowTestUtils.TestTaskWindowContainerController(stackController);
+        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController);
 
         final TaskStack stack = stackController.mContainer;
-        final Task task = taskController.mContainer;
         assertNotNull(stack);
         assertNotNull(task);
         stackController.removeContainer();
         // Assert that the container was removed.
         assertNull(stackController.mContainer);
-        assertNull(taskController.mContainer);
         assertNull(stack.getDisplayContent());
         assertNull(task.getDisplayContent());
         assertNull(task.mStack);
@@ -61,11 +58,9 @@
     public void testRemoveContainer_deferRemoval() {
         final StackWindowController stackController =
                 createStackControllerOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTaskWindowContainerController taskController =
-                new WindowTestUtils.TestTaskWindowContainerController(stackController);
+        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController);
 
         final TaskStack stack = stackController.mContainer;
-        final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer;
         // Stack removal is deferred if one of its child is animating.
         task.setLocalIsAnimating(true);
 
@@ -75,11 +70,12 @@
         // the stack window container is removed.
         assertNull(stackController.mContainer);
         assertNull(stack.getController());
-        assertNotNull(taskController.mContainer);
-        assertNotNull(task.getController());
+        assertNotNull(task);
 
         stack.removeImmediately();
-        assertNull(taskController.mContainer);
+        // After removing, the task will be isolated.
+        assertNull(task.getParent());
+        assertEquals(task.getChildCount(), 0);
         assertNull(task.getController());
     }
 
@@ -89,9 +85,7 @@
         final StackWindowController stack1Controller =
                 createStackControllerOnDisplay(mDisplayContent);
         final TaskStack stack1 = stack1Controller.mContainer;
-        final WindowTestUtils.TestTaskWindowContainerController taskController =
-                new WindowTestUtils.TestTaskWindowContainerController(stack1Controller);
-        final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer;
+        final WindowTestUtils.TestTask task1 = WindowTestUtils.createTestTask(stack1Controller);
         task1.mOnDisplayChangedCalled = false;
 
         // Create second display and put second stack on it.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 630a8bf..6b6b33c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 
@@ -31,10 +34,12 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.Xml;
+import android.view.DisplayInfo;
 
 import androidx.test.filters.MediumTest;
 
@@ -66,10 +71,13 @@
 
     private static final String TASK_TAG = "task";
 
+    private Rect mParentBounds;
+
     @Before
     public void setUp() throws Exception {
         TaskRecord.setTaskRecordFactory(null);
         setupActivityTaskManagerService();
+        mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/);
     }
 
     @Test
@@ -124,6 +132,72 @@
         assertTrue(task.returnsToHomeStack());
     }
 
+    /** Ensures that bounds are clipped to their parent. */
+    @Test
+    public void testAppBounds_BoundsClipping() {
+        final Rect shiftedBounds = new Rect(mParentBounds);
+        shiftedBounds.offset(10, 10);
+        final Rect expectedBounds = new Rect(mParentBounds);
+        expectedBounds.intersect(shiftedBounds);
+        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds,
+                expectedBounds);
+    }
+
+    /** Ensures that empty bounds are not propagated to the configuration. */
+    @Test
+    public void testAppBounds_EmptyBounds() {
+        final Rect emptyBounds = new Rect();
+        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds,
+                null /*ExpectedBounds*/);
+    }
+
+    /** Ensures that bounds on freeform stacks are not clipped. */
+    @Test
+    public void testAppBounds_FreeFormBounds() {
+        final Rect freeFormBounds = new Rect(mParentBounds);
+        freeFormBounds.offset(10, 10);
+        testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds,
+                freeFormBounds);
+    }
+
+    /** Ensures that fully contained bounds are not clipped. */
+    @Test
+    public void testAppBounds_ContainedBounds() {
+        final Rect insetBounds = new Rect(mParentBounds);
+        insetBounds.inset(5, 5, 5, 5);
+        testStackBoundsConfiguration(
+                WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds);
+    }
+
+    /** Ensures that full screen free form bounds are clipped */
+    @Test
+    public void testAppBounds_FullScreenFreeFormBounds() {
+        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        DisplayInfo info = new DisplayInfo();
+        display.mDisplay.getDisplayInfo(info);
+        final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
+        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds,
+                mParentBounds);
+    }
+
+    private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
+            Rect expectedConfigBounds) {
+
+        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        ActivityStack stack = display.createStack(windowingMode, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */);
+        TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build();
+
+        final Configuration parentConfig = stack.getConfiguration();
+        parentConfig.windowConfiguration.setAppBounds(parentBounds);
+        task.setBounds(bounds);
+
+        task.resolveOverrideConfiguration(parentConfig);
+        // Assert that both expected and actual are null or are equal to each other
+        assertEquals(expectedConfigBounds,
+                task.getResolvedOverrideConfiguration().windowConfiguration.getAppBounds());
+    }
+
     private byte[] serializeToBytes(TaskRecord r) throws IOException, XmlPullParserException {
         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
             final XmlSerializer serializer = Xml.newSerializer();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
new file mode 100644
index 0000000..3e32ed3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Test class for {@link Task}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:TaskTests
+ */
+@SmallTest
+@Presubmit
+public class TaskTests extends WindowTestsBase {
+
+    @Test
+    public void testRemoveContainer() {
+        final StackWindowController stackController1 =
+                createStackControllerOnDisplay(mDisplayContent);
+        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+        final WindowTestUtils.TestAppWindowToken appToken =
+                WindowTestUtils.createAppWindowTokenInTask(mDisplayContent, task);
+
+        task.removeIfPossible();
+        // Assert that the container was removed.
+        assertNull(task.getParent());
+        assertEquals(task.getChildCount(), 0);
+        assertNull(appToken.getParent());
+    }
+
+    @Test
+    public void testRemoveContainer_deferRemoval() {
+        final StackWindowController stackController1 =
+                createStackControllerOnDisplay(mDisplayContent);
+        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+        final WindowTestUtils.TestAppWindowToken appToken =
+                WindowTestUtils.createAppWindowTokenInTask(mDisplayContent, task);
+
+        task.mShouldDeferRemoval = true;
+
+        task.removeIfPossible();
+        // For the case of deferred removal the task will still be connected to the its app token
+        // until the task window container is removed.
+        assertNotNull(task.getParent());
+        assertNotEquals(task.getChildCount(), 0);
+        assertNotNull(appToken.getParent());
+
+        task.removeImmediately();
+        assertNull(task.getParent());
+        assertEquals(task.getChildCount(), 0);
+        assertNull(appToken.getParent());
+    }
+
+    @Test
+    public void testReparent() {
+        final StackWindowController stackController1 =
+                createStackControllerOnDisplay(mDisplayContent);
+        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+        final StackWindowController stackController2 =
+                createStackControllerOnDisplay(mDisplayContent);
+        final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stackController2);
+
+        boolean gotException = false;
+        try {
+            task.reparent(stackController1, 0, false/* moveParents */);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue("Should not be able to reparent to the same parent", gotException);
+
+        final StackWindowController stackController3 =
+                createStackControllerOnDisplay(mDisplayContent);
+        stackController3.setContainer(null);
+        gotException = false;
+        try {
+            task.reparent(stackController3, 0, false/* moveParents */);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue("Should not be able to reparent to a stack that doesn't have a container",
+                gotException);
+
+        task.reparent(stackController2, 0, false/* moveParents */);
+        assertEquals(stackController2.mContainer, task.getParent());
+        assertEquals(0, task.positionInParent());
+        assertEquals(1, task2.positionInParent());
+    }
+
+    @Test
+    public void testReparent_BetweenDisplays() {
+        // Create first stack on primary display.
+        final StackWindowController stack1Controller =
+                createStackControllerOnDisplay(mDisplayContent);
+        final TaskStack stack1 = stack1Controller.mContainer;
+        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack1Controller);
+        task.mOnDisplayChangedCalled = false;
+        assertEquals(mDisplayContent, stack1.getDisplayContent());
+
+        // Create second display and put second stack on it.
+        final DisplayContent dc = createNewDisplay();
+        final StackWindowController stack2Controller = createStackControllerOnDisplay(dc);
+        final TaskStack stack2 = stack2Controller.mContainer;
+        final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stack2Controller);
+        // Reparent and check state
+        task.reparent(stack2Controller, 0, false /* moveParents */);
+        assertEquals(stack2, task.getParent());
+        assertEquals(0, task.positionInParent());
+        assertEquals(1, task2.positionInParent());
+        assertTrue(task.mOnDisplayChangedCalled);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
deleted file mode 100644
index bbf508d..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-/**
- * Test class for {@link TaskWindowContainerController}.
- *
- * Build/Install/Run:
- *  atest FrameworksServicesTests:TaskWindowContainerControllerTests
- */
-@SmallTest
-@Presubmit
-public class TaskWindowContainerControllerTests extends WindowTestsBase {
-
-    /* Comment out due to removal of AppWindowContainerController
-    @Test
-    public void testRemoveContainer() {
-        final WindowTestUtils.TestTaskWindowContainerController taskController =
-                new WindowTestUtils.TestTaskWindowContainerController(this);
-        final WindowTestUtils.TestAppWindowContainerController appController =
-                new WindowTestUtils.TestAppWindowContainerController(taskController);
-
-        taskController.removeContainer();
-        // Assert that the container was removed.
-        assertNull(taskController.mContainer);
-        assertNull(appController.mContainer);
-    }
-    */
-
-    /* Comment out due to removal of AppWindowContainerController
-    @Test
-    public void testRemoveContainer_deferRemoval() {
-        final WindowTestUtils.TestTaskWindowContainerController taskController =
-                new WindowTestUtils.TestTaskWindowContainerController(this);
-        final WindowTestUtils.TestAppWindowContainerController appController =
-                new WindowTestUtils.TestAppWindowContainerController(taskController);
-
-        final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer;
-        final AppWindowToken app = appController.mContainer;
-        task.mShouldDeferRemoval = true;
-
-        taskController.removeContainer();
-        // For the case of deferred removal the task controller will no longer be connected to the
-        // container, but the app controller will still be connected to the its container until
-        // the task window container is removed.
-        assertNull(taskController.mContainer);
-        assertNull(task.getController());
-        assertNotNull(appController.mContainer);
-        assertNotNull(app.getController());
-
-        task.removeImmediately();
-        assertNull(appController.mContainer);
-        assertNull(app.getController());
-    }
-    */
-
-    @Test
-    public void testReparent() {
-        final StackWindowController stackController1 =
-                createStackControllerOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTaskWindowContainerController taskController =
-                new WindowTestUtils.TestTaskWindowContainerController(stackController1);
-        final StackWindowController stackController2 =
-                createStackControllerOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTaskWindowContainerController taskController2 =
-                new WindowTestUtils.TestTaskWindowContainerController(stackController2);
-
-        boolean gotException = false;
-        try {
-            taskController.reparent(stackController1, 0, false/* moveParents */);
-        } catch (IllegalArgumentException e) {
-            gotException = true;
-        }
-        assertTrue("Should not be able to reparent to the same parent", gotException);
-
-        final StackWindowController stackController3 =
-                createStackControllerOnDisplay(mDisplayContent);
-        stackController3.setContainer(null);
-        gotException = false;
-        try {
-            taskController.reparent(stackController3, 0, false/* moveParents */);
-        } catch (IllegalArgumentException e) {
-            gotException = true;
-        }
-        assertTrue("Should not be able to reparent to a stack that doesn't have a container",
-                gotException);
-
-        taskController.reparent(stackController2, 0, false/* moveParents */);
-        assertEquals(stackController2.mContainer, taskController.mContainer.getParent());
-        assertEquals(0, ((WindowTestUtils.TestTask) taskController.mContainer).positionInParent());
-        assertEquals(1, ((WindowTestUtils.TestTask) taskController2.mContainer).positionInParent());
-    }
-
-    @Test
-    public void testReparent_BetweenDisplays() {
-        // Create first stack on primary display.
-        final StackWindowController stack1Controller =
-                createStackControllerOnDisplay(mDisplayContent);
-        final TaskStack stack1 = stack1Controller.mContainer;
-        final WindowTestUtils.TestTaskWindowContainerController taskController =
-                new WindowTestUtils.TestTaskWindowContainerController(stack1Controller);
-        final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer;
-        task1.mOnDisplayChangedCalled = false;
-        assertEquals(mDisplayContent, stack1.getDisplayContent());
-
-        // Create second display and put second stack on it.
-        final DisplayContent dc = createNewDisplay();
-        final StackWindowController stack2Controller = createStackControllerOnDisplay(dc);
-        final TaskStack stack2 = stack2Controller.mContainer;
-        final WindowTestUtils.TestTaskWindowContainerController taskController2 =
-                new WindowTestUtils.TestTaskWindowContainerController(stack2Controller);
-        final WindowTestUtils.TestTask task2 =
-                (WindowTestUtils.TestTask) taskController2.mContainer;
-
-        // Reparent and check state
-        taskController.reparent(stack2Controller, 0, false /* moveParents */);
-        assertEquals(stack2, task1.getParent());
-        assertEquals(0, task1.positionInParent());
-        assertEquals(1, task2.positionInParent());
-        assertTrue(task1.mOnDisplayChangedCalled);
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java
index 885a7e0..c653762f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_ALWAYS_ON_TOP;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_ROTATION;
@@ -174,70 +173,4 @@
         assertEquals(appBounds.width(), info.appWidth);
         assertEquals(appBounds.height(), info.appHeight);
     }
-
-    /** Ensures that bounds are clipped to their parent. */
-    @Test
-    public void testAppBounds_BoundsClipping() {
-        final Rect shiftedBounds = new Rect(mParentBounds);
-        shiftedBounds.offset(10, 10);
-        final Rect expectedBounds = new Rect(mParentBounds);
-        expectedBounds.intersect(shiftedBounds);
-        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds,
-                expectedBounds);
-    }
-
-    /** Ensures that empty bounds are not propagated to the configuration. */
-    @Test
-    public void testAppBounds_EmptyBounds() {
-        final Rect emptyBounds = new Rect();
-        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds,
-                null /*ExpectedBounds*/);
-    }
-
-    /** Ensures that bounds on freeform stacks are not clipped. */
-    @Test
-    public void testAppBounds_FreeFormBounds() {
-        final Rect freeFormBounds = new Rect(mParentBounds);
-        freeFormBounds.offset(10, 10);
-        testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds,
-                freeFormBounds);
-    }
-
-    /** Ensures that fully contained bounds are not clipped. */
-    @Test
-    public void testAppBounds_ContainedBounds() {
-        final Rect insetBounds = new Rect(mParentBounds);
-        insetBounds.inset(5, 5, 5, 5);
-        testStackBoundsConfiguration(
-                WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds);
-    }
-
-    /** Ensures that full screen free form bounds are clipped */
-    @Test
-    public void testAppBounds_FullScreenFreeFormBounds() {
-        final Rect fullScreenBounds = new Rect(0, 0, mDisplayInfo.logicalWidth,
-                mDisplayInfo.logicalHeight);
-        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds,
-                mParentBounds);
-    }
-
-    private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
-            Rect expectedConfigBounds) {
-        final StackWindowController stackController = createStackControllerOnStackOnDisplay(
-                        windowingMode, ACTIVITY_TYPE_STANDARD, mDisplayContent);
-
-        final Configuration parentConfig = mDisplayContent.getConfiguration();
-        parentConfig.windowConfiguration.setAppBounds(parentBounds);
-
-        final Configuration config = new Configuration();
-        final WindowConfiguration winConfig = config.windowConfiguration;
-        stackController.adjustConfigurationForBounds(bounds,
-                new Rect() /*nonDecorBounds*/, new Rect() /*stableBounds*/, false /*overrideWidth*/,
-                false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig,
-                windowingMode);
-        // Assert that both expected and actual are null or are equal to each other
-
-        assertEquals(expectedConfigBounds, winConfig.getAppBounds());
-    }
-
 }
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 42954b7..20fc5ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -19,11 +19,6 @@
 import static android.app.AppOpsManager.OP_NONE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -34,8 +29,6 @@
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
@@ -46,8 +39,6 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowManager;
 
-import org.mockito.invocation.InvocationOnMock;
-
 /**
  * A collection of static functions that can be referenced by other test packages to provide access
  * to WindowManager related test functionality.
@@ -117,17 +108,6 @@
     public static StackWindowController createMockStackWindowContainerController() {
         StackWindowController controller = mock(StackWindowController.class);
         controller.mContainer = mock(TestTaskStack.class);
-
-        // many components rely on the {@link StackWindowController#adjustConfigurationForBounds}
-        // to properly set bounds values in the configuration. We must mimick those actions here.
-        doAnswer((InvocationOnMock invocationOnMock) -> {
-            final Configuration config = invocationOnMock.<Configuration>getArgument(6);
-            final Rect bounds = invocationOnMock.<Rect>getArgument(0);
-            config.windowConfiguration.setBounds(bounds);
-            return null;
-        }).when(controller).adjustConfigurationForBounds(any(), any(), any(),
-                anyBoolean(), anyBoolean(), anyFloat(), any(), any(), anyInt());
-
         return controller;
     }
 
@@ -142,6 +122,13 @@
         }
     }
 
+    /** Creates an {@link AppWindowToken} and adds it to the specified {@link Task}. */
+    public static TestAppWindowToken createAppWindowTokenInTask(DisplayContent dc, Task task) {
+        final TestAppWindowToken newToken = createTestAppWindowToken(dc);
+        task.addChild(newToken, POSITION_TOP);
+        return newToken;
+    }
+
     /**
      * An extension of {@link TestTaskStack}, which overrides package scoped methods that would not
      * normally be mocked out.
@@ -266,9 +253,10 @@
 
         TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service,
                 int resizeMode, boolean supportsPictureInPicture,
-                TaskWindowContainerController controller) {
+                TaskRecord taskRecord) {
             super(taskId, stack, userId, service, resizeMode, supportsPictureInPicture,
-                    new ActivityManager.TaskDescription(), controller);
+                    new ActivityManager.TaskDescription(), taskRecord);
+            stack.addTask(this, POSITION_TOP);
         }
 
         boolean shouldDeferRemoval() {
@@ -295,49 +283,10 @@
         }
     }
 
-    /**
-     * Used so we can gain access to some protected members of {@link TaskWindowContainerController}
-     * class.
-     */
-    public static class TestTaskWindowContainerController extends TaskWindowContainerController {
-
-        static final TaskWindowContainerListener NOP_LISTENER = new TaskWindowContainerListener() {
-            @Override
-            public void registerConfigurationChangeListener(
-                    ConfigurationContainerListener listener) {
-            }
-
-            @Override
-            public void unregisterConfigurationChangeListener(
-                    ConfigurationContainerListener listener) {
-            }
-
-            @Override
-            public void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) {
-            }
-
-            @Override
-            public void requestResize(Rect bounds, int resizeMode) {
-            }
-        };
-
-        TestTaskWindowContainerController(WindowTestsBase testsBase) {
-            this(testsBase.createStackControllerOnDisplay(testsBase.mDisplayContent));
-        }
-
-        TestTaskWindowContainerController(StackWindowController stackController) {
-            super(sNextTaskId++, NOP_LISTENER, stackController, 0 /* userId */, null /* bounds */,
-                    RESIZE_MODE_UNRESIZEABLE, false /* supportsPictureInPicture */, true /* toTop*/,
-                    true /* showForAllUsers */, new ActivityManager.TaskDescription(),
-                    stackController.mService);
-        }
-
-        @Override
-        TestTask createTask(int taskId, TaskStack stack, int userId, int resizeMode,
-                boolean supportsPictureInPicture, ActivityManager.TaskDescription taskDescription) {
-            return new TestTask(taskId, stack, userId, mService, resizeMode,
-                    supportsPictureInPicture, this);
-        }
+    public static TestTask createTestTask(StackWindowController stackWindowController) {
+        return new TestTask(sNextTaskId++, stackWindowController.mContainer, 0,
+                stackWindowController.mService, RESIZE_MODE_UNRESIZEABLE, false,
+                mock(TaskRecord.class));
     }
 
     public static class TestIApplicationToken implements IApplicationToken {
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 4f573a4..65ed85d 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -842,8 +842,8 @@
             final boolean previouslyIdle = mAppIdleHistory.isIdle(
                     event.mPackage, userId, elapsedRealtime);
             // Inform listeners if necessary
-            if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND
-                    || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
+            if ((event.mEventType == UsageEvents.Event.ACTIVITY_RESUMED
+                    || event.mEventType == UsageEvents.Event.ACTIVITY_PAUSED
                     || event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
                     || event.mEventType == UsageEvents.Event.USER_INTERACTION
                     || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN
@@ -894,8 +894,8 @@
 
     private int usageEventToSubReason(int eventType) {
         switch (eventType) {
-            case UsageEvents.Event.MOVE_TO_FOREGROUND: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
-            case UsageEvents.Event.MOVE_TO_BACKGROUND: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND;
+            case UsageEvents.Event.ACTIVITY_RESUMED: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
+            case UsageEvents.Event.ACTIVITY_PAUSED: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND;
             case UsageEvents.Event.SYSTEM_INTERACTION: return REASON_SUB_USAGE_SYSTEM_INTERACTION;
             case UsageEvents.Event.USER_INTERACTION: return REASON_SUB_USAGE_USER_INTERACTION;
             case UsageEvents.Event.NOTIFICATION_SEEN: return REASON_SUB_USAGE_NOTIFICATION_SEEN;
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 8405267..94cc650 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -15,10 +15,31 @@
  */
 package com.android.server.usage;
 
+import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
+import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
+import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE;
+import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY;
+import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
+import static android.app.usage.UsageEvents.Event.END_OF_DAY;
+import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
+import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START;
+import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP;
+import static android.app.usage.UsageEvents.Event.KEYGUARD_HIDDEN;
+import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN;
+import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
+import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
+import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE;
+import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE;
+import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED;
+import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION;
+
 import android.app.usage.ConfigurationStats;
 import android.app.usage.EventList;
 import android.app.usage.EventStats;
-import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.content.res.Configuration;
 import android.util.ArrayMap;
@@ -125,8 +146,8 @@
     /**
      * Builds a UsageEvents.Event, but does not add it internally.
      */
-    UsageEvents.Event buildEvent(String packageName, String className) {
-        UsageEvents.Event event = new UsageEvents.Event();
+    Event buildEvent(String packageName, String className) {
+        Event event = new Event();
         event.mPackage = getCachedStringRef(packageName);
         if (className != null) {
             event.mClass = getCachedStringRef(className);
@@ -138,9 +159,9 @@
      * Builds a UsageEvents.Event from a proto, but does not add it internally.
      * Built here to take advantage of the cached String Refs
      */
-    UsageEvents.Event buildEvent(ProtoInputStream parser, List<String> stringPool)
+    Event buildEvent(ProtoInputStream parser, List<String> stringPool)
             throws IOException {
-        final UsageEvents.Event event = new UsageEvents.Event();
+        final Event event = new Event();
         while (true) {
             switch (parser.nextField()) {
                 case (int) IntervalStatsProto.Event.PACKAGE:
@@ -190,20 +211,23 @@
                             parser.readInt(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX)
                                     - 1));
                     break;
+                case (int) IntervalStatsProto.Event.INSTANCE_ID:
+                    event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID);
+                    break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     // Handle default values for certain events types
                     switch (event.mEventType) {
-                        case UsageEvents.Event.CONFIGURATION_CHANGE:
+                        case CONFIGURATION_CHANGE:
                             if (event.mConfiguration == null) {
                                 event.mConfiguration = new Configuration();
                             }
                             break;
-                        case UsageEvents.Event.SHORTCUT_INVOCATION:
+                        case SHORTCUT_INVOCATION:
                             if (event.mShortcutId == null) {
                                 event.mShortcutId = "";
                             }
                             break;
-                        case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                        case NOTIFICATION_INTERRUPTION:
                             if (event.mNotificationChannelId == null) {
                                 event.mNotificationChannelId = "";
                             }
@@ -220,14 +244,15 @@
 
     private boolean isStatefulEvent(int eventType) {
         switch (eventType) {
-            case UsageEvents.Event.MOVE_TO_FOREGROUND:
-            case UsageEvents.Event.MOVE_TO_BACKGROUND:
-            case UsageEvents.Event.FOREGROUND_SERVICE_START:
-            case UsageEvents.Event.FOREGROUND_SERVICE_STOP:
-            case UsageEvents.Event.END_OF_DAY:
-            case UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE:
-            case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
-            case UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE:
+            case ACTIVITY_RESUMED:
+            case ACTIVITY_PAUSED:
+            case ACTIVITY_STOPPED:
+            case FOREGROUND_SERVICE_START:
+            case FOREGROUND_SERVICE_STOP:
+            case END_OF_DAY:
+            case ROLLOVER_FOREGROUND_SERVICE:
+            case CONTINUE_PREVIOUS_DAY:
+            case CONTINUING_FOREGROUND_SERVICE:
                 return true;
         }
         return false;
@@ -238,17 +263,56 @@
      * interaction. Excludes those that are internally generated.
      */
     private boolean isUserVisibleEvent(int eventType) {
-        return eventType != UsageEvents.Event.SYSTEM_INTERACTION
-                && eventType != UsageEvents.Event.STANDBY_BUCKET_CHANGED;
+        return eventType != SYSTEM_INTERACTION
+                && eventType != STANDBY_BUCKET_CHANGED;
     }
 
     /**
+     * Update the IntervalStats by a activity or foreground service event.
+     * @param packageName package name of this event. Is null if event targets to all packages.
+     * @param className class name of a activity or foreground service, could be null to if this
+     *                  is sent to all activities/services in this package.
+     * @param timeStamp Epoch timestamp in milliseconds.
+     * @param eventType event type as in {@link Event}
+     * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken.
+     *                 if className is not an activity, instanceId is not used.
      * @hide
      */
     @VisibleForTesting
-    public void update(String packageName, String className, long timeStamp, int eventType) {
-        UsageStats usageStats = getOrCreateUsageStats(packageName);
-        usageStats.update(className, timeStamp, eventType);
+    public void update(String packageName, String className, long timeStamp, int eventType,
+            int instanceId) {
+        if (eventType == FLUSH_TO_DISK) {
+            // FLUSH_TO_DISK are sent to all packages.
+            final int size = packageStats.size();
+            for (int i = 0; i < size; i++) {
+                UsageStats usageStats = packageStats.valueAt(i);
+                usageStats.update(null, timeStamp, eventType, instanceId);
+            }
+        } else if (eventType == ACTIVITY_DESTROYED) {
+            UsageStats usageStats = packageStats.get(packageName);
+            if (usageStats != null) {
+                // If previous event is not ACTIVITY_STOPPED, convert ACTIVITY_DESTROYED
+                // to ACTIVITY_STOPPED and add to event list.
+                // Otherwise do not add anything to event list. (Because we want to save space
+                // and we do not want a ACTIVITY_STOPPED followed by
+                // ACTIVITY_DESTROYED in event list).
+                final int index = usageStats.mActivities.indexOfKey(instanceId);
+                if (index >= 0) {
+                    final int type = usageStats.mActivities.valueAt(index);
+                    if (type != ACTIVITY_STOPPED) {
+                        Event event = new Event(ACTIVITY_STOPPED, timeStamp);
+                        event.mPackage = packageName;
+                        event.mClass = className;
+                        event.mInstanceId = instanceId;
+                        addEvent(event);
+                    }
+                }
+                usageStats.update(className, timeStamp, ACTIVITY_DESTROYED, instanceId);
+            }
+        } else {
+            UsageStats usageStats = getOrCreateUsageStats(packageName);
+            usageStats.update(className, timeStamp, eventType, instanceId);
+        }
         endTime = timeStamp;
     }
 
@@ -256,7 +320,7 @@
      * @hide
      */
     @VisibleForTesting
-    public void addEvent(UsageEvents.Event event) {
+    public void addEvent(Event event) {
         if (events == null) {
             events = new EventList();
         }
@@ -265,7 +329,7 @@
         if (event.mClass != null) {
             event.mClass = getCachedStringRef(event.mClass);
         }
-        if (event.mEventType == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
+        if (event.mEventType == NOTIFICATION_INTERRUPTION) {
             event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId);
         }
         events.insert(event);
@@ -338,13 +402,13 @@
     }
 
     void addEventStatsTo(List<EventStats> out) {
-        interactiveTracker.addToEventStats(out, UsageEvents.Event.SCREEN_INTERACTIVE,
+        interactiveTracker.addToEventStats(out, SCREEN_INTERACTIVE,
                 beginTime, endTime);
-        nonInteractiveTracker.addToEventStats(out, UsageEvents.Event.SCREEN_NON_INTERACTIVE,
+        nonInteractiveTracker.addToEventStats(out, SCREEN_NON_INTERACTIVE,
                 beginTime, endTime);
-        keyguardShownTracker.addToEventStats(out, UsageEvents.Event.KEYGUARD_SHOWN,
+        keyguardShownTracker.addToEventStats(out, KEYGUARD_SHOWN,
                 beginTime, endTime);
-        keyguardHiddenTracker.addToEventStats(out, UsageEvents.Event.KEYGUARD_HIDDEN,
+        keyguardHiddenTracker.addToEventStats(out, KEYGUARD_HIDDEN,
                 beginTime, endTime);
     }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index 8e1c060..d706537 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -135,6 +135,15 @@
                     stats.mTotalTimeForegroundServiceUsed = proto.readLong(
                             IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS);
                     break;
+                case (int) IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS:
+                    // Time attributes stored is an offset of the beginTime.
+                    stats.mLastTimeVisible = statsOut.beginTime + proto.readLong(
+                            IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS);
+                    break;
+                case (int) IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS:
+                    stats.mTotalTimeVisible = proto.readLong(
+                            IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS);
+                    break;
             }
         }
         if (stats.mLastTimeUsed == 0) {
@@ -337,6 +346,11 @@
                 usageStats.mLastTimeForegroundServiceUsed - stats.beginTime);
         proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS,
                 usageStats.mTotalTimeForegroundServiceUsed);
+        // Time attributes stored as an offset of the beginTime.
+        proto.write(IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS,
+                usageStats.mLastTimeVisible - stats.beginTime);
+        proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS,
+                usageStats.mTotalTimeVisible);
         proto.write(IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT, usageStats.mAppLaunchCount);
         writeChooserCounts(proto, usageStats);
         proto.end(token);
@@ -427,6 +441,7 @@
         proto.write(IntervalStatsProto.Event.TIME_MS, event.mTimeStamp - stats.beginTime);
         proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags);
         proto.write(IntervalStatsProto.Event.TYPE, event.mEventType);
+        proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId);
         switch (event.mEventType) {
             case UsageEvents.Event.CONFIGURATION_CHANGE:
                 if (event.mConfiguration != null) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 2621252..57dc08f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -16,6 +16,12 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.UsageEvents.Event.CHOOSER_ACTION;
+import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE;
+import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
+import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
+import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+
 import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
@@ -28,10 +34,10 @@
 import android.app.usage.EventStats;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManager;
-import android.app.usage.UsageStatsManager.StandbyBuckets;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.app.usage.UsageStatsManager.StandbyBuckets;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -75,9 +81,7 @@
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 
 /**
  * A service that collects, aggregates, and persists application usage data.
@@ -106,6 +110,7 @@
     static final int MSG_FLUSH_TO_DISK = 1;
     static final int MSG_REMOVE_USER = 2;
     static final int MSG_UID_STATE_CHANGED = 3;
+    static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4;
 
     private final Object mLock = new Object();
     Handler mHandler;
@@ -135,12 +140,10 @@
                 @Override
                 public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
                         int bucket, int reason) {
-                    Event event = new Event();
-                    event.mEventType = Event.STANDBY_BUCKET_CHANGED;
+                    Event event = new Event(Event.STANDBY_BUCKET_CHANGED,
+                            SystemClock.elapsedRealtime());
                     event.mBucketAndReason = (bucket << 16) | (reason & 0xFFFF);
                     event.mPackage = packageName;
-                    // This will later be converted to system time.
-                    event.mTimeStamp = SystemClock.elapsedRealtime();
                     mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
                 }
 
@@ -397,7 +400,7 @@
      * Assuming the event's timestamp is measured in milliseconds since boot,
      * convert it to a system wall time.
      */
-    private void convertToSystemTimeLocked(UsageEvents.Event event) {
+    private void convertToSystemTimeLocked(Event event) {
         event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
     }
 
@@ -406,7 +409,6 @@
      */
     void shutdown() {
         synchronized (mLock) {
-            mHandler.removeMessages(MSG_REPORT_EVENT);
             flushToDiskLocked();
         }
     }
@@ -414,7 +416,7 @@
     /**
      * Called by the Binder stub.
      */
-    void reportEvent(UsageEvents.Event event, int userId) {
+    void reportEvent(Event event, int userId) {
         synchronized (mLock) {
             final long timeNow = checkAndGetTimeLocked();
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -431,14 +433,14 @@
 
             mAppStandby.reportEvent(event, elapsedRealtime, userId);
             switch (event.mEventType) {
-                case Event.MOVE_TO_FOREGROUND:
+                case Event.ACTIVITY_RESUMED:
                     try {
                         mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
                     } catch (IllegalArgumentException iae) {
                         Slog.e(TAG, "Failed to note usage start", iae);
                     }
                     break;
-                case Event.MOVE_TO_BACKGROUND:
+                case Event.ACTIVITY_PAUSED:
                     try {
                         mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
                     } catch (IllegalArgumentException iae) {
@@ -450,10 +452,31 @@
     }
 
     /**
-     * Called by the Binder stub.
+     * Some events like FLUSH_TO_DISK need to be sent to all userId.
+     * @param event
+     */
+    void reportEventToAllUserId(Event event) {
+        synchronized (mLock) {
+            final int userCount = mUserState.size();
+            for (int i = 0; i < userCount; i++) {
+                Event copy = new Event(event);
+                reportEvent(copy, mUserState.keyAt(i));
+            }
+        }
+    }
+
+    /**
+     * Called by the Handler for message MSG_FLUSH_TO_DISK.
      */
     void flushToDisk() {
         synchronized (mLock) {
+            // Before flush to disk, report FLUSH_TO_DISK event to signal UsageStats to update app
+            // usage. In case of abrupt power shutdown like battery drain or cold temperature,
+            // all UsageStats has correct data up to last flush to disk.
+            // The FLUSH_TO_DISK event is an internal event, it will not show up in IntervalStats'
+            // EventList.
+            Event event = new Event(FLUSH_TO_DISK, SystemClock.elapsedRealtime());
+            reportEventToAllUserId(event);
             flushToDiskLocked();
         }
     }
@@ -656,9 +679,11 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_REPORT_EVENT:
-                    reportEvent((UsageEvents.Event) msg.obj, msg.arg1);
+                    reportEvent((Event) msg.obj, msg.arg1);
                     break;
-
+                case MSG_REPORT_EVENT_TO_ALL_USERID:
+                    reportEventToAllUserId((Event) msg.obj);
+                    break;
                 case MSG_FLUSH_TO_DISK:
                     flushToDisk();
                     break;
@@ -1120,20 +1145,11 @@
                 return;
             }
 
-            UsageEvents.Event event = new UsageEvents.Event();
+            Event event = new Event(CHOOSER_ACTION, SystemClock.elapsedRealtime());
             event.mPackage = packageName;
-
-            // This will later be converted to system time.
-            event.mTimeStamp = SystemClock.elapsedRealtime();
-
-            event.mEventType = Event.CHOOSER_ACTION;
-
             event.mAction = action;
-
             event.mContentType = contentType;
-
             event.mContentAnnotations = annotations;
-
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
 
@@ -1251,37 +1267,29 @@
     private final class LocalService extends UsageStatsManagerInternal {
 
         @Override
-        public void reportEvent(ComponentName component, int userId, int eventType) {
+        public void reportEvent(ComponentName component, int userId, int eventType,
+                int instanceId) {
             if (component == null) {
                 Slog.w(TAG, "Event reported without a component name");
                 return;
             }
 
-            UsageEvents.Event event = new UsageEvents.Event();
+            Event event = new Event(eventType, SystemClock.elapsedRealtime());
             event.mPackage = component.getPackageName();
             event.mClass = component.getClassName();
-
-            // This will later be converted to system time.
-            event.mTimeStamp = SystemClock.elapsedRealtime();
-
-            event.mEventType = eventType;
+            event.mInstanceId = instanceId;
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
 
         @Override
         public void reportEvent(String packageName, int userId, int eventType) {
             if (packageName == null) {
-                Slog.w(TAG, "Event reported without a package name");
+                Slog.w(TAG, "Event reported without a package name, eventType:" + eventType);
                 return;
             }
 
-            UsageEvents.Event event = new UsageEvents.Event();
+            Event event = new Event(eventType, SystemClock.elapsedRealtime());
             event.mPackage = packageName;
-
-            // This will later be converted to system time.
-            event.mTimeStamp = SystemClock.elapsedRealtime();
-
-            event.mEventType = eventType;
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
 
@@ -1292,13 +1300,8 @@
                 return;
             }
 
-            UsageEvents.Event event = new UsageEvents.Event();
+            Event event = new Event(CONFIGURATION_CHANGE, SystemClock.elapsedRealtime());
             event.mPackage = "android";
-
-            // This will later be converted to system time.
-            event.mTimeStamp = SystemClock.elapsedRealtime();
-
-            event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE;
             event.mConfiguration = new Configuration(config);
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
@@ -1311,14 +1314,9 @@
                 return;
             }
 
-            UsageEvents.Event event = new UsageEvents.Event();
+            Event event = new Event(NOTIFICATION_INTERRUPTION, SystemClock.elapsedRealtime());
             event.mPackage = packageName.intern();
             event.mNotificationChannelId = channelId.intern();
-
-            // This will later be converted to system time.
-            event.mTimeStamp = SystemClock.elapsedRealtime();
-
-            event.mEventType = Event.NOTIFICATION_INTERRUPTION;
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
 
@@ -1329,14 +1327,9 @@
                 return;
             }
 
-            UsageEvents.Event event = new UsageEvents.Event();
+            Event event = new Event(SHORTCUT_INVOCATION, SystemClock.elapsedRealtime());
             event.mPackage = packageName.intern();
             event.mShortcutId = shortcutId.intern();
-
-            // This will later be converted to system time.
-            event.mTimeStamp = SystemClock.elapsedRealtime();
-
-            event.mEventType = Event.SHORTCUT_INVOCATION;
             mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
         }
 
@@ -1372,7 +1365,7 @@
             // This method *WILL* do IO work, but we must block until it is finished or else
             // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because
             // we are shutting down.
-            shutdown();
+            UsageStatsService.this.shutdown();
         }
 
         @Override
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index 01e566c..ec475bf 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -61,11 +61,13 @@
     private static final String FLAGS_ATTR = "flags";
     private static final String CLASS_ATTR = "class";
     private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive";
+    private static final String TOTAL_TIME_VISIBLE_ATTR = "timeVisible";
     private static final String TOTAL_TIME_SERVICE_USED_ATTR = "timeServiceUsed";
     private static final String COUNT_ATTR = "count";
     private static final String ACTIVE_ATTR = "active";
     private static final String LAST_EVENT_ATTR = "lastEvent";
     private static final String TYPE_ATTR = "type";
+    private static final String INSTANCE_ID_ATTR = "instanceId";
     private static final String SHORTCUT_ID_ATTR = "shortcutId";
     private static final String STANDBY_BUCKET_ATTR = "standbyBucket";
     private static final String APP_LAUNCH_COUNT_ATTR = "appLaunchCount";
@@ -75,6 +77,7 @@
 
     // Time attributes stored as an offset of the beginTime.
     private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive";
+    private static final String LAST_TIME_VISIBLE_ATTR = "lastTimeVisible";
     private static final String LAST_TIME_SERVICE_USED_ATTR = "lastTimeServiceUsed";
     private static final String END_TIME_ATTR = "endTime";
     private static final String TIME_ATTR = "time";
@@ -92,6 +95,13 @@
                 parser, LAST_TIME_ACTIVE_ATTR);
 
         try {
+            stats.mLastTimeVisible = statsOut.beginTime + XmlUtils.readLongAttribute(
+                    parser, LAST_TIME_VISIBLE_ATTR);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to parse mLastTimeVisible", e);
+        }
+
+        try {
             stats.mLastTimeForegroundServiceUsed = statsOut.beginTime + XmlUtils.readLongAttribute(
                     parser, LAST_TIME_SERVICE_USED_ATTR);
         } catch (IOException e) {
@@ -101,8 +111,14 @@
         stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR);
 
         try {
+            stats.mTotalTimeVisible = XmlUtils.readLongAttribute(parser, TOTAL_TIME_VISIBLE_ATTR);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to parse mTotalTimeVisible", e);
+        }
+
+        try {
             stats.mTotalTimeForegroundServiceUsed = XmlUtils.readLongAttribute(parser,
-                TOTAL_TIME_SERVICE_USED_ATTR);
+                    TOTAL_TIME_SERVICE_USED_ATTR);
         } catch (IOException e) {
             Log.e(TAG, "Failed to parse mTotalTimeForegroundServiceUsed", e);
         }
@@ -199,6 +215,13 @@
         event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR);
 
         event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR);
+
+        try {
+            event.mInstanceId = XmlUtils.readIntAttribute(parser, INSTANCE_ID_ATTR);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to parse mInstanceId", e);
+        }
+
         switch (event.mEventType) {
             case UsageEvents.Event.CONFIGURATION_CHANGE:
                 event.mConfiguration = new Configuration();
@@ -227,10 +250,13 @@
         // Write the time offset.
         XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
                 usageStats.mLastTimeUsed - stats.beginTime);
+        XmlUtils.writeLongAttribute(xml, LAST_TIME_VISIBLE_ATTR,
+                usageStats.mLastTimeVisible - stats.beginTime);
         XmlUtils.writeLongAttribute(xml, LAST_TIME_SERVICE_USED_ATTR,
                 usageStats.mLastTimeForegroundServiceUsed - stats.beginTime);
         XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
         XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
+        XmlUtils.writeLongAttribute(xml, TOTAL_TIME_VISIBLE_ATTR, usageStats.mTotalTimeVisible);
         XmlUtils.writeLongAttribute(xml, TOTAL_TIME_SERVICE_USED_ATTR,
                 usageStats.mTotalTimeForegroundServiceUsed);
         XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);
@@ -317,6 +343,7 @@
         }
         XmlUtils.writeIntAttribute(xml, FLAGS_ATTR, event.mFlags);
         XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType);
+        XmlUtils.writeIntAttribute(xml, INSTANCE_ID_ATTR, event.mInstanceId);
 
         switch (event.mEventType) {
             case UsageEvents.Event.CONFIGURATION_CHANGE:
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 94d7dbb..5128ae1 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -16,10 +16,18 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.UsageStatsManager.INTERVAL_BEST;
+import static android.app.usage.UsageStatsManager.INTERVAL_COUNT;
+import static android.app.usage.UsageStatsManager.INTERVAL_DAILY;
+import static android.app.usage.UsageStatsManager.INTERVAL_MONTHLY;
+import static android.app.usage.UsageStatsManager.INTERVAL_WEEKLY;
+import static android.app.usage.UsageStatsManager.INTERVAL_YEARLY;
+
 import android.app.usage.ConfigurationStats;
 import android.app.usage.EventList;
 import android.app.usage.EventStats;
 import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
@@ -29,6 +37,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseIntArray;
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.usage.UsageStatsDatabase.StatCombiner;
@@ -65,7 +74,7 @@
     private final int mUserId;
 
     // STOPSHIP: Temporary member variable for debugging b/110930764.
-    private UsageEvents.Event mLastEvent;
+    private Event mLastEvent;
 
     private static final long[] INTERVAL_LENGTH = new long[] {
             UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
@@ -87,7 +96,7 @@
         mContext = context;
         mDailyExpiryDate = new UnixCalendar(0);
         mDatabase = new UsageStatsDatabase(usageStatsDir);
-        mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
+        mCurrentStats = new IntervalStats[INTERVAL_COUNT];
         mListener = listener;
         mLogPrefix = "User[" + Integer.toString(userId) + "] ";
         mUserId = userId;
@@ -125,28 +134,6 @@
             updateRolloverDeadline();
         }
 
-        // Now close off any events that were open at the time this was saved.
-        for (IntervalStats stat : mCurrentStats) {
-            final int pkgCount = stat.packageStats.size();
-            for (int i = 0; i < pkgCount; i++) {
-                final UsageStats pkgStats = stat.packageStats.valueAt(i);
-                if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()
-                        || !pkgStats.mLastForegroundServiceEventMap.isEmpty()) {
-                    if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()) {
-                        stat.update(pkgStats.mPackageName, null, stat.lastTimeSaved,
-                                UsageEvents.Event.END_OF_DAY);
-                    }
-                    if (!pkgStats.mLastForegroundServiceEventMap.isEmpty()) {
-                        stat.update(pkgStats.mPackageName, null , stat.lastTimeSaved,
-                                UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE);
-                    }
-                    notifyStatsChanged();
-                }
-            }
-
-            stat.updateConfigurationStats(null, stat.lastTimeSaved);
-        }
-
         if (mDatabase.isNewUpdate()) {
             notifyNewUpdate();
         }
@@ -158,40 +145,46 @@
         loadActiveStats(newTime);
     }
 
-    void reportEvent(UsageEvents.Event event) {
+    void reportEvent(Event event) {
         if (DEBUG) {
             Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
                     + "[" + event.mTimeStamp + "]: "
                     + eventToString(event.mEventType));
         }
 
-        mLastEvent = new UsageEvents.Event(event);
+        mLastEvent = new Event(event);
 
         if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
             // Need to rollover
             rolloverStats(event.mTimeStamp);
         }
 
-        final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
+        final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
 
         final Configuration newFullConfig = event.mConfiguration;
-        if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
-                currentDailyStats.activeConfiguration != null) {
+        if (event.mEventType == Event.CONFIGURATION_CHANGE
+                && currentDailyStats.activeConfiguration != null) {
             // Make the event configuration a delta.
             event.mConfiguration = Configuration.generateDelta(
                     currentDailyStats.activeConfiguration, newFullConfig);
         }
 
-        if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) {
+        if (event.mEventType != Event.SYSTEM_INTERACTION
+                // ACTIVITY_DESTROYED is a private event. If there is preceding ACTIVITY_STOPPED
+                // ACTIVITY_DESTROYED will be dropped. Otherwise it will be converted to
+                // ACTIVITY_STOPPED.
+                && event.mEventType != Event.ACTIVITY_DESTROYED
+                // FLUSH_TO_DISK is a private event.
+                && event.mEventType != Event.FLUSH_TO_DISK) {
             currentDailyStats.addEvent(event);
         }
 
         boolean incrementAppLaunch = false;
-        if (event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
+        if (event.mEventType == Event.ACTIVITY_RESUMED) {
             if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) {
                 incrementAppLaunch = true;
             }
-        } else if (event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND) {
+        } else if (event.mEventType == Event.ACTIVITY_PAUSED) {
             if (event.mPackage != null) {
                 mLastBackgroundedPackage = event.mPackage;
             }
@@ -199,10 +192,10 @@
 
         for (IntervalStats stats : mCurrentStats) {
             switch (event.mEventType) {
-                case UsageEvents.Event.CONFIGURATION_CHANGE: {
+                case Event.CONFIGURATION_CHANGE: {
                     stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
                 } break;
-                case UsageEvents.Event.CHOOSER_ACTION: {
+                case Event.CHOOSER_ACTION: {
                     stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction);
                     String[] annotations = event.mContentAnnotations;
                     if (annotations != null) {
@@ -211,21 +204,21 @@
                         }
                     }
                 } break;
-                case UsageEvents.Event.SCREEN_INTERACTIVE: {
+                case Event.SCREEN_INTERACTIVE: {
                     stats.updateScreenInteractive(event.mTimeStamp);
                 } break;
-                case UsageEvents.Event.SCREEN_NON_INTERACTIVE: {
+                case Event.SCREEN_NON_INTERACTIVE: {
                     stats.updateScreenNonInteractive(event.mTimeStamp);
                 } break;
-                case UsageEvents.Event.KEYGUARD_SHOWN: {
+                case Event.KEYGUARD_SHOWN: {
                     stats.updateKeyguardShown(event.mTimeStamp);
                 } break;
-                case UsageEvents.Event.KEYGUARD_HIDDEN: {
+                case Event.KEYGUARD_HIDDEN: {
                     stats.updateKeyguardHidden(event.mTimeStamp);
                 } break;
                 default: {
                     stats.update(event.mPackage, event.getClassName(),
-                            event.mTimeStamp, event.mEventType);
+                            event.mTimeStamp, event.mEventType, event.mInstanceId);
                     if (incrementAppLaunch) {
                         stats.incrementAppLaunchCount(event.mPackage);
                     }
@@ -286,12 +279,12 @@
      */
     private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
             StatCombiner<T> combiner) {
-        if (intervalType == UsageStatsManager.INTERVAL_BEST) {
+        if (intervalType == INTERVAL_BEST) {
             intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
             if (intervalType < 0) {
                 // Nothing saved to disk yet, so every stat is just as equal (no rollover has
                 // occurred.
-                intervalType = UsageStatsManager.INTERVAL_DAILY;
+                intervalType = INTERVAL_DAILY;
             }
         }
 
@@ -316,10 +309,10 @@
             }
 
             // STOPSHIP: Temporary logging for b/110930764.
-            if (intervalType == UsageStatsManager.INTERVAL_DAILY
+            if (intervalType == INTERVAL_DAILY
                     && mLastEvent != null && mLastEvent.mTimeStamp >= beginTime) {
                 final IntervalStats diskStats = mDatabase.getLatestUsageStats(
-                        UsageStatsManager.INTERVAL_DAILY);
+                        INTERVAL_DAILY);
                 StringBuilder sb = new StringBuilder(256);
                 sb.append("Last 24 hours of UsageStats missing! timeRange : ");
                 sb.append(beginTime);
@@ -395,11 +388,11 @@
     UsageEvents queryEvents(final long beginTime, final long endTime,
             boolean obfuscateInstantApps) {
         final ArraySet<String> names = new ArraySet<>();
-        List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
-                beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
+        List<Event> results = queryStats(INTERVAL_DAILY,
+                beginTime, endTime, new StatCombiner<Event>() {
                     @Override
                     public void combine(IntervalStats stats, boolean mutable,
-                            List<UsageEvents.Event> accumulatedResult) {
+                            List<Event> accumulatedResult) {
                         if (stats.events == null) {
                             return;
                         }
@@ -411,11 +404,13 @@
                                 return;
                             }
 
-                            UsageEvents.Event event = stats.events.get(i);
+                            Event event = stats.events.get(i);
                             if (obfuscateInstantApps) {
                                 event = event.getObfuscatedIfInstantApp();
                             }
-                            names.add(event.mPackage);
+                            if (event.mPackage != null) {
+                                names.add(event.mPackage);
+                            }
                             if (event.mClass != null) {
                                 names.add(event.mClass);
                             }
@@ -437,7 +432,7 @@
             final String packageName) {
         final ArraySet<String> names = new ArraySet<>();
         names.add(packageName);
-        final List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
+        final List<Event> results = queryStats(INTERVAL_DAILY,
                 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
                     if (stats.events == null) {
                         return;
@@ -450,7 +445,7 @@
                             return;
                         }
 
-                        final UsageEvents.Event event = stats.events.get(i);
+                        final Event event = stats.events.get(i);
                         if (!packageName.equals(event.mPackage)) {
                             continue;
                         }
@@ -492,33 +487,33 @@
         // Make a note of which components need a new CONTINUE_PREVIOUS_DAY or
         // CONTINUING_FOREGROUND_SERVICE entry.
         final Configuration previousConfig =
-                mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
-        ArraySet<String> continuePreviousDay = new ArraySet<>();
-        ArrayMap<String, ArrayMap<String, Integer>> continuePreviousDayForegroundActivity =
+                mCurrentStats[INTERVAL_DAILY].activeConfiguration;
+        ArraySet<String> continuePkgs = new ArraySet<>();
+        ArrayMap<String, SparseIntArray> continueActivity =
                 new ArrayMap<>();
-        ArrayMap<String, ArrayMap<String, Integer>> continuePreviousDayForegroundService =
+        ArrayMap<String, ArrayMap<String, Integer>> continueForegroundService =
                 new ArrayMap<>();
         for (IntervalStats stat : mCurrentStats) {
             final int pkgCount = stat.packageStats.size();
             for (int i = 0; i < pkgCount; i++) {
                 final UsageStats pkgStats = stat.packageStats.valueAt(i);
-                if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()
-                        || !pkgStats.mLastForegroundServiceEventMap.isEmpty()) {
-                    if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()) {
-                        continuePreviousDayForegroundActivity.put(pkgStats.mPackageName,
-                                pkgStats.mLastForegroundActivityEventMap);
+                if (pkgStats.mActivities.size() > 0
+                        || !pkgStats.mForegroundServices.isEmpty()) {
+                    if (pkgStats.mActivities.size() > 0) {
+                        continueActivity.put(pkgStats.mPackageName,
+                                pkgStats.mActivities);
                         stat.update(pkgStats.mPackageName, null,
                                 mDailyExpiryDate.getTimeInMillis() - 1,
-                                UsageEvents.Event.END_OF_DAY);
+                                Event.END_OF_DAY, 0);
                     }
-                    if (!pkgStats.mLastForegroundServiceEventMap.isEmpty()) {
-                        continuePreviousDayForegroundService.put(pkgStats.mPackageName,
-                                pkgStats.mLastForegroundServiceEventMap);
+                    if (!pkgStats.mForegroundServices.isEmpty()) {
+                        continueForegroundService.put(pkgStats.mPackageName,
+                                pkgStats.mForegroundServices);
                         stat.update(pkgStats.mPackageName, null,
                                 mDailyExpiryDate.getTimeInMillis() - 1,
-                                UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE);
+                                Event.ROLLOVER_FOREGROUND_SERVICE, 0);
                     }
-                    continuePreviousDay.add(pkgStats.mPackageName);
+                    continuePkgs.add(pkgStats.mPackageName);
                     notifyStatsChanged();
                 }
             }
@@ -532,27 +527,27 @@
         mDatabase.prune(currentTimeMillis);
         loadActiveStats(currentTimeMillis);
 
-        final int continueCount = continuePreviousDay.size();
+        final int continueCount = continuePkgs.size();
         for (int i = 0; i < continueCount; i++) {
-            String pkgName = continuePreviousDay.valueAt(i);
-            final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime;
+            String pkgName = continuePkgs.valueAt(i);
+            final long beginTime = mCurrentStats[INTERVAL_DAILY].beginTime;
             for (IntervalStats stat : mCurrentStats) {
-                if (continuePreviousDayForegroundActivity.containsKey(pkgName)) {
-                    final ArrayMap<String, Integer> foregroundActivityEventMap =
-                            continuePreviousDayForegroundActivity.get(pkgName);
-                    final int size = foregroundActivityEventMap.size();
+                if (continueActivity.containsKey(pkgName)) {
+                    final SparseIntArray eventMap =
+                            continueActivity.get(pkgName);
+                    final int size = eventMap.size();
                     for (int j = 0; j < size; j++) {
-                        stat.update(pkgName, foregroundActivityEventMap.keyAt(j), beginTime,
-                                UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
+                        stat.update(pkgName, null, beginTime,
+                                eventMap.valueAt(j), eventMap.keyAt(j));
                     }
                 }
-                if (continuePreviousDayForegroundService.containsKey(pkgName)) {
-                    final ArrayMap<String, Integer> foregroundServiceEventMap =
-                            continuePreviousDayForegroundService.get(pkgName);
-                    final int size = foregroundServiceEventMap.size();
+                if (continueForegroundService.containsKey(pkgName)) {
+                    final ArrayMap<String, Integer> eventMap =
+                            continueForegroundService.get(pkgName);
+                    final int size = eventMap.size();
                     for (int j = 0; j < size; j++) {
-                        stat.update(pkgName, foregroundServiceEventMap.keyAt(j), beginTime,
-                                UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE);
+                        stat.update(pkgName, eventMap.keyAt(j), beginTime,
+                                eventMap.valueAt(j), 0);
                     }
                 }
                 stat.updateConfigurationStats(previousConfig, beginTime);
@@ -611,7 +606,7 @@
 
     private void updateRolloverDeadline() {
         mDailyExpiryDate.setTimeInMillis(
-                mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
+                mCurrentStats[INTERVAL_DAILY].beginTime);
         mDailyExpiryDate.addDays(1);
         Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
                 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
@@ -660,7 +655,7 @@
     }
 
 
-    void printEvent(IndentingPrintWriter pw, UsageEvents.Event event, boolean prettyDates) {
+    void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) {
         pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
         pw.printPair("type", eventToString(event.mEventType));
         pw.printPair("package", event.mPackage);
@@ -673,10 +668,15 @@
         if (event.mShortcutId != null) {
             pw.printPair("shortcutId", event.mShortcutId);
         }
-        if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) {
+        if (event.mEventType == Event.STANDBY_BUCKET_CHANGED) {
             pw.printPair("standbyBucket", event.getStandbyBucket());
             pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason()));
+        } else if (event.mEventType == Event.ACTIVITY_RESUMED
+                || event.mEventType == Event.ACTIVITY_PAUSED
+                || event.mEventType == Event.ACTIVITY_STOPPED) {
+            pw.printPair("instanceId", event.getInstanceId());
         }
+
         if (event.mNotificationChannelId != null) {
             pw.printPair("channelId", event.mNotificationChannelId);
         }
@@ -691,11 +691,11 @@
 
         final long beginTime = yesterday.getTimeInMillis();
 
-        List<UsageEvents.Event> events = queryStats(UsageStatsManager.INTERVAL_DAILY,
-                beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
+        List<Event> events = queryStats(INTERVAL_DAILY,
+                beginTime, endTime, new StatCombiner<Event>() {
                     @Override
                     public void combine(IntervalStats stats, boolean mutable,
-                            List<UsageEvents.Event> accumulatedResult) {
+                            List<Event> accumulatedResult) {
                         if (stats.events == null) {
                             return;
                         }
@@ -707,7 +707,7 @@
                                 return;
                             }
 
-                            UsageEvents.Event event = stats.events.get(i);
+                            Event event = stats.events.get(i);
                             if (pkg != null && !pkg.equals(event.mPackage)) {
                                 continue;
                             }
@@ -727,7 +727,7 @@
         pw.println(")");
         if (events != null) {
             pw.increaseIndent();
-            for (UsageEvents.Event event : events) {
+            for (Event event : events) {
                 printEvent(pw, event, prettyDates);
             }
             pw.decreaseIndent();
@@ -772,9 +772,17 @@
                 continue;
             }
             pw.printPair("package", usageStats.mPackageName);
-            pw.printPair("totalTime",
+            pw.printPair("totalTimeUsed",
                     formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
-            pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
+            pw.printPair("lastTimeUsed", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
+            pw.printPair("totalTimeVisible",
+                    formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates));
+            pw.printPair("lastTimeVisible",
+                    formatDateTime(usageStats.mLastTimeVisible, prettyDates));
+            pw.printPair("totalTimeFS",
+                    formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates));
+            pw.printPair("lastTimeFS",
+                    formatDateTime(usageStats.mLastTimeForegroundServiceUsed, prettyDates));
             pw.printPair("appLaunchCount", usageStats.mAppLaunchCount);
             pw.println();
         }
@@ -845,7 +853,7 @@
             final EventList events = stats.events;
             final int eventCount = events != null ? events.size() : 0;
             for (int i = 0; i < eventCount; i++) {
-                final UsageEvents.Event event = events.get(i);
+                final Event event = events.get(i);
                 if (pkg != null && !pkg.equals(event.mPackage)) {
                     continue;
                 }
@@ -858,13 +866,13 @@
 
     private static String intervalToString(int interval) {
         switch (interval) {
-            case UsageStatsManager.INTERVAL_DAILY:
+            case INTERVAL_DAILY:
                 return "daily";
-            case UsageStatsManager.INTERVAL_WEEKLY:
+            case INTERVAL_WEEKLY:
                 return "weekly";
-            case UsageStatsManager.INTERVAL_MONTHLY:
+            case INTERVAL_MONTHLY:
                 return "monthly";
-            case UsageStatsManager.INTERVAL_YEARLY:
+            case INTERVAL_YEARLY:
                 return "yearly";
             default:
                 return "?";
@@ -873,47 +881,49 @@
 
     private static String eventToString(int eventType) {
         switch (eventType) {
-            case UsageEvents.Event.NONE:
+            case Event.NONE:
                 return "NONE";
-            case UsageEvents.Event.MOVE_TO_BACKGROUND:
-                return "MOVE_TO_BACKGROUND";
-            case UsageEvents.Event.MOVE_TO_FOREGROUND:
-                return "MOVE_TO_FOREGROUND";
-            case UsageEvents.Event.FOREGROUND_SERVICE_START:
+            case Event.ACTIVITY_PAUSED:
+                return "ACTIVITY_PAUSED";
+            case Event.ACTIVITY_RESUMED:
+                return "ACTIVITY_RESUMED";
+            case Event.FOREGROUND_SERVICE_START:
                 return "FOREGROUND_SERVICE_START";
-            case UsageEvents.Event.FOREGROUND_SERVICE_STOP:
+            case Event.FOREGROUND_SERVICE_STOP:
                 return "FOREGROUND_SERVICE_STOP";
-            case UsageEvents.Event.END_OF_DAY:
+            case Event.ACTIVITY_STOPPED:
+                return "ACTIVITY_STOPPED";
+            case Event.END_OF_DAY:
                 return "END_OF_DAY";
-            case UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE:
+            case Event.ROLLOVER_FOREGROUND_SERVICE:
                 return "ROLLOVER_FOREGROUND_SERVICE";
-            case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
+            case Event.CONTINUE_PREVIOUS_DAY:
                 return "CONTINUE_PREVIOUS_DAY";
-            case UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE:
+            case Event.CONTINUING_FOREGROUND_SERVICE:
                 return "CONTINUING_FOREGROUND_SERVICE";
-            case UsageEvents.Event.CONFIGURATION_CHANGE:
+            case Event.CONFIGURATION_CHANGE:
                 return "CONFIGURATION_CHANGE";
-            case UsageEvents.Event.SYSTEM_INTERACTION:
+            case Event.SYSTEM_INTERACTION:
                 return "SYSTEM_INTERACTION";
-            case UsageEvents.Event.USER_INTERACTION:
+            case Event.USER_INTERACTION:
                 return "USER_INTERACTION";
-            case UsageEvents.Event.SHORTCUT_INVOCATION:
+            case Event.SHORTCUT_INVOCATION:
                 return "SHORTCUT_INVOCATION";
-            case UsageEvents.Event.CHOOSER_ACTION:
+            case Event.CHOOSER_ACTION:
                 return "CHOOSER_ACTION";
-            case UsageEvents.Event.NOTIFICATION_SEEN:
+            case Event.NOTIFICATION_SEEN:
                 return "NOTIFICATION_SEEN";
-            case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
+            case Event.STANDBY_BUCKET_CHANGED:
                 return "STANDBY_BUCKET_CHANGED";
-            case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+            case Event.NOTIFICATION_INTERRUPTION:
                 return "NOTIFICATION_INTERRUPTION";
-            case UsageEvents.Event.SLICE_PINNED:
+            case Event.SLICE_PINNED:
                 return "SLICE_PINNED";
-            case UsageEvents.Event.SLICE_PINNED_PRIV:
+            case Event.SLICE_PINNED_PRIV:
                 return "SLICE_PINNED_PRIV";
-            case UsageEvents.Event.SCREEN_INTERACTIVE:
+            case Event.SCREEN_INTERACTIVE:
                 return "SCREEN_INTERACTIVE";
-            case UsageEvents.Event.SCREEN_NON_INTERACTIVE:
+            case Event.SCREEN_NON_INTERACTIVE:
                 return "SCREEN_NON_INTERACTIVE";
             case UsageEvents.Event.KEYGUARD_SHOWN:
                 return "KEYGUARD_SHOWN";
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/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl b/telecomm/java/android/telecom/CallIdentification.aidl
similarity index 63%
copy from telephony/java/com/android/internal/telephony/rcs/IRcs.aidl
copy to telecomm/java/android/telecom/CallIdentification.aidl
index 4c289ac..532535c 100644
--- a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl
+++ b/telecomm/java/android/telecom/CallIdentification.aidl
@@ -1,11 +1,11 @@
 /*
- * 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.
  * 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,12 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony.rcs;
+package android.telecom;
 
-interface IRcs {
-    // RcsManager APIs
-    void deleteThread(int threadId);
-
-    // RcsThread APIs
-    int getMessageCount(int rcsThreadId);
-}
\ No newline at end of file
+/**
+ * {@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/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 9f0bdd7..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.
@@ -460,6 +463,12 @@
             "android.telecom.extra.START_CALL_WITH_RTT";
 
     /**
+     * A boolean extra set to indicate whether an app is eligible to be bound to when there are
+     * ongoing calls on the device.
+     */
+    public static final String EXTRA_IS_ENABLED = "android.telecom.extra.IS_ENABLED";
+
+    /**
      * A boolean meta-data value indicating whether an {@link InCallService} implements an
      * in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which
      * would also like to replace the in-call interface should set this meta-data to {@code true} in
@@ -470,9 +479,7 @@
     /**
      * A boolean meta-data value indicating whether an {@link InCallService} implements an
      * in-call user interface to be used while the device is in car-mode (see
-     * {@link android.content.res.Configuration.UI_MODE_TYPE_CAR}).
-     *
-     * @hide
+     * {@link android.content.res.Configuration#UI_MODE_TYPE_CAR}).
      */
     public static final String METADATA_IN_CALL_SERVICE_CAR_MODE_UI =
             "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI";
@@ -1184,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})
@@ -1217,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.
      *
@@ -1462,7 +1400,6 @@
      * otherwise.
      */
     @RequiresPermission(Manifest.permission.ANSWER_PHONE_CALLS)
-    @SystemApi
     public boolean endCall() {
         try {
             if (isServiceConnected()) {
@@ -1539,7 +1476,6 @@
     /**
      * Returns whether TTY is supported on this device.
      */
-    @SystemApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             android.Manifest.permission.READ_PHONE_STATE
@@ -2040,7 +1976,6 @@
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException handleCallIntent: " + e);
         }
-
     }
 
     private ITelecomService getTelecomService() {
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/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 6a396ce..1cbe5a2 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -3640,8 +3640,9 @@
 
         /**
          * Generates a content {@link Uri} used to receive updates on precise carrier identity
-         * change on the given subscriptionId
-         * {@link TelephonyManager#ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED}.
+         * change on the given subscriptionId returned by
+         * {@link TelephonyManager#getSimPreciseCarrierId()}.
+         * @see TelephonyManager#ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED
          * <p>
          * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the
          * precise carrier identity {@link TelephonyManager#getSimPreciseCarrierId()}
@@ -3652,7 +3653,6 @@
          *
          * @param subscriptionId the subscriptionId to receive updates on
          * @return the Uri used to observe precise carrier identity changes
-         * @hide
          */
         public static Uri getPreciseCarrierIdUriForSubscriptionId(int subscriptionId) {
             return Uri.withAppendedPath(Uri.withAppendedPath(CONTENT_URI, "precise"),
@@ -3674,24 +3674,22 @@
         public static final String CARRIER_ID = "carrier_id";
 
         /**
-         * A user facing carrier name for precise carrier id.
-         * @see TelephonyManager#getSimPreciseCarrierIdName()
-         * This is not a database column, only used to notify content observers for
-         * {@link #getPreciseCarrierIdUriForSubscriptionId(int)}
-         * @hide
-         */
-        public static final String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name";
-
-        /**
          * A fine-grained carrier id.
          * @see TelephonyManager#getSimPreciseCarrierId()
          * This is not a database column, only used to notify content observers for
          * {@link #getPreciseCarrierIdUriForSubscriptionId(int)}
-         * @hide
          */
         public static final String PRECISE_CARRIER_ID = "precise_carrier_id";
 
         /**
+         * A user facing carrier name for precise carrier id {@link #PRECISE_CARRIER_ID}.
+         * @see TelephonyManager#getSimPreciseCarrierIdName()
+         * This is not a database column, only used to notify content observers for
+         * {@link #getPreciseCarrierIdUriForSubscriptionId(int)}
+         */
+        public static final String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name";
+
+        /**
          * A unique parent carrier id. The parent-child
          * relationship can be used to further differentiate a single carrier by different networks,
          * by prepaid v.s. postpaid or even by 4G v.s. 3G plan. It's an optional field.
@@ -3703,18 +3701,6 @@
         public static final String PARENT_CARRIER_ID = "parent_carrier_id";
 
         /**
-         * A unique mno carrier id. mno carrier shares the same {@link All#MCCMNC} as carrier id
-         * and can be solely identified by {@link All#MCCMNC} only. If there is no such mno
-         * carrier, then mno carrier id equals to {@link #CARRIER_ID carrier id}.
-         *
-         * <p>mno carrier id can be used as fallback id. When the exact carrier id configurations
-         * are not found, usually fall back to its mno carrier id.
-         * <P>Type: INTEGER </P>
-         * @hide
-         */
-        public static final String MNO_CARRIER_ID = "mno_carrier_id";
-
-        /**
          * Contains mappings between matching rules with carrier id for all carriers.
          * @hide
          */
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 60eb18c..fd14916 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -68,7 +68,13 @@
      * This intent is broadcast by the system when carrier config changes. An int is specified in
      * {@link #EXTRA_SLOT_INDEX} to indicate the slot index that this is for. An optional int extra
      * {@link #EXTRA_SUBSCRIPTION_INDEX} is included to indicate the subscription index if a valid
-     * one is available for the slot index.
+     * one is available for the slot index. An optional int extra
+     * {@link TelephonyManager#EXTRA_CARRIER_ID} is included to indicate the carrier id for the
+     * changed carrier configuration. An optional int extra
+     * {@link TelephonyManager#EXTRA_PRECISE_CARRIER_ID} is included to indicate the precise
+     * carrier id for the changed carrier configuration.
+     * @see TelephonyManager#getSimCarrierId()
+     * @see TelephonyManager#getSimPreciseCarrierId()
      */
     public static final String
             ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
@@ -81,7 +87,6 @@
      * Specifies a value that identifies the version of the carrier configuration that is
      * currently in use. This string is displayed on the UI.
      * The format of the string is not specified.
-     * @hide
      */
     public static final String KEY_CARRIER_CONFIG_VERSION_STRING =
             "carrier_config_version_string";
@@ -403,7 +408,6 @@
      * @see SubscriptionManager#getSubscriptionPlans(int)
      * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List)
      */
-    @SystemApi
     public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING =
             "config_plans_package_override_string";
 
@@ -1394,9 +1398,9 @@
      * Example: "default"
      *
      * {@code ERROR_CODE_1} is an integer defined in
-     * {@link com.android.internal.telephony.dataconnection.DcFailCause DcFailure}
+     * {@link DataFailCause DcFailure}
      * Example:
-     * {@link com.android.internal.telephony.dataconnection.DcFailCause#MISSING_UNKNOWN_APN}
+     * {@link DataFailCause#MISSING_UNKNOWN_APN}
      *
      * {@code CARRIER_ACTION_IDX_1} is an integer defined in
      * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
@@ -2654,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/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
new file mode 100644
index 0000000..c6f7d0e
--- /dev/null
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -0,0 +1,259 @@
+/*
+ * 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.telephony;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Returned as the reason for a connection failure as defined
+ * by RIL_DataCallFailCause in ril.h and some local errors.
+ * @hide
+ */
+public enum DataFailCause {
+    NONE(0),
+
+    // This series of errors as specified by the standards
+    // specified in ril.h
+    OPERATOR_BARRED(0x08),                  /* no retry */
+    NAS_SIGNALLING(0x0E),
+    LLC_SNDCP(0x19),
+    INSUFFICIENT_RESOURCES(0x1A),
+    MISSING_UNKNOWN_APN(0x1B),              /* no retry */
+    UNKNOWN_PDP_ADDRESS_TYPE(0x1C),         /* no retry */
+    USER_AUTHENTICATION(0x1D),              /* no retry */
+    ACTIVATION_REJECT_GGSN(0x1E),           /* no retry */
+    ACTIVATION_REJECT_UNSPECIFIED(0x1F),
+    SERVICE_OPTION_NOT_SUPPORTED(0x20),     /* no retry */
+    SERVICE_OPTION_NOT_SUBSCRIBED(0x21),    /* no retry */
+    SERVICE_OPTION_OUT_OF_ORDER(0x22),
+    NSAPI_IN_USE(0x23),                     /* no retry */
+    REGULAR_DEACTIVATION(0x24),             /* possibly restart radio, based on config */
+    QOS_NOT_ACCEPTED(0x25),
+    NETWORK_FAILURE(0x26),
+    UMTS_REACTIVATION_REQ(0x27),
+    FEATURE_NOT_SUPP(0x28),
+    TFT_SEMANTIC_ERROR(0x29),
+    TFT_SYTAX_ERROR(0x2A),
+    UNKNOWN_PDP_CONTEXT(0x2B),
+    FILTER_SEMANTIC_ERROR(0x2C),
+    FILTER_SYTAX_ERROR(0x2D),
+    PDP_WITHOUT_ACTIVE_TFT(0x2E),
+    ONLY_IPV4_ALLOWED(0x32),                /* no retry */
+    ONLY_IPV6_ALLOWED(0x33),                /* no retry */
+    ONLY_SINGLE_BEARER_ALLOWED(0x34),
+    ESM_INFO_NOT_RECEIVED(0x35),
+    PDN_CONN_DOES_NOT_EXIST(0x36),
+    MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED(0x37),
+    MAX_ACTIVE_PDP_CONTEXT_REACHED(0x41),
+    UNSUPPORTED_APN_IN_CURRENT_PLMN(0x42),
+    INVALID_TRANSACTION_ID(0x51),
+    MESSAGE_INCORRECT_SEMANTIC(0x5F),
+    INVALID_MANDATORY_INFO(0x60),
+    MESSAGE_TYPE_UNSUPPORTED(0x61),
+    MSG_TYPE_NONCOMPATIBLE_STATE(0x62),
+    UNKNOWN_INFO_ELEMENT(0x63),
+    CONDITIONAL_IE_ERROR(0x64),
+    MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE(0x65),
+    PROTOCOL_ERRORS(0x6F),                  /* no retry */
+    APN_TYPE_CONFLICT(0x70),
+    INVALID_PCSCF_ADDR(0x71),
+    INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN(0x72),
+    EMM_ACCESS_BARRED(0x73),
+    EMERGENCY_IFACE_ONLY(0x74),
+    IFACE_MISMATCH(0x75),
+    COMPANION_IFACE_IN_USE(0x76),
+    IP_ADDRESS_MISMATCH(0x77),
+    IFACE_AND_POL_FAMILY_MISMATCH(0x78),
+    EMM_ACCESS_BARRED_INFINITE_RETRY(0x79),
+    AUTH_FAILURE_ON_EMERGENCY_CALL(0x7A),
+
+    // OEM sepecific error codes. To be used by OEMs when they don't
+    // want to reveal error code which would be replaced by ERROR_UNSPECIFIED
+    OEM_DCFAILCAUSE_1(0x1001),
+    OEM_DCFAILCAUSE_2(0x1002),
+    OEM_DCFAILCAUSE_3(0x1003),
+    OEM_DCFAILCAUSE_4(0x1004),
+    OEM_DCFAILCAUSE_5(0x1005),
+    OEM_DCFAILCAUSE_6(0x1006),
+    OEM_DCFAILCAUSE_7(0x1007),
+    OEM_DCFAILCAUSE_8(0x1008),
+    OEM_DCFAILCAUSE_9(0x1009),
+    OEM_DCFAILCAUSE_10(0x100A),
+    OEM_DCFAILCAUSE_11(0x100B),
+    OEM_DCFAILCAUSE_12(0x100C),
+    OEM_DCFAILCAUSE_13(0x100D),
+    OEM_DCFAILCAUSE_14(0x100E),
+    OEM_DCFAILCAUSE_15(0x100F),
+
+    // Local errors generated by Vendor RIL
+    // specified in ril.h
+    REGISTRATION_FAIL(-1),
+    GPRS_REGISTRATION_FAIL(-2),
+    SIGNAL_LOST(-3),                        /* no retry */
+    PREF_RADIO_TECH_CHANGED(-4),
+    RADIO_POWER_OFF(-5),                    /* no retry */
+    TETHERED_CALL_ACTIVE(-6),               /* no retry */
+    ERROR_UNSPECIFIED(0xFFFF),
+
+    // Errors generated by the Framework
+    // specified here
+    UNKNOWN(0x10000),
+    RADIO_NOT_AVAILABLE(0x10001),                   /* no retry */
+    UNACCEPTABLE_NETWORK_PARAMETER(0x10002),        /* no retry */
+    CONNECTION_TO_DATACONNECTIONAC_BROKEN(0x10003),
+    LOST_CONNECTION(0x10004),
+    RESET_BY_FRAMEWORK(0x10005);
+
+    private final int mErrorCode;
+    private static final HashMap<Integer, DataFailCause> sErrorCodeToFailCauseMap;
+    static {
+        sErrorCodeToFailCauseMap = new HashMap<Integer, DataFailCause>();
+        for (DataFailCause fc : values()) {
+            sErrorCodeToFailCauseMap.put(fc.getErrorCode(), fc);
+        }
+    }
+
+    /**
+     * Map of subId -> set of data call setup permanent failure for the carrier.
+     */
+    private static final HashMap<Integer, HashSet<DataFailCause>> sPermanentFailureCache =
+            new HashMap<>();
+
+    DataFailCause(int errorCode) {
+        mErrorCode = errorCode;
+    }
+
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /**
+     * Returns whether or not the fail cause is a failure that requires a modem restart
+     *
+     * @param context device context
+     * @param subId subscription index
+     * @return true if the fail cause code needs platform to trigger a modem restart.
+     */
+    public boolean isRadioRestartFailure(Context context, int subId) {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle b = configManager.getConfigForSubId(subId);
+
+            if (b != null) {
+                if (this == REGULAR_DEACTIVATION
+                        && b.getBoolean(CarrierConfigManager
+                        .KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL)) {
+                    // This is for backward compatibility support. We need to continue support this
+                    // old configuration until it gets removed in the future.
+                    return true;
+                }
+                // Check the current configurations.
+                int[] causeCodes = b.getIntArray(CarrierConfigManager
+                        .KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY);
+                if (causeCodes != null) {
+                    return Arrays.stream(causeCodes).anyMatch(i -> i == getErrorCode());
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public boolean isPermanentFailure(Context context, int subId) {
+
+        synchronized (sPermanentFailureCache) {
+
+            HashSet<DataFailCause> permanentFailureSet = sPermanentFailureCache.get(subId);
+
+            // In case of cache miss, we need to look up the settings from carrier config.
+            if (permanentFailureSet == null) {
+                // Retrieve the permanent failure from carrier config
+                CarrierConfigManager configManager = (CarrierConfigManager)
+                        context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+                if (configManager != null) {
+                    PersistableBundle b = configManager.getConfigForSubId(subId);
+                    if (b != null) {
+                        String[] permanentFailureStrings = b.getStringArray(CarrierConfigManager.
+                                KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS);
+
+                        if (permanentFailureStrings != null) {
+                            permanentFailureSet = new HashSet<>();
+                            for (String failure : permanentFailureStrings) {
+                                permanentFailureSet.add(DataFailCause.valueOf(failure));
+                            }
+                        }
+                    }
+                }
+
+                // If we are not able to find the configuration from carrier config, use the default
+                // ones.
+                if (permanentFailureSet == null) {
+                    permanentFailureSet = new HashSet<DataFailCause>() {
+                        {
+                            add(OPERATOR_BARRED);
+                            add(MISSING_UNKNOWN_APN);
+                            add(UNKNOWN_PDP_ADDRESS_TYPE);
+                            add(USER_AUTHENTICATION);
+                            add(ACTIVATION_REJECT_GGSN);
+                            add(SERVICE_OPTION_NOT_SUPPORTED);
+                            add(SERVICE_OPTION_NOT_SUBSCRIBED);
+                            add(NSAPI_IN_USE);
+                            add(ONLY_IPV4_ALLOWED);
+                            add(ONLY_IPV6_ALLOWED);
+                            add(PROTOCOL_ERRORS);
+                            add(RADIO_POWER_OFF);
+                            add(TETHERED_CALL_ACTIVE);
+                            add(RADIO_NOT_AVAILABLE);
+                            add(UNACCEPTABLE_NETWORK_PARAMETER);
+                            add(SIGNAL_LOST);
+                        }
+                    };
+                }
+
+                sPermanentFailureCache.put(subId, permanentFailureSet);
+            }
+
+            return permanentFailureSet.contains(this);
+        }
+    }
+
+    public boolean isEventLoggable() {
+        return (this == OPERATOR_BARRED) || (this == INSUFFICIENT_RESOURCES) ||
+                (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) ||
+                (this == ACTIVATION_REJECT_GGSN) || (this == ACTIVATION_REJECT_UNSPECIFIED) ||
+                (this == SERVICE_OPTION_NOT_SUBSCRIBED) ||
+                (this == SERVICE_OPTION_NOT_SUPPORTED) ||
+                (this == SERVICE_OPTION_OUT_OF_ORDER) || (this == NSAPI_IN_USE) ||
+                (this == ONLY_IPV4_ALLOWED) || (this == ONLY_IPV6_ALLOWED) ||
+                (this == PROTOCOL_ERRORS) || (this == SIGNAL_LOST) ||
+                (this == RADIO_POWER_OFF) || (this == TETHERED_CALL_ACTIVE) ||
+                (this == UNACCEPTABLE_NETWORK_PARAMETER);
+    }
+
+    public static DataFailCause fromInt(int errorCode) {
+        DataFailCause fc = sErrorCodeToFailCauseMap.get(errorCode);
+        if (fc == null) {
+            fc = UNKNOWN;
+        }
+        return fc;
+    }
+}
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/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index ea408bf..1b37bad 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -365,7 +365,6 @@
      *
      * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
      */
-    @SystemApi
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(allOf = {
             android.Manifest.permission.MODIFY_PHONE_STATE,
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index e932928..8c65eb6 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -53,6 +53,7 @@
 import android.os.ServiceManager;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsMmTelManager;
+import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 
@@ -68,6 +69,7 @@
 import java.util.Locale;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * SubscriptionManager is the application interface to SubscriptionController
@@ -570,7 +572,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
      */
@@ -634,7 +635,6 @@
      * the user is interested in.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    @SystemApi
     public static final String ACTION_MANAGE_SUBSCRIPTION_PLANS
             = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS";
 
@@ -654,7 +654,6 @@
      * {@code android.permission.MANAGE_SUBSCRIPTION_PLANS} permission.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    @SystemApi
     public static final String ACTION_REFRESH_SUBSCRIPTION_PLANS
             = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS";
 
@@ -2060,7 +2059,6 @@
      * @throws SecurityException if the caller doesn't meet the requirements
      *             outlined above.
      */
-    @SystemApi
     public @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) {
         try {
             SubscriptionPlan[] subscriptionPlans =
@@ -2092,7 +2090,6 @@
      * @throws SecurityException if the caller doesn't meet the requirements
      *             outlined above.
      */
-    @SystemApi
     public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) {
         try {
             getNetworkPolicy().setSubscriptionPlans(subId,
@@ -2134,7 +2131,6 @@
      * @throws SecurityException if the caller doesn't meet the requirements
      *             outlined above.
      */
-    @SystemApi
     public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
             @DurationMillisLong long timeoutMillis) {
         try {
@@ -2170,7 +2166,6 @@
      * @throws SecurityException if the caller doesn't meet the requirements
      *             outlined above.
      */
-    @SystemApi
     public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
             @DurationMillisLong long timeoutMillis) {
         try {
@@ -2383,18 +2378,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;
     }
 
     /**
@@ -2407,7 +2416,7 @@
      *
      * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
      * permission or had carrier privilege permission on the subscriptions:
-     * {@link TelephonyManager#hasCarrierPrivileges(int)} or
+     * {@link TelephonyManager#hasCarrierPrivileges()} or
      * {@link #canManageSubscription(SubscriptionInfo)}
      *
      * @throws SecurityException if the caller doesn't meet the requirements
@@ -2445,7 +2454,7 @@
      *
      * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
      * permission or had carrier privilege permission on the subscriptions:
-     * {@link TelephonyManager#hasCarrierPrivileges(int)} or
+     * {@link TelephonyManager#hasCarrierPrivileges()} or
      * {@link #canManageSubscription(SubscriptionInfo)}
      *
      * @throws SecurityException if the caller doesn't meet the requirements
@@ -2481,7 +2490,7 @@
      *
      * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE}
      * permission or had carrier privilege permission on the subscription.
-     * {@link TelephonyManager#hasCarrierPrivileges(int)}
+     * {@link TelephonyManager#hasCarrierPrivileges()}
      *
      * @throws SecurityException if the caller doesn't meet the requirements
      *             outlined above.
@@ -2514,18 +2523,62 @@
     }
 
     /**
-     * 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;
+    }
+
+    /**
+     * Whether system UI should hide a subscription. If it's a bundled opportunistic
+     * subscription, it shouldn't show up in anywhere in Settings app, dialer app,
+     * or status bar.
+     *
+     * @param info the subscriptionInfo to check against.
+     * @return true if this subscription should be hidden.
+     *
+     * @hide
+     */
+    public static boolean shouldHideSubscription(SubscriptionInfo info) {
+        return (info != null && !TextUtils.isEmpty(info.getGroupUuid()) && info.isOpportunistic());
+    }
+
+    /**
+     * Return a list of subscriptions that are available and visible to the user.
+     * Used by Settings app to show a list of subscriptions for user to pick.
+     *
+     * <p>
+     * Permissions android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE is required
+     * for getSelectableSubscriptionInfoList to be invoked.
+     * @return list of user selectable subscriptions.
+     *
+     * @hide
+     */
+    public @Nullable List<SubscriptionInfo> getSelectableSubscriptionInfoList() {
+        List<SubscriptionInfo> availableList = getAvailableSubscriptionInfoList();
+        if (availableList == null) {
+            return null;
+        } else {
+            return getAvailableSubscriptionInfoList().stream()
+                    .filter(subInfo -> !shouldHideSubscription(subInfo))
+                    .collect(Collectors.toList());
+        }
     }
 
     private interface CallISubMethodHelper {
diff --git a/telephony/java/android/telephony/SubscriptionPlan.java b/telephony/java/android/telephony/SubscriptionPlan.java
index e8bbe42..d67169c 100644
--- a/telephony/java/android/telephony/SubscriptionPlan.java
+++ b/telephony/java/android/telephony/SubscriptionPlan.java
@@ -45,7 +45,6 @@
  * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List)
  * @see SubscriptionManager#getSubscriptionPlans(int)
  */
-@SystemApi
 public final class SubscriptionPlan implements Parcelable {
     /** {@hide} */
     @IntDef(prefix = "LIMIT_BEHAVIOR_", value = {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 33f8567..e0632b1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -225,6 +225,13 @@
     @SystemApi
     public static final int SRVCC_STATE_HANDOVER_CANCELED  = 3;
 
+    /**
+     * An invalid card identifier.
+     * @hide
+     */
+    @SystemApi
+    public static final int INVALID_CARD_ID = -1;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"SRVCC_STATE_"},
@@ -1217,81 +1224,79 @@
             "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED";
 
     /**
-     * Broadcast Action: The subscription precise carrier identity has changed.
-     * Similar like {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}, this intent will be sent
-     * on the event of {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}. However, its possible
-     * that precise carrier identity changes while
-     * {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same e.g, the same
-     * subscription switches to different IMSI could potentially change its precise carrier id.
-     *
-     * The intent will have the following extra values:
-     * <ul>
-     *   <li>{@link #EXTRA_PRECISE_CARRIER_ID} The up-to-date precise carrier id of the
-     *   current subscription.
-     *   </li>
-     *   <li>{@link #EXTRA_PRECISE_CARRIER_NAME} The up-to-date carrier name of the current
-     *   subscription.
-     *   </li>
-     *   <li>{@link #EXTRA_SUBSCRIPTION_ID} The subscription id associated with the changed carrier
-     *   identity.
-     *   </li>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system.
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED =
-            "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED";
-
-    /**
      * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates
-     * the updated carrier id {@link TelephonyManager#getSimCarrierId()} of
-     * the current subscription.
+     * the updated carrier id returned by {@link TelephonyManager#getSimCarrierId()}.
      * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or
      * the carrier cannot be identified.
      */
     public static final String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID";
 
     /**
-     * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates
-     * the updated mno carrier id of the current subscription.
-     * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or
-     * the carrier cannot be identified.
-     *
-     *@hide
-     */
-    public static final String EXTRA_MNO_CARRIER_ID = "android.telephony.extra.MNO_CARRIER_ID";
-
-    /**
      * An string extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which
      * indicates the updated carrier name of the current subscription.
-     * {@see TelephonyManager#getSimCarrierIdName()}
+     * @see TelephonyManager#getSimCarrierIdName()
      * <p>Carrier name is a user-facing name of the carrier id {@link #EXTRA_CARRIER_ID},
      * usually the brand name of the subsidiary (e.g. T-Mobile).
      */
     public static final String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME";
 
     /**
+     * Broadcast Action: The subscription precise carrier identity has changed.
+     * The precise carrier id can be used to further differentiate a carrier by different
+     * networks, by prepaid v.s.postpaid or even by 4G v.s.3G plan. Each carrier has a unique
+     * carrier id returned by {@link #getSimCarrierId()} but could have multiple precise carrier id.
+     * e.g, {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM,
+     * while {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based
+     * on the current subscription IMSI. For carriers without any fine-grained ids, precise carrier
+     * id is same as carrier id.
+     *
+     * <p>Similar like {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}, this intent will be
+     * sent on the event of {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} while its also
+     * possible to be sent without {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} when
+     * precise carrier id changes with the same carrier id.
+     * e.g, the same subscription switches to different IMSI could potentially change its
+     * precise carrier id while carrier id remains the same.
+     * @see #getSimPreciseCarrierId()
+     * @see #getSimCarrierId()
+     *
+     * The intent will have the following extra values:
+     * <ul>
+     *   <li>{@link #EXTRA_PRECISE_CARRIER_ID} The up-to-date precise carrier id of the
+     *   current subscription.
+     *   </li>
+     *   <li>{@link #EXTRA_PRECISE_CARRIER_NAME} The up-to-date name of the precise carrier id.
+     *   </li>
+     *   <li>{@link #EXTRA_SUBSCRIPTION_ID} The subscription id associated with the changed carrier
+     *   identity.
+     *   </li>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED =
+            "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED";
+
+    /**
      * An int extra used with {@link #ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED} which
-     * indicates the updated precise carrier id {@link TelephonyManager#getSimPreciseCarrierId()} of
-     * the current subscription. Note, its possible precise carrier id changes while
-     * {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same e.g, when
-     * subscription switch to different IMSI.
+     * indicates the updated precise carrier id returned by
+     * {@link TelephonyManager#getSimPreciseCarrierId()}. Note, its possible precise carrier id
+     * changes while {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same
+     * e.g, when subscription switch to different IMSIs.
      * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or
      * the carrier cannot be identified.
-     * @hide
      */
     public static final String EXTRA_PRECISE_CARRIER_ID =
             "android.telephony.extra.PRECISE_CARRIER_ID";
 
     /**
      * An string extra used with {@link #ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED} which
-     * indicates the updated precise carrier name of the current subscription.
-     * {@see TelephonyManager#getSimPreciseCarrierIdName()}
-     * <p>it's a user-facing name of the precise carrier id {@link #EXTRA_PRECISE_CARRIER_ID},
-     * @hide
+     * indicates the updated precise carrier name returned by
+     * {@link TelephonyManager#getSimPreciseCarrierIdName()}.
+     * <p>it's a user-facing name of the precise carrier id {@link #EXTRA_PRECISE_CARRIER_ID}, e.g,
+     * Tracfone-AT&T.
      */
-    public static final String EXTRA_PRECISE_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME";
+    public static final String EXTRA_PRECISE_CARRIER_NAME =
+            "android.telephony.extra.PRECISE_CARRIER_NAME";
 
     /**
      * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} to indicate the
@@ -3143,6 +3148,34 @@
     }
 
     /**
+     * Get the card ID of the default eUICC card. If there is no eUICC, returns
+     * {@link #INVALID_CARD_ID}.
+     *
+     * <p>The card ID is a unique identifier associated with a UICC or eUICC card. Card IDs are
+     * unique to a device, and always refer to the same UICC or eUICC card unless the device goes
+     * through a factory reset.
+     *
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @return card ID of the default eUICC card.
+     * @hide
+     */
+    @SystemApi
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    public int getCardIdForDefaultEuicc() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) {
+                return INVALID_CARD_ID;
+            }
+            return telephony.getCardIdForDefaultEuicc(mSubId, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            return INVALID_CARD_ID;
+        }
+    }
+
+    /**
      * Gets all the UICC slots. The objects in the array can be null if the slot info is not
      * available, which is possible between phone process starting and getting slot info from modem.
      *
@@ -5596,7 +5629,7 @@
         if (value == null) {
             value = "";
         }
-
+        value.replace(',', ' ');
         if (prop != null) {
             p = prop.split(",");
         }
@@ -5622,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;
@@ -6632,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);
@@ -8518,7 +8557,7 @@
 
     /**
      * Returns carrier id name of the current subscription.
-     * <p>Carrier id name is a user-facing name of carrier id
+     * <p>Carrier id name is a user-facing name of carrier id returned by
      * {@link #getSimCarrierId()}, usually the brand name of the subsidiary
      * (e.g. T-Mobile). Each carrier could configure multiple {@link #getSimOperatorName() SPN} but
      * should have a single carrier name. Carrier name is not a canonical identity,
@@ -8528,7 +8567,7 @@
      * @return Carrier name of the current subscription. Return {@code null} if the subscription is
      * unavailable or the carrier cannot be identified.
      */
-    public CharSequence getSimCarrierIdName() {
+    public @Nullable CharSequence getSimCarrierIdName() {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
@@ -8545,10 +8584,10 @@
      *
      * <p>The precise carrier id can be used to further differentiate a carrier by different
      * networks, by prepaid v.s.postpaid or even by 4G v.s.3G plan. Each carrier has a unique
-     * carrier id {@link #getSimCarrierId()} but can have multiple precise carrier id. e.g,
-     * {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, while
-     * {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based on the
-     * current subscription IMSI.
+     * carrier id returned by {@link #getSimCarrierId()} but could have multiple precise carrier id.
+     * e.g, {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM,
+     * while {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based
+     * on the current subscription IMSI.
      *
      * <p>For carriers without any fine-grained carrier ids, return {@link #getSimCarrierId()}
      * <p>Precise carrier ids are defined in the same way as carrier id
@@ -8558,8 +8597,6 @@
      * @return Returns fine-grained carrier id of the current subscription.
      * Return {@link #UNKNOWN_CARRIER_ID} if the subscription is unavailable or the carrier cannot
      * be identified.
-     *
-     * @hide
      */
     public int getSimPreciseCarrierId() {
         try {
@@ -8575,16 +8612,14 @@
 
     /**
      * Similar like {@link #getSimCarrierIdName()}, returns user-facing name of the
-     * precise carrier id {@link #getSimPreciseCarrierId()}
+     * precise carrier id returned by {@link #getSimPreciseCarrierId()}.
      *
      * <p>The returned name is unlocalized.
      *
      * @return user-facing name of the subscription precise carrier id. Return {@code null} if the
      * subscription is unavailable or the carrier cannot be identified.
-     *
-     * @hide
      */
-    public CharSequence getSimPreciseCarrierIdName() {
+    public @Nullable CharSequence getSimPreciseCarrierIdName() {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
@@ -8597,6 +8632,62 @@
     }
 
     /**
+     * Returns carrier id based on sim MCCMNC (returned by {@link #getSimOperator()}) only.
+     * This is used for fallback when configurations/logic for exact carrier id
+     * {@link #getSimCarrierId()} are not found.
+     *
+     * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+     * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier
+     * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id
+     * by default. After carrier id table update, a new carrier id was assigned. If apps don't
+     * take the update with the new id, it might be helpful to always fallback by using carrier
+     * id based on MCCMNC if there is no match.
+     *
+     * @return matching carrier id from sim MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the
+     * subscription is unavailable or the carrier cannot be identified.
+     */
+    public int getCarrierIdFromSimMccMnc() {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getCarrierIdFromMccMnc(getSlotIndex(), getSimOperator(), true);
+            }
+        } catch (RemoteException ex) {
+            // This could happen if binder process crashes.
+        }
+        return UNKNOWN_CARRIER_ID;
+    }
+
+     /**
+      * Returns carrier id based on MCCMNC (returned by {@link #getSimOperator()}) only. This is
+      * used for fallback when configurations/logic for exact carrier id {@link #getSimCarrierId()}
+      * are not found.
+      *
+      * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+      * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier
+      * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id
+      * by default. After carrier id table update, a new carrier id was assigned. If apps don't
+      * take the update with the new id, it might be helpful to always fallback by using carrier
+      * id based on MCCMNC if there is no match.
+      *
+      * @return matching carrier id from passing MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the
+      * subscription is unavailable or the carrier cannot be identified.
+      * @hide
+      */
+     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+     public int getCarrierIdFromMccMnc(String mccmnc) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getCarrierIdFromMccMnc(getSlotIndex(), mccmnc, false);
+            }
+        } catch (RemoteException ex) {
+            // This could happen if binder process crashes.
+        }
+        return UNKNOWN_CARRIER_ID;
+    }
+
+    /**
      * Return a list of certs in hex string from loaded carrier privileges access rules.
      *
      * @return a list of certificate in hex string. return {@code null} if there is no certs
@@ -8620,48 +8711,6 @@
     }
 
     /**
-     * Returns MNO carrier id of the current subscription’s MCCMNC.
-     * <p>MNO carrier id can be solely identified by subscription mccmnc. This is mainly used
-     * for MNO fallback when exact carrier id {@link #getSimCarrierId()}
-     * configurations are not found.
-     *
-     * @return MNO carrier id of the current subscription. Return the value same as carrier id
-     * {@link #getSimCarrierId()}, if MNO carrier id cannot be identified.
-     * @hide
-     */
-    public int getSimMNOCarrierId() {
-        try {
-            ITelephony service = getITelephony();
-            if (service != null) {
-                return service.getSubscriptionMNOCarrierId(getSubId());
-            }
-        } catch (RemoteException ex) {
-            // This could happen if binder process crashes.
-        }
-        return UNKNOWN_CARRIER_ID;
-    }
-
-     /**
-      * Returns carrier id based on MCCMNC only. This is for fallback when exact carrier id
-      * {@link #getSimCarrierId()} configurations are not found
-      *
-      * @return matching carrier id from passing mccmnc.
-      * @hide
-      */
-     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-     public int getCarrierIdFromMccMnc(String mccmnc) {
-        try {
-            ITelephony service = getITelephony();
-            if (service != null) {
-                return service.getCarrierIdFromMccMnc(getSlotIndex(), mccmnc);
-            }
-        } catch (RemoteException ex) {
-            // This could happen if binder process crashes.
-        }
-        return UNKNOWN_CARRIER_ID;
-    }
-
-    /**
      * Return the application ID for the uicc application type like {@link #APPTYPE_CSIM}.
      * All uicc applications are uniquely identified by application ID, represented by the hex
      * string. e.g, A00000015141434C00. See ETSI 102.221 and 101.220
@@ -8907,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/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 4af31b5..bef1142 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -125,7 +125,6 @@
      *
      * @param result The result code. Must be one of the {@link ResultCode}.
      */
-    @SystemApi
     public void onSetDataProfileComplete(@ResultCode int result) {
         IDataServiceCallback callback = mCallback.get();
         if (callback != null) {
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/android/telephony/ims/RcsManager.java b/telephony/java/android/telephony/ims/RcsManager.java
new file mode 100644
index 0000000..d50b516
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsManager.java
@@ -0,0 +1,36 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.SystemService;
+import android.content.Context;
+
+/**
+ * The manager class for RCS related utilities.
+ * @hide
+ */
+@SystemService(Context.TELEPHONY_RCS_SERVICE)
+public class RcsManager {
+
+    private static final RcsMessageStore sRcsMessageStoreInstance = new RcsMessageStore();
+
+    /**
+     * Returns an instance of RcsMessageStore.
+     */
+    public RcsMessageStore getRcsMessageStore() {
+        return sRcsMessageStoreInstance;
+    }
+}
diff --git a/telephony/java/android/telephony/rcs/RcsManager.java b/telephony/java/android/telephony/ims/RcsMessageStore.java
similarity index 78%
rename from telephony/java/android/telephony/rcs/RcsManager.java
rename to telephony/java/android/telephony/ims/RcsMessageStore.java
index 0ef4e15..c89c0be 100644
--- a/telephony/java/android/telephony/rcs/RcsManager.java
+++ b/telephony/java/android/telephony/ims/RcsMessageStore.java
@@ -14,24 +14,20 @@
  * limitations under the License.
  */
 
-package android.telephony.rcs;
+package android.telephony.ims;
 
-import android.annotation.SystemService;
-import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.telephony.Rlog;
-
-import com.android.internal.telephony.rcs.IRcs;
+import android.telephony.ims.aidl.IRcs;
 
 /**
- * RcsManager is the application interface to RcsProvider and provides access methods to
+ * RcsMessageStore is the application interface to RcsProvider and provides access methods to
  * RCS related database tables.
  * @hide - TODO make this public
  */
-@SystemService(Context.TELEPHONY_RCS_SERVICE)
-public class RcsManager {
-    private static final String TAG = "RcsManager";
+public class RcsMessageStore {
+    private static final String TAG = "RcsMessageStore";
     private static final boolean VDBG = false;
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl b/telephony/java/android/telephony/ims/RcsThread.aidl
similarity index 63%
copy from telephony/java/com/android/internal/telephony/rcs/IRcs.aidl
copy to telephony/java/android/telephony/ims/RcsThread.aidl
index 4c289ac..79d4732 100644
--- a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/RcsThread.aidl
@@ -1,11 +1,12 @@
 /*
- * 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.
  * 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,12 +15,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony.rcs;
+package android.telephony;
 
-interface IRcs {
-    // RcsManager APIs
-    void deleteThread(int threadId);
-
-    // RcsThread APIs
-    int getMessageCount(int rcsThreadId);
-}
\ No newline at end of file
+parcelable RcsThread;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/rcs/RcsThread.java b/telephony/java/android/telephony/ims/RcsThread.java
similarity index 96%
rename from telephony/java/android/telephony/rcs/RcsThread.java
rename to telephony/java/android/telephony/ims/RcsThread.java
index 83eb973..b7f440d 100644
--- a/telephony/java/android/telephony/rcs/RcsThread.java
+++ b/telephony/java/android/telephony/ims/RcsThread.java
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package android.telephony.rcs;
+package android.telephony.ims;
 
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-
-import com.android.internal.telephony.rcs.IRcs;
+import android.telephony.ims.aidl.IRcs;
 
 /**
  * RcsThread represents a single RCS conversation thread. It holds messages that were sent and
diff --git a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
similarity index 83%
rename from telephony/java/com/android/internal/telephony/rcs/IRcs.aidl
rename to telephony/java/android/telephony/ims/aidl/IRcs.aidl
index 4c289ac..b2e2fad 100644
--- a/telephony/java/com/android/internal/telephony/rcs/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony.rcs;
+package android.telephony.ims.aidl;
 
+/**
+ * RPC definition between RCS storage APIs and phone process.
+ * {@hide}
+ */
 interface IRcs {
-    // RcsManager APIs
+    // RcsMessageStore APIs
     void deleteThread(int threadId);
 
     // RcsThread APIs
diff --git a/telephony/java/android/telephony/rcs/RcsThread.aidl b/telephony/java/android/telephony/rcs/RcsThread.aidl
deleted file mode 100644
index e2e0da5d..0000000
--- a/telephony/java/android/telephony/rcs/RcsThread.aidl
+++ /dev/null
@@ -1,20 +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.telephony;
-
-parcelable RcsThread;
\ No newline at end of file
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..65eedb8 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -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 88b9302..399e255 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1337,18 +1337,6 @@
     String getSubscriptionCarrierName(int subId);
 
     /**
-     * Returns MNO carrier id of the current subscription’s MCCMNC.
-     * <p>MNO carrier id can be solely identified by subscription mccmnc. This is mainly used
-     * for MNO fallback when exact carrier id {@link #getSimCarrierId()}
-     * configurations are not found.
-     *
-     * @return MNO carrier id of the current subscription. Return the value same as carrier id
-     * {@link #getSimCarrierId()}, if MNO carrier id cannot be identified.
-     * @hide
-     */
-    int getSubscriptionMNOCarrierId(int subId);
-
-    /**
      * Returns fine-grained carrier id of the current subscription.
      *
      * <p>The precise carrier id can be used to further differentiate a carrier by different
@@ -1383,10 +1371,13 @@
      * Returns carrier id based on MCCMNC only. This will return a MNO carrier id used for fallback
      * check when exact carrier id {@link #getSimCarrierId()} configurations are not found
      *
+     * @param isSubscriptionMccMnc. If {@true} it means this is a query for subscription mccmnc
+     * {@false} otherwise.
+     *
      * @return carrier id from passing mccmnc.
      * @hide
      */
-    int getCarrierIdFromMccMnc(int slotIndex, String mccmnc);
+    int getCarrierIdFromMccMnc(int slotIndex, String mccmnc, boolean isSubscriptionMccMnc);
 
     /**
      * Action set from carrier signalling broadcast receivers to enable/disable metered apns
@@ -1417,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.
      *
@@ -1482,6 +1481,19 @@
     SignalStrength getSignalStrength(int subId);
 
     /**
+     * Get the card ID of the default eUICC card. If there is no eUICC, returns
+     * {@link #INVALID_CARD_ID}.
+     *
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @param subId subscription ID used for authentication
+     * @param callingPackage package making the call
+     * @return card ID of the default eUICC card.
+     * @hide
+     */
+    int getCardIdForDefaultEuicc(int subId, String callingPackage); 
+
+    /**
      * Get slot info for all the UICC slots.
      * @return UiccSlotInfo array.
      * @hide
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/test-base/Android.bp b/test-base/Android.bp
index 4d765d3..157609c 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -37,8 +37,6 @@
         "junit.framework",
     ],
 
-    droiddoc_options: ["-stubsourceonly"],
-    metalava_enabled: false,
     compile_dex: true,
 }
 
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 0a0d50c..db5053e 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -40,8 +40,6 @@
         "junit.textui",
     ],
 
-    droiddoc_options: ["-stubsourceonly"],
-    metalava_enabled: false,
     compile_dex: true
 }
 
diff --git a/test-runner/api/current.txt b/test-runner/api/current.txt
index 1170eb5..4ba1b8f 100644
--- a/test-runner/api/current.txt
+++ b/test-runner/api/current.txt
@@ -125,8 +125,8 @@
     method public static void assertEquals(double[], double[]);
     method public static void assertEquals(java.lang.String, java.lang.Object[], java.lang.Object[]);
     method public static void assertEquals(java.lang.Object[], java.lang.Object[]);
-    method public static void assertEquals(java.lang.String, java.util.Set<? extends java.lang.Object>, java.util.Set<? extends java.lang.Object>);
-    method public static void assertEquals(java.util.Set<? extends java.lang.Object>, java.util.Set<? extends java.lang.Object>);
+    method public static void assertEquals(java.lang.String, java.util.Set<?>, java.util.Set<?>);
+    method public static void assertEquals(java.util.Set<?>, java.util.Set<?>);
     method public static java.util.regex.MatchResult assertMatchesRegex(java.lang.String, java.lang.String, java.lang.String);
     method public static java.util.regex.MatchResult assertMatchesRegex(java.lang.String, java.lang.String);
     method public static void assertNotContainsRegex(java.lang.String, java.lang.String, java.lang.String);
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/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index d8b3b20..75ee089 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -18,20 +18,23 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.UiAutomation;
 import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.util.EventLog;
+
 import dalvik.system.DexClassLoader;
 
-import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
@@ -40,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.Formatter;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Integration tests for {@link com.android.server.pm.dex.DexLogger}.
@@ -47,10 +51,10 @@
  * The setup for the test dynamically loads code in a jar extracted
  * from our assets (a secondary dex file).
  *
- * We then use adb to trigger secondary dex file reconcilation (and
- * wait for it to complete). As a side-effect of this DexLogger should
- * be notified of the file and should log the hash of the file's name
- * and content.  We verify that this message appears in the event log.
+ * We then use shell commands to trigger dynamic code logging (and wait
+ * for it to complete). This causes DexLogger to log the hash of the
+ * file's name and content.  We verify that this message appears in
+ * the event log.
  *
  * Run with "atest DexLoggerIntegrationTests".
  */
@@ -58,29 +62,89 @@
 @RunWith(JUnit4.class)
 public final class DexLoggerIntegrationTests {
 
-    private static final String PACKAGE_NAME = "com.android.frameworks.dexloggertest";
-
     // Event log tag used for SNET related events
     private static final int SNET_TAG = 0x534e4554;
+
     // Subtag used to distinguish dynamic code loading events
     private static final String DCL_SUBTAG = "dcl";
 
-    // Obtained via "echo -n copied.jar | sha256sum"
-    private static final String EXPECTED_NAME_HASH =
-            "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
+    // All the tags we care about
+    private static final int[] TAG_LIST = new int[] { SNET_TAG };
 
-    private static String expectedContentHash;
+    // This is {@code DynamicCodeLoggingService#JOB_ID}
+    private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028;
+
+    private static Context sContext;
+    private static int sMyUid;
 
     @BeforeClass
-    public static void setUpAll() throws Exception {
-        Context context = InstrumentationRegistry.getTargetContext();
+    public static void setUpAll() {
+        sContext = InstrumentationRegistry.getTargetContext();
+        sMyUid = android.os.Process.myUid();
+    }
+
+    @Before
+    public void primeEventLog() {
+        // Force a round trip to logd to make sure everything is up to date.
+        // Without this the first test passes and others don't - we don't see new events in the
+        // log. The exact reason is unclear.
+        EventLog.writeEvent(SNET_TAG, "Dummy event");
+    }
+
+    @Test
+    public void testDexLoggerGeneratesEvents() throws Exception {
+        File privateCopyFile = fileForJar("copied.jar");
+        // Obtained via "echo -n copied.jar | sha256sum"
+        String expectedNameHash =
+                "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
+        String expectedContentHash = copyAndHashJar(privateCopyFile);
+
+        // Feed the jar to a class loader and make sure it contains what we expect.
+        ClassLoader parentClassLoader = sContext.getClass().getClassLoader();
+        ClassLoader loader =
+                new DexClassLoader(privateCopyFile.toString(), null, null, parentClassLoader);
+        loader.loadClass("com.android.dcl.Simple");
+
+        // And make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDexLogger();
+
+        assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+    }
+
+    @Test
+
+    public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception {
+        File privateCopyFile = fileForJar("copied2.jar");
+        String expectedNameHash =
+                "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93";
+        String expectedContentHash = copyAndHashJar(privateCopyFile);
+
+        // This time make sure an unknown class loader is an ancestor of the class loader we use.
+        ClassLoader knownClassLoader = sContext.getClass().getClassLoader();
+        ClassLoader unknownClassLoader = new UnknownClassLoader(knownClassLoader);
+        ClassLoader loader =
+                new DexClassLoader(privateCopyFile.toString(), null, null, unknownClassLoader);
+        loader.loadClass("com.android.dcl.Simple");
+
+        // And make sure we log events about it
+        long previousEventNanos = mostRecentEventTimeNanos();
+        runDexLogger();
+
+        assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash);
+    }
+
+    private static File fileForJar(String name) {
+        return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name);
+    }
+
+    private static String copyAndHashJar(File copyTo) throws Exception {
         MessageDigest hasher = MessageDigest.getInstance("SHA-256");
 
         // Copy the jar from our Java resources to a private data directory
-        File privateCopy = new File(context.getDir("jars", Context.MODE_PRIVATE), "copied.jar");
         Class<?> thisClass = DexLoggerIntegrationTests.class;
         try (InputStream input = thisClass.getResourceAsStream("/javalib.jar");
-                OutputStream output = new FileOutputStream(privateCopy)) {
+                OutputStream output = new FileOutputStream(copyTo)) {
             byte[] buffer = new byte[1024];
             while (true) {
                 int numRead = input.read(buffer);
@@ -92,42 +156,63 @@
             }
         }
 
-        // Remember the SHA-256 of the file content to check that it is the same as
-        // the value we see logged.
+        // Compute the SHA-256 of the file content so we can check that it is the same as the value
+        // we see logged.
         Formatter formatter = new Formatter();
         for (byte b : hasher.digest()) {
             formatter.format("%02X", b);
         }
-        expectedContentHash = formatter.toString();
 
-        // Feed the jar to a class loader and make sure it contains what we expect.
-        ClassLoader loader =
-                new DexClassLoader(
-                    privateCopy.toString(), null, null, context.getClass().getClassLoader());
-        loader.loadClass("com.android.dcl.Simple");
+        return formatter.toString();
     }
 
-    @Test
-    public void testDexLoggerReconcileGeneratesEvents() throws Exception {
-        int[] tagList = new int[] { SNET_TAG };
+    private static long mostRecentEventTimeNanos() throws Exception {
         List<EventLog.Event> events = new ArrayList<>();
 
-        // There may already be events in the event log - figure out the most recent one
-        EventLog.readEvents(tagList, events);
-        long previousEventNanos =
-                events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
-        events.clear();
+        EventLog.readEvents(TAG_LIST, events);
+        return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos();
+    }
 
-        Process process = Runtime.getRuntime().exec(
-            "cmd package reconcile-secondary-dex-files " + PACKAGE_NAME);
-        int exitCode = process.waitFor();
-        assertThat(exitCode).isEqualTo(0);
+    private static void runDexLogger() throws Exception {
+        // This forces {@code DynamicCodeLoggingService} to start now.
+        runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+        // Wait for the job to have run.
+        long startTime = SystemClock.elapsedRealtime();
+        while (true) {
+            String response = runCommand(
+                    "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID);
+            if (!response.contains("pending") && !response.contains("active")) {
+                break;
+            }
+            if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) {
+                throw new AssertionError("Job has not completed: " + response);
+            }
+            SystemClock.sleep(100);
+        }
+    }
 
-        int myUid = android.os.Process.myUid();
-        String expectedMessage = EXPECTED_NAME_HASH + " " + expectedContentHash;
+    private static String runCommand(String command) throws Exception {
+        ByteArrayOutputStream response = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1000];
+        UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        ParcelFileDescriptor fd = ui.executeShellCommand(command);
+        try (InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
+            while (true) {
+                int count = input.read(buffer);
+                if (count == -1) {
+                    break;
+                }
+                response.write(buffer, 0, count);
+            }
+        }
+        return response.toString("UTF-8");
+    }
 
-        EventLog.readEvents(tagList, events);
-        boolean found = false;
+    private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash,
+            String expectedContentHash) throws Exception {
+        List<EventLog.Event> events = new ArrayList<>();
+        EventLog.readEvents(TAG_LIST, events);
+        int found = 0;
         for (EventLog.Event event : events) {
             if (event.getTimeNanos() <= previousEventNanos) {
                 continue;
@@ -140,15 +225,28 @@
                 continue;
             }
             int uid = (int) data[1];
-            if (uid != myUid) {
+            if (uid != sMyUid) {
                 continue;
             }
 
             String message = (String) data[2];
-            assertThat(message).isEqualTo(expectedMessage);
-            found = true;
+            if (!message.startsWith(expectedNameHash)) {
+                continue;
+            }
+
+            assertThat(message).endsWith(expectedContentHash);
+            ++found;
         }
 
-        assertThat(found).isTrue();
+        assertThat(found).isEqualTo(1);
+    }
+
+    /**
+     * A class loader that does nothing useful, but importantly doesn't extend BaseDexClassLoader.
+     */
+    private static class UnknownClassLoader extends ClassLoader {
+        UnknownClassLoader(ClassLoader parent) {
+            super(parent);
+        }
     }
 }
diff --git a/tests/RcsTests/src/com/android/tests/rcs/RcsManagerTest.java b/tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java
similarity index 83%
rename from tests/RcsTests/src/com/android/tests/rcs/RcsManagerTest.java
rename to tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java
index 7f5f03e..290e04c 100644
--- a/tests/RcsTests/src/com/android/tests/rcs/RcsManagerTest.java
+++ b/tests/RcsTests/src/com/android/tests/rcs/RcsMessageStoreTest.java
@@ -16,17 +16,17 @@
 package com.android.tests.rcs;
 
 import android.support.test.runner.AndroidJUnit4;
-import android.telephony.rcs.RcsManager;
+import android.telephony.ims.RcsMessageStore;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
-public class RcsManagerTest {
+public class RcsMessageStoreTest {
     //TODO(sahinc): Add meaningful tests once we have more of the implementation in place
     @Test
     public void testDeleteThreadDoesntCrash() {
-        RcsManager mRcsManager = new RcsManager();
-        mRcsManager.deleteThread(0);
+        RcsMessageStore mRcsMessageStore = new RcsMessageStore();
+        mRcsMessageStore.deleteThread(0);
     }
 }
diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
index be74a6d..7a5e732 100644
--- a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
+++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
@@ -92,11 +92,11 @@
             event.mPackage = "fake.package.name" + pkg;
             event.mClass = event.mPackage + ".class1";
             event.mTimeStamp = 1;
-            event.mEventType = UsageEvents.Event.MOVE_TO_FOREGROUND;
+            event.mEventType = UsageEvents.Event.ACTIVITY_RESUMED;
             for (int evt = 0; evt < eventsPerPackage; evt++) {
                 intervalStats.events.insert(event);
                 intervalStats.update(event.mPackage, event.mClass, event.mTimeStamp,
-                        event.mEventType);
+                        event.mEventType, 1);
             }
         }
     }
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java
index 3480e96..53afa26 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java
@@ -21,13 +21,14 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
-import androidx.collection.CircularArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.TextView;
 
+import androidx.collection.CircularArray;
+
 public class UsageLogActivity extends ListActivity implements Runnable {
     private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
 
@@ -155,10 +156,10 @@
 
         private String eventToString(int eventType) {
             switch (eventType) {
-                case UsageEvents.Event.MOVE_TO_FOREGROUND:
+                case UsageEvents.Event.ACTIVITY_RESUMED:
                     return "Foreground";
 
-                case UsageEvents.Event.MOVE_TO_BACKGROUND:
+                case UsageEvents.Event.ACTIVITY_PAUSED:
                     return "Background";
 
                 case UsageEvents.Event.CONFIGURATION_CHANGE:
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 9838020..151b559 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -46,6 +46,7 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.text.format.DateUtils;
+import android.util.Log;
 import com.android.frameworks.tests.net.R;
 import com.android.internal.util.HexDump;
 import java.io.File;
@@ -89,6 +90,7 @@
         System.loadLibrary("frameworksnettestsjni");
     }
 
+    private static final String TAG = "ApfTest";
     // Expected return codes from APF interpreter.
     private static final int PASS = 1;
     private static final int DROP = 0;
@@ -869,6 +871,37 @@
         }
     }
 
+    /**
+     * Generate APF program, run pcap file though APF filter, then check all the packets in the file
+     * should be dropped.
+     */
+    @Test
+    public void testApfFilterPcapFile() throws Exception {
+        final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151};
+        String pcapFilename = stageFile(R.raw.apfPcap);
+        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16);
+        LinkProperties lp = new LinkProperties();
+        lp.addLinkAddress(link);
+
+        ApfConfiguration config = getDefaultConfig();
+        ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER);
+        config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES;
+        config.multicastFilter = DROP_MULTICAST;
+        config.ieee802_3Filter = DROP_802_3_FRAMES;
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+        apfFilter.setLinkProperties(lp);
+        byte[] program = ipClientCallback.getApfProgram();
+        byte[] data = new byte[ApfFilter.Counter.totalSize()];
+        final boolean result;
+
+        result = dropsAllPackets(program, data, pcapFilename);
+        Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false));
+
+        assertTrue("Failed to drop all packets by filter. \nAPF counters:" +
+            HexDump.toHexString(data, false), result);
+    }
+
     private class MockIpClientCallback extends IpClient.Callback {
         private final ConditionVariable mGotApfProgram = new ConditionVariable();
         private byte[] mLastApfProgram;
@@ -1015,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.
@@ -1366,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());
@@ -1398,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();
     }
@@ -1706,6 +1751,14 @@
     private native static boolean compareBpfApf(String filter, String pcap_filename,
             byte[] apf_program);
 
+
+    /**
+     * Open packet capture file {@code pcapFilename} and run it through APF filter. Then
+     * checks whether all the packets are dropped and populates data[] {@code data} with
+     * the APF counters.
+     */
+    private native static boolean dropsAllPackets(byte[] program, byte[] data, String pcapFilename);
+
     @Test
     public void testBroadcastAddress() throws Exception {
         assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
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/net/jni/apf_jni.cpp b/tests/net/jni/apf_jni.cpp
index 1ea9e27..4222adf 100644
--- a/tests/net/jni/apf_jni.cpp
+++ b/tests/net/jni/apf_jni.cpp
@@ -21,37 +21,40 @@
 #include <stdlib.h>
 #include <string>
 #include <utils/Log.h>
+#include <vector>
 
 #include "apf_interpreter.h"
+#include "nativehelper/scoped_primitive_array.h"
 
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 
 // JNI function acting as simply call-through to native APF interpreter.
 static jint com_android_server_ApfTest_apfSimulate(
-        JNIEnv* env, jclass, jbyteArray program, jbyteArray packet,
-        jbyteArray data, jint filter_age) {
-    uint8_t* program_raw = (uint8_t*)env->GetByteArrayElements(program, nullptr);
-    uint8_t* packet_raw = (uint8_t*)env->GetByteArrayElements(packet, nullptr);
-    uint8_t* data_raw = (uint8_t*)(data ? env->GetByteArrayElements(data, nullptr) : nullptr);
-    uint32_t program_len = env->GetArrayLength(program);
-    uint32_t packet_len = env->GetArrayLength(packet);
-    uint32_t data_len = data ? env->GetArrayLength(data) : 0;
+        JNIEnv* env, jclass, jbyteArray jprogram, jbyteArray jpacket,
+        jbyteArray jdata, jint filter_age) {
 
-    // Merge program and data into a single buffer.
-    uint8_t* program_and_data = (uint8_t*)malloc(program_len + data_len);
-    memcpy(program_and_data, program_raw, program_len);
-    memcpy(program_and_data + program_len, data_raw, data_len);
+    ScopedByteArrayRO packet(env, jpacket);
+    uint32_t packet_len = (uint32_t)packet.size();
+    uint32_t program_len = env->GetArrayLength(jprogram);
+    uint32_t data_len = jdata ? env->GetArrayLength(jdata) : 0;
+    std::vector<uint8_t> buf(program_len + data_len, 0);
+
+    env->GetByteArrayRegion(jprogram, 0, program_len, reinterpret_cast<jbyte*>(buf.data()));
+    if (jdata) {
+        // Merge program and data into a single buffer.
+        env->GetByteArrayRegion(jdata, 0, data_len,
+                                reinterpret_cast<jbyte*>(buf.data() + program_len));
+    }
 
     jint result =
-        accept_packet(program_and_data, program_len, program_len + data_len,
-                      packet_raw, packet_len, filter_age);
-    if (data) {
-        memcpy(data_raw, program_and_data + program_len, data_len);
-        env->ReleaseByteArrayElements(data, (jbyte*)data_raw, 0 /* copy back */);
+        accept_packet(buf.data(), program_len, program_len + data_len,
+                        reinterpret_cast<const uint8_t*>(packet.get()), packet_len, filter_age);
+
+    if (jdata) {
+        env->SetByteArrayRegion(jdata, 0, data_len,
+                                reinterpret_cast<jbyte*>(buf.data() + program_len));
     }
-    free(program_and_data);
-    env->ReleaseByteArrayElements(packet, (jbyte*)packet_raw, JNI_ABORT);
-    env->ReleaseByteArrayElements(program, (jbyte*)program_raw, JNI_ABORT);
+
     return result;
 }
 
@@ -118,8 +121,7 @@
         jstring jpcap_filename, jbyteArray japf_program) {
     ScopedUtfChars filter(env, jfilter);
     ScopedUtfChars pcap_filename(env, jpcap_filename);
-    uint8_t* apf_program = (uint8_t*)env->GetByteArrayElements(japf_program, NULL);
-    uint32_t apf_program_len = env->GetArrayLength(japf_program);
+    ScopedByteArrayRO apf_program(env, japf_program);
 
     // Open pcap file for BPF filtering
     ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb"));
@@ -161,14 +163,15 @@
         do {
             apf_packet = pcap_next(apf_pcap.get(), &apf_header);
         } while (apf_packet != NULL && !accept_packet(
-                apf_program, apf_program_len, 0 /* data_len */,
+                reinterpret_cast<uint8_t*>(const_cast<int8_t*>(apf_program.get())),
+                apf_program.size(), 0 /* data_len */,
                 apf_packet, apf_header.len, 0 /* filter_age */));
 
         // Make sure both filters matched the same packet.
         if (apf_packet == NULL && bpf_packet == NULL)
-             break;
+            break;
         if (apf_packet == NULL || bpf_packet == NULL)
-             return false;
+            return false;
         if (apf_header.len != bpf_header.len ||
                 apf_header.ts.tv_sec != bpf_header.ts.tv_sec ||
                 apf_header.ts.tv_usec != bpf_header.ts.tv_usec ||
@@ -178,6 +181,48 @@
     return true;
 }
 
+static jboolean com_android_server_ApfTest_dropsAllPackets(JNIEnv* env, jclass, jbyteArray jprogram,
+        jbyteArray jdata, jstring jpcap_filename) {
+    ScopedUtfChars pcap_filename(env, jpcap_filename);
+    ScopedByteArrayRO apf_program(env, jprogram);
+    uint32_t apf_program_len = (uint32_t)apf_program.size();
+    uint32_t data_len = env->GetArrayLength(jdata);
+    pcap_pkthdr apf_header;
+    const uint8_t* apf_packet;
+    char pcap_error[PCAP_ERRBUF_SIZE];
+    std::vector<uint8_t> buf(apf_program_len + data_len, 0);
+
+    // Merge program and data into a single buffer.
+    env->GetByteArrayRegion(jprogram, 0, apf_program_len, reinterpret_cast<jbyte*>(buf.data()));
+    env->GetByteArrayRegion(jdata, 0, data_len,
+                            reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
+
+    // Open pcap file
+    ScopedFILE apf_fp(fopen(pcap_filename.c_str(), "rb"));
+    ScopedPcap apf_pcap(pcap_fopen_offline(apf_fp.get(), pcap_error));
+
+    if (apf_pcap.get() == NULL) {
+        throwException(env, "pcap_fopen_offline failed: " + std::string(pcap_error));
+        return false;
+    }
+
+    while ((apf_packet = pcap_next(apf_pcap.get(), &apf_header)) != NULL) {
+        int result = accept_packet(buf.data(), apf_program_len,
+                                    apf_program_len + data_len, apf_packet, apf_header.len, 0);
+
+        // Return false once packet passes the filter
+        if (result) {
+            env->SetByteArrayRegion(jdata, 0, data_len,
+                                    reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
+            return false;
+         }
+    }
+
+    env->SetByteArrayRegion(jdata, 0, data_len,
+                            reinterpret_cast<jbyte*>(buf.data() + apf_program_len));
+    return true;
+}
+
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
     JNIEnv *env;
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -192,6 +237,8 @@
                     (void*)com_android_server_ApfTest_compileToBpf },
             { "compareBpfApf", "(Ljava/lang/String;Ljava/lang/String;[B)Z",
                     (void*)com_android_server_ApfTest_compareBpfApf },
+            { "dropsAllPackets", "([B[BLjava/lang/String;)Z",
+                    (void*)com_android_server_ApfTest_dropsAllPackets },
     };
 
     jniRegisterNativeMethods(env, "android/net/apf/ApfTest",
diff --git a/tests/net/res/raw/apfPcap.pcap b/tests/net/res/raw/apfPcap.pcap
new file mode 100644
index 0000000..6f69c4a
--- /dev/null
+++ b/tests/net/res/raw/apfPcap.pcap
Binary files differ
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/Debug.cpp b/tools/aapt2/Debug.cpp
index 583f14a..9460c9e 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -306,31 +306,6 @@
             break;
         }
 
-        for (size_t i = 0; i < entry->overlayable_declarations.size(); i++) {
-          printer->Print((i == 0) ? " " : "|");
-          printer->Print("OVERLAYABLE");
-
-          if (entry->overlayable_declarations[i].policy) {
-            switch (entry->overlayable_declarations[i].policy.value()) {
-              case Overlayable::Policy::kProduct:
-                printer->Print("_PRODUCT");
-                break;
-              case Overlayable::Policy::kProductServices:
-                printer->Print("_PRODUCT_SERVICES");
-                break;
-              case Overlayable::Policy::kSystem:
-                printer->Print("_SYSTEM");
-                break;
-              case Overlayable::Policy::kVendor:
-                printer->Print("_VENDOR");
-                break;
-              case Overlayable::Policy::kPublic:
-                printer->Print("_PUBLIC");
-                break;
-            }
-          }
-        }
-
         printer->Println();
 
         if (options.show_values) {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 4f25e09..9587704 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -99,7 +99,7 @@
   ResourceId id;
   Visibility::Level visibility_level = Visibility::Level::kUndefined;
   bool allow_new = false;
-  std::vector<Overlayable> overlayable_declarations;
+  Maybe<Overlayable> overlayable;
 
   std::string comment;
   std::unique_ptr<Value> value;
@@ -133,8 +133,8 @@
     }
   }
 
-  for (auto& overlayable : res->overlayable_declarations) {
-    if (!table->AddOverlayable(res->name, overlayable, diag)) {
+  if (res->overlayable) {
+    if (!table->SetOverlayable(res->name, res->overlayable.value(), diag)) {
       return false;
     }
   }
@@ -1063,20 +1063,19 @@
                     << "' for <overlayable> tag");
   }
 
-  std::string comment;
-  std::vector<Overlayable::Policy> policies;
-
   bool error = false;
+  std::string comment;
+  Overlayable::PolicyFlags current_policies = Overlayable::Policy::kNone;
   const size_t start_depth = parser->depth();
   while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
     xml::XmlPullParser::Event event = parser->event();
     if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) {
-      // Break the loop when exiting the overyabale element
+      // Break the loop when exiting the overlayable element
       break;
     } else if (event == xml::XmlPullParser::Event::kEndElement
                && parser->depth() == start_depth + 1) {
       // Clear the current policies when exiting the policy element
-      policies.clear();
+      current_policies = Overlayable::Policy::kNone;
       continue;
     } else if (event == xml::XmlPullParser::Event::kComment) {
       // Get the comment of individual item elements
@@ -1090,43 +1089,71 @@
     const Source item_source = source_.WithLine(parser->line_number());
     const std::string& element_name = parser->element_name();
     const std::string& element_namespace = parser->element_namespace();
-
     if (element_namespace.empty() && element_name == "item") {
-      if (!ParseOverlayableItem(parser, policies, comment, out_resource)) {
+      // Items specify the name and type of resource that should be overlayable
+      Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
+      if (!maybe_name) {
+        diag_->Error(DiagMessage(item_source)
+                     << "<item> within an <overlayable> tag must have a 'name' attribute");
         error = true;
+        continue;
       }
+
+      Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
+      if (!maybe_type) {
+        diag_->Error(DiagMessage(item_source)
+                     << "<item> within an <overlayable> tag must have a 'type' attribute");
+        error = true;
+        continue;
+      }
+
+      const ResourceType* type = ParseResourceType(maybe_type.value());
+      if (type == nullptr) {
+        diag_->Error(DiagMessage(item_source)
+                     << "invalid resource type '" << maybe_type.value()
+                     << "' in <item> within an <overlayable>");
+        error = true;
+        continue;
+      }
+
+      ParsedResource child_resource;
+      child_resource.name.type = *type;
+      child_resource.name.entry = maybe_name.value().to_string();
+      child_resource.overlayable = Overlayable{current_policies, item_source, comment};
+      out_resource->child_resources.push_back(std::move(child_resource));
+
     } else if (element_namespace.empty() && element_name == "policy") {
-      if (!policies.empty()) {
+      if (current_policies != Overlayable::Policy::kNone) {
         // If the policy list is not empty, then we are currently inside a policy element
         diag_->Error(DiagMessage(item_source) << "<policy> blocks cannot be recursively nested");
         error = true;
         break;
       } else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
         // Parse the polices separated by vertical bar characters to allow for specifying multiple
-        // policies at once
+        // policies
         for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) {
           StringPiece trimmed_part = util::TrimWhitespace(part);
           if (trimmed_part == "public") {
-            policies.push_back(Overlayable::Policy::kPublic);
+            current_policies |= Overlayable::Policy::kPublic;
           } else if (trimmed_part == "product") {
-            policies.push_back(Overlayable::Policy::kProduct);
+            current_policies |= Overlayable::Policy::kProduct;
           } else if (trimmed_part == "product_services") {
-            policies.push_back(Overlayable::Policy::kProductServices);
+            current_policies |= Overlayable::Policy::kProductServices;
           } else if (trimmed_part == "system") {
-            policies.push_back(Overlayable::Policy::kSystem);
+            current_policies |= Overlayable::Policy::kSystem;
           } else if (trimmed_part == "vendor") {
-            policies.push_back(Overlayable::Policy::kVendor);
+            current_policies |= Overlayable::Policy::kVendor;
           } else {
-            diag_->Error(DiagMessage(out_resource->source)
-                             << "<policy> has unsupported type '" << trimmed_part << "'");
+            diag_->Error(DiagMessage(item_source)
+                         << "<policy> has unsupported type '" << trimmed_part << "'");
             error = true;
             continue;
           }
         }
       }
     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
-      diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> in "
-                                            << " <overlayable>");
+      diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> "
+                                            << " in <overlayable>");
       error = true;
       break;
     }
@@ -1135,61 +1162,6 @@
   return !error;
 }
 
-bool ResourceParser::ParseOverlayableItem(xml::XmlPullParser* parser,
-                                          const std::vector<Overlayable::Policy>& policies,
-                                          const std::string& comment,
-                                          ParsedResource* out_resource) {
-  const Source item_source = source_.WithLine(parser->line_number());
-
-  Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
-  if (!maybe_name) {
-    diag_->Error(DiagMessage(item_source)
-                     << "<item> within an <overlayable> tag must have a 'name' attribute");
-    return false;
-  }
-
-  Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
-  if (!maybe_type) {
-    diag_->Error(DiagMessage(item_source)
-                     << "<item> within an <overlayable> tag must have a 'type' attribute");
-    return false;
-  }
-
-  const ResourceType* type = ParseResourceType(maybe_type.value());
-  if (type == nullptr) {
-    diag_->Error(DiagMessage(out_resource->source)
-                     << "invalid resource type '" << maybe_type.value()
-                     << "' in <item> within an <overlayable>");
-    return false;
-  }
-
-  ParsedResource child_resource;
-  child_resource.name.type = *type;
-  child_resource.name.entry = maybe_name.value().to_string();
-  child_resource.source = item_source;
-
-  if (policies.empty()) {
-    Overlayable overlayable;
-    overlayable.source = item_source;
-    overlayable.comment = comment;
-    child_resource.overlayable_declarations.push_back(overlayable);
-  } else {
-    for (Overlayable::Policy policy : policies) {
-      Overlayable overlayable;
-      overlayable.policy = policy;
-      overlayable.source = item_source;
-      overlayable.comment = comment;
-      child_resource.overlayable_declarations.push_back(overlayable);
-    }
-  }
-
-  if (options_.visibility) {
-    child_resource.visibility_level = options_.visibility.value();
-  }
-  out_resource->child_resources.push_back(std::move(child_resource));
-  return true;
-}
-
 bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
   if (ParseSymbolImpl(parser, out_resource)) {
     out_resource->visibility_level = Visibility::Level::kUndefined;
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index ebacd6f..06bb0c9 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -96,10 +96,6 @@
   bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
-  bool ParseOverlayableItem(xml::XmlPullParser* parser,
-                            const std::vector<Overlayable::Policy>& policies,
-                            const std::string& comment,
-                            ParsedResource* out_resource);
   bool ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
   bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index c6f29ac..03e6197 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -905,16 +905,16 @@
   auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  EXPECT_THAT(search_result.value().entry->overlayable.value().policies,
+              Eq(Overlayable::Policy::kNone));
 
   search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  EXPECT_THAT(search_result.value().entry->overlayable.value().policies,
+              Eq(Overlayable::Policy::kNone));
 }
 
 TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
@@ -945,49 +945,44 @@
   auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  Overlayable& overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kNone));
 
   search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kProduct));
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct));
 
   search_result = table_.FindResource(test::ParseNameOrDie("string/baz"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kProductServices));
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProductServices));
 
   search_result = table_.FindResource(test::ParseNameOrDie("string/fiz"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kSystem));
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kSystem));
 
   search_result = table_.FindResource(test::ParseNameOrDie("string/fuz"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kVendor));
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor));
 
   search_result = table_.FindResource(test::ParseNameOrDie("string/faz"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kPublic));
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kPublic));
 }
 
 TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) {
@@ -1031,22 +1026,18 @@
   auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kVendor));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy,
-              Eq(Overlayable::Policy::kProductServices));
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  Overlayable& overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor
+                                       | Overlayable::Policy::kProductServices));
 
   search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kProduct));
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy,
-              Eq(Overlayable::Policy::kSystem));
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct
+                                       | Overlayable::Policy::kSystem));
 }
 
 TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
@@ -1067,7 +1058,7 @@
   EXPECT_FALSE(TestParse(input));
 
   input = R"(
-      <overlayable">
+      <overlayable>
         <policy type="product">
           <item type="string" name="foo" />
           <item type="string" name="foo" />
@@ -1080,6 +1071,26 @@
         <policy type="product">
           <item type="string" name="foo" />
         </policy>
+        <item type="string" name="foo" />
+      </overlayable>)";
+  EXPECT_FALSE(TestParse(input));
+
+  input = R"(
+      <overlayable>
+        <policy type="product">
+          <item type="string" name="foo" />
+        </policy>
+        <policy type="vendor">
+          <item type="string" name="foo" />
+        </policy>
+      </overlayable>)";
+  EXPECT_FALSE(TestParse(input));
+
+  input = R"(
+      <overlayable>
+        <policy type="product">
+          <item type="string" name="foo" />
+        </policy>
       </overlayable>
 
       <overlayable>
@@ -1090,41 +1101,6 @@
   EXPECT_FALSE(TestParse(input));
 }
 
-TEST_F(ResourceParserTest, PolicyAndNonPolicyOverlayableError) {
-  std::string input = R"(
-        <overlayable policy="product">
-          <item type="string" name="foo" />
-        </overlayable>
-        <overlayable policy="">
-          <item type="string" name="foo" />
-        </overlayable>)";
-  EXPECT_FALSE(TestParse(input));
-
-  input = R"(
-        <overlayable policy="">
-          <item type="string" name="foo" />
-        </overlayable>
-        <overlayable policy="product">
-          <item type="string" name="foo" />
-        </overlayable>)";
-  EXPECT_FALSE(TestParse(input));
-}
-
-TEST_F(ResourceParserTest, DuplicateOverlayableMultiplePolicyError) {
-  std::string input = R"(
-      <overlayable>
-        <policy type="vendor|product">
-          <item type="string" name="foo" />
-        </policy>
-      </overlayable>
-      <overlayable>
-        <policy type="product_services|vendor">
-          <item type="string" name="foo" />
-        </policy>
-      </overlayable>)";
-  EXPECT_FALSE(TestParse(input));
-}
-
 TEST_F(ResourceParserTest, NestPolicyInOverlayableError) {
   std::string input = R"(
       <overlayable>
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index bc8a4d1..54633ad 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -625,18 +625,18 @@
   return true;
 }
 
-bool ResourceTable::AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
                                    IDiagnostics* diag) {
-  return AddOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
+  return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
 }
 
-bool ResourceTable::AddOverlayableMangled(const ResourceNameRef& name,
+bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name,
                                           const Overlayable& overlayable, IDiagnostics* diag) {
-  return AddOverlayableImpl(name, overlayable, SkipNameValidator, diag);
+  return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag);
 }
 
-bool ResourceTable::AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
-                                       NameValidator name_validator, IDiagnostics* diag) {
+bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+                                       NameValidator name_validator, IDiagnostics *diag) {
   CHECK(diag != nullptr);
 
   if (!ValidateName(name_validator, name, overlayable.source, diag)) {
@@ -647,27 +647,14 @@
   ResourceTableType* type = package->FindOrCreateType(name.type);
   ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
 
-  for (auto& overlayable_declaration : entry->overlayable_declarations) {
-    // An overlayable resource cannot be declared twice with the same policy
-    if (overlayable.policy == overlayable_declaration.policy) {
-      diag->Error(DiagMessage(overlayable.source)
+  if (entry->overlayable) {
+    diag->Error(DiagMessage(overlayable.source)
                     << "duplicate overlayable declaration for resource '" << name << "'");
-      diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here");
-      return false;
-    }
-
-    // An overlayable resource cannot be declared once with a policy and without a policy because
-    // the policy becomes unused
-    if (!overlayable.policy || !overlayable_declaration.policy) {
-      diag->Error(DiagMessage(overlayable.source)
-                    << "overlayable resource '" << name << "'"
-                    << " declared once with a policy and once with no policy");
-      diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here");
-      return false;
-    }
+    diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here");
+    return false;
   }
 
-  entry->overlayable_declarations.push_back(overlayable);
+  entry->overlayable = overlayable;
   return true;
 }
 
@@ -703,7 +690,7 @@
         new_entry->id = entry->id;
         new_entry->visibility = entry->visibility;
         new_entry->allow_new = entry->allow_new;
-        new_entry->overlayable_declarations = entry->overlayable_declarations;
+        new_entry->overlayable = entry->overlayable;
 
         for (const auto& config_value : entry->values) {
           ResourceConfigValue* new_value =
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 3dd0a769..e646f5b 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -57,27 +57,32 @@
   std::string comment;
 };
 
-// Represents a declaration that a resource is overayable at runtime.
+// Represents a declaration that a resource is overlayable at runtime.
 struct Overlayable {
+
   // Represents the types overlays that are allowed to overlay the resource.
-  enum class Policy {
+  enum Policy : uint32_t {
+    kNone = 0x00,
+
     // The resource can be overlaid by any overlay.
-    kPublic,
+    kPublic = 0x01,
 
     // The resource can be overlaid by any overlay on the system partition.
-    kSystem,
+    kSystem = 0x02,
 
     // The resource can be overlaid by any overlay on the vendor partition.
-    kVendor,
+    kVendor = 0x04,
 
     // The resource can be overlaid by any overlay on the product partition.
-    kProduct,
+    kProduct = 0x08,
 
     // The resource can be overlaid by any overlay on the product services partition.
-    kProductServices,
+    kProductServices = 0x10
   };
 
-  Maybe<Policy> policy;
+  typedef uint32_t PolicyFlags;
+  PolicyFlags policies = Policy::kNone;
+
   Source source;
   std::string comment;
 };
@@ -116,7 +121,7 @@
   Maybe<AllowNew> allow_new;
 
   // The declarations of this resource as overlayable for RROs
-  std::vector<Overlayable> overlayable_declarations;
+  Maybe<Overlayable> overlayable;
 
   // The resource's values for each configuration.
   std::vector<std::unique_ptr<ResourceConfigValue>> values;
@@ -246,9 +251,9 @@
   bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility,
                                   const ResourceId& res_id, IDiagnostics* diag);
 
-  bool AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
-                      IDiagnostics* diag);
-  bool AddOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
+  bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+                      IDiagnostics *diag);
+  bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
                              IDiagnostics* diag);
 
   bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag);
@@ -323,8 +328,8 @@
   bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
                        NameValidator name_validator, IDiagnostics* diag);
 
-  bool AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
-                          NameValidator name_validator, IDiagnostics* diag);
+  bool SetOverlayableImpl(const ResourceNameRef &name, const Overlayable &overlayable,
+                          NameValidator name_validator, IDiagnostics *diag);
 
   bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
                           const Visibility& symbol, NameValidator name_validator,
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 7c28f07..31095c4 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -242,69 +242,50 @@
   ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second"));
 }
 
-TEST(ResourceTableTest, AddOverlayable) {
+TEST(ResourceTableTest, SetOverlayable) {
   ResourceTable table;
+  Overlayable overlayable{};
+  overlayable.policies |= Overlayable::Policy::kProduct;
+  overlayable.policies |= Overlayable::Policy::kProductServices;
+  overlayable.comment = "comment";
+
   const ResourceName name = test::ParseNameOrDie("android:string/foo");
+  ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+  Maybe<ResourceTable::SearchResult> search_result = table.FindResource(name);
 
-  Overlayable overlayable;
-  overlayable.policy = Overlayable::Policy::kProduct;
-  overlayable.comment = "first";
-  ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
-  Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
-  ASSERT_TRUE(result);
-  ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
-  ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first"));
-  ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kProduct));
+  ASSERT_TRUE(search_result);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
 
-  Overlayable overlayable2;
-  overlayable2.comment = "second";
-  overlayable2.policy = Overlayable::Policy::kProductServices;
-  ASSERT_TRUE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
-  result = table.FindResource(name);
-  ASSERT_TRUE(result);
-  ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
-  ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first"));
-  ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kProduct));
-  ASSERT_THAT(result.value().entry->overlayable_declarations[1].comment, StrEq("second"));
-  ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy,
-              Eq(Overlayable::Policy::kProductServices));
+  Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+  ASSERT_THAT(result_overlayable.comment, StrEq("comment"));
+  EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct
+                                              | Overlayable::Policy::kProductServices));
 }
 
-TEST(ResourceTableTest, AddDuplicateOverlayableFail) {
+TEST(ResourceTableTest, AddDuplicateOverlayableSamePolicyFail) {
   ResourceTable table;
   const ResourceName name = test::ParseNameOrDie("android:string/foo");
 
-  Overlayable overlayable;
-  overlayable.policy = Overlayable::Policy::kProduct;
-  ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
+  Overlayable overlayable{};
+  overlayable.policies = Overlayable::Policy::kProduct;
+  ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
 
-  Overlayable overlayable2;
-  overlayable2.policy = Overlayable::Policy::kProduct;
-  ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
+  Overlayable overlayable2{};
+  overlayable2.policies = Overlayable::Policy::kProduct;
+  ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics()));
 }
 
-TEST(ResourceTableTest, AddOverlayablePolicyAndNoneFirstFail) {
+TEST(ResourceTableTest, AddDuplicateOverlayableDifferentPolicyFail) {
   ResourceTable table;
   const ResourceName name = test::ParseNameOrDie("android:string/foo");
 
-  ASSERT_TRUE(table.AddOverlayable(name, {}, test::GetDiagnostics()));
+  Overlayable overlayable{};
+  overlayable.policies = Overlayable::Policy::kProduct;
+  ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
 
-  Overlayable overlayable2;
-  overlayable2.policy = Overlayable::Policy::kProduct;
-  ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
-}
-
-TEST(ResourceTableTest, AddOverlayablePolicyAndNoneLastFail) {
-  ResourceTable table;
-  const ResourceName name = test::ParseNameOrDie("android:string/foo");
-
-  Overlayable overlayable;
-  overlayable.policy = Overlayable::Policy::kProduct;
-  ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
-
-  ASSERT_FALSE(table.AddOverlayable(name, {}, test::GetDiagnostics()));
+  Overlayable overlayable2{};
+  overlayable2.policies = Overlayable::Policy::kVendor;
+  ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics()));
 }
 
 TEST(ResourceTableTest, AllowDuplictaeResourcesNames) {
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index bf9fe49..81a2c2e 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -136,12 +136,11 @@
 // Represents a declaration that a resource is overayable at runtime.
 message Overlayable {
   enum Policy {
-    NONE = 0;
-    PUBLIC = 1;
-    SYSTEM = 2;
-    VENDOR = 3;
-    PRODUCT = 4;
-    PRODUCT_SERVICES = 5;
+    PUBLIC = 0;
+    SYSTEM = 1;
+    VENDOR = 2;
+    PRODUCT = 3;
+    PRODUCT_SERVICES = 4;
   }
 
   // Where this declaration was defined in source.
@@ -150,8 +149,8 @@
   // Any comment associated with the declaration.
   string comment = 2;
 
-  // The policy of the overlayable declaration
-  Policy policy = 3;
+  // The policy defined in the overlayable declaration.
+  repeated Policy policy = 3;
 }
 
 // An entry ID in the range [0x0000, 0xffff].
@@ -181,7 +180,7 @@
   AllowNew allow_new = 4;
 
   // Whether this resource can be overlaid by a runtime resource overlay (RRO).
-  repeated Overlayable overlayable = 5;
+  Overlayable overlayable = 5;
 
   // The set of values defined for this entry, each corresponding to a different
   // configuration/variant.
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 89d19cf..5cf056e 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -255,6 +255,7 @@
     AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true);
+    // TODO(b/120609160): Add aapt2 overlayable dump command
   }
 
   int Action(const std::vector<std::string>& args) override {
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index df0daeb..61ebd4e 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -441,25 +441,25 @@
       const ResTable_overlayable_policy_header* policy_header =
           ConvertTo<ResTable_overlayable_policy_header>(parser.chunk());
 
-      std::vector<Overlayable::Policy> policies;
+      Overlayable::PolicyFlags policies = Overlayable::Policy::kNone;
       if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) {
-        policies.push_back(Overlayable::Policy::kPublic);
+        policies |= Overlayable::Policy::kPublic;
       }
       if (policy_header->policy_flags
           & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) {
-        policies.push_back(Overlayable::Policy::kSystem);
+        policies |= Overlayable::Policy::kSystem;
       }
       if (policy_header->policy_flags
           & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) {
-        policies.push_back(Overlayable::Policy::kVendor);
+        policies |= Overlayable::Policy::kVendor;
       }
       if (policy_header->policy_flags
           & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) {
-        policies.push_back(Overlayable::Policy::kProduct);
+        policies |= Overlayable::Policy::kProduct;
       }
       if (policy_header->policy_flags
           & ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION) {
-        policies.push_back(Overlayable::Policy::kProductServices);
+        policies |= Overlayable::Policy::kProductServices;
       }
 
       const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>(
@@ -478,13 +478,11 @@
           return false;
         }
 
-        for (Overlayable::Policy policy : policies) {
-          Overlayable overlayable;
-          overlayable.source = source_.WithLine(0);
-          overlayable.policy = policy;
-          if (!table_->AddOverlayable(iter->second, overlayable, diag_)) {
-            return false;
-          }
+        Overlayable overlayable{};
+        overlayable.source = source_.WithLine(0);
+        overlayable.policies = policies;
+        if (!table_->SetOverlayable(iter->second, overlayable, diag_)) {
+          return false;
         }
       }
     }
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 976c328..200e2d4 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -429,56 +429,52 @@
       CHECK(bool(type->id)) << "type must have an ID set when flattening <overlayable>";
       for (auto& entry : type->entries) {
         CHECK(bool(type->id)) << "entry must have an ID set when flattening <overlayable>";
-
-        // TODO(b/120298168): Convert the policies vector to a policy set or bitmask
-        if (!entry->overlayable_declarations.empty()) {
-          uint16_t policy_flags = 0;
-          for (Overlayable overlayable : entry->overlayable_declarations) {
-            if (overlayable.policy) {
-              switch (overlayable.policy.value()) {
-                case Overlayable::Policy::kPublic:
-                  policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
-                  break;
-                case Overlayable::Policy::kSystem:
-                  policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION;
-                  break;
-                case Overlayable::Policy::kVendor:
-                  policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION;
-                  break;
-                case Overlayable::Policy::kProduct:
-                  policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION;
-                  break;
-                case Overlayable::Policy::kProductServices:
-                  policy_flags |=
-                      ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION;
-                  break;
-              }
-            } else {
-              // Encode overlayable entries defined without a policy as publicly overlayable
-              policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
-            }
-          }
-
-          // Find the overlayable policy chunk with the same policies as the entry
-          PolicyChunk* policy_chunk = nullptr;
-          for (PolicyChunk& policy : policies) {
-            if (policy.policy_flags == policy_flags) {
-              policy_chunk = &policy;
-              break;
-            }
-          }
-
-          // Create a new policy chunk if an existing one with the same policy cannot be found
-          if (policy_chunk == nullptr) {
-            PolicyChunk p;
-            p.policy_flags = policy_flags;
-            policies.push_back(p);
-            policy_chunk = &policies.back();
-          }
-
-          policy_chunk->ids.insert(android::make_resid(package_->id.value(), type->id.value(),
-                                                       entry->id.value()));
+        if (!entry->overlayable) {
+          continue;
         }
+
+        Overlayable overlayable = entry->overlayable.value();
+        uint32_t policy_flags = Overlayable::Policy::kNone;
+        if (overlayable.policies & Overlayable::Policy::kPublic) {
+          policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
+        }
+        if (overlayable.policies & Overlayable::Policy::kSystem) {
+          policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION;
+        }
+        if (overlayable.policies & Overlayable::Policy::kVendor) {
+          policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION;
+        }
+        if (overlayable.policies & Overlayable::Policy::kProduct) {
+          policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION;
+        }
+        if (overlayable.policies & Overlayable::Policy::kProductServices) {
+          policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION;
+        }
+
+        if (overlayable.policies == Overlayable::Policy::kNone) {
+          // Encode overlayable entries defined without a policy as publicly overlayable
+          policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
+        }
+
+        // Find the overlayable policy chunk with the same policies as the entry
+        PolicyChunk* policy_chunk = nullptr;
+        for (PolicyChunk& policy : policies) {
+          if (policy.policy_flags == policy_flags) {
+            policy_chunk = &policy;
+            break;
+          }
+        }
+
+        // Create a new policy chunk if an existing one with the same policy cannot be found
+        if (policy_chunk == nullptr) {
+          PolicyChunk p;
+          p.policy_flags = policy_flags;
+          policies.push_back(p);
+          policy_chunk = &policies.back();
+        }
+
+        policy_chunk->ids.insert(android::make_resid(package_->id.value(), type->id.value(),
+                                                     entry->id.value()));
       }
     }
 
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 410efbe..e99ab1f 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -628,14 +628,17 @@
 }
 
 TEST_F(TableFlattenerTest, FlattenOverlayable) {
+  Overlayable overlayable{};
+  overlayable.policies |= Overlayable::Policy::kProduct;
+  overlayable.policies |= Overlayable::Policy::kSystem;
+  overlayable.policies |= Overlayable::Policy::kVendor;
+
   std::string name = "com.app.test:integer/overlayable";
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.test", 0x7f)
           .AddSimple(name, ResourceId(0x7f020000))
-          .AddOverlayable(name, Overlayable::Policy::kProduct)
-          .AddOverlayable(name, Overlayable::Policy::kSystem)
-          .AddOverlayable(name, Overlayable::Policy::kVendor)
+          .SetOverlayable(name, overlayable)
           .Build();
 
   ResourceTable output_table;
@@ -644,39 +647,45 @@
   auto search_result = output_table.FindResource(test::ParseNameOrDie(name));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
-            Overlayable::Policy::kSystem);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
-            Overlayable::Policy::kVendor);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy,
-            Overlayable::Policy::kProduct);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem
+                                         | Overlayable::Policy::kVendor
+                                         | Overlayable::Policy::kProduct);
 }
 
 TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
   std::string name_zero = "com.app.test:integer/overlayable_zero";
+  Overlayable overlayable_zero{};
+  overlayable_zero.policies |= Overlayable::Policy::kProduct;
+  overlayable_zero.policies |= Overlayable::Policy::kSystem;
+  overlayable_zero.policies |= Overlayable::Policy::kProductServices;
+
   std::string name_one = "com.app.test:integer/overlayable_one";
+  Overlayable overlayable_one{};
+  overlayable_one.policies |= Overlayable::Policy::kPublic;
+  overlayable_one.policies |= Overlayable::Policy::kProductServices;
+
   std::string name_two = "com.app.test:integer/overlayable_two";
+  Overlayable overlayable_two{};
+  overlayable_two.policies |= Overlayable::Policy::kProduct;
+  overlayable_two.policies |= Overlayable::Policy::kSystem;
+  overlayable_two.policies |= Overlayable::Policy::kVendor;
+
   std::string name_three = "com.app.test:integer/overlayable_three";
+  Overlayable overlayable_three{};
+
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.test", 0x7f)
           .AddSimple(name_zero, ResourceId(0x7f020000))
-          .AddOverlayable(name_zero, Overlayable::Policy::kProduct)
-          .AddOverlayable(name_zero, Overlayable::Policy::kSystem)
-          .AddOverlayable(name_zero, Overlayable::Policy::kProductServices)
+          .SetOverlayable(name_zero, overlayable_zero)
           .AddSimple(name_one, ResourceId(0x7f020001))
-          .AddOverlayable(name_one, Overlayable::Policy::kPublic)
-          .AddOverlayable(name_one, Overlayable::Policy::kSystem)
+          .SetOverlayable(name_one, overlayable_one)
           .AddSimple(name_two, ResourceId(0x7f020002))
-          .AddOverlayable(name_two, Overlayable::Policy::kProduct)
-          .AddOverlayable(name_two, Overlayable::Policy::kSystem)
-          .AddOverlayable(name_two, Overlayable::Policy::kProductServices)
+          .SetOverlayable(name_two, overlayable_two)
           .AddSimple(name_three, ResourceId(0x7f020003))
-          .AddOverlayable(name_three, {})
+          .SetOverlayable(name_three, overlayable_three)
           .Build();
 
   ResourceTable output_table;
@@ -685,51 +694,35 @@
   auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
-            Overlayable::Policy::kSystem);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
-            Overlayable::Policy::kProduct);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy,
-            Overlayable::Policy::kProductServices);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem
+                                         | Overlayable::Policy::kProduct
+                                         | Overlayable::Policy::kProductServices);
 
   search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 2);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
-            Overlayable::Policy::kPublic);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
-            Overlayable::Policy::kSystem);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic
+                                         | Overlayable::Policy::kProductServices);
 
   search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
-            Overlayable::Policy::kSystem);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy,
-            Overlayable::Policy::kProduct);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy,
-            Overlayable::Policy::kProductServices);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem
+                                         | Overlayable::Policy::kProduct
+                                         | Overlayable::Policy::kVendor);
 
   search_result = output_table.FindResource(test::ParseNameOrDie(name_three));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 1);
-  EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy);
-  EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy,
-            Overlayable::Policy::kPublic);
-
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic);
 }
 
-
 }  // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index f612914..cf2ab0f 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -437,37 +437,39 @@
         entry->allow_new = std::move(allow_new);
       }
 
-      for (const pb::Overlayable& pb_overlayable : pb_entry.overlayable()) {
-        Overlayable overlayable;
-        switch (pb_overlayable.policy()) {
-          case pb::Overlayable::NONE:
-            overlayable.policy = {};
-            break;
-          case pb::Overlayable::PUBLIC:
-            overlayable.policy = Overlayable::Policy::kPublic;
-            break;
-          case pb::Overlayable::PRODUCT:
-            overlayable.policy = Overlayable::Policy::kProduct;
-            break;
-          case pb::Overlayable::PRODUCT_SERVICES:
-            overlayable.policy = Overlayable::Policy::kProductServices;
-            break;
-          case pb::Overlayable::SYSTEM:
-            overlayable.policy = Overlayable::Policy::kSystem;
-            break;
-          case pb::Overlayable::VENDOR:
-            overlayable.policy = Overlayable::Policy::kVendor;
-            break;
-          default:
-            *out_error = "unknown overlayable policy";
-            return false;
+      if (pb_entry.has_overlayable()) {
+        Overlayable overlayable{};
+
+        const pb::Overlayable& pb_overlayable = pb_entry.overlayable();
+        for (const int policy : pb_overlayable.policy()) {
+          switch (policy) {
+            case pb::Overlayable::PUBLIC:
+              overlayable.policies |= Overlayable::Policy::kPublic;
+              break;
+            case pb::Overlayable::SYSTEM:
+              overlayable.policies |= Overlayable::Policy::kSystem;
+              break;
+            case pb::Overlayable::VENDOR:
+              overlayable.policies |= Overlayable::Policy::kVendor;
+              break;
+            case pb::Overlayable::PRODUCT:
+              overlayable.policies |= Overlayable::Policy::kProduct;
+              break;
+            case pb::Overlayable::PRODUCT_SERVICES:
+              overlayable.policies |= Overlayable::Policy::kProductServices;
+              break;
+            default:
+              *out_error = "unknown overlayable policy";
+              return false;
+          }
         }
 
         if (pb_overlayable.has_source()) {
           DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source);
         }
+
         overlayable.comment = pb_overlayable.comment();
-        entry->overlayable_declarations.push_back(overlayable);
+        entry->overlayable = overlayable;
       }
 
       ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index ecf34d1..70bf868 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -310,26 +310,24 @@
           pb_allow_new->set_comment(entry->allow_new.value().comment);
         }
 
-        for (const Overlayable& overlayable : entry->overlayable_declarations) {
-          pb::Overlayable* pb_overlayable = pb_entry->add_overlayable();
-          if (overlayable.policy) {
-            switch (overlayable.policy.value()) {
-              case Overlayable::Policy::kPublic:
-                pb_overlayable->set_policy(pb::Overlayable::PUBLIC);
-                break;
-              case Overlayable::Policy::kProduct:
-                pb_overlayable->set_policy(pb::Overlayable::PRODUCT);
-                break;
-              case Overlayable::Policy::kProductServices:
-                pb_overlayable->set_policy(pb::Overlayable::PRODUCT_SERVICES);
-                break;
-              case Overlayable::Policy::kSystem:
-                pb_overlayable->set_policy(pb::Overlayable::SYSTEM);
-                break;
-              case Overlayable::Policy::kVendor:
-                pb_overlayable->set_policy(pb::Overlayable::VENDOR);
-                break;
-            }
+        if (entry->overlayable) {
+          pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable();
+
+          Overlayable overlayable = entry->overlayable.value();
+          if (overlayable.policies & Overlayable::Policy::kPublic) {
+            pb_overlayable->add_policy(pb::Overlayable::PUBLIC);
+          }
+          if (overlayable.policies & Overlayable::Policy::kProduct) {
+            pb_overlayable->add_policy(pb::Overlayable::PRODUCT);
+          }
+          if (overlayable.policies & Overlayable::Policy::kProductServices) {
+            pb_overlayable->add_policy(pb::Overlayable::PRODUCT_SERVICES);
+          }
+          if (overlayable.policies & Overlayable::Policy::kSystem) {
+            pb_overlayable->add_policy(pb::Overlayable::SYSTEM);
+          }
+          if (overlayable.policies & Overlayable::Policy::kVendor) {
+            pb_overlayable->add_policy(pb::Overlayable::VENDOR);
           }
 
           SerializeSourceToPb(overlayable.source, &source_pool,
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 1cd2f0b..fb913f40 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -93,7 +93,7 @@
       util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
 
   // Make an overlayable resource.
-  ASSERT_TRUE(table->AddOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
+  ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
                                     Overlayable{}, test::GetDiagnostics()));
 
   pb::ResourceTable pb_table;
@@ -160,8 +160,9 @@
       new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable"));
   ASSERT_TRUE(search_result);
   ASSERT_THAT(search_result.value().entry, NotNull());
-  EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  EXPECT_THAT(search_result.value().entry->overlayable.value().policies,
+              Eq(Overlayable::Policy::kNone));
 }
 
 TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
@@ -502,15 +503,26 @@
 }
 
 TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
+  Overlayable overlayable_foo{};
+  overlayable_foo.policies |= Overlayable::Policy::kSystem;
+  overlayable_foo.policies |= Overlayable::Policy::kProduct;
+
+  Overlayable overlayable_bar{};
+  overlayable_bar.policies |= Overlayable::Policy::kProductServices;
+  overlayable_bar.policies |= Overlayable::Policy::kVendor;
+
+  Overlayable overlayable_baz{};
+  overlayable_baz.policies |= Overlayable::Policy::kPublic;
+
+  Overlayable overlayable_biz{};
+
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
-          .AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kSystem)
-          .AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kProduct)
-          .AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kProductServices)
-          .AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kVendor)
-          .AddOverlayable("com.app.a:bool/baz", Overlayable::Policy::kPublic)
-          .AddOverlayable("com.app.a:bool/biz", {})
+          .SetOverlayable("com.app.a:bool/foo", overlayable_foo)
+          .SetOverlayable("com.app.a:bool/bar", overlayable_bar)
+          .SetOverlayable("com.app.a:bool/baz", overlayable_baz)
+          .SetOverlayable("com.app.a:bool/biz", overlayable_biz)
           .AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true"))
           .Build();
 
@@ -523,37 +535,36 @@
   ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
   EXPECT_THAT(error, IsEmpty());
 
-  Maybe<ResourceTable::SearchResult> result =
+  Maybe<ResourceTable::SearchResult> search_result =
       new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
-  ASSERT_TRUE(result);
-  ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
-  EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kSystem));
-  EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy,
-              Eq(Overlayable::Policy::kProduct));
+  ASSERT_TRUE(search_result);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  Overlayable result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kSystem
+                                              | Overlayable::Policy::kProduct));
 
-  result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar"));
-  ASSERT_TRUE(result);
-  ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
-  EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kProductServices));
-  EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy,
-              Eq(Overlayable::Policy::kVendor));
+  search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar"));
+  ASSERT_TRUE(search_result);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProductServices
+                                              | Overlayable::Policy::kVendor));
 
-  result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz"));
-  ASSERT_TRUE(result);
-  ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kPublic));
+  search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz"));
+  ASSERT_TRUE(search_result);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kPublic);
 
-  result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz"));
-  ASSERT_TRUE(result);
-  ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
-  EXPECT_FALSE(result.value().entry->overlayable_declarations[0].policy);
+  search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz"));
+  ASSERT_TRUE(search_result);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kNone);
 
-  result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz"));
-  ASSERT_TRUE(result);
-  EXPECT_THAT(result.value().entry->overlayable_declarations.size(), Eq(0));
+  search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz"));
+  ASSERT_TRUE(search_result);
+  ASSERT_FALSE(search_result.value().entry->overlayable);
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 1b6626a..8cbc037 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -374,8 +374,8 @@
         }
 
         // Ensure that definitions for values declared as overlayable exist
-        if (!entry->overlayable_declarations.empty() && entry->values.empty()) {
-          context->GetDiagnostics()->Error(DiagMessage(entry->overlayable_declarations[0].source)
+        if (entry->overlayable && entry->values.empty()) {
+          context->GetDiagnostics()->Error(DiagMessage(entry->overlayable.value().source)
                                            << "no definition for overlayable symbol '"
                                            << name << "'");
           error = true;
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index d777e22..22e1723 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -134,35 +134,21 @@
     dst_entry->allow_new = std::move(src_entry->allow_new);
   }
 
-  for (auto& src_overlayable : src_entry->overlayable_declarations) {
-    for (auto& dst_overlayable : dst_entry->overlayable_declarations) {
-      // An overlayable resource cannot be declared twice with the same policy
-      if (src_overlayable.policy == dst_overlayable.policy) {
-        context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source)
-                                             << "duplicate overlayable declaration for resource '"
-                                             << src_entry->name << "'");
-        context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source)
-                                             << "previous declaration here");
-        return false;
-      }
-
-      // An overlayable resource cannot be declared once with a policy and without a policy because
-      // the policy becomes unused
-      if (!src_overlayable.policy || !dst_overlayable.policy) {
-        context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source)
-                                             << "overlayable resource '" << src_entry->name
-                                             << "' declared once with a policy and once with no "
-                                             << "policy");
-        context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source)
-                                             << "previous declaration here");
-        return false;
-      }
+  if (src_entry->overlayable) {
+    if (dst_entry->overlayable) {
+      // Do not allow a resource with an overlayable declaration to have that overlayable
+      // declaration redefined
+      context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source)
+                                       << "duplicate overlayable declaration for resource '"
+                                       << src_entry->name << "'");
+      context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source)
+                                       << "previous declaration here");
+      return false;
+    } else {
+      dst_entry->overlayable = std::move(src_entry->overlayable);
     }
   }
 
-  dst_entry->overlayable_declarations.insert(dst_entry->overlayable_declarations.end(),
-                                             src_entry->overlayable_declarations.begin(),
-                                             src_entry->overlayable_declarations.end());
   return true;
 }
 
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index d6579d3..17b2a83 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -436,17 +436,21 @@
               Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent")))));
 }
 
-TEST_F(TableMergerTest, AddOverlayable) {
+TEST_F(TableMergerTest, SetOverlayable) {
+  Overlayable overlayable{};
+  overlayable.policies |= Overlayable::Policy::kProduct;
+  overlayable.policies |= Overlayable::Policy::kVendor;
+
   std::unique_ptr<ResourceTable> table_a =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.a", 0x7f)
-          .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
+          .SetOverlayable("bool/foo", overlayable)
           .Build();
 
   std::unique_ptr<ResourceTable> table_b =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.a", 0x7f)
-          .AddOverlayable("bool/foo", Overlayable::Policy::kProductServices)
+          .AddSimple("bool/foo")
           .Build();
 
   ResourceTable final_table;
@@ -457,26 +461,61 @@
   ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
 
   const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo");
-  Maybe<ResourceTable::SearchResult> result = final_table.FindResource(name);
-  ASSERT_TRUE(result);
-  ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
-  ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
-              Eq(Overlayable::Policy::kProduct));
-  ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy,
-              Eq(Overlayable::Policy::kProductServices));
+  Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name);
+  ASSERT_TRUE(search_result);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct
+                                              | Overlayable::Policy::kVendor));
 }
 
-TEST_F(TableMergerTest, AddDuplicateOverlayableFail) {
+TEST_F(TableMergerTest, SetOverlayableLater) {
   std::unique_ptr<ResourceTable> table_a =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.a", 0x7f)
-          .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
+          .AddSimple("bool/foo")
           .Build();
 
+  Overlayable overlayable{};
+  overlayable.policies |= Overlayable::Policy::kPublic;
+  overlayable.policies |= Overlayable::Policy::kProductServices;
   std::unique_ptr<ResourceTable> table_b =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.a", 0x7f)
-          .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
+          .SetOverlayable("bool/foo", overlayable)
+          .Build();
+
+  ResourceTable final_table;
+  TableMergerOptions options;
+  options.auto_add_overlay = true;
+  TableMerger merger(context_.get(), &final_table, options);
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
+
+  const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo");
+  Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name);
+  ASSERT_TRUE(search_result);
+  ASSERT_TRUE(search_result.value().entry->overlayable);
+  Overlayable& result_overlayable = search_result.value().entry->overlayable.value();
+  EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kPublic
+                                              | Overlayable::Policy::kProductServices));
+}
+
+TEST_F(TableMergerTest, SetOverlayableSamePolicesFail) {
+  Overlayable overlayable_first{};
+  overlayable_first.policies |= Overlayable::Policy::kProduct;
+  std::unique_ptr<ResourceTable> table_a =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.a", 0x7f)
+          .SetOverlayable("bool/foo", overlayable_first)
+          .Build();
+
+  Overlayable overlayable_second{};
+  overlayable_second.policies |= Overlayable::Policy::kProduct;
+  std::unique_ptr<ResourceTable> table_b =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.a", 0x7f)
+          .SetOverlayable("bool/foo", overlayable_second)
           .Build();
 
   ResourceTable final_table;
@@ -487,38 +526,21 @@
   ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
 }
 
-TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneFirstFail) {
+TEST_F(TableMergerTest, SetOverlayableDifferentPolicesFail) {
+  Overlayable overlayable_first{};
+  overlayable_first.policies |= Overlayable::Policy::kVendor;
   std::unique_ptr<ResourceTable> table_a =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.a", 0x7f)
-          .AddOverlayable("bool/foo", {})
+          .SetOverlayable("bool/foo",overlayable_first)
           .Build();
 
+  Overlayable overlayable_second{};
+  overlayable_second.policies |= Overlayable::Policy::kProduct;
   std::unique_ptr<ResourceTable> table_b =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.a", 0x7f)
-          .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
-          .Build();
-
-  ResourceTable final_table;
-  TableMergerOptions options;
-  options.auto_add_overlay = true;
-  TableMerger merger(context_.get(), &final_table, options);
-  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
-  ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
-}
-
-TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneLastFail) {
-  std::unique_ptr<ResourceTable> table_a =
-      test::ResourceTableBuilder()
-          .SetPackageId("com.app.a", 0x7f)
-          .AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
-          .Build();
-
-  std::unique_ptr<ResourceTable> table_b =
-      test::ResourceTableBuilder()
-          .SetPackageId("com.app.a", 0x7f)
-          .AddOverlayable("bool/foo", {})
+          .SetOverlayable("bool/foo", overlayable_second)
           .Build();
 
   ResourceTable final_table;
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 2e717ff..9c5b5d3 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -248,7 +248,7 @@
             if (!split_entry->id) {
               split_entry->id = entry->id;
               split_entry->visibility = entry->visibility;
-              split_entry->overlayable_declarations = entry->overlayable_declarations;
+              split_entry->overlayable = entry->overlayable;
             }
 
             // Copy the selected values into the new Split Entry.
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 03b59e0..884ec38 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -135,12 +135,11 @@
   return *this;
 }
 
-ResourceTableBuilder& ResourceTableBuilder::AddOverlayable(const StringPiece& name,
-                                                           const Maybe<Overlayable::Policy> p) {
+ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name,
+                                                           const Overlayable& overlayable) {
+
   ResourceName res_name = ParseNameOrDie(name);
-  Overlayable overlayable;
-  overlayable.policy = p;
-  CHECK(table_->AddOverlayable(res_name, overlayable, GetDiagnostics()));
+  CHECK(table_->SetOverlayable(res_name, overlayable, GetDiagnostics()));
   return *this;
 }
 
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index d68c24d..a120484 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -73,8 +73,8 @@
                                  const ResourceId& id, std::unique_ptr<Value> value);
   ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
                                        Visibility::Level level, bool allow_new = false);
-  ResourceTableBuilder& AddOverlayable(const android::StringPiece& name,
-                                       Maybe<Overlayable::Policy> policy);
+  ResourceTableBuilder& SetOverlayable(const android::StringPiece& name,
+                                       const Overlayable& overlayable);
 
   StringPool* string_pool();
   std::unique_ptr<ResourceTable> Build();
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/tools/processors/view_inspector/OWNERS b/tools/processors/view_inspector/OWNERS
new file mode 100644
index 0000000..0473f54
--- /dev/null
+++ b/tools/processors/view_inspector/OWNERS
@@ -0,0 +1,3 @@
+alanv@google.com
+ashleyrose@google.com
+aurimas@google.com
\ No newline at end of file
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java
index f0b0ff6..579745d 100644
--- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java
@@ -18,16 +18,20 @@
 
 import com.squareup.javapoet.ClassName;
 
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Optional;
 
 /**
  * Model of an inspectable class derived from annotations.
  *
- * This class does not use any {javax.lang.model} objects to facilitate building models for testing
- * {@link InspectionCompanionGenerator}.
+ * This class does not use any {@code javax.lang.model} objects to facilitate building models for
+ * testing {@link InspectionCompanionGenerator}.
  */
 public final class InspectableClassModel {
     private final ClassName mClassName;
+    private final Map<String, Property> mPropertyMap;
     private Optional<String> mNodeName = Optional.empty();
 
     /**
@@ -35,6 +39,7 @@
      */
     public InspectableClassModel(ClassName className) {
         mClassName = className;
+        mPropertyMap = new HashMap<>();
     }
 
     public ClassName getClassName() {
@@ -48,4 +53,148 @@
     public void setNodeName(Optional<String> nodeName) {
         mNodeName = nodeName;
     }
+
+    /**
+     * Add a property to the model, replacing an existing property of the same name.
+     *
+     * @param property The property to add or replace
+     */
+    public void putProperty(Property property) {
+        mPropertyMap.put(property.getName(), property);
+    }
+
+    /**
+     * Get a property by name.
+     *
+     * @param name The name of the property
+     * @return The property or an empty optional
+     */
+    public Optional<Property> getProperty(String name) {
+        return Optional.of(mPropertyMap.get(name));
+    }
+
+    /**
+     * Get all the properties defined on this model.
+     *
+     * @return An un-ordered collection of properties
+     */
+    public Collection<Property> getAllProperties() {
+        return mPropertyMap.values();
+    }
+
+    /**
+     * Model an inspectable property
+     */
+    public static final class Property {
+        private final String mName;
+        private String mGetter;
+        private Type mType;
+        private boolean mAttributeIdInferrableFromR = true;
+        private int mAttributeId = 0;
+
+        public Property(String name) {
+            mName = name;
+        }
+
+        public int getAttributeId() {
+            return mAttributeId;
+        }
+
+        /**
+         * Set the attribute ID, and mark the attribute ID as non-inferrable.
+         *
+         * @param attributeId The attribute ID for this property
+         */
+        public void setAttributeId(int attributeId) {
+            mAttributeIdInferrableFromR = false;
+            mAttributeId = attributeId;
+        }
+
+        public boolean isAttributeIdInferrableFromR() {
+            return mAttributeIdInferrableFromR;
+        }
+
+        public void setAttributeIdInferrableFromR(boolean attributeIdInferrableFromR) {
+            mAttributeIdInferrableFromR = attributeIdInferrableFromR;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public String getGetter() {
+            return mGetter;
+        }
+
+        public void setGetter(String getter) {
+            mGetter = getter;
+        }
+
+        public Type getType() {
+            return mType;
+        }
+
+        public void setType(Type type) {
+            mType = type;
+        }
+
+        public enum Type {
+            /** Primitive or boxed {@code boolean} */
+            BOOLEAN,
+
+            /** Primitive or boxed {@code byte} */
+            BYTE,
+
+            /** Primitive or boxed {@code char} */
+            CHAR,
+
+            /** Primitive or boxed {@code double} */
+            DOUBLE,
+
+            /** Primitive or boxed {@code float} */
+            FLOAT,
+
+            /** Primitive or boxed {@code int} */
+            INT,
+
+            /** Primitive or boxed {@code long} */
+            LONG,
+
+            /** Primitive or boxed {@code short} */
+            SHORT,
+
+            /** Any other object */
+            OBJECT,
+
+            /**
+             * A color object or packed color {@code int} or {@code long}.
+             *
+             * @see android.graphics.Color
+             * @see android.annotation.ColorInt
+             * @see android.annotation.ColorLong
+             */
+            COLOR,
+
+            /**
+             * An {@code int} packed with a gravity specification
+             *
+             * @see android.view.Gravity
+             */
+            GRAVITY,
+
+            /**
+             * An enumeration packed into an {@code int}.
+             *
+             * @see android.view.inspector.IntEnumMapping
+             */
+            INT_ENUM,
+
+            /**
+             * Non-exclusive or partially-exclusive flags packed into an {@code int}.
+             *
+             * @see android.view.inspector.IntFlagMapping
+             */
+            INT_FLAG
+        }
+    }
 }
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java
index fe0153d..3b85dbb 100644
--- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java
@@ -16,13 +16,23 @@
 
 package android.processor.view.inspector;
 
+import android.processor.view.inspector.InspectableClassModel.Property;
+
 import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.NameAllocator;
 import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Optional;
 
 import javax.annotation.processing.Filer;
@@ -36,10 +46,62 @@
     private final Class mRequestingClass;
 
     /**
+     * The class name for {@code R.java}.
+     */
+    private static final ClassName R_CLASS_NAME = ClassName.get("android", "R");
+
+    /**
+     * The class name of {@link android.content.res.ResourceId}.
+     */
+    private static final ClassName RESOURCE_ID_CLASS_NAME = ClassName.get(
+            "android.content.res", "ResourceId");
+
+    /**
+     * The class name of {@link android.view.inspector.InspectionCompanion}.
+     */
+    private static final ClassName INSPECTION_COMPANION = ClassName.get(
+            "android.view.inspector", "InspectionCompanion");
+
+    /**
+     * The class name of {@link android.view.inspector.PropertyMapper}.
+     */
+    private static final ClassName PROPERTY_MAPPER = ClassName.get(
+            "android.view.inspector", "PropertyMapper");
+
+    /**
+     * The class name of {@link android.view.inspector.PropertyReader}.
+     */
+    private static final ClassName PROPERTY_READER = ClassName.get(
+            "android.view.inspector", "PropertyReader");
+
+    /**
+     * The {@code mPropertiesMapped} field.
+     */
+    private static final FieldSpec M_PROPERTIES_MAPPED = FieldSpec
+            .builder(TypeName.BOOLEAN, "mPropertiesMapped", Modifier.PRIVATE)
+            .initializer("false")
+            .addJavadoc(
+                    "Set by {@link #mapProperties($T)} once properties have been mapped.\n",
+                    PROPERTY_MAPPER)
+            .build();
+
+    /**
+     * The suffix of the generated class name after the class's binary name.
+     */
+    private static final String GENERATED_CLASS_SUFFIX = "$$InspectionCompanion";
+
+    /**
+     * The null resource ID.
+     *
+     * @see android.content.res.ResourceId#ID_NULL
+     */
+    private static final int NO_ID = 0;
+
+    /**
      * @param filer A filer to write the generated source to
      * @param requestingClass A class object representing the class that invoked the generator
      */
-    public InspectionCompanionGenerator(final Filer filer, final Class requestingClass) {
+    public InspectionCompanionGenerator(Filer filer, Class requestingClass) {
         mFiler = filer;
         mRequestingClass = requestingClass;
     }
@@ -77,17 +139,24 @@
      * @return A TypeSpec of the inspection companion
      */
     private TypeSpec generateTypeSpec(InspectableClassModel model) {
+        final List<PropertyIdField> propertyIdFields = generatePropertyIdFields(model);
+
         TypeSpec.Builder builder = TypeSpec
                 .classBuilder(generateClassName(model))
                 .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                 .addSuperinterface(ParameterizedTypeName.get(
-                        ClassName.get("android.view.inspector", "InspectionCompanion"),
-                        model.getClassName()))
+                        INSPECTION_COMPANION, model.getClassName()))
                 .addJavadoc("Inspection companion for {@link $T}.\n\n", model.getClassName())
                 .addJavadoc("Generated by {@link $T}\n", getClass())
                 .addJavadoc("on behalf of {@link $T}.\n", mRequestingClass)
-                .addMethod(generateMapProperties(model))
-                .addMethod(generateReadProperties(model));
+                .addField(M_PROPERTIES_MAPPED);
+
+        for (PropertyIdField propertyIdField : propertyIdFields) {
+            builder.addField(propertyIdField.mFieldSpec);
+        }
+
+        builder.addMethod(generateMapProperties(propertyIdFields))
+                .addMethod(generateReadProperties(model, propertyIdFields));
 
         generateGetNodeName(model).ifPresent(builder::addMethod);
 
@@ -95,6 +164,42 @@
     }
 
     /**
+     * Build a list of {@link PropertyIdField}'s for a model.
+     *
+     * To insure idempotency of the generated code, this method sorts the list of properties
+     * alphabetically by name.
+     *
+     * A {@link NameAllocator} is used to ensure that the field names are valid Java identifiers,
+     * and it prevents overlaps in names by suffixing them as needed.
+     *
+     * @param model The model to get properties from
+     * @return A list of properties and fields
+     */
+    private List<PropertyIdField> generatePropertyIdFields(InspectableClassModel model) {
+        final NameAllocator nameAllocator = new NameAllocator();
+        final List<Property> sortedProperties = new ArrayList<>(model.getAllProperties());
+        final List<PropertyIdField> propertyIdFields = new ArrayList<>(sortedProperties.size());
+
+        sortedProperties.sort(Comparator.comparing(Property::getName));
+
+        for (Property property : sortedProperties) {
+            // Format a property to a member field name like "someProperty" -> "mSomePropertyId"
+            final String memberName = String.format(
+                    "m%s%sId",
+                    property.getName().substring(0, 1).toUpperCase(),
+                    property.getName().substring(1));
+            final FieldSpec fieldSpec = FieldSpec
+                    .builder(TypeName.INT, nameAllocator.newName(memberName), Modifier.PRIVATE)
+                    .addJavadoc("Property ID of {@code $L}.\n", property.getName())
+                    .build();
+
+            propertyIdFields.add(new PropertyIdField(fieldSpec, property));
+        }
+
+        return propertyIdFields;
+    }
+
+    /**
      * Generate a method definition for
      * {@link android.view.inspector.InspectionCompanion#getNodeName()}, if needed.
      *
@@ -119,21 +224,19 @@
      * {@link android.view.inspector.InspectionCompanion#mapProperties(
      * android.view.inspector.PropertyMapper)}.
      *
-     * TODO: implement
-     *
-     * @param model The model to generate from
+     * @param propertyIdFields A list of properties to map to ID fields
      * @return The method definition
      */
-    private MethodSpec generateMapProperties(InspectableClassModel model) {
-        final ClassName propertyMapper = ClassName.get(
-                "android.view.inspector", "PropertyMapper");
-
-        return MethodSpec.methodBuilder("mapProperties")
+    private MethodSpec generateMapProperties(List<PropertyIdField> propertyIdFields) {
+        final MethodSpec.Builder builder = MethodSpec.methodBuilder("mapProperties")
                 .addAnnotation(Override.class)
                 .addModifiers(Modifier.PUBLIC)
-                .addParameter(propertyMapper, "propertyMapper")
-                // TODO: add method body
-                .build();
+                .addParameter(PROPERTY_MAPPER, "propertyMapper");
+
+        propertyIdFields.forEach(p -> builder.addStatement(generatePropertyMapperInvocation(p)));
+        builder.addStatement("$N = true", M_PROPERTIES_MAPPED);
+
+        return builder.build();
     }
 
     /**
@@ -141,21 +244,91 @@
      * {@link android.view.inspector.InspectionCompanion#readProperties(
      * Object, android.view.inspector.PropertyReader)}.
      *
-     * TODO: implement
-     *
      * @param model The model to generate from
+     * @param propertyIdFields A list of properties and ID fields to read from
      * @return The method definition
      */
-    private MethodSpec generateReadProperties(InspectableClassModel model) {
-        final ClassName propertyReader = ClassName.get(
-                "android.view.inspector", "PropertyReader");
-
-        return MethodSpec.methodBuilder("readProperties")
+    private MethodSpec generateReadProperties(
+            InspectableClassModel model,
+            List<PropertyIdField> propertyIdFields) {
+        final MethodSpec.Builder builder =  MethodSpec.methodBuilder("readProperties")
                 .addAnnotation(Override.class)
                 .addModifiers(Modifier.PUBLIC)
                 .addParameter(model.getClassName(), "inspectable")
-                .addParameter(propertyReader, "propertyReader")
-                // TODO: add method body
+                .addParameter(PROPERTY_READER, "propertyReader")
+                .addCode(generatePropertyMapInitializationCheck());
+
+        for (PropertyIdField propertyIdField : propertyIdFields) {
+            builder.addStatement(
+                    "propertyReader.read$L($N, inspectable.$L())",
+                    methodSuffixForPropertyType(propertyIdField.mProperty.getType()),
+                    propertyIdField.mFieldSpec,
+                    propertyIdField.mProperty.getGetter());
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Generate a statement maps a property with a {@link android.view.inspector.PropertyMapper}.
+     *
+     * @param propertyIdField The property model and ID field to generate from
+     * @return A statement that invokes property mapper method
+     */
+    private CodeBlock generatePropertyMapperInvocation(PropertyIdField propertyIdField) {
+        final CodeBlock.Builder builder = CodeBlock.builder();
+        final Property property = propertyIdField.mProperty;
+        final FieldSpec fieldSpec = propertyIdField.mFieldSpec;
+
+        builder.add(
+                "$N = propertyMapper.map$L($S,$W",
+                fieldSpec,
+                methodSuffixForPropertyType(property.getType()),
+                property.getName());
+
+        if (property.isAttributeIdInferrableFromR()) {
+            builder.add("$T.attr.$L", R_CLASS_NAME, property.getName());
+        } else {
+            if (property.getAttributeId() == NO_ID) {
+                builder.add("$T.ID_NULL", RESOURCE_ID_CLASS_NAME);
+            } else {
+                builder.add("$L", String.format("0x%08x", property.getAttributeId()));
+            }
+        }
+
+        switch (property.getType()) {
+            case INT_ENUM:
+                throw new RuntimeException("IntEnumMapping generation not implemented");
+            case INT_FLAG:
+                throw new RuntimeException("IntFlagMapping generation not implemented");
+            default:
+                builder.add(")");
+                break;
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Generate a check that throws
+     * {@link android.view.inspector.InspectionCompanion.UninitializedPropertyMapException}
+     * if the properties haven't been initialized.
+     *
+     * <pre>
+     *     if (!mPropertiesMapped) {
+     *         throw new InspectionCompanion.UninitializedPropertyMapException();
+     *     }
+     * </pre>
+     *
+     * @return A codeblock containing the property map initialization check
+     */
+    private CodeBlock generatePropertyMapInitializationCheck() {
+        return CodeBlock.builder()
+                .beginControlFlow("if (!$N)", M_PROPERTIES_MAPPED)
+                .addStatement(
+                        "throw new $T()",
+                        INSPECTION_COMPANION.nestedClass("UninitializedPropertyMapException"))
+                .endControlFlow()
                 .build();
     }
 
@@ -163,19 +336,71 @@
      * Generate the final class name for the inspection companion from the model's class name.
      *
      * The generated class is added to the same package as the source class. If the class in the
-     * model is a nested class, the nested class names are joined with {"$"}. The suffix
-     * {"$$InspectionCompanion"} is always added the the generated name. E.g.: For modeled class
-     * {com.example.Outer.Inner}, the generated class name will be
-     * {com.example.Outer$Inner$$InspectionCompanion}.
+     * model is a nested class, the nested class names are joined with {@code "$"}. The suffix
+     * {@code "$$InspectionCompanion"} is always added the the generated name. E.g.: For modeled
+     * class {@code com.example.Outer.Inner}, the generated class name will be
+     * {@code com.example.Outer$Inner$$InspectionCompanion}.
      *
      * @param model The model to generate from
      * @return A class name for the generated inspection companion class
      */
-    private ClassName generateClassName(final InspectableClassModel model) {
+    private static ClassName generateClassName(InspectableClassModel model) {
         final ClassName className = model.getClassName();
 
         return ClassName.get(
                 className.packageName(),
-                String.join("$", className.simpleNames()) + "$$InspectionCompanion");
+                String.join("$", className.simpleNames()) + GENERATED_CLASS_SUFFIX);
+    }
+
+    /**
+     * Get the suffix for a {@code map} or {@code read} method for a property type.
+     *
+     * @param type The requested property type
+     * @return A method suffix
+     */
+    private static String methodSuffixForPropertyType(Property.Type type) {
+        switch (type) {
+            case BOOLEAN:
+                return "Boolean";
+            case BYTE:
+                return "Byte";
+            case CHAR:
+                return "Char";
+            case DOUBLE:
+                return "Double";
+            case FLOAT:
+                return "Float";
+            case INT:
+                return "Int";
+            case LONG:
+                return "Long";
+            case SHORT:
+                return "Short";
+            case OBJECT:
+                return "Object";
+            case COLOR:
+                return "Color";
+            case GRAVITY:
+                return "Gravity";
+            case INT_ENUM:
+                return "IntEnum";
+            case INT_FLAG:
+                return "IntFlag";
+            default:
+                throw new NoSuchElementException(String.format("No such property type, %s", type));
+        }
+    }
+
+    /**
+     * Value class that holds a {@link Property} and a {@link FieldSpec} for that property.
+     */
+    private static final class PropertyIdField {
+        private final FieldSpec mFieldSpec;
+        private final Property mProperty;
+
+        private PropertyIdField(FieldSpec fieldSpec, Property property) {
+            mFieldSpec = fieldSpec;
+            mProperty = property;
+        }
     }
 }
diff --git a/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java
index c02b0bd..f639719 100644
--- a/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java
+++ b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java
@@ -16,6 +16,8 @@
 
 package android.processor.view.inspector;
 
+import android.processor.view.inspector.InspectableClassModel.Property;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.TestCase.fail;
@@ -61,6 +63,54 @@
         assertGeneratedFileEquals("NestedClass");
     }
 
+    @Test
+    public void testSimpleProperties() {
+        addProperty("boolean", Property.Type.BOOLEAN, "getBoolean");
+        addProperty("byte", Property.Type.BYTE, "getByte");
+        addProperty("char", Property.Type.CHAR, "getChar");
+        addProperty("double", Property.Type.DOUBLE, "getDouble");
+        addProperty("float", Property.Type.FLOAT, "getFloat");
+        addProperty("int", Property.Type.INT, "getInt");
+        addProperty("long", Property.Type.LONG, "getLong");
+        addProperty("short", Property.Type.SHORT, "getShort");
+
+        addProperty("object", Property.Type.OBJECT, "getObject");
+        addProperty("color", Property.Type.COLOR, "getColor");
+        addProperty("gravity", Property.Type.GRAVITY, "getGravity");
+
+        assertGeneratedFileEquals("SimpleProperties");
+    }
+
+    @Test
+    public void testNoAttributeId() {
+        final Property property = new Property("noAttributeProperty");
+        property.setType(Property.Type.INT);
+        property.setGetter("getNoAttributeProperty");
+        property.setAttributeIdInferrableFromR(false);
+        mModel.putProperty(property);
+
+        assertGeneratedFileEquals("NoAttributeId");
+    }
+
+    @Test
+    public void testSuppliedAttributeId() {
+        final Property property = new Property("suppliedAttributeProperty");
+        property.setType(Property.Type.INT);
+        property.setGetter("getSuppliedAttributeProperty");
+        property.setAttributeId(0xdecafbad);
+        mModel.putProperty(property);
+
+        assertGeneratedFileEquals("SuppliedAttributeId");
+    }
+
+    private Property addProperty(String name, Property.Type type, String getter) {
+        final Property property = new Property(name);
+        property.setType(type);
+        property.setGetter(getter);
+        mModel.putProperty(property);
+        return property;
+    }
+
     private void assertGeneratedFileEquals(String fileName) {
         assertEquals(
                 loadTextResource(String.format(RESOURCE_PATH_TEMPLATE, fileName)),
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt
index e5fb6a2..2fc242c 100644
--- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt
@@ -12,11 +12,20 @@
  * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
  */
 public final class Outer$Inner$$InspectionCompanion implements InspectionCompanion<Outer.Inner> {
+    /**
+     * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
+     */
+    private boolean mPropertiesMapped = false;
+
     @Override
     public void mapProperties(PropertyMapper propertyMapper) {
+        mPropertiesMapped = true;
     }
 
     @Override
     public void readProperties(Outer.Inner inspectable, PropertyReader propertyReader) {
+        if (!mPropertiesMapped) {
+            throw new InspectionCompanion.UninitializedPropertyMapException();
+        }
     }
 }
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt
new file mode 100644
index 0000000..277e840
--- /dev/null
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt
@@ -0,0 +1,39 @@
+package com.android.inspectable;
+
+import android.content.res.ResourceId;
+import android.view.inspector.InspectionCompanion;
+import android.view.inspector.PropertyMapper;
+import android.view.inspector.PropertyReader;
+import java.lang.Override;
+
+/**
+ * Inspection companion for {@link TestInspectable}.
+ *
+ * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
+ * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
+ */
+public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> {
+    /**
+     * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
+     */
+    private boolean mPropertiesMapped = false;
+
+    /**
+     * Property ID of {@code noAttributeProperty}.
+     */
+    private int mNoAttributePropertyId;
+
+    @Override
+    public void mapProperties(PropertyMapper propertyMapper) {
+        mNoAttributePropertyId = propertyMapper.mapInt("noAttributeProperty", ResourceId.ID_NULL);
+        mPropertiesMapped = true;
+    }
+
+    @Override
+    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+        if (!mPropertiesMapped) {
+            throw new InspectionCompanion.UninitializedPropertyMapException();
+        }
+        propertyReader.readInt(mNoAttributePropertyId, inspectable.getNoAttributeProperty());
+    }
+}
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt
index a334f50..1142548 100644
--- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt
@@ -13,12 +13,21 @@
  * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
  */
 public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> {
+    /**
+     * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
+     */
+    private boolean mPropertiesMapped = false;
+
     @Override
     public void mapProperties(PropertyMapper propertyMapper) {
+        mPropertiesMapped = true;
     }
 
     @Override
     public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+        if (!mPropertiesMapped) {
+            throw new InspectionCompanion.UninitializedPropertyMapException();
+        }
     }
 
     @Override
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt
new file mode 100644
index 0000000..57eb080
--- /dev/null
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt
@@ -0,0 +1,109 @@
+package com.android.inspectable;
+
+import android.R;
+import android.view.inspector.InspectionCompanion;
+import android.view.inspector.PropertyMapper;
+import android.view.inspector.PropertyReader;
+import java.lang.Override;
+
+/**
+ * Inspection companion for {@link TestInspectable}.
+ *
+ * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
+ * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
+ */
+public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> {
+    /**
+     * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
+     */
+    private boolean mPropertiesMapped = false;
+
+    /**
+     * Property ID of {@code boolean}.
+     */
+    private int mBooleanId;
+
+    /**
+     * Property ID of {@code byte}.
+     */
+    private int mByteId;
+
+    /**
+     * Property ID of {@code char}.
+     */
+    private int mCharId;
+
+    /**
+     * Property ID of {@code color}.
+     */
+    private int mColorId;
+
+    /**
+     * Property ID of {@code double}.
+     */
+    private int mDoubleId;
+
+    /**
+     * Property ID of {@code float}.
+     */
+    private int mFloatId;
+
+    /**
+     * Property ID of {@code gravity}.
+     */
+    private int mGravityId;
+
+    /**
+     * Property ID of {@code int}.
+     */
+    private int mIntId;
+
+    /**
+     * Property ID of {@code long}.
+     */
+    private int mLongId;
+
+    /**
+     * Property ID of {@code object}.
+     */
+    private int mObjectId;
+
+    /**
+     * Property ID of {@code short}.
+     */
+    private int mShortId;
+
+    @Override
+    public void mapProperties(PropertyMapper propertyMapper) {
+        mBooleanId = propertyMapper.mapBoolean("boolean", R.attr.boolean);
+        mByteId = propertyMapper.mapByte("byte", R.attr.byte);
+        mCharId = propertyMapper.mapChar("char", R.attr.char);
+        mColorId = propertyMapper.mapColor("color", R.attr.color);
+        mDoubleId = propertyMapper.mapDouble("double", R.attr.double);
+        mFloatId = propertyMapper.mapFloat("float", R.attr.float);
+        mGravityId = propertyMapper.mapGravity("gravity", R.attr.gravity);
+        mIntId = propertyMapper.mapInt("int", R.attr.int);
+        mLongId = propertyMapper.mapLong("long", R.attr.long);
+        mObjectId = propertyMapper.mapObject("object", R.attr.object);
+        mShortId = propertyMapper.mapShort("short", R.attr.short);
+        mPropertiesMapped = true;
+    }
+
+    @Override
+    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+        if (!mPropertiesMapped) {
+            throw new InspectionCompanion.UninitializedPropertyMapException();
+        }
+        propertyReader.readBoolean(mBooleanId, inspectable.getBoolean());
+        propertyReader.readByte(mByteId, inspectable.getByte());
+        propertyReader.readChar(mCharId, inspectable.getChar());
+        propertyReader.readColor(mColorId, inspectable.getColor());
+        propertyReader.readDouble(mDoubleId, inspectable.getDouble());
+        propertyReader.readFloat(mFloatId, inspectable.getFloat());
+        propertyReader.readGravity(mGravityId, inspectable.getGravity());
+        propertyReader.readInt(mIntId, inspectable.getInt());
+        propertyReader.readLong(mLongId, inspectable.getLong());
+        propertyReader.readObject(mObjectId, inspectable.getObject());
+        propertyReader.readShort(mShortId, inspectable.getShort());
+    }
+}
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt
new file mode 100644
index 0000000..6b6ce21
--- /dev/null
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt
@@ -0,0 +1,39 @@
+package com.android.inspectable;
+
+import android.view.inspector.InspectionCompanion;
+import android.view.inspector.PropertyMapper;
+import android.view.inspector.PropertyReader;
+import java.lang.Override;
+
+/**
+ * Inspection companion for {@link TestInspectable}.
+ *
+ * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
+ * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
+ */
+public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> {
+    /**
+     * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
+     */
+    private boolean mPropertiesMapped = false;
+
+    /**
+     * Property ID of {@code suppliedAttributeProperty}.
+     */
+    private int mSuppliedAttributePropertyId;
+
+    @Override
+    public void mapProperties(PropertyMapper propertyMapper) {
+        mSuppliedAttributePropertyId = propertyMapper.mapInt("suppliedAttributeProperty",
+                0xdecafbad);
+        mPropertiesMapped = true;
+    }
+
+    @Override
+    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+        if (!mPropertiesMapped) {
+            throw new InspectionCompanion.UninitializedPropertyMapException();
+        }
+        propertyReader.readInt(mSuppliedAttributePropertyId, inspectable.getSuppliedAttributeProperty());
+    }
+}
diff --git a/tools/signedconfig/debug_key.pem b/tools/signedconfig/debug_key.pem
new file mode 100644
index 0000000..0af577b
--- /dev/null
+++ b/tools/signedconfig/debug_key.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIEfgtO+KPOoqJqTnqkDDKkAcOzyvtovsUO/ShLE6y4XRoAoGCCqGSM49
+AwEHoUQDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60pj1pnU8
+SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+-----END EC PRIVATE KEY-----
diff --git a/tools/signedconfig/debug_public.pem b/tools/signedconfig/debug_public.pem
new file mode 100644
index 0000000..f61f813
--- /dev/null
+++ b/tools/signedconfig/debug_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoE
+CGbTEBTKKvdd2hO60pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+-----END PUBLIC KEY-----
diff --git a/tools/signedconfig/debug_sign.sh b/tools/signedconfig/debug_sign.sh
new file mode 100755
index 0000000..28e5428
--- /dev/null
+++ b/tools/signedconfig/debug_sign.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+# Script to sign data with the debug keys. Outputs base64 for embedding into
+# APK metadata.
+
+openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem  $1 | base64 -w 0
+echo
diff --git a/tools/signedconfig/gen_priv_key.sh b/tools/signedconfig/gen_priv_key.sh
new file mode 100755
index 0000000..834c86b
--- /dev/null
+++ b/tools/signedconfig/gen_priv_key.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# This script acts as a record of how the debug key was generated. There should
+# be no need to run it again.
+
+openssl ecparam -name prime256v1 -genkey -noout -out debug_key.pem
+openssl ec -in debug_key.pem -pubout -out debug_public.pem
diff --git a/tools/signedconfig/verify_b64.sh b/tools/signedconfig/verify_b64.sh
new file mode 100755
index 0000000..8e1f58c
--- /dev/null
+++ b/tools/signedconfig/verify_b64.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# Script to verify signatures, with both signature & data given in b64
+# Args:
+# 1. data (base64 encoded)
+# 2. signature (base64 encoded)
+# The arg values can be taken from the debug log for SignedConfigService when verbose logging is
+# enabled.
+
+openssl dgst -sha256 -verify $(dirname $0)/debug_public.pem -signature <(echo $2 | base64 -d) <(echo $1 | base64 -d)
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index c6acd02..21d6b94 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -193,5 +193,7 @@
     int addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName);
 
     int removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName);
+
+    String[] getFactoryMacAddresses();
 }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index cad6d29..57c97ea 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2062,7 +2062,6 @@
     /**
      * @return true if this adapter supports Device-to-AP RTT
      */
-    @SystemApi
     public boolean isDeviceToApRttSupported() {
         return isFeatureSupported(WIFI_FEATURE_D2AP_RTT);
     }
@@ -4435,4 +4434,19 @@
     public boolean isOweSupported() {
         return isFeatureSupported(WIFI_FEATURE_OWE);
     }
+
+    /**
+     * Gets the factory Wi-Fi MAC addresses.
+     * @return Array of String representing Wi-Fi MAC addresses sorted lexically or an empty Array
+     * if failed.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public String[] getFactoryMacAddresses() {
+        try {
+            return mService.getFactoryMacAddresses();
+        } 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/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java
index 0f4e3a8..36f66aa 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java
@@ -452,4 +452,9 @@
             List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public String[] getFactoryMacAddresses() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 13c8c9e..1001b10 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -29,6 +29,7 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -88,6 +89,7 @@
     private static final int TEST_UID = 14553;
     private static final String TEST_PACKAGE_NAME = "TestPackage";
     private static final String TEST_COUNTRY_CODE = "US";
+    private static final String[] TEST_MAC_ADDRESSES = {"da:a1:19:0:0:0"};
 
     @Mock Context mContext;
     @Mock
@@ -1320,4 +1322,15 @@
         assertEquals(WifiManager.NETWORK_SUGGESTIONS_MAX_PER_APP,
                 mWifiManager.getMaxNumberOfNetworkSuggestionsPerApp());
     }
+
+    /**
+     * Verify getting the factory MAC address.
+     * @throws Exception
+     */
+    @Test
+    public void testGetFactoryMacAddress() throws Exception {
+        when(mWifiService.getFactoryMacAddresses()).thenReturn(TEST_MAC_ADDRESSES);
+        assertArrayEquals(TEST_MAC_ADDRESSES, mWifiManager.getFactoryMacAddresses());
+        verify(mWifiService).getFactoryMacAddresses();
+    }
 }
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());
+    }
 }