Merge "Decouple downgrade and optimization processes."
diff --git a/Android.bp b/Android.bp
index 56f3c23..a5cc89c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -719,8 +719,6 @@
     exclude_srcs: [
         // See comment on framework-atb-backward-compatibility module below
         "core/java/android/content/pm/AndroidTestBaseUpdater.java",
-        // See comment on framework-oahl-backward-compatibility module below
-        "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
     ],
 
     no_framework_libs: true,
diff --git a/api/current.txt b/api/current.txt
index 912d52e..55f5a6d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5793,6 +5793,7 @@
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
     method @Nullable public String getNotificationDelegate();
     method public android.app.NotificationManager.Policy getNotificationPolicy();
+    method public boolean isNotificationAssistantAccessGranted(android.content.ComponentName);
     method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
     method public boolean isNotificationPolicyAccessGranted();
     method public void notify(int, android.app.Notification);
@@ -11218,6 +11219,7 @@
 
   public class LauncherApps {
     method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle);
+    method @Nullable public android.content.pm.LauncherApps.AppUsageLimit getAppUsageLimit(String, android.os.UserHandle);
     method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
     method public java.util.List<android.os.UserHandle> getProfiles();
@@ -11245,6 +11247,14 @@
     field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
   }
 
+  public static final class LauncherApps.AppUsageLimit implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getTotalUsageLimit();
+    method public long getUsageRemaining();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR;
+  }
+
   public abstract static class LauncherApps.Callback {
     ctor public LauncherApps.Callback();
     method public abstract void onPackageAdded(String, android.os.UserHandle);
@@ -11441,6 +11451,7 @@
     method public void setAppIcon(@Nullable android.graphics.Bitmap);
     method public void setAppLabel(@Nullable CharSequence);
     method public void setAppPackageName(@Nullable String);
+    method public void setInstallAsApex();
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
     method public void setMultiPackage();
@@ -41371,6 +41382,22 @@
 
 package android.service.notification {
 
+  public final class Adjustment implements android.os.Parcelable {
+    ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
+    method public int describeContents();
+    method public CharSequence getExplanation();
+    method public String getKey();
+    method public String getPackage();
+    method public android.os.Bundle getSignals();
+    method public int getUser();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
+    field public static final String KEY_IMPORTANCE = "key_importance";
+    field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
+    field public static final String KEY_SMART_REPLIES = "key_smart_replies";
+    field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+  }
+
   public final class Condition implements android.os.Parcelable {
     ctor public Condition(android.net.Uri, String, int);
     ctor public Condition(android.net.Uri, String, String, String, int, int, int);
@@ -41417,6 +41444,24 @@
     field @Deprecated public static final String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
   }
 
+  public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
+    ctor public NotificationAssistantService();
+    method public final void adjustNotification(android.service.notification.Adjustment);
+    method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
+    method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method public void onNotificationDirectReplied(@NonNull String);
+    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
+    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
+    method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
+    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
+    method public void onNotificationsSeen(java.util.List<java.lang.String>);
+    method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
+    field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
+    field public static final int SOURCE_FROM_APP = 0; // 0x0
+    field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
+  }
+
   public abstract class NotificationListenerService extends android.app.Service {
     ctor public NotificationListenerService();
     method public final void cancelAllNotifications();
@@ -41497,6 +41542,8 @@
     method public long getLastAudiblyAlertedMillis();
     method public String getOverrideGroupKey();
     method public int getRank();
+    method public java.util.List<android.app.Notification.Action> getSmartActions();
+    method public java.util.List<java.lang.CharSequence> getSmartReplies();
     method public int getSuppressedVisualEffects();
     method public int getUserSentiment();
     method public boolean isAmbient();
@@ -41515,6 +41562,37 @@
     field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR;
   }
 
+  public final class NotificationStats implements android.os.Parcelable {
+    ctor public NotificationStats();
+    method public int describeContents();
+    method public int getDismissalSentiment();
+    method public int getDismissalSurface();
+    method public boolean hasDirectReplied();
+    method public boolean hasExpanded();
+    method public boolean hasInteracted();
+    method public boolean hasSeen();
+    method public boolean hasSnoozed();
+    method public boolean hasViewedSettings();
+    method public void setDirectReplied();
+    method public void setDismissalSentiment(int);
+    method public void setDismissalSurface(int);
+    method public void setExpanded();
+    method public void setSeen();
+    method public void setSnoozed();
+    method public void setViewedSettings();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
+    field public static final int DISMISSAL_AOD = 2; // 0x2
+    field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
+    field public static final int DISMISSAL_OTHER = 0; // 0x0
+    field public static final int DISMISSAL_PEEK = 1; // 0x1
+    field public static final int DISMISSAL_SHADE = 3; // 0x3
+    field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
+    field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
+    field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
+    field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
+  }
+
   public class StatusBarNotification implements android.os.Parcelable {
     ctor @Deprecated public StatusBarNotification(String, String, int, String, int, int, int, android.app.Notification, android.os.UserHandle, long);
     ctor public StatusBarNotification(android.os.Parcel);
diff --git a/api/system-current.txt b/api/system-current.txt
index 3466e2e..315a941 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1113,6 +1113,7 @@
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
     method public int getUsageSource();
+    method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
     method public void reportUsageStart(@NonNull android.app.Activity, @NonNull String);
@@ -1120,6 +1121,7 @@
     method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int);
     method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
+    method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int);
     method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(String, long, android.os.UserHandle);
@@ -6342,77 +6344,6 @@
 
 package android.service.notification {
 
-  public final class Adjustment implements android.os.Parcelable {
-    ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
-    ctor protected Adjustment(android.os.Parcel);
-    method public int describeContents();
-    method public CharSequence getExplanation();
-    method public String getKey();
-    method public String getPackage();
-    method public android.os.Bundle getSignals();
-    method public int getUser();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
-    field public static final String KEY_IMPORTANCE = "key_importance";
-    field public static final String KEY_PEOPLE = "key_people";
-    field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
-    field public static final String KEY_SMART_REPLIES = "key_smart_replies";
-    field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
-    field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
-  }
-
-  public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
-    ctor public NotificationAssistantService();
-    method public final void adjustNotification(android.service.notification.Adjustment);
-    method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
-    method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method public void onNotificationDirectReplied(@NonNull String);
-    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
-    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
-    method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
-    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
-    method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
-    method public void onNotificationsSeen(java.util.List<java.lang.String>);
-    method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
-    method public final void unsnoozeNotification(String);
-    field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
-    field public static final int SOURCE_FROM_APP = 0; // 0x0
-    field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
-  }
-
-  public final class NotificationStats implements android.os.Parcelable {
-    ctor public NotificationStats();
-    ctor protected NotificationStats(android.os.Parcel);
-    method public int describeContents();
-    method public int getDismissalSentiment();
-    method public int getDismissalSurface();
-    method public boolean hasDirectReplied();
-    method public boolean hasExpanded();
-    method public boolean hasInteracted();
-    method public boolean hasSeen();
-    method public boolean hasSnoozed();
-    method public boolean hasViewedSettings();
-    method public void setDirectReplied();
-    method public void setDismissalSentiment(int);
-    method public void setDismissalSurface(int);
-    method public void setExpanded();
-    method public void setSeen();
-    method public void setSnoozed();
-    method public void setViewedSettings();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
-    field public static final int DISMISSAL_AOD = 2; // 0x2
-    field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
-    field public static final int DISMISSAL_OTHER = 0; // 0x0
-    field public static final int DISMISSAL_PEEK = 1; // 0x1
-    field public static final int DISMISSAL_SHADE = 3; // 0x3
-    field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
-    field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
-    field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
-    field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
-  }
-
   public final class SnoozeCriterion implements android.os.Parcelable {
     ctor public SnoozeCriterion(String, CharSequence, CharSequence);
     ctor protected SnoozeCriterion(android.os.Parcel);
diff --git a/api/test-current.txt b/api/test-current.txt
index 3dfd6e4..1b5bb10 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1751,84 +1751,14 @@
 
 package android.service.notification {
 
-  public final class Adjustment implements android.os.Parcelable {
-    ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
-    ctor protected Adjustment(android.os.Parcel);
-    method public int describeContents();
-    method public CharSequence getExplanation();
-    method public String getKey();
-    method public String getPackage();
-    method public android.os.Bundle getSignals();
-    method public int getUser();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
-    field public static final String KEY_IMPORTANCE = "key_importance";
-    field public static final String KEY_PEOPLE = "key_people";
-    field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
-    field public static final String KEY_SMART_REPLIES = "key_smart_replies";
-    field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
-    field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
-  }
-
   @Deprecated public abstract class ConditionProviderService extends android.app.Service {
     method @Deprecated public boolean isBound();
   }
 
-  public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
-    ctor public NotificationAssistantService();
-    method public final void adjustNotification(android.service.notification.Adjustment);
-    method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
-    method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method public void onNotificationDirectReplied(@NonNull String);
-    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
-    method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
-    method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
-    method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
-    method public void onNotificationsSeen(java.util.List<java.lang.String>);
-    method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
-    method public final void unsnoozeNotification(String);
-    field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
-    field public static final int SOURCE_FROM_APP = 0; // 0x0
-    field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
-  }
-
   public abstract class NotificationListenerService extends android.app.Service {
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
   }
 
-  public final class NotificationStats implements android.os.Parcelable {
-    ctor public NotificationStats();
-    ctor protected NotificationStats(android.os.Parcel);
-    method public int describeContents();
-    method public int getDismissalSentiment();
-    method public int getDismissalSurface();
-    method public boolean hasDirectReplied();
-    method public boolean hasExpanded();
-    method public boolean hasInteracted();
-    method public boolean hasSeen();
-    method public boolean hasSnoozed();
-    method public boolean hasViewedSettings();
-    method public void setDirectReplied();
-    method public void setDismissalSentiment(int);
-    method public void setDismissalSurface(int);
-    method public void setExpanded();
-    method public void setSeen();
-    method public void setSnoozed();
-    method public void setViewedSettings();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
-    field public static final int DISMISSAL_AOD = 2; // 0x2
-    field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
-    field public static final int DISMISSAL_OTHER = 0; // 0x0
-    field public static final int DISMISSAL_PEEK = 1; // 0x1
-    field public static final int DISMISSAL_SHADE = 3; // 0x3
-    field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
-    field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
-    field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
-    field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
-  }
-
   public final class SnoozeCriterion implements android.os.Parcelable {
     ctor public SnoozeCriterion(String, CharSequence, CharSequence);
     ctor protected SnoozeCriterion(android.os.Parcel);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 75c9054..181acce 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -101,6 +101,12 @@
     public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION";
 
     /**
+     * A boolean value to forward to {@link android.hardware.biometrics.BiometricPrompt}.
+     * @hide
+     */
+    public static final String EXTRA_USE_IMPLICIT = "android.app.extra.USE_IMPLICIT";
+
+    /**
      * A CharSequence description to show to the user on the alternate button when used with
      * {@link #ACTION_CONFIRM_FRP_CREDENTIAL}.
      * @hide
@@ -123,14 +129,39 @@
      * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
      * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
      *
+     * @param title Title to be shown on the dialog.
+     * @param description Description to be shown on the dialog.
      * @return the intent for launching the activity or null if no password is required.
      **/
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) {
-        if (!isDeviceSecure()) return null;
+        return createConfirmDeviceCredentialIntent(title, description, false /* useImplicit */);
+    }
+
+    /**
+     * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
+     * for the current user of the device. The caller is expected to launch this activity using
+     * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+     * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
+     *
+     * @param title Title to be shown on the dialog.
+     * @param description Description to be shown on the dialog.
+     * @param useImplicit If useImplicit is set to true, ConfirmDeviceCredentials will invoke
+     *      {@link android.hardware.biometrics.BiometricPrompt} with
+     *      {@link android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(
+     *      boolean)} set to false.
+     * @return the intent for launching the activity or null if no password is required.
+     * @hide
+     */
+    public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description,
+            boolean useImplicit) {
+        if (!isDeviceSecure()) {
+            return null;
+        }
         Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
         intent.putExtra(EXTRA_TITLE, title);
         intent.putExtra(EXTRA_DESCRIPTION, description);
+        intent.putExtra(EXTRA_USE_IMPLICIT, useImplicit);
 
         // explicitly set the package for security
         intent.setPackage(getSettingsPackageForIntent(intent));
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 43614fe..c4b4b40 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1137,9 +1137,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
     public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
         INotificationManager service = getService();
         try {
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index d2934b9..b1500c1 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -55,6 +55,9 @@
             long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent,
             in PendingIntent sessionEndCallbackIntent, String callingPackage);
     void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
+    void registerAppUsageLimitObserver(int observerId, in String[] packages, long timeLimitMs,
+            in PendingIntent callback, String callingPackage);
+    void unregisterAppUsageLimitObserver(int observerId, String callingPackage);
     void reportUsageStart(in IBinder activity, String token, String callingPackage);
     void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
             String callingPackage);
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index d2de887..51397a2 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -619,7 +619,7 @@
      * @param timeLimit The total time the set of apps can be in the foreground before the
      *                  callbackIntent is delivered. Must be at least one minute.
      * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
-     * @param callbackIntent The PendingIntent that will be dispatched when the time limit is
+     * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is
      *                       exceeded by the group of apps. The delivered Intent will also contain
      *                       the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
      *                       {@link #EXTRA_TIME_USED}. Cannot be null.
@@ -682,14 +682,14 @@
      * @param sessionThresholdTimeUnit The unit for time specified in {@code sessionThreshold}.
      *                                 Cannot be null.
      * @param limitReachedCallbackIntent The {@link PendingIntent} that will be dispatched when the
-     *                                   time limit is exceeded by the group of apps. The delivered
-     *                                   Intent will also contain the extras {@link
+     *                                   usage limit is exceeded by the group of apps. The
+     *                                   delivered Intent will also contain the extras {@link
      *                                   #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and {@link
      *                                   #EXTRA_TIME_USED}. Cannot be null.
      * @param sessionEndCallbackIntent The {@link PendingIntent}  that will be dispatched when the
-     *                                 session has ended after the time limit has been exceeded. The
-     *                                 session is considered at its end after the {@code observed}
-     *                                 usage has stopped and an additional {@code
+     *                                 session has ended after the usage limit has been exceeded.
+     *                                 The session is considered at its end after the {@code
+     *                                 observed} usage has stopped and an additional {@code
      *                                 sessionThresholdTime} has passed. The delivered Intent will
      *                                 also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link
      *                                 #EXTRA_TIME_USED}. Can be null.
@@ -736,6 +736,74 @@
     }
 
     /**
+     * Register a usage limit observer that receives a callback on the provided intent when the
+     * sum of usages of apps and tokens in the provided {@code observedEntities} array exceeds the
+     * {@code timeLimit} specified. The structure of a token is a {@link String} with the reporting
+     * package's name and a token that the calling app will use, separated by the forward slash
+     * character. Example: com.reporting.package/5OM3*0P4QU3-7OK3N
+     * <p>
+     * Registering an {@code observerId} that was already registered will override the previous one.
+     * No more than 1000 unique {@code observerId} may be registered by a single uid
+     * at any one time.
+     * A limit may be unregistered via {@link #unregisterAppUsageLimitObserver}
+     * <p>
+     * This method is similar to {@link #registerAppUsageObserver}, but the usage limit set here
+     * will be visible to the launcher so that it can report the limit to the user and how much
+     * of it is remaining.
+     * @see android.content.pm.LauncherApps#getAppUsageLimit
+     *
+     * @param observerId A unique id associated with the group of apps to be monitored. There can
+     *                  be multiple groups with common packages and different time limits.
+     * @param observedEntities The list of packages and token to observe for usage time. Cannot be
+     *                         null and must include at least one package or token.
+     * @param timeLimit The total time the set of apps can be in the foreground before the
+     *                  callbackIntent is delivered. Must be at least one minute.
+     * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+     * @param callbackIntent The PendingIntent that will be dispatched when the  usage limit is
+     *                       exceeded by the group of apps. The delivered Intent will also contain
+     *                       the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
+     *                       {@link #EXTRA_TIME_USED}. Cannot be null.
+     * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+     *                           permissions.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.SUSPEND_APPS,
+            android.Manifest.permission.OBSERVE_APP_USAGE})
+    public void registerAppUsageLimitObserver(int observerId, @NonNull String[] observedEntities,
+            long timeLimit, @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
+        try {
+            mService.registerAppUsageLimitObserver(observerId, observedEntities,
+                    timeUnit.toMillis(timeLimit), callbackIntent, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister the app usage limit observer specified by the {@code observerId}.
+     * This will only apply to any observer registered by this application. Unregistering
+     * an observer that was already unregistered or never registered will have no effect.
+     *
+     * @param observerId The id of the observer that was previously registered.
+     * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+     *                           permissions.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.SUSPEND_APPS,
+            android.Manifest.permission.OBSERVE_APP_USAGE})
+    public void unregisterAppUsageLimitObserver(int observerId) {
+        try {
+            mService.unregisterAppUsageLimitObserver(observerId, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Report usage associated with a particular {@code token} has started. Tokens are app defined
      * strings used to represent usage of in-app features. Apps with the {@link
      * android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers
@@ -743,6 +811,7 @@
      * and usage will be considered stopped if the activity stops or crashes.
      * @see #registerAppUsageObserver
      * @see #registerUsageSessionObserver
+     * @see #registerAppUsageLimitObserver
      *
      * @param activity The activity {@code token} is associated with.
      * @param token The token to report usage against.
@@ -766,6 +835,7 @@
      * {@code activity} and usage will be considered stopped if the activity stops or crashes.
      * @see #registerAppUsageObserver
      * @see #registerUsageSessionObserver
+     * @see #registerAppUsageLimitObserver
      *
      * @param activity The activity {@code token} is associated with.
      * @param token The token to report usage against.
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index d2d0cf9c..3d3c03a 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -20,6 +20,7 @@
 import android.app.usage.UsageStatsManager.StandbyBuckets;
 import android.content.ComponentName;
 import android.content.res.Configuration;
+import android.os.UserHandle;
 
 import java.util.List;
 import java.util.Set;
@@ -270,4 +271,40 @@
      * @param userId which user the app is associated with
      */
     public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId);
+
+    /**
+     * Returns an object describing the app usage limit for the given package which was set via
+     * {@link UsageStatsManager#registerAppUsageLimitObserver}.
+     * If there are multiple limits that apply to the package, the one with the smallest
+     * time remaining will be returned.
+     *
+     * @param packageName name of the package whose app usage limit will be returned
+     * @param user the user associated with the limit
+     * @return an {@link AppUsageLimitData} object describing the app time limit containing
+     * the given package, with the smallest time remaining.
+     */
+    public abstract AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user);
+
+    /** A class which is used to share the usage limit data for an app or a group of apps. */
+    public static class AppUsageLimitData {
+        private final boolean mGroupLimit;
+        private final long mTotalUsageLimit;
+        private final long mUsageRemaining;
+
+        public AppUsageLimitData(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
+            this.mGroupLimit = groupLimit;
+            this.mTotalUsageLimit = totalUsageLimit;
+            this.mUsageRemaining = usageRemaining;
+        }
+
+        public boolean isGroupLimit() {
+            return mGroupLimit;
+        }
+        public long getTotalUsageLimit() {
+            return mTotalUsageLimit;
+        }
+        public long getUsageRemaining() {
+            return mUsageRemaining;
+        }
+    }
 }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 576466f..5d6d144 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1955,6 +1955,11 @@
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0;
     }
 
+    /** @hide */
+    public boolean isCodeIntegrityPreferred() {
+        return (privateFlags & PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0;
+    }
+
     /**
      * Returns whether or not this application was installed as a virtual preload.
      */
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index db2b6fd..d1bc377 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -23,6 +23,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.LauncherApps;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
@@ -56,6 +57,9 @@
     ApplicationInfo getApplicationInfo(
             String callingPackage, String packageName, int flags, in UserHandle user);
 
+    LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, String packageName,
+            in UserHandle user);
+
     ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
             in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
     void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
diff --git a/core/java/android/content/pm/LauncherApps.aidl b/core/java/android/content/pm/LauncherApps.aidl
new file mode 100644
index 0000000..1d98ad1
--- /dev/null
+++ b/core/java/android/content/pm/LauncherApps.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2019, 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;
+
+parcelable LauncherApps.AppUsageLimit;
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 766c566..89630e1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -758,6 +758,27 @@
     }
 
     /**
+     * Returns an object describing the app usage limit for the given package.
+     * If there are multiple limits that apply to the package, the one with the smallest
+     * time remaining will be returned.
+     *
+     * @param packageName name of the package whose app usage limit will be returned
+     * @param user the user of the package
+     *
+     * @return an {@link AppUsageLimit} object describing the app time limit containing
+     * the given package with the smallest time remaining, or {@code null} if none exist.
+     * @throws SecurityException when the caller is not the active launcher.
+     */
+    @Nullable
+    public LauncherApps.AppUsageLimit getAppUsageLimit(String packageName, UserHandle user) {
+        try {
+            return mService.getAppUsageLimit(mContext.getPackageName(), packageName, user);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks if the activity exists and it enabled for a profile.
      *
      * @param component The activity to check.
@@ -1632,4 +1653,86 @@
             return 0;
         }
     }
+
+    /**
+     * A class that encapsulates information about the usage limit set for an app or
+     * a group of apps.
+     *
+     * <p>The launcher can query specifics about the usage limit such as if it is a group limit,
+     * how much usage time the limit has, and how much of the total usage time is remaining
+     * via the APIs available in this class.
+     *
+     * @see #getAppUsageLimit(String, UserHandle)
+     */
+    public static final class AppUsageLimit implements Parcelable {
+        private final boolean mGroupLimit;
+        private final long mTotalUsageLimit;
+        private final long mUsageRemaining;
+
+        /** @hide */
+        public AppUsageLimit(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
+            this.mGroupLimit = groupLimit;
+            this.mTotalUsageLimit = totalUsageLimit;
+            this.mUsageRemaining = usageRemaining;
+        }
+
+        /**
+         * Returns whether this limit refers to a group of apps.
+         *
+         * @return {@code TRUE} if the limit refers to a group of apps, {@code FALSE} otherwise.
+         * @hide
+         */
+        public boolean isGroupLimit() {
+            return mGroupLimit;
+        }
+
+        /**
+         * Returns the total usage limit in milliseconds set for an app or a group of apps.
+         *
+         * @return the total usage limit in milliseconds
+         */
+        public long getTotalUsageLimit() {
+            return mTotalUsageLimit;
+        }
+
+        /**
+         * Returns the usage remaining in milliseconds for an app or the group of apps
+         * this limit refers to.
+         *
+         * @return the usage remaining in milliseconds
+         */
+        public long getUsageRemaining() {
+            return mUsageRemaining;
+        }
+
+        private AppUsageLimit(Parcel source) {
+            mGroupLimit = source.readBoolean();
+            mTotalUsageLimit = source.readLong();
+            mUsageRemaining = source.readLong();
+        }
+
+        public static final Creator<AppUsageLimit> CREATOR = new Creator<AppUsageLimit>() {
+            @Override
+            public AppUsageLimit createFromParcel(Parcel source) {
+                return new AppUsageLimit(source);
+            }
+
+            @Override
+            public AppUsageLimit[] newArray(int size) {
+                return new AppUsageLimit[size];
+            }
+        };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeBoolean(mGroupLimit);
+            dest.writeLong(mTotalUsageLimit);
+            dest.writeLong(mUsageRemaining);
+        }
+    }
 }
diff --git a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
index 81e4105..7790067 100644
--- a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
+++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
@@ -25,12 +25,6 @@
  * Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is
  * included by default.
  *
- * <p>This is separated out so that it can be conditionally included at build time depending on
- * whether org.apache.http.legacy is on the bootclasspath or not. In order to include this at
- * build time, and remove org.apache.http.legacy from the bootclasspath pass
- * REMOVE_OAHL_FROM_BCP=true on the build command line, otherwise this class will not be included
- * and the
- *
  * @hide
  */
 @VisibleForTesting
diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index 03eefed..b19196a 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -45,13 +45,9 @@
     static {
         final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>();
 
-        // Attempt to load and add the optional updater that will only be available when
-        // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that
-        // will remove any references to org.apache.http.library from the package so that it does
-        // not try and load the library when it is on the bootclasspath.
-        boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters,
-                "android.content.pm.OrgApacheHttpLegacyUpdater",
-                RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new);
+        // Automatically add the org.apache.http.legacy library to the app classpath if the app
+        // targets < P.
+        packageUpdaters.add(new OrgApacheHttpLegacyUpdater());
 
         packageUpdaters.add(new AndroidHidlUpdater());
 
@@ -70,7 +66,7 @@
         PackageSharedLibraryUpdater[] updaterArray = packageUpdaters
                 .toArray(new PackageSharedLibraryUpdater[0]);
         INSTANCE = new PackageBackwardCompatibility(
-                bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray);
+                bootClassPathContainsATB, updaterArray);
     }
 
     /**
@@ -116,15 +112,12 @@
         return INSTANCE;
     }
 
-    private final boolean mBootClassPathContainsOAHL;
-
     private final boolean mBootClassPathContainsATB;
 
     private final PackageSharedLibraryUpdater[] mPackageUpdaters;
 
-    public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL,
+    public PackageBackwardCompatibility(
             boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) {
-        this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL;
         this.mBootClassPathContainsATB = bootClassPathContainsATB;
         this.mPackageUpdaters = packageUpdaters;
     }
@@ -148,14 +141,6 @@
     }
 
     /**
-     * True if the org.apache.http.legacy is on the bootclasspath, false otherwise.
-     */
-    @VisibleForTesting
-    public static boolean bootClassPathContainsOAHL() {
-        return INSTANCE.mBootClassPathContainsOAHL;
-    }
-
-    /**
      * True if the android.test.base is on the bootclasspath, false otherwise.
      */
     @VisibleForTesting
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 94b7c45..73b1f4e 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1543,6 +1543,13 @@
             this.isStaged = true;
         }
 
+        /**
+         * Set this session to be installing an APEX package.
+         */
+        public void setInstallAsApex() {
+            installFlags |= PackageManager.INSTALL_APEX;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 95cfb6e..bdeacdf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8436,6 +8436,13 @@
                 new SettingsValidators.PackageNameListValidator(",");
 
         /**
+         * Controls whether aware is enabled.
+         * @hide
+         */
+        public static final String AWARE_ENABLED = "aware_enabled";
+
+        private static final Validator AWARE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
@@ -8559,6 +8566,7 @@
             SKIP_GESTURE,
             SILENCE_GESTURE,
             THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+            AWARE_ENABLED,
         };
 
         /**
@@ -8731,6 +8739,7 @@
             VALIDATORS.put(SILENCE_GESTURE, SILENCE_GESTURE_VALIDATOR);
             VALIDATORS.put(THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                     THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR);
+            VALIDATORS.put(AWARE_ENABLED, AWARE_ENABLED_VALIDATOR);
         }
 
         /**
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index de532b7..b6788f5 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -15,8 +15,6 @@
  */
 package android.service.notification;
 
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.app.Notification;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -24,10 +22,7 @@
 
 /**
  * Ranking updates from the Assistant.
- * @hide
  */
-@SystemApi
-@TestApi
 public final class Adjustment implements Parcelable {
     private final String mPackage;
     private final String mKey;
@@ -39,6 +34,7 @@
      * Data type: ArrayList of {@code String}, where each is a representation of a
      * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
      * See {@link android.app.Notification.Builder#addPerson(String)}.
+     * @hide
      */
     public static final String KEY_PEOPLE = "key_people";
     /**
@@ -46,6 +42,7 @@
      * users. If a user chooses to snooze a notification until one of these criterion, the
      * assistant will be notified via
      * {@link NotificationAssistantService#onNotificationSnoozedUntilContext}.
+     * @hide
      */
     public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
     /**
@@ -112,7 +109,7 @@
         mUser = user;
     }
 
-    protected Adjustment(Parcel in) {
+    private Adjustment(Parcel in) {
         if (in.readInt() == 1) {
             mPackage = in.readString();
         } else {
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index ad34ab3..e93b158 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -21,8 +21,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.admin.DevicePolicyManager;
@@ -61,11 +59,7 @@
  * <p>
  *     All callbacks are called on the main thread.
  * </p>
- *
- * @hide
  */
-@SystemApi
-@TestApi
 public abstract class NotificationAssistantService extends NotificationListenerService {
     private static final String TAG = "NotificationAssistants";
 
@@ -109,6 +103,7 @@
      *
      * @param sbn the notification to snooze
      * @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context.
+     * @hide
      */
     abstract public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
             String snoozeCriterionId);
@@ -250,6 +245,7 @@
      * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
      * notification.
      * @param key The key of the notification to snooze
+     * @hide
      */
     public final void unsnoozeNotification(String key) {
         if (!isBound()) return;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 0e63cd37..c734b63 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1617,14 +1617,16 @@
         }
 
         /**
-         * @hide
+         * Returns a list of smart {@link Notification.Action} that can be added by the
+         * {@link NotificationAssistantService}
          */
         public List<Notification.Action> getSmartActions() {
             return mSmartActions;
         }
 
         /**
-         * @hide
+         * Returns a list of smart replies that can be added by the
+         * {@link NotificationAssistantService}
          */
         public List<CharSequence> getSmartReplies() {
             return mSmartReplies;
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index e5f3dfb..814b477 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -16,8 +16,6 @@
 package android.service.notification;
 
 import android.annotation.IntDef;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.app.RemoteInput;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -25,11 +23,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-/**
- * @hide
- */
-@TestApi
-@SystemApi
 public final class NotificationStats implements Parcelable {
 
     private boolean mSeen;
@@ -105,7 +98,7 @@
     public NotificationStats() {
     }
 
-    protected NotificationStats(Parcel in) {
+    private NotificationStats(Parcel in) {
         mSeen = in.readByte() != 0;
         mExpanded = in.readByte() != 0;
         mDirectReplied = in.readByte() != 0;
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 9ab963e..a5b7c62 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -78,6 +78,9 @@
  */
 public final class TextClassifierImpl implements TextClassifier {
 
+    /** @hide */
+    public static final String ACTIONS_INTENTS = "actions-intents";
+
     private static final String LOG_TAG = DEFAULT_LOG_TAG;
 
     private static final boolean DEBUG = false;
@@ -567,6 +570,7 @@
         // TODO: Make this configurable.
         final float foreignTextThreshold = typeCount == 0 ? 0.5f : 0.7f;
         boolean isPrimaryAction = true;
+        final ArrayList<Intent> sourceIntents = new ArrayList<>();
         for (LabeledIntent labeledIntent : IntentFactory.create(
                 mContext, classifiedText, isForeignText(classifiedText, foreignTextThreshold),
                 referenceTime, highestScoringResult)) {
@@ -586,9 +590,15 @@
                 isPrimaryAction = false;
             }
             builder.addAction(action);
+            sourceIntents.add(labeledIntent.getIntent());
         }
 
-        return builder.setId(createId(text, start, end)).build();
+        final Bundle extras = new Bundle();
+        extras.putParcelableArrayList(ACTIONS_INTENTS, sourceIntents);
+
+        return builder.setId(createId(text, start, end))
+                .setExtras(extras)
+                .build();
     }
 
     private boolean isForeignText(String text, float threshold) {
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 6cdba33..eb716ac 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2183,4 +2183,10 @@
     // OPEN: Settings > Display > Adaptive sleep
     // OS: Q
     SETTINGS_ADAPTIVE_SLEEP = 1628;
+
+    // OPEN: Settings > System > Aware
+    SETTINGS_AWARE = 1632;
+
+    // OPEN: Settings > System > Aware > Disable > Dialog
+    DIALOG_AWARE_DISABLE = 1633;
 }
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index c0d6139..aaf6c63 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -530,7 +530,9 @@
     optional SettingProto silence_gesture_enabled = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto theme_customization_overlay_packages = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
+    optional SettingProto aware_enabled = 77 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 77;
+    // Next tag = 78;
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dd8ecdb..bbc55a3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3762,4 +3762,7 @@
 
     <!-- Whether cbrs is supported on the device or not -->
     <bool translatable="false" name="config_cbrs_supported">false</bool>
+
+    <!-- Whether or not aware is enabled by default -->
+    <bool name="config_awareSettingAvailable">false</bool>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 65a8959..a761baf 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3387,6 +3387,9 @@
     <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
     <string name="wifi_no_internet_detailed">Tap for options</string>
 
+    <!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] -->
+    <string name="captive_portal_logged_in_detailed">Connected</string>
+
     <!-- A notification is shown when the user's softap config has been changed due to underlying
          hardware restrictions. This is the notifications's title.
          [CHAR_LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5991d88..cbb4cb2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -695,6 +695,7 @@
   <java-symbol type="string" name="capability_title_canControlMagnification" />
   <java-symbol type="string" name="capability_desc_canPerformGestures" />
   <java-symbol type="string" name="capability_title_canPerformGestures" />
+  <java-symbol type="string" name="captive_portal_logged_in_detailed" />
   <java-symbol type="string" name="cfTemplateForwarded" />
   <java-symbol type="string" name="cfTemplateForwardedTime" />
   <java-symbol type="string" name="cfTemplateNotForwarded" />
@@ -3553,4 +3554,6 @@
 
   <!-- For CBRS -->
   <java-symbol type="bool" name="config_cbrs_supported" />
+
+  <java-symbol type="bool" name="config_awareSettingAvailable" />
 </resources>
diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
index 3d7aab0..ad9814b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
@@ -48,21 +48,11 @@
     }
 
     /**
-     * Detect when the org.apache.http.legacy is not on the bootclasspath.
-     *
-     * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
-     * succeed otherwise. This allows a developer to ensure that the tests are being
-     */
-    @Test
-    public void detectWhenOAHLisOnBCP() {
-        Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsOAHL());
-    }
-
-    /**
      * Detect when the android.test.base is not on the bootclasspath.
      *
      * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
-     * succeed otherwise. This allows a developer to ensure that the tests are being
+     * succeed otherwise. This allows a developer to ensure that the tests are being run in the
+     * correct environment.
      */
     @Test
     public void detectWhenATBisOnBCP() {
@@ -85,9 +75,7 @@
         if (!PackageBackwardCompatibility.bootClassPathContainsATB()) {
             expected.add(ANDROID_TEST_BASE);
         }
-        if (!PackageBackwardCompatibility.bootClassPathContainsOAHL()) {
-            expected.add(ORG_APACHE_HTTP_LEGACY);
-        }
+        expected.add(ORG_APACHE_HTTP_LEGACY);
 
         PackageBuilder after = builder()
                 .targetSdkVersion(Build.VERSION_CODES.O)
@@ -98,30 +86,6 @@
 
     /**
      * Ensures that the {@link PackageBackwardCompatibility} uses
-     * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}
-     * when necessary.
-     *
-     * <p>More comprehensive tests for that class can be found in
-     * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}.
-     */
-    @Test
-    public void org_apache_http_legacy_in_usesLibraries() {
-        Assume.assumeTrue("Test requires that "
-                        + ORG_APACHE_HTTP_LEGACY + " is on the bootclasspath",
-                PackageBackwardCompatibility.bootClassPathContainsOAHL());
-
-        PackageBuilder before = builder()
-                .requiredLibraries(ORG_APACHE_HTTP_LEGACY);
-
-        // org.apache.http.legacy should be removed from the libraries because it is provided
-        // on the bootclasspath and providing both increases start up cost unnecessarily.
-        PackageBuilder after = builder();
-
-        checkBackwardsCompatibility(before, after);
-    }
-
-    /**
-     * Ensures that the {@link PackageBackwardCompatibility} uses
      * {@link RemoveUnnecessaryAndroidTestBaseLibrary}
      * when necessary.
      *
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index 7009fb2..4d78e40 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -262,6 +262,9 @@
         assertEquals(
                 context.getString(com.android.internal.R.string.translate),
                 classification.getActions().get(0).getTitle());
+        Intent intent = (Intent) classification.getExtras()
+                .getParcelableArrayList(TextClassifierImpl.ACTIONS_INTENTS).get(0);
+        assertEquals(Intent.ACTION_TRANSLATE, intent.getAction());
 
         LocaleList.setDefault(originalLocales);
     }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 64d68bf..850a3c2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2389,6 +2389,10 @@
                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                 SecureSettingsProto.THEME_CUSTOMIZATION_OVERLAY_PACKAGES);
 
+        dumpSetting(s, p,
+                Settings.Secure.AWARE_ENABLED,
+                SecureSettingsProto.AWARE_ENABLED);
+
         // Please insert new settings using the same order as in SecureSettingsProto.
         p.end(token);
 
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index efa4e79..d1aa84f 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6862,6 +6862,14 @@
     // OS: Q
     FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS = 1631;
 
+    // OPEN: Settings > System > Aware
+    // OS: Q
+    SETTINGS_AWARE = 1632;
+
+    // OPEN: Settings > System > Aware > Disable > Dialog
+    // OS: Q
+    DIALOG_AWARE_DISABLE = 1633;
+
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index f128d86..3a89316 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -241,6 +241,8 @@
     NOTE_NETWORK_LOST_INTERNET = 742;
     // The system default network switched to a different network
     NOTE_NETWORK_SWITCH = 743;
+    // Device logged-in captive portal network successfully
+    NOTE_NETWORK_LOGGED_IN = 744;
 
     // Notify the user that their work profile has been deleted
     // Package: android
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 6656919..12f666e 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -491,6 +491,9 @@
 
   // List of PNO scan stats, one element for each mobility state
   repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128;
+
+  // Wifi p2p statistics
+  optional WifiP2pStats wifi_p2p_stats = 129;
 }
 
 // Information that gets logged for every WiFi connection.
@@ -1860,3 +1863,135 @@
   // the total duration elapsed while in this mobility state with PNO scans running, in ms
   optional int64 pno_duration_ms = 4;
 }
+
+// The information about the Wifi P2p events.
+message WifiP2pStats {
+
+  // Group event list tracking sessions and client counts in tethered mode.
+  repeated GroupEvent group_event = 1;
+
+  // Session information that gets logged for every Wifi P2p connection.
+  repeated P2pConnectionEvent connection_event = 2;
+
+  // Number of persistent group in the user profile.
+  optional int32 num_persistent_group = 3;
+
+  // Number of peer scan.
+  optional int32 num_total_peer_scans = 4;
+
+  // Number of service scan.
+  optional int32 num_total_service_scans = 5;
+}
+
+message P2pConnectionEvent {
+
+  enum ConnectionType {
+
+    // fresh new connection.
+    CONNECTION_FRESH = 0;
+
+    // reinvoke a group.
+    CONNECTION_REINVOKE = 1;
+
+    // create a group with the current device as the group owner locally.
+    CONNECTION_LOCAL = 2;
+
+    // create a group or join a group with config.
+    CONNECTION_FAST = 3;
+  }
+
+  enum ConnectivityLevelFailure {
+
+    // Failure is unknown.
+    CLF_UNKNOWN = 0;
+
+    // No failure.
+    CLF_NONE = 1;
+
+    // Timeout for current connecting request.
+    CLF_TIMEOUT = 2;
+
+    // The connecting request is canceled by the user.
+    CLF_CANCEL = 3;
+
+    // Provision discovery failure, e.g. no pin code, timeout, rejected by the peer.
+    CLF_PROV_DISC_FAIL = 4;
+
+    // Invitation failure, e.g. rejected by the peer.
+    CLF_INVITATION_FAIL = 5;
+
+    // Incoming request is rejected by the user.
+    CLF_USER_REJECT = 6;
+
+    // New connection request is issued before ending previous connecting request.
+    CLF_NEW_CONNECTION_ATTEMPT = 7;
+  }
+
+  // WPS method.
+  enum WpsMethod {
+    // WPS is skipped for Group Reinvoke.
+    WPS_NA = -1;
+
+    // Push button configuration.
+    WPS_PBC = 0;
+
+    // Display pin method configuration - pin is generated and displayed on device.
+    WPS_DISPLAY = 1;
+
+    // Keypad pin method configuration - pin is entered on device.
+    WPS_KEYPAD = 2;
+
+    // Label pin method configuration - pin is labelled on device.
+    WPS_LABEL = 3;
+  }
+
+  // Start time of the connection.
+  optional int64 start_time_millis = 1;
+
+  // Type of the connection.
+  optional ConnectionType connection_type = 2;
+
+  // WPS method.
+  optional WpsMethod wps_method = 3 [default = WPS_NA];
+
+  // Duration to connect.
+  optional int32 duration_taken_to_connect_millis = 4;
+
+  // Failures that happen at the connectivity layer.
+  optional ConnectivityLevelFailure connectivity_level_failure_code = 5;
+}
+
+// GroupEvent tracking group information from GroupStarted to GroupRemoved.
+message GroupEvent {
+
+  enum GroupRole {
+
+    GROUP_OWNER = 0;
+
+    GROUP_CLIENT = 1;
+  }
+
+  // The ID of network in supplicant for this group.
+  optional int32 net_id = 1;
+
+  // Start time of the group.
+  optional int64 start_time_millis = 2;
+
+  // Channel frequency used for Group.
+  optional int32 channel_frequency = 3;
+
+  // Is group owner or group client.
+  optional GroupRole group_role = 5;
+
+  // Number of connected clients.
+  optional int32 num_connected_clients = 6;
+
+  // Cumulative number of connected clients.
+  optional int32 num_cumulative_clients = 7;
+
+  // The session duration.
+  optional int32 session_duration_millis = 8;
+
+  // The idle duration. A group without any client is in idle.
+  optional int32 idle_duration_millis = 9;
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fda7279..8b32afb 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -98,10 +98,10 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
+import android.net.shared.NetdService;
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
-import android.net.shared.NetdService;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -238,6 +238,9 @@
     // connect anyway?" dialog after the user selects a network that doesn't validate.
     private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
 
+    // How long to dismiss network notification.
+    private static final int TIMEOUT_NOTIFICATION_DELAY_MS = 20 * 1000;
+
     // Default to 30s linger time-out. Modifiable only for testing.
     private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
     private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@@ -474,6 +477,11 @@
     public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
 
     /**
+     * This event can handle dismissing notification by given network id.
+     */
+    public static final int EVENT_TIMEOUT_NOTIFICATION = 44;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -2477,6 +2485,11 @@
                     final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
                     final boolean wasValidated = nai.lastValidated;
                     final boolean wasDefault = isDefaultNetwork(nai);
+                    if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
+                            && valid) {
+                        nai.captivePortalLoginNotified = true;
+                        showNetworkNotification(nai, NotificationType.LOGGED_IN);
+                    }
 
                     final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
 
@@ -2497,7 +2510,15 @@
                         updateCapabilities(oldScore, nai, nai.networkCapabilities);
                         // If score has changed, rebroadcast to NetworkFactories. b/17726566
                         if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
-                        if (valid) handleFreshlyValidatedNetwork(nai);
+                        if (valid) {
+                            handleFreshlyValidatedNetwork(nai);
+                            // Clear NO_INTERNET and LOST_INTERNET notifications if network becomes
+                            // valid.
+                            mNotifier.clearNotification(nai.network.netId,
+                                    NotificationType.NO_INTERNET);
+                            mNotifier.clearNotification(nai.network.netId,
+                                    NotificationType.LOST_INTERNET);
+                        }
                     }
                     updateInetCondition(nai);
                     // Let the NetworkAgent know the state of its network
@@ -2521,6 +2542,9 @@
                         final int oldScore = nai.getCurrentScore();
                         nai.lastCaptivePortalDetected = visible;
                         nai.everCaptivePortalDetected |= visible;
+                        if (visible) {
+                            nai.captivePortalLoginNotified = false;
+                        }
                         if (nai.lastCaptivePortalDetected &&
                             Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
                             if (DBG) log("Avoiding captive portal network: " + nai.name());
@@ -2532,7 +2556,10 @@
                         updateCapabilities(oldScore, nai, nai.networkCapabilities);
                     }
                     if (!visible) {
-                        mNotifier.clearNotification(netId);
+                        // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other
+                        // notifications belong to the same network may be cleared unexpected.
+                        mNotifier.clearNotification(netId, NotificationType.SIGN_IN);
+                        mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH);
                     } else {
                         if (nai == null) {
                             loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
@@ -3238,9 +3265,15 @@
         pw.decreaseIndent();
     }
 
-    private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) {
+    private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) {
         final String action;
         switch (type) {
+            case LOGGED_IN:
+                action = Settings.ACTION_WIFI_SETTINGS;
+                mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION);
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION,
+                        nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS);
+                break;
             case NO_INTERNET:
                 action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
                 break;
@@ -3253,10 +3286,12 @@
         }
 
         Intent intent = new Intent(action);
-        intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.setClassName("com.android.settings",
-                "com.android.settings.wifi.WifiNoInternetDialog");
+        if (type != NotificationType.LOGGED_IN) {
+            intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.setClassName("com.android.settings",
+                    "com.android.settings.wifi.WifiNoInternetDialog");
+        }
 
         PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
                 mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
@@ -3274,7 +3309,7 @@
                 !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
             return;
         }
-        showValidationNotification(nai, NotificationType.NO_INTERNET);
+        showNetworkNotification(nai, NotificationType.NO_INTERNET);
     }
 
     private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
@@ -3283,7 +3318,7 @@
 
         if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
             mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
-            showValidationNotification(nai, NotificationType.LOST_INTERNET);
+            showNetworkNotification(nai, NotificationType.LOST_INTERNET);
         }
     }
 
@@ -3429,6 +3464,9 @@
                 case EVENT_DATA_SAVER_CHANGED:
                     handleRestrictBackgroundChanged(toBool(msg.arg1));
                     break;
+                case EVENT_TIMEOUT_NOTIFICATION:
+                    mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c698039..64ee30a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1502,7 +1502,7 @@
                 mService.mNativeDebuggingApp = null;
             }
 
-            if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0
+            if (app.info.isCodeIntegrityPreferred()
                     || (app.info.isPrivilegedApp()
                         && DexManager.isPackageSelectedToRunOob(app.pkgList.mPkgList.keySet()))) {
                 runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 9ea73fb..d0cff25 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -152,6 +152,10 @@
     // Whether a captive portal was found during the last network validation attempt.
     public boolean lastCaptivePortalDetected;
 
+    // Indicates the user was notified of a successful captive portal login since a portal was
+    // last detected.
+    public boolean captivePortalLoginNotified;
+
     // Networks are lingered when they become unneeded as a result of their NetworkRequests being
     // satisfied by a higher-scoring network. so as to allow communication to wrap up before the
     // network is taken down.  This usually only happens to the default network. Lingering ends with
@@ -618,18 +622,19 @@
     }
 
     public String toString() {
-        return "NetworkAgentInfo{ ni{" + networkInfo + "}  " +
-                "network{" + network + "}  nethandle{" + network.getNetworkHandle() + "}  " +
-                "lp{" + linkProperties + "}  " +
-                "nc{" + networkCapabilities + "}  Score{" + getCurrentScore() + "}  " +
-                "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  " +
-                "created{" + created + "} lingering{" + isLingering() + "} " +
-                "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
-                "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
-                "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
-                "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " +
-                "clat{" + clatd + "} " +
-                "}";
+        return "NetworkAgentInfo{ ni{" + networkInfo + "}  "
+                + "network{" + network + "}  nethandle{" + network.getNetworkHandle() + "}  "
+                + "lp{" + linkProperties + "}  "
+                + "nc{" + networkCapabilities + "}  Score{" + getCurrentScore() + "}  "
+                + "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  "
+                + "created{" + created + "} lingering{" + isLingering() + "} "
+                + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
+                + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+                + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+                + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+                + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} "
+                + "clat{" + clatd + "} "
+                + "}";
     }
 
     public String name() {
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 36a2476..b50477b 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -16,13 +16,16 @@
 
 package com.android.server.connectivity;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.net.NetworkCapabilities;
 import android.net.wifi.WifiInfo;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
@@ -31,15 +34,12 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.widget.Toast;
+
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-
 public class NetworkNotificationManager {
 
 
@@ -47,7 +47,8 @@
         LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
         NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
         NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
-        SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
+        SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN),
+        LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN);
 
         public final int eventId;
 
@@ -192,6 +193,9 @@
                     details = r.getString(R.string.network_available_sign_in_detailed, name);
                     break;
             }
+        } else if (notifyType == NotificationType.LOGGED_IN) {
+            title = WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID());
+            details = r.getString(R.string.captive_portal_logged_in_detailed);
         } else if (notifyType == NotificationType.NETWORK_SWITCH) {
             String fromTransport = getTransportName(transportType);
             String toTransport = getTransportName(getFirstTransportType(switchToNai));
@@ -239,6 +243,18 @@
         }
     }
 
+    /**
+     * Clear the notification with the given id, only if it matches the given type.
+     */
+    public void clearNotification(int id, NotificationType notifyType) {
+        final int previousEventId = mNotificationTypeMap.get(id);
+        final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+        if (notifyType != previousNotifyType) {
+            return;
+        }
+        clearNotification(id);
+    }
+
     public void clearNotification(int id) {
         if (mNotificationTypeMap.indexOfKey(id) < 0) {
             return;
@@ -290,6 +306,10 @@
         return (t != null) ? t.name() : "UNKNOWN";
     }
 
+    /**
+     * A notification with a higher number will take priority over a notification with a lower
+     * number.
+     */
     private static int priority(NotificationType t) {
         if (t == null) {
             return 0;
@@ -302,6 +322,7 @@
             case NETWORK_SWITCH:
                 return 2;
             case LOST_INTERNET:
+            case LOGGED_IN:
                 return 1;
             default:
                 return 0;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a33f14b..d0ef4f1 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -25,6 +25,7 @@
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -135,6 +136,7 @@
         private final Context mContext;
         private final UserManager mUm;
         private final UserManagerInternal mUserManagerInternal;
+        private final UsageStatsManagerInternal mUsageStatsManagerInternal;
         private final ActivityManagerInternal mActivityManagerInternal;
         private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
         private final ShortcutServiceInternal mShortcutServiceInternal;
@@ -156,6 +158,8 @@
             mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
             mUserManagerInternal = Preconditions.checkNotNull(
                     LocalServices.getService(UserManagerInternal.class));
+            mUsageStatsManagerInternal = Preconditions.checkNotNull(
+                    LocalServices.getService(UsageStatsManagerInternal.class));
             mActivityManagerInternal = Preconditions.checkNotNull(
                     LocalServices.getService(ActivityManagerInternal.class));
             mActivityTaskManagerInternal = Preconditions.checkNotNull(
@@ -671,6 +675,30 @@
             }
         }
 
+        @Override
+        public LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage,
+                String packageName, UserHandle user) {
+            verifyCallingPackage(callingPackage);
+            if (!canAccessProfile(user.getIdentifier(), "Cannot access usage limit")) {
+                return null;
+            }
+
+            final PackageManagerInternal pmi =
+                    LocalServices.getService(PackageManagerInternal.class);
+            final ComponentName cn = pmi.getDefaultHomeActivity(user.getIdentifier());
+            if (!cn.getPackageName().equals(callingPackage)) {
+                throw new SecurityException("Caller is not the active launcher");
+            }
+
+            final UsageStatsManagerInternal.AppUsageLimitData data =
+                    mUsageStatsManagerInternal.getAppUsageLimit(packageName, user);
+            if (data == null) {
+                return null;
+            }
+            return new LauncherApps.AppUsageLimit(
+                    data.isGroupLimit(), data.getTotalUsageLimit(), data.getUsageRemaining());
+        }
+
         private void ensureShortcutPermission(@NonNull String callingPackage) {
             verifyCallingPackage(callingPackage);
             if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 5412e94..94b1b36 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -506,8 +506,10 @@
             boolean isUsedByOtherApps) {
         int flags = info.flags;
         boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
-        // When a priv app is configured to run out of box, only verify it.
-        if (info.isPrivilegedApp() && DexManager.isPackageSelectedToRunOob(info.packageName)) {
+        // When an app or priv app is configured to run out of box, only verify it.
+        if (info.isCodeIntegrityPreferred()
+                || (info.isPrivilegedApp()
+                    && DexManager.isPackageSelectedToRunOob(info.packageName))) {
             return "verify";
         }
         if (vmSafeMode) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 3562630..692c032 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2307,7 +2307,7 @@
                     sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
                     break;
                 case "--apex":
-                    sessionParams.installFlags |= PackageManager.INSTALL_APEX;
+                    sessionParams.setInstallAsApex();
                     sessionParams.setStaged();
                     break;
                 case "--multi-package":
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index 4015c4f..a4f3064 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -49,6 +49,14 @@
      */
     public Instant timestamp;
 
+    /**
+     * Whether this Rollback is currently in progress. This field is true from the point
+     * we commit a {@code PackageInstaller} session containing these packages to the point the
+     * {@code PackageInstaller} calls into the {@code onFinished} callback.
+     */
+    // NOTE: All accesses to this field are from the RollbackManager handler thread.
+    public boolean inProgress = false;
+
     RollbackData(int rollbackId, File backupDir) {
         this.rollbackId = rollbackId;
         this.backupDir = backupDir;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 7f515bf..693d5d6 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -285,14 +285,19 @@
         Log.i(TAG, "Initiating rollback of " + targetPackageName);
 
         // Get the latest RollbackData for the target package.
-        RollbackData data = getRollbackForPackage(targetPackageName);
+        final RollbackData data = getRollbackForPackage(targetPackageName);
         if (data == null) {
             sendFailure(statusReceiver, "No rollback available for package.");
             return;
         }
 
         if (data.rollbackId != rollback.getRollbackId()) {
-            sendFailure(statusReceiver, "Rollback for package is out of date");
+            sendFailure(statusReceiver, "Rollback for package is out of date.");
+            return;
+        }
+
+        if (data.inProgress) {
+            sendFailure(statusReceiver, "Rollback for package is already in progress.");
             return;
         }
 
@@ -371,6 +376,10 @@
 
             final LocalIntentReceiver receiver = new LocalIntentReceiver(
                     (Intent result) -> {
+                        // We've now completed the rollback, so we mark it as no longer in
+                        // progress.
+                        data.inProgress = false;
+
                         int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                 PackageInstaller.STATUS_FAILURE);
                         if (status != PackageInstaller.STATUS_SUCCESS) {
@@ -392,6 +401,7 @@
                     }
             );
 
+            data.inProgress = true;
             parentSession.commit(receiver.getIntentSender());
         } catch (IOException e) {
             Log.e(TAG, "Unable to roll back " + targetPackageName, e);
@@ -774,10 +784,15 @@
 
         getHandler().post(() -> {
             PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-            // TODO(narayan): Should we make sure we're in the middle of a session commit for a
-            // a package with this package name ? Otherwise it's possible we may roll back data
-            // for some other downgrade.
-            if (getRollbackForPackage(packageName) == null) {
+            final RollbackData rollbackData = getRollbackForPackage(packageName);
+            if (rollbackData == null) {
+                pmi.finishPackageInstall(token, false);
+                return;
+            }
+
+            if (!rollbackData.inProgress) {
+                Log.e(TAG, "Request to restore userData for: " + packageName
+                        + ", but no rollback in progress.");
                 pmi.finishPackageInstall(token, false);
                 return;
             }
diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
index 56db32a..146c516 100644
--- a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
+++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
@@ -41,8 +41,8 @@
     private static final boolean DBG = false;
 
     private static final String DEBUG_KEY =
-            "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60"
-            + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==";
+            "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1"
+            + "x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==";
     private static final String PROD_KEY =
             "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+lky6wKyGL6lE1VrD0YTMHwb0Xwc+tzC8MvnrzVxodvTp"
             + "VY/jV7V+Zktcx+pry43XPABFRXtbhTo+qykhyBA1g==";
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index bc1f798..6845f15 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -43,6 +43,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.IUidObserver;
 import android.app.Person;
+import android.app.admin.DevicePolicyManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -143,8 +144,10 @@
             switch (name) {
                 case Context.USER_SERVICE:
                     return mMockUserManager;
+                case Context.DEVICE_POLICY_SERVICE:
+                    return mMockDevicePolicyManager;
             }
-            throw new UnsupportedOperationException();
+            throw new UnsupportedOperationException("Couldn't find system service: " + name);
         }
 
         @Override
@@ -610,6 +613,7 @@
     protected PackageManager mMockPackageManager;
     protected PackageManagerInternal mMockPackageManagerInternal;
     protected UserManager mMockUserManager;
+    protected DevicePolicyManager mMockDevicePolicyManager;
     protected UserManagerInternal mMockUserManagerInternal;
     protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
     protected ActivityManagerInternal mMockActivityManagerInternal;
@@ -750,6 +754,7 @@
         mMockPackageManager = mock(PackageManager.class);
         mMockPackageManagerInternal = mock(PackageManagerInternal.class);
         mMockUserManager = mock(UserManager.class);
+        mMockDevicePolicyManager = mock(DevicePolicyManager.class);
         mMockUserManagerInternal = mock(UserManagerInternal.class);
         mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
         mMockActivityManagerInternal = mock(ActivityManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
index b348aee..5d69bbd 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -18,12 +18,15 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.UserHandle;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -64,7 +67,7 @@
 
     private static final long TIME_30_MIN = 30 * 60_000L;
     private static final long TIME_10_MIN = 10 * 60_000L;
-    private static final long TIME_1_MIN = 10 * 60_000L;
+    private static final long TIME_1_MIN = 1 * 60_000L;
 
     private static final long MAX_OBSERVER_PER_UID = 10;
     private static final long MIN_TIME_LIMIT = 4_000L;
@@ -128,6 +131,11 @@
         }
 
         @Override
+        protected long getAppUsageLimitObserverPerUidLimit() {
+            return MAX_OBSERVER_PER_UID;
+        }
+
+        @Override
         protected long getMinTimeLimit() {
             return MIN_TIME_LIMIT;
         }
@@ -164,6 +172,16 @@
         assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2));
     }
 
+    /** Verify app usage limit observer is added */
+    @Test
+    public void testAppUsageLimitObserver_AddObserver() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+        addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID2));
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** Verify app usage observer is removed */
     @Test
     public void testAppUsageObserver_RemoveObserver() {
@@ -182,6 +200,15 @@
         assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** Verify app usage limit observer is removed */
+    @Test
+    public void testAppUsageLimitObserver_RemoveObserver() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+        mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+        assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** Verify nothing happens when a nonexistent app usage observer is removed */
     @Test
     public void testAppUsageObserver_RemoveMissingObserver() {
@@ -218,6 +245,24 @@
         assertFalse("Observer should not exist", hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** Verify nothing happens when a nonexistent app usage limit observer is removed */
+    @Test
+    public void testAppUsageLimitObserver_RemoveMissingObserver() {
+        assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+        try {
+            mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+        } catch (Exception e) {
+            StringWriter sw = new StringWriter();
+            sw.write("Hit exception trying to remove nonexistent observer:\n");
+            sw.write(e.toString());
+            PrintWriter pw = new PrintWriter(sw);
+            e.printStackTrace(pw);
+            sw.write("\nTest Failed!");
+            fail(sw.toString());
+        }
+        assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** Re-adding an observer should result in only one copy */
     @Test
     public void testAppUsageObserver_ObserverReAdd() {
@@ -242,22 +287,39 @@
         assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** Re-adding an observer should result in only one copy */
+    @Test
+    public void testAppUsageLimitObserver_ObserverReAdd() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+        assertTrue("Observer wasn't added",
+                getAppUsageLimitObserver(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+        mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+        assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** Different type observers can be registered to the same observerId value */
     @Test
     public void testAllObservers_ExclusiveObserverIds() {
         addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
         addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
         assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
         assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+        assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
 
         AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID,
                 OBS_ID1);
         AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID,
                 OBS_ID1);
+        AppTimeLimitController.UsageGroup appUsageLimitGroup = getAppUsageLimitObserver(
+                UID, OBS_ID1);
 
         // Verify data still intact
         assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs());
         assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs());
+        assertEquals(TIME_10_MIN, appUsageLimitGroup.getTimeLimitMs());
     }
 
     /** Verify that usage across different apps within a group are added up */
@@ -299,7 +361,7 @@
     @Test
     public void testUsageSessionObserver_Accumulation() throws Exception {
         setTime(0L);
-        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_10_MIN);
         startUsage(PKG_SOC1);
         // Add 10 mins
         setTime(TIME_10_MIN);
@@ -330,6 +392,41 @@
         assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
     }
 
+    /** Verify that usage across different apps within a group are added up */
+    @Test
+    public void testAppUsageLimitObserver_Accumulation() throws Exception {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        startUsage(PKG_SOC1);
+        // Add 10 mins
+        setTime(TIME_10_MIN);
+        stopUsage(PKG_SOC1);
+
+        AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+
+        long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+        assertEquals(TIME_10_MIN * 2, timeRemaining);
+
+        startUsage(PKG_SOC1);
+        setTime(TIME_10_MIN * 2);
+        stopUsage(PKG_SOC1);
+
+        timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+        assertEquals(TIME_10_MIN, timeRemaining);
+
+        setTime(TIME_30_MIN);
+
+        assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+        // Add a different package in the group
+        startUsage(PKG_GAME1);
+        setTime(TIME_30_MIN + TIME_10_MIN);
+        stopUsage(PKG_GAME1);
+
+        assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+        assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+    }
+
     /** Verify that time limit does not get triggered due to a different app */
     @Test
     public void testAppUsageObserver_TimeoutOtherApp() throws Exception {
@@ -355,6 +452,18 @@
 
     }
 
+    /** Verify that time limit does not get triggered due to a different app */
+    @Test
+    public void testAppUsageLimitObserver_TimeoutOtherApp() throws Exception {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+        startUsage(PKG_SOC2);
+        assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        setTime(6_000L);
+        stopUsage(PKG_SOC2);
+        assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+    }
+
     /** Verify the timeout message is delivered at the right time */
     @Test
     public void testAppUsageObserver_Timeout() throws Exception {
@@ -385,6 +494,19 @@
         assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** Verify the timeout message is delivered at the right time */
+    @Test
+    public void testAppUsageLimitObserver_Timeout() throws Exception {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+        startUsage(PKG_SOC1);
+        setTime(6_000L);
+        assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_SOC1);
+        // Verify that the observer was not removed
+        assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /** If an app was already running, make sure it is partially counted towards the time limit */
     @Test
     public void testAppUsageObserver_AlreadyRunning() throws Exception {
@@ -423,6 +545,25 @@
         assertTrue(hasUsageSessionObserver(UID, OBS_ID2));
     }
 
+    /** If an app was already running, make sure it is partially counted towards the time limit */
+    @Test
+    public void testAppUsageLimitObserver_AlreadyRunning() throws Exception {
+        setTime(TIME_10_MIN);
+        startUsage(PKG_GAME1);
+        setTime(TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+        setTime(TIME_30_MIN + TIME_10_MIN);
+        stopUsage(PKG_GAME1);
+        assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+        startUsage(PKG_GAME2);
+        setTime(TIME_30_MIN + TIME_30_MIN);
+        stopUsage(PKG_GAME2);
+        assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+        // Verify that the observer was not removed
+        assertTrue(hasAppUsageLimitObserver(UID, OBS_ID2));
+    }
+
     /** If watched app is already running, verify the timeout callback happens at the right time */
     @Test
     public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception {
@@ -464,6 +605,24 @@
         assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
     }
 
+    /** If watched app is already running, verify the timeout callback happens at the right time */
+    @Test
+    public void testAppUsageLimitObserver_AlreadyRunningTimeout() throws Exception {
+        setTime(0);
+        startUsage(PKG_SOC1);
+        setTime(TIME_10_MIN);
+        // 10 second time limit
+        addAppUsageLimitObserver(OBS_ID1, GROUP_SOC, 10_000L);
+        setTime(TIME_10_MIN + 5_000L);
+        // Shouldn't call back in 6 seconds
+        assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        setTime(TIME_10_MIN + 10_000L);
+        // Should call back by 11 seconds (6 earlier + 5 now)
+        assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
+        // Verify that the observer was not removed
+        assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+    }
+
     /**
      * Verify that App Time Limit Controller will limit the number of observerIds for app usage
      * observers
@@ -525,6 +684,37 @@
         assertTrue("Should have caused an IllegalStateException", receivedException);
     }
 
+    /**
+     * Verify that App Time Limit Controller will limit the number of observerIds for app usage
+     * limit observers
+     */
+    @Test
+    public void testAppUsageLimitObserver_MaxObserverLimit() throws Exception {
+        boolean receivedException = false;
+        int ANOTHER_UID = UID + 1;
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID3, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID4, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID6, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID7, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID8, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID9, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID10, GROUP1, TIME_30_MIN);
+        // Readding an observer should not cause an IllegalStateException
+        addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+        // Adding an observer for a different uid shouldn't cause an IllegalStateException
+        mController.addAppUsageLimitObserver(
+                ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
+        try {
+            addAppUsageLimitObserver(OBS_ID11, GROUP1, TIME_30_MIN);
+        } catch (IllegalStateException ise) {
+            receivedException = true;
+        }
+        assertTrue("Should have caused an IllegalStateException", receivedException);
+    }
+
     /** Verify that addAppUsageObserver minimum time limit is one minute */
     @Test
     public void testAppUsageObserver_MinimumTimeLimit() throws Exception {
@@ -553,6 +743,20 @@
         assertTrue("Should have caused an IllegalArgumentException", receivedException);
     }
 
+    /** Verify that addAppUsageLimitObserver minimum time limit is one minute */
+    @Test
+    public void testAppUsageLimitObserver_MinimumTimeLimit() throws Exception {
+        boolean receivedException = false;
+        // adding an observer with a one minute time limit should not cause an exception
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
+        try {
+            addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
+        } catch (IllegalArgumentException iae) {
+            receivedException = true;
+        }
+        assertTrue("Should have caused an IllegalArgumentException", receivedException);
+    }
+
     /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
     @Test
     public void testAppUsageObserver_ConcurrentUsage() throws Exception {
@@ -599,6 +803,29 @@
         assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
     }
 
+    /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+    @Test
+    public void testAppUsageLimitObserver_ConcurrentUsage() throws Exception {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+        startUsage(PKG_SOC1);
+        // Add 10 mins
+        setTime(TIME_10_MIN);
+
+        // Add a different package in the group will first package is still in use
+        startUsage(PKG_GAME1);
+        setTime(TIME_10_MIN * 2);
+        // Stop first package usage
+        stopUsage(PKG_SOC1);
+
+        setTime(TIME_30_MIN);
+        stopUsage(PKG_GAME1);
+
+        assertEquals(TIME_30_MIN, group.getUsageTimeMs());
+        assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+    }
+
     /** Verify that a session will continue if usage starts again within the session threshold */
     @Test
     public void testUsageSessionObserver_ContinueSession() throws Exception {
@@ -737,6 +964,97 @@
         assertFalse(hasAppUsageObserver(UID, OBS_ID1));
     }
 
+    /** Verify app usage limit observer added correctly reports it being a group limit */
+    @Test
+    public void testAppUsageLimitObserver_IsGroupLimit() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+        assertNotNull("Observer wasn't added", group);
+        assertTrue("Observer didn't correctly report being a group limit",
+                group.isGroupLimit());
+    }
+
+    /** Verify app usage limit observer added correctly reports it being not a group limit */
+    @Test
+    public void testAppUsageLimitObserver_IsNotGroupLimit() {
+        addAppUsageLimitObserver(OBS_ID1, new String[]{PKG_PROD}, TIME_30_MIN);
+        AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+        assertNotNull("Observer wasn't added", group);
+        assertFalse("Observer didn't correctly report not being a group limit",
+                group.isGroupLimit());
+    }
+
+    /** Verify app usage limit observer added correctly reports its total usage limit */
+    @Test
+    public void testAppUsageLimitObserver_GetTotalUsageLimit() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+        assertNotNull("Observer wasn't added", group);
+        assertEquals("Observer didn't correctly report total usage limit",
+                TIME_30_MIN, group.getTotaUsageLimit());
+    }
+
+    /** Verify app usage limit observer added correctly reports its total usage limit */
+    @Test
+    public void testAppUsageLimitObserver_GetUsageRemaining() {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        startUsage(PKG_SOC1);
+        setTime(TIME_10_MIN);
+        stopUsage(PKG_SOC1);
+        AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+        assertNotNull("Observer wasn't added", group);
+        assertEquals("Observer didn't correctly report total usage limit",
+                TIME_10_MIN * 2, group.getUsageRemaining());
+    }
+
+    /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+     *  when querying the getAppUsageLimit API.
+     */
+    @Test
+    public void testAppUsageLimitObserver_GetAppUsageLimit() {
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+        UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+        assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+                TIME_10_MIN, group.getTotalUsageLimit());
+    }
+
+    /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+     *  when querying the getAppUsageLimit API.
+     */
+    @Test
+    public void testAppUsageLimitObserver_GetAppUsageLimitUsed() {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+        startUsage(PKG_GAME1);
+        setTime(TIME_10_MIN * 2 + TIME_1_MIN);
+        stopUsage(PKG_GAME1);
+        // PKG_GAME1 is only in GROUP1 but since we're querying for PCK_SOC1 which is
+        // in both groups, GROUP1 should be returned since it has a smaller time remaining
+        UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+        assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+                TIME_1_MIN * 9, group.getUsageRemaining());
+    }
+
+    /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+     *  when querying the getAppUsageLimit API.
+     */
+    @Test
+    public void testAppUsageLimitObserver_GetAppUsageLimitAllUsed() {
+        setTime(0L);
+        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+        startUsage(PKG_SOC1);
+        setTime(TIME_10_MIN);
+        stopUsage(PKG_SOC1);
+        // GROUP_SOC should be returned since it should be completely used up (0ms remaining)
+        UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+        assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+                0L, group.getUsageRemaining());
+    }
+
     private void startUsage(String packageName) {
         mController.noteUsageStart(packageName, USER_ID);
     }
@@ -759,6 +1077,10 @@
                 null, null, USER_ID);
     }
 
+    private void addAppUsageLimitObserver(int observerId, String[] packages, long timeLimit) {
+        mController.addAppUsageLimitObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+    }
+
     /** Is there still an app usage observer by that id */
     private boolean hasAppUsageObserver(int uid, int observerId) {
         return mController.getAppUsageGroup(uid, observerId) != null;
@@ -769,6 +1091,20 @@
         return mController.getSessionUsageGroup(uid, observerId) != null;
     }
 
+    /** Is there still an app usage limit observer by that id */
+    private boolean hasAppUsageLimitObserver(int uid, int observerId) {
+        return mController.getAppUsageLimitGroup(uid, observerId) != null;
+    }
+
+    private AppTimeLimitController.AppUsageLimitGroup getAppUsageLimitObserver(
+            int uid, int observerId) {
+        return mController.getAppUsageLimitGroup(uid, observerId);
+    }
+
+    private UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(String packageName) {
+        return mController.getAppUsageLimit(packageName, UserHandle.of(USER_ID));
+    }
+
     private void setTime(long time) {
         mUptimeMillis = time;
     }
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 2ed11fe..fa472e2 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -18,11 +18,14 @@
 
 import android.annotation.UserIdInt;
 import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -163,6 +166,9 @@
         /** Map of observerId to details of the time limit group */
         SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>();
 
+        /** Map of observerId to details of the app usage limit group */
+        SparseArray<AppUsageLimitGroup> appUsageLimitGroups = new SparseArray<>();
+
         private ObserverAppData(int uid) {
             this.uid = uid;
         }
@@ -177,6 +183,10 @@
             sessionUsageGroups.remove(observerId);
         }
 
+        @GuardedBy("mLock")
+        void removeAppUsageLimitGroup(int observerId) {
+            appUsageLimitGroups.remove(observerId);
+        }
 
         @GuardedBy("mLock")
         void dump(PrintWriter pw) {
@@ -194,6 +204,12 @@
                 sessionUsageGroups.valueAt(i).dump(pw);
                 pw.println();
             }
+            pw.println("    App Usage Limit Groups:");
+            final int nAppUsageLimitGroups = appUsageLimitGroups.size();
+            for (int i = 0; i < nAppUsageLimitGroups; i++) {
+                appUsageLimitGroups.valueAt(i).dump(pw);
+                pw.println();
+            }
         }
     }
 
@@ -493,6 +509,54 @@
         }
     }
 
+    class AppUsageLimitGroup extends UsageGroup {
+        private boolean mGroupLimit;
+
+        public AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId,
+                String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
+            super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+            mGroupLimit = observed.length > 1;
+        }
+
+        @Override
+        @GuardedBy("mLock")
+        public void remove() {
+            super.remove();
+            ObserverAppData observerApp = mObserverAppRef.get();
+            if (observerApp != null) {
+                observerApp.removeAppUsageLimitGroup(mObserverId);
+            }
+        }
+
+        @GuardedBy("mLock")
+        boolean isGroupLimit() {
+            return mGroupLimit;
+        }
+
+        @GuardedBy("mLock")
+        long getTotaUsageLimit() {
+            return mTimeLimitMs;
+        }
+
+        @GuardedBy("mLock")
+        long getUsageRemaining() {
+            // If there is currently an active session, account for its usage
+            if (mActives > 0) {
+                return mTimeLimitMs - mUsageTimeMs - (getUptimeMillis() - mLastKnownUsageTimeMs);
+            } else {
+                return mTimeLimitMs - mUsageTimeMs;
+            }
+        }
+
+        @Override
+        @GuardedBy("mLock")
+        void dump(PrintWriter pw) {
+            super.dump(pw);
+            pw.print(" groupLimit=");
+            pw.print(mGroupLimit);
+        }
+    }
+
 
     private class MyHandler extends Handler {
         static final int MSG_CHECK_TIMEOUT = 1;
@@ -553,6 +617,12 @@
 
     /** Overrideable for testing purposes */
     @VisibleForTesting
+    protected long getAppUsageLimitObserverPerUidLimit() {
+        return MAX_OBSERVER_PER_UID;
+    }
+
+    /** Overrideable for testing purposes */
+    @VisibleForTesting
     protected long getMinTimeLimit() {
         return ONE_MINUTE;
     }
@@ -572,6 +642,61 @@
         }
     }
 
+    @VisibleForTesting
+    AppUsageLimitGroup getAppUsageLimitGroup(int observerAppUid, int observerId) {
+        synchronized (mLock) {
+            return getOrCreateObserverAppDataLocked(observerAppUid).appUsageLimitGroups.get(
+                    observerId);
+        }
+    }
+
+    /**
+     * Returns an object describing the app usage limit for the given package which was set via
+     * {@link #addAppUsageLimitObserver).
+     * If there are multiple limits that apply to the package, the one with the smallest
+     * time remaining will be returned.
+     */
+    public UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(
+            String packageName, UserHandle user) {
+        synchronized (mLock) {
+            final UserData userData = getOrCreateUserDataLocked(user.getIdentifier());
+            if (userData == null) {
+                return null;
+            }
+
+            final ArrayList<UsageGroup> usageGroups = userData.observedMap.get(packageName);
+            if (usageGroups == null || usageGroups.isEmpty()) {
+                return null;
+            }
+
+            final ArraySet<AppUsageLimitGroup> usageLimitGroups = new ArraySet<>();
+            for (int i = 0; i < usageGroups.size(); i++) {
+                if (usageGroups.get(i) instanceof AppUsageLimitGroup) {
+                    final AppUsageLimitGroup group = (AppUsageLimitGroup) usageGroups.get(i);
+                    for (int j = 0; j < group.mObserved.length; j++) {
+                        if (group.mObserved[j].equals(packageName)) {
+                            usageLimitGroups.add(group);
+                            break;
+                        }
+                    }
+                }
+            }
+            if (usageLimitGroups.isEmpty()) {
+                return null;
+            }
+
+            AppUsageLimitGroup smallestGroup = usageLimitGroups.valueAt(0);
+            for (int i = 1; i < usageLimitGroups.size(); i++) {
+                final AppUsageLimitGroup otherGroup = usageLimitGroups.valueAt(i);
+                if (otherGroup.getUsageRemaining() < smallestGroup.getUsageRemaining()) {
+                    smallestGroup = otherGroup;
+                }
+            }
+            return new UsageStatsManagerInternal.AppUsageLimitData(smallestGroup.isGroupLimit(),
+                    smallestGroup.getTotaUsageLimit(), smallestGroup.getUsageRemaining());
+        }
+    }
+
     /** Returns an existing UserData object for the given userId, or creates one */
     @GuardedBy("mLock")
     private UserData getOrCreateUserDataLocked(int userId) {
@@ -726,6 +851,61 @@
     }
 
     /**
+     * Registers an app usage limit observer with the given details.
+     * Existing app usage limit observer with the same observerId will be removed.
+     */
+    public void addAppUsageLimitObserver(int requestingUid, int observerId, String[] observed,
+            long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) {
+        if (timeLimit < getMinTimeLimit()) {
+            throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
+        }
+        synchronized (mLock) {
+            UserData user = getOrCreateUserDataLocked(userId);
+            ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+            AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+            if (group != null) {
+                // Remove previous app usage group associated with observerId
+                group.remove();
+            }
+
+            final int observerIdCount = observerApp.appUsageLimitGroups.size();
+            if (observerIdCount >= getAppUsageLimitObserverPerUidLimit()) {
+                throw new IllegalStateException(
+                        "Too many app usage observers added by uid " + requestingUid);
+            }
+            group = new AppUsageLimitGroup(user, observerApp, observerId, observed, timeLimit,
+                    callbackIntent);
+            observerApp.appUsageLimitGroups.append(observerId, group);
+
+            if (DEBUG) {
+                Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
+            }
+
+            user.addUsageGroup(group);
+            noteActiveLocked(user, group, getUptimeMillis());
+        }
+    }
+
+    /**
+     * Remove a registered observer by observerId and calling uid.
+     *
+     * @param requestingUid The calling uid
+     * @param observerId    The unique observer id for this user
+     * @param userId        The user id of the observer
+     */
+    public void removeAppUsageLimitObserver(int requestingUid, int observerId,
+            @UserIdInt int userId) {
+        synchronized (mLock) {
+            final ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+            final AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+            if (group != null) {
+                // Remove previous app usage group associated with observerId
+                group.remove();
+            }
+        }
+    }
+
+    /**
      * Called when an entity becomes active.
      *
      * @param name      The entity that became active
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 6ad698b..85939d4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -855,6 +855,22 @@
                     == PackageManager.PERMISSION_GRANTED;
         }
 
+        private boolean hasPermissions(String callingPackage, String... permissions) {
+            final int callingUid = Binder.getCallingUid();
+            if (callingUid == Process.SYSTEM_UID) {
+                // Caller is the system, so proceed.
+                return true;
+            }
+
+            boolean hasPermissions = true;
+            final Context context = getContext();
+            for (int i = 0; i < permissions.length; i++) {
+                hasPermissions = hasPermissions && (context.checkCallingPermission(permissions[i])
+                        == PackageManager.PERMISSION_GRANTED);
+            }
+            return hasPermissions;
+        }
+
         private void checkCallerIsSystemOrSameApp(String pkg) {
             if (isCallingUidSystem()) {
                 return;
@@ -1346,6 +1362,51 @@
         }
 
         @Override
+        public void registerAppUsageLimitObserver(int observerId, String[] packages,
+                long timeLimitMs, PendingIntent callbackIntent, String callingPackage) {
+            if (!hasPermissions(callingPackage,
+                    Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+                throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+                        + "OBSERVE_APP_USAGE permissions");
+            }
+
+            if (packages == null || packages.length == 0) {
+                throw new IllegalArgumentException("Must specify at least one package");
+            }
+            if (callbackIntent == null) {
+                throw new NullPointerException("callbackIntent can't be null");
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(callingUid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                UsageStatsService.this.registerAppUsageLimitObserver(callingUid, observerId,
+                        packages, timeLimitMs, callbackIntent, userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) {
+            if (!hasPermissions(callingPackage,
+                    Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+                throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+                        + "OBSERVE_APP_USAGE permissions");
+            }
+
+            final int callingUid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(callingUid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                UsageStatsService.this.unregisterAppUsageLimitObserver(
+                        callingUid, observerId, userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void reportUsageStart(IBinder activity, String token, String callingPackage) {
             reportPastUsageStart(activity, token, 0, callingPackage);
         }
@@ -1447,6 +1508,16 @@
         mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId);
     }
 
+    void registerAppUsageLimitObserver(int callingUid, int observerId, String[] packages,
+            long timeLimitMs, PendingIntent callbackIntent, int userId) {
+        mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages, timeLimitMs,
+                callbackIntent, userId);
+    }
+
+    void unregisterAppUsageLimitObserver(int callingUid, int observerId, int userId) {
+        mAppTimeLimit.removeAppUsageLimitObserver(callingUid, observerId, userId);
+    }
+
     /**
      * This local service implementation is primarily used by ActivityManagerService.
      * ActivityManagerService will call these methods holding the 'am' lock, which means we
@@ -1652,5 +1723,10 @@
         public void reportExemptedSyncStart(String packageName, int userId) {
             mAppStandby.postReportExemptedSyncStart(packageName, userId);
         }
+
+        @Override
+        public AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user) {
+            return mAppTimeLimit.getAppUsageLimit(packageName, user);
+        }
     }
 }
diff --git a/tests/UsageStatsTest/AndroidManifest.xml b/tests/UsageStatsTest/AndroidManifest.xml
index 4b1c1bd..fefd993 100644
--- a/tests/UsageStatsTest/AndroidManifest.xml
+++ b/tests/UsageStatsTest/AndroidManifest.xml
@@ -11,6 +11,7 @@
 
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
     <uses-permission android:name="android.permission.OBSERVE_APP_USAGE" />
+    <uses-permission android:name="android.permission.SUSPEND_APPS" />
 
     <application android:label="Usage Access Test">
         <activity android:name=".UsageStatsActivity"
diff --git a/tests/UsageStatsTest/res/menu/main.xml b/tests/UsageStatsTest/res/menu/main.xml
index 612267c..272e0f4 100644
--- a/tests/UsageStatsTest/res/menu/main.xml
+++ b/tests/UsageStatsTest/res/menu/main.xml
@@ -6,4 +6,6 @@
         android:title="Call isAppInactive()"/>
     <item android:id="@+id/set_app_limit"
         android:title="Set App Limit" />
+    <item android:id="@+id/set_app_usage_limit"
+          android:title="Set App Usage Limit" />
 </menu>
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index 3c628f6..0105893 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -21,6 +21,8 @@
 import android.app.PendingIntent;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
+import android.content.ClipData;
+import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -49,6 +51,8 @@
     private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
     private static final String EXTRA_KEY_TIMEOUT = "com.android.tests.usagestats.extra.TIMEOUT";
     private UsageStatsManager mUsageStatsManager;
+    private ClipboardManager mClipboard;
+    private ClipData mClip;
     private Adapter mAdapter;
     private Comparator<UsageStats> mComparator = new Comparator<UsageStats>() {
         @Override
@@ -61,6 +65,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
+        mClipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
         mAdapter = new Adapter();
         setListAdapter(mAdapter);
         Bundle extras = getIntent().getExtras();
@@ -98,6 +103,8 @@
             case R.id.set_app_limit:
                 callSetAppLimit();
                 return true;
+            case R.id.set_app_usage_limit:
+                callSetAppUsageLimit();
             default:
                 return super.onOptionsItemSelected(item);
         }
@@ -170,6 +177,40 @@
         builder.show();
     }
 
+    private void callSetAppUsageLimit() {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle("Enter package name");
+        final EditText input = new EditText(this);
+        input.setInputType(InputType.TYPE_CLASS_TEXT);
+        input.setHint("com.android.tests.usagestats");
+        builder.setView(input);
+
+        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                final String packageName = input.getText().toString().trim();
+                if (!TextUtils.isEmpty(packageName)) {
+                    String[] packages = packageName.split(",");
+                    Intent intent = new Intent(Intent.ACTION_MAIN);
+                    intent.setClass(UsageStatsActivity.this, UsageStatsActivity.class);
+                    intent.setPackage(getPackageName());
+                    intent.putExtra(EXTRA_KEY_TIMEOUT, true);
+                    mUsageStatsManager.registerAppUsageLimitObserver(1, packages,
+                            60, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this,
+                                    1, intent, 0));
+                }
+            }
+        });
+        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                dialog.cancel();
+            }
+        });
+
+        builder.show();
+    }
+
     private void showInactive(String packageName) {
         final AlertDialog.Builder builder = new AlertDialog.Builder(this);
         builder.setMessage(
@@ -232,6 +273,21 @@
             holder.packageName.setText(mStats.get(position).getPackageName());
             holder.usageTime.setText(DateUtils.formatDuration(
                     mStats.get(position).getTotalTimeInForeground()));
+
+            //copy package name to the clipboard for convenience
+            holder.packageName.setOnLongClickListener(new View.OnLongClickListener() {
+                @Override
+                public boolean onLongClick(View v) {
+                    String text = holder.packageName.getText().toString();
+                    mClip = ClipData.newPlainText("package_name", text);
+                    mClipboard.setPrimaryClip(mClip);
+
+                    Toast.makeText(getApplicationContext(), "package name copied to clipboard",
+                            Toast.LENGTH_SHORT).show();
+                    return true;
+                }
+            });
+
             return convertView;
         }
     }
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 125fe725..273b8fc 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
@@ -34,26 +35,24 @@
 import android.content.res.Resources;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.support.test.runner.AndroidJUnit4;
 import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 
+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.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class NetworkNotificationManagerTest {
@@ -194,4 +193,54 @@
         mManager.clearNotification(id);
         verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
     }
+
+    @Test
+    public void testSameLevelNotifications() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+        mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+    }
+
+    @Test
+    public void testClearNotificationByType() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        // clearNotification(int id, NotificationType notifyType) will check if given type is equal
+        // to previous type or not. If they are equal then clear the notification; if they are not
+        // equal then return.
+
+        mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+        // Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification
+        // should be cleared.
+        mManager.clearNotification(id, LOGGED_IN);
+        verify(mNotificationManager, times(1))
+                .cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any());
+
+        mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(2))
+                .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+        // LOST_INTERNET notification popup after LOGGED_IN notification.
+        mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+
+        // Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification
+        // shouldn't be cleared.
+        mManager.clearNotification(id, LOGGED_IN);
+        // LOST_INTERNET shouldn't be cleared.
+        verify(mNotificationManager, never())
+                .cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any());
+    }
 }
diff --git a/tools/signedconfig/debug_key.pem b/tools/signedconfig/debug_key.pem
index 0af577b..17a1dff 100644
--- a/tools/signedconfig/debug_key.pem
+++ b/tools/signedconfig/debug_key.pem
@@ -1,5 +1,5 @@
 -----BEGIN EC PRIVATE KEY-----
-MHcCAQEEIEfgtO+KPOoqJqTnqkDDKkAcOzyvtovsUO/ShLE6y4XRoAoGCCqGSM49
-AwEHoUQDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60pj1pnU8
-SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MHcCAQEEIFbNNr1/TsFlvnmH1z6e0xyact9t7PDs+VFWc7QFtoRcoAoGCCqGSM49
+AwEHoUQDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1x7A8PV1
+ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
 -----END EC PRIVATE KEY-----
diff --git a/tools/signedconfig/debug_public.pem b/tools/signedconfig/debug_public.pem
index f61f813..d9f0d38 100644
--- a/tools/signedconfig/debug_public.pem
+++ b/tools/signedconfig/debug_public.pem
@@ -1,4 +1,4 @@
 -----BEGIN PUBLIC KEY-----
-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoE
-CGbTEBTKKvdd2hO60pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13Yr
+U1haIhVC5296InRu1x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
 -----END PUBLIC KEY-----
diff --git a/tools/signedconfig/debug_sign.sh b/tools/signedconfig/debug_sign.sh
index 28e5428..3a2814a 100755
--- a/tools/signedconfig/debug_sign.sh
+++ b/tools/signedconfig/debug_sign.sh
@@ -2,5 +2,5 @@
 # Script to sign data with the debug keys. Outputs base64 for embedding into
 # APK metadata.
 
-openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem  $1 | base64 -w 0
+openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem  <(echo -n "$1" | base64 -d) | base64 -w 0
 echo