Merge "Add CallerIdentification to CallScreeningService API."
diff --git a/Android.bp b/Android.bp
index 32bd408..e096c9d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -535,6 +535,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 +612,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",
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 95c5f80..620aef4 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";
@@ -703,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
@@ -5949,6 +5951,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();
@@ -5974,6 +5985,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();
@@ -5983,6 +5995,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
@@ -5997,6 +6012,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);
   }
 
@@ -6434,6 +6450,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();
@@ -6496,11 +6519,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";
@@ -6574,6 +6599,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);
@@ -6745,10 +6771,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
@@ -6827,6 +6856,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
@@ -7615,13 +7648,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
@@ -7638,9 +7674,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;
   }
@@ -11189,6 +11227,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();
@@ -11405,6 +11452,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();
@@ -11412,6 +11460,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;
@@ -13523,6 +13572,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);
   }
@@ -13585,7 +13674,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);
@@ -14207,6 +14297,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);
@@ -14261,7 +14352,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();
@@ -14281,6 +14372,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);
@@ -14314,7 +14406,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
@@ -14596,7 +14688,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);
   }
 
@@ -15152,7 +15244,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);
@@ -16491,6 +16583,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;
@@ -17061,6 +17154,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);
@@ -22829,9 +22934,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";
@@ -23963,6 +24069,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);
@@ -24953,6 +25060,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
   }
 
@@ -25408,6 +25516,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
   }
 
@@ -25458,6 +25567,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
@@ -26223,9 +26333,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;
@@ -35936,9 +36049,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;
@@ -35946,6 +36061,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 {
@@ -37439,25 +37555,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";
@@ -38482,10 +38610,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 {
@@ -40631,13 +40762,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;
@@ -43150,6 +43284,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";
@@ -43161,6 +43296,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
@@ -43350,6 +43486,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";
@@ -44219,6 +44356,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);
@@ -44256,6 +44394,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);
@@ -44312,6 +44452,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
@@ -44344,6 +44485,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;
@@ -48866,7 +49009,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();
@@ -52495,9 +52640,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";
@@ -52538,19 +52684,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);
   }
 
@@ -52558,6 +52704,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();
@@ -52568,6 +52715,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);
@@ -52730,9 +52878,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;
@@ -52750,6 +52900,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";
@@ -52767,6 +52918,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);
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 56afb3e..cc93bba 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -205,6 +205,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
   }
 
@@ -899,6 +900,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
@@ -1007,7 +1009,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);
   }
 
@@ -1214,6 +1216,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);
@@ -1225,11 +1228,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);
@@ -3966,6 +3971,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";
@@ -5339,9 +5357,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";
   }
 
@@ -5850,6 +5869,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();
@@ -5916,6 +5936,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
@@ -6539,6 +6560,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
@@ -6562,26 +6584,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_TRANSACTION_DOES_NOT_EXIST = 343; // 0x157
+    field public static final int CODE_SIP_TOO_MANY_HOPS = 374; // 0x176
+    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
@@ -6591,9 +6626,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
@@ -7236,6 +7273,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 8d8999e..627ef22 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -362,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";
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..410bd19 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -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..bf14438 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
@@ -3763,8 +3742,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/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/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..f4fd5d1 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -154,7 +154,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;
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 6fb0d7e..5a7124e 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -296,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)
@@ -425,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";
 
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/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..b39010d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4462,7 +4462,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/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 07d6e47..c361ac1 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 {}
@@ -1730,6 +1738,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/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 96c30f1..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.
@@ -481,6 +488,8 @@
      * <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.
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..61a74de 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3710,6 +3710,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) {
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 740cdae..f1a4db2 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -203,11 +203,13 @@
             if (FEATURE_FLAG_IDMAP2) {
                 final String[] systemIdmapPaths =
                     nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
-                if (systemIdmapPaths == null) {
-                    throw new IOException("idmap2 scan failed");
-                }
-                for (String idmapPath : systemIdmapPaths) {
-                    apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+                if (systemIdmapPaths != null) {
+                    for (String idmapPath : systemIdmapPaths) {
+                        apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+                    }
+                } else {
+                    Log.w(TAG, "'idmap2 --scan' failed: no static=\"true\" overlays targeting "
+                            + "\"android\" will be loaded");
                 }
             } else {
                 nativeVerifySystemIdmaps();
diff --git a/core/java/android/hardware/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/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/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/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/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a8726e9..cd991cc 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -1275,7 +1275,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Bitmap getDocumentThumbnail(ContentResolver content, Uri documentUri, Point size,
             CancellationSignal signal) throws FileNotFoundException {
         return getDocumentThumbnail((ContentInterface) content, documentUri, size, signal);
@@ -1307,7 +1307,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Uri createDocument(ContentResolver content, Uri parentDocumentUri,
             String mimeType, String displayName) throws FileNotFoundException {
         return createDocument((ContentInterface) content, parentDocumentUri, mimeType, displayName);
@@ -1345,7 +1345,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static boolean isChildDocument(ContentResolver content, Uri parentDocumentUri,
             Uri childDocumentUri) throws FileNotFoundException {
         return isChildDocument((ContentInterface) content, parentDocumentUri, childDocumentUri);
@@ -1382,7 +1382,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Uri renameDocument(ContentResolver content, Uri documentUri,
             String displayName) throws FileNotFoundException {
         return renameDocument((ContentInterface) content, documentUri, displayName);
@@ -1410,7 +1410,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static boolean deleteDocument(ContentResolver content, Uri documentUri)
             throws FileNotFoundException {
         return deleteDocument((ContentInterface) content, documentUri);
@@ -1441,7 +1441,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Uri copyDocument(ContentResolver content, Uri sourceDocumentUri,
             Uri targetParentDocumentUri) throws FileNotFoundException {
         return copyDocument((ContentInterface) content, sourceDocumentUri, targetParentDocumentUri);
@@ -1474,7 +1474,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Uri moveDocument(ContentResolver content, Uri sourceDocumentUri,
             Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException {
         return moveDocument((ContentInterface) content, sourceDocumentUri, sourceParentDocumentUri,
@@ -1508,7 +1508,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static boolean removeDocument(ContentResolver content, Uri documentUri,
             Uri parentDocumentUri) throws FileNotFoundException {
         return removeDocument((ContentInterface) content, documentUri, parentDocumentUri);
@@ -1531,7 +1531,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static void ejectRoot(ContentResolver content, Uri rootUri) {
         ejectRoot((ContentInterface) content, rootUri);
     }
@@ -1581,7 +1581,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Bundle getDocumentMetadata(ContentResolver content, Uri documentUri)
             throws FileNotFoundException {
         return getDocumentMetadata((ContentInterface) content, documentUri);
@@ -1618,7 +1618,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static Path findDocumentPath(ContentResolver content, Uri treeUri)
             throws FileNotFoundException {
         return findDocumentPath((ContentInterface) content, treeUri);
@@ -1697,7 +1697,7 @@
         }
     }
 
-    /** @removed */
+    @Deprecated
     public static IntentSender createWebLinkIntent(ContentResolver content, Uri uri,
             Bundle options) throws FileNotFoundException {
         return createWebLinkIntent((ContentInterface) content, uri, options);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 93a5950..cbcc492 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7867,6 +7867,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
          */
@@ -8382,6 +8400,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 +8563,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);
         }
 
         /**
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/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl
index 254a710..7941794 100644
--- a/core/java/android/service/textclassifier/ITextClassifierService.aidl
+++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl
@@ -26,6 +26,7 @@
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationSessionId;
+import android.view.textclassifier.TextClassifierEvent;
 import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextLanguage;
 import android.view.textclassifier.TextSelection;
@@ -52,10 +53,15 @@
             in TextLinks.Request request,
             in ITextLinksCallback callback);
 
+    // TODO: Remove
     void onSelectionEvent(
             in TextClassificationSessionId sessionId,
             in SelectionEvent event);
 
+    void onTextClassifierEvent(
+            in TextClassificationSessionId sessionId,
+            in TextClassifierEvent event);
+
     void onCreateTextClassificationSession(
             in TextClassificationContext context,
             in TextClassificationSessionId sessionId);
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 3b813c7..2221d6e 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -39,6 +39,7 @@
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassificationSessionId;
 import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextClassifierEvent;
 import android.view.textclassifier.TextLanguage;
 import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextSelection;
@@ -193,6 +194,15 @@
 
         /** {@inheritDoc} */
         @Override
+        public void onTextClassifierEvent(
+                TextClassificationSessionId sessionId,
+                TextClassifierEvent event) {
+            Preconditions.checkNotNull(event);
+            TextClassifierService.this.onTextClassifierEvent(sessionId, event);
+        }
+
+        /** {@inheritDoc} */
+        @Override
         public void onDetectLanguage(
                 TextClassificationSessionId sessionId,
                 TextLanguage.Request request,
@@ -368,11 +378,28 @@
      *
      * @param sessionId the session id
      * @param event the selection event
+     * @deprecated
+     *      Use {@link #onTextClassifierEvent(TextClassificationSessionId, TextClassifierEvent)}
+     *      instead
      */
+    @Deprecated
     public void onSelectionEvent(
             @Nullable TextClassificationSessionId sessionId, @NonNull SelectionEvent event) {}
 
     /**
+     * Writes the TextClassifier event.
+     * This is called when a TextClassifier event occurs. e.g. user changed selection,
+     * smart selection happened, or a link was clicked.
+     *
+     * <p>The default implementation ignores the event.
+     *
+     * @param sessionId the session id
+     * @param event the TextClassifier event
+     */
+    public void onTextClassifierEvent(
+            @Nullable TextClassificationSessionId sessionId, @NonNull TextClassifierEvent event) {}
+
+    /**
      * Creates a new text classification session for the specified context.
      *
      * @param context the text classification context
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index d4edde9..13ac9ff 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -259,7 +259,8 @@
      * Returns the source string that was saved during construction.
      *
      * @return the source string that was saved during construction
-     * @see #ImageSpan(Drawable, String) and this{@link #ImageSpan(Context, Uri)}
+     * @see #ImageSpan(Drawable, String)
+     * @see #ImageSpan(Context, Uri)
      */
     @Nullable
     public String getSource() {
diff --git a/core/java/android/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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e36e258..9fe0ddc 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;
 
@@ -7166,6 +7188,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;
 
@@ -7258,7 +7281,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.
@@ -7361,7 +7383,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();
         }
@@ -7369,19 +7406,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;
@@ -7452,7 +7476,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/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/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/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..687b1055 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..df24bc4 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -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);
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/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..7fe3be8 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -417,7 +417,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 +429,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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ff73df6..dca15bd 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
@@ -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.
@@ -4685,6 +4696,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/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index ab4bd05..35263a3 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1603,6 +1603,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
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 101f92b..97a21a5 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>
@@ -3592,4 +3597,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..eac8b48 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2930,6 +2930,7 @@
         <public name="dataRetentionTime" />
         <public name="selectionDividerHeight" />
         <public name="foregroundServiceType" />
+        <public name="hasFragileUserData" />
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b4">
@@ -2970,6 +2971,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 0a167dd..cab01f9 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
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d573c09..161e416 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" />
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/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/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 0a2f057c..dcf95fd 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">
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/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/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/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/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/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/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/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..cb92c42
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
@@ -0,0 +1,161 @@
+/*
+ * 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.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.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.NotificationViewController;
+import com.android.car.notification.PreprocessingManager;
+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 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();
+        mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper);
+        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/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/SettingsLayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml
index 01d9c00..e27ae7d 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml
+++ b/packages/SettingsLib/SettingsLayoutPreference/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/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/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/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/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/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/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/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/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 7876b24..1d79152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -225,7 +225,7 @@
         initDimens();
     }
 
-    public void onUiModeChanged() {
+    protected void updateBackgroundColors() {
         updateColors();
         initBackground();
         updateBackgroundTint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 8bed366..383446c 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
@@ -121,6 +121,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 +588,10 @@
         updateIconVisibilities();
         updateShelfIconColor();
         updateRippleAllowed();
+        if (mUpdateBackgroundOnUpdate) {
+            mUpdateBackgroundOnUpdate = false;
+            updateBackgroundColors();
+        }
     }
 
     /** Called when the notification's ranking was changed (but nothing else changed). */
@@ -1212,9 +1217,8 @@
         }
     }
 
-    @Override
     public void onUiModeChanged() {
-        super.onUiModeChanged();
+        mUpdateBackgroundOnUpdate = true;
         reInflateViews();
         if (mChildrenContainer != null) {
             for (ExpandableNotificationRow child : mChildrenContainer.getNotificationChildren()) {
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/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/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/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 913b2ae..4fa8321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -36,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;
@@ -63,6 +64,7 @@
 
     private final SmartReplyConstants mConstants;
     private final KeyguardDismissUtil mKeyguardDismissUtil;
+    private final NotificationRemoteInputManager mRemoteInputManager;
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
 
     /**
@@ -112,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);
@@ -242,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();
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 2d30640..eb0090b 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6637,6 +6637,11 @@
     // 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;
+
     // ---- End Q Constants, all Q constants go above this line ----
 
     // Add new aosp constants above this line.
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/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/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 4678fec..8869af4 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -164,8 +164,6 @@
 
     private static final int MAX_UID_RANGES_PER_COMMAND = 10;
 
-    private static final  String[] EMPTY_STRING_ARRAY = new String[0];
-
     /**
      * Name representing {@link #setGlobalAlert(long)} limit when delivered to
      * {@link INetworkManagementEventObserver#limitReached(String, String)}.
@@ -959,8 +957,7 @@
     public String[] listInterfaces() {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
-            final List<String> result = mNetdService.interfaceGetList();
-            return result.toArray(EMPTY_STRING_ARRAY);
+            return mNetdService.interfaceGetList();
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -1252,8 +1249,7 @@
     public String[] listTetheredInterfaces() {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
-            final List<String> result = mNetdService.tetherInterfaceList();
-            return result.toArray(EMPTY_STRING_ARRAY);
+            return mNetdService.tetherInterfaceList();
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -1276,8 +1272,7 @@
     public String[] getDnsForwarders() {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
-            final List<String> result = mNetdService.tetherDnsList();
-            return result.toArray(EMPTY_STRING_ARRAY);
+            return mNetdService.tetherDnsList();
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
diff --git a/services/core/java/com/android/server/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/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/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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6e9db88..6700a53 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.
      *
@@ -2396,6 +2402,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 +2758,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 +6298,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 +6384,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 +6418,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 +6520,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) {
@@ -9168,6 +9271,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 +9544,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 +10902,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;
@@ -19068,9 +19223,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 +19557,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/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/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..1325f04 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -839,6 +839,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 +861,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 +873,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?
@@ -1042,6 +1061,8 @@
                 mJobPackageTracker.notePending(jobStatus);
                 addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
                 maybeRunPendingJobsLocked();
+            } else {
+                evaluateControllerStatesLocked(jobStatus);
             }
         }
         return JobScheduler.RESULT_SUCCESS;
@@ -1884,6 +1905,8 @@
                     newReadyJobs = new ArrayList<JobStatus>();
                 }
                 newReadyJobs.add(job);
+            } else {
+                evaluateControllerStatesLocked(job);
             }
         }
 
@@ -1957,6 +1980,8 @@
                     runnableJobs = new ArrayList<>();
                 }
                 runnableJobs.add(job);
+            } else {
+                evaluateControllerStatesLocked(job);
             }
         }
 
@@ -2087,6 +2112,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.
@@ -2219,6 +2253,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
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..58ee217 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -98,6 +98,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 +164,7 @@
         return "<" + userId + ">" + packageName;
     }
 
-    @VisibleForTesting
-    static final class Package {
+    private static final class Package {
         public final String packageName;
         public final int userId;
 
@@ -372,6 +384,38 @@
         }
     }
 
+    @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);
+        }
+    }
+
+    @Override
+    public void onUserRemovedLocked(int userId) {
+        mTrackedJobs.delete(userId);
+        mPkgTimers.delete(userId);
+        mTimingSessions.delete(userId);
+        mInQuotaAlarmListeners.delete(userId);
+    }
+
     /**
      * Returns an appropriate standby bucket for the job, taking into account any standby
      * exemptions.
@@ -387,8 +431,9 @@
 
     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,
@@ -579,7 +624,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.
@@ -765,18 +813,18 @@
         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) {
             this.startTimeElapsed = startElapsed;
             this.endTimeElapsed = endElapsed;
-            this.jobCount = jobCount;
+            this.bgJobCount = jobCount;
         }
 
         @Override
         public String toString() {
-            return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + jobCount
+            return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount
                     + "}";
         }
 
@@ -786,7 +834,7 @@
                 TimingSession other = (TimingSession) obj;
                 return startTimeElapsed == other.startTimeElapsed
                         && endTimeElapsed == other.endTimeElapsed
-                        && jobCount == other.jobCount;
+                        && bgJobCount == other.bgJobCount;
             } else {
                 return false;
             }
@@ -794,7 +842,7 @@
 
         @Override
         public int hashCode() {
-            return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, jobCount});
+            return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, bgJobCount});
         }
 
         public void dump(IndentingPrintWriter pw) {
@@ -804,8 +852,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 +864,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,23 +874,32 @@
     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();
                         scheduleCutoff();
@@ -853,7 +911,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 +919,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 +956,7 @@
          */
         public boolean isActive() {
             synchronized (mLock) {
-                return mJobCount > 0;
+                return mBgJobCount > 0;
             }
         }
 
@@ -905,12 +972,12 @@
                     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();
                         // Schedule cutoff since we're now actively tracking for quotas again.
                         scheduleCutoff();
                     }
@@ -958,12 +1025,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 +1046,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);
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/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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fc364c0..36a027a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -583,7 +583,6 @@
     private void loadPolicyFile() {
         if (DBG) Slog.d(TAG, "loadPolicyFile");
         synchronized (mPolicyFile) {
-
             InputStream infile = null;
             try {
                 infile = mPolicyFile.openRead();
@@ -3357,14 +3356,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 +3380,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);
             }
         }
 
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 57922d0..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;
@@ -486,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.
@@ -736,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();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 26a92a4..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;
@@ -194,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")
@@ -340,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);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index edab94c..6cfb846 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;
@@ -2168,10 +2166,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 +9210,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 +15264,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, installArgs.user);
                 if (deletePackageAction == null) {
                     throw new ReconcileFailure(
                             PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
@@ -15353,12 +15351,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);
@@ -17818,12 +17813,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 +17841,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 +17877,8 @@
                 }
             }
         }
-        return new DeletePackageAction(ps, disabledPs, outInfo);
+        return new DeletePackageAction(ps, disabledPs, outInfo, flags, user,
+                mayDeleteUnupdatedSystemApp);
     }
 
     /*
@@ -17883,7 +17893,7 @@
             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 (null == action) {
             return false;
@@ -17892,8 +17902,8 @@
         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) {
             return false;
         }
@@ -17910,11 +17920,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) {
 
@@ -17940,8 +17952,7 @@
         }
 
 
-        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
@@ -20169,11 +20180,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/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..b58c8116 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/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/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/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 12690a9..10231826 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..4373675 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -96,6 +96,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 +123,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 +145,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 +158,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;
@@ -716,8 +718,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 +1041,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 +1052,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 +1065,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 +1094,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 +1157,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 +1810,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 +2017,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 +2181,7 @@
                 .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
         if (info != null) {
             mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
-                    info.windowsFullyDrawnDelayMs);
+                    info.windowsFullyDrawnDelayMs, info.getLaunchState());
         }
     }
 
@@ -2187,8 +2205,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 +2561,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);
@@ -2774,7 +2791,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 c458c94..8624bff 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -746,6 +746,9 @@
     @Override
     void removeImmediately() {
         onRemovedFromDisplay();
+        if (mActivityRecord != null) {
+            mActivityRecord.unregisterConfigurationChangeListener(this);
+        }
         super.removeImmediately();
     }
 
@@ -1175,21 +1178,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(
@@ -1220,6 +1216,7 @@
             onDisplayChanged(displayContent);
             prevDisplayContent.setLayoutNeeded();
         }
+        getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
     }
 
     @Override
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/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..7186cdf 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;
@@ -184,6 +191,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 +369,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.
@@ -4714,6 +4737,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 +5385,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 {
@@ -5765,13 +5805,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() {
@@ -5831,22 +5880,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 +5912,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 +6030,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 +6099,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 +6119,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 +6144,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);
+            }
         }
     }
 
@@ -6971,9 +7110,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.");
             }
@@ -7032,14 +7178,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) {
@@ -8496,7 +8650,8 @@
 
     @Override
     public boolean isCallerApplicationRestrictionsManagingPackage(String callerPackage) {
-        return isCallerDelegate(callerPackage, DELEGATION_APP_RESTRICTIONS);
+        return isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
+                DELEGATION_APP_RESTRICTIONS);
     }
 
     @Override
@@ -10715,6 +10870,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);
@@ -12507,13 +12680,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 +12788,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 +12814,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
@@ -13658,4 +13837,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..e1b83fc 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;
@@ -1667,6 +1668,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..effb5a7 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
@@ -87,7 +87,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;
@@ -271,6 +270,60 @@
     }
 
     @Test
+    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);
+
+        mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001);
+        assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
+        assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
+    }
+
+    @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);
+
+        mQuotaController.onUserRemovedLocked(0);
+        assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
+        assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test"));
+    }
+
+    @Test
     public void testGetTrailingExecutionTimeLocked_NoTimer() {
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         // Added in chronological order.
@@ -365,7 +418,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 +469,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 +523,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);
@@ -529,19 +585,22 @@
 
         // 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());
@@ -775,6 +834,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/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..729fac5 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;
@@ -5133,6 +5137,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/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/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/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 65e1835..65cde77 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.IBinder;
 import android.view.Display;
@@ -45,8 +38,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.
@@ -116,17 +107,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;
     }
 
@@ -141,6 +121,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.
@@ -264,9 +251,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() {
@@ -293,49 +281,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/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 2657576..6c4b1af8 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -463,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
@@ -473,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";
@@ -1972,7 +1976,6 @@
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException handleCallIntent: " + e);
         }
-
     }
 
     private ITelecomService getTelecomService() {
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 17f3261..388b5fb 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";
@@ -1393,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}
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/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/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index c40eb9a..387453f 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2403,7 +2403,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
@@ -2441,7 +2441,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
@@ -2477,7 +2477,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.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 33f8567..11b6674 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;
@@ -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
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..79d47326 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/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 88b9302..46366d6 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
@@ -1482,6 +1473,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 7f5f03e0d..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/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/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 46ecc49..57c97ea 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -4434,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/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();
+    }
 }