Merge "Enable checkstyle for all frameworks/base projects."
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index e224fa3..35d3802 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -245,10 +245,11 @@
             state.pauseTiming();
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
-            final RecordingCanvas c = node.start(1200, 200);
+            final RecordingCanvas c = node.startRecording(1200, 200);
             state.resumeTiming();
 
             layout.draw(c);
+            node.endRecording();
         }
     }
 
@@ -261,10 +262,11 @@
             final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
-            final RecordingCanvas c = node.start(1200, 200);
+            final RecordingCanvas c = node.startRecording(1200, 200);
             state.resumeTiming();
 
             layout.draw(c);
+            node.endRecording();
         }
     }
 
@@ -277,10 +279,11 @@
             final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
-            final RecordingCanvas c = node.start(1200, 200);
+            final RecordingCanvas c = node.startRecording(1200, 200);
             state.resumeTiming();
 
             layout.draw(c);
+            node.endRecording();
         }
     }
 
@@ -293,11 +296,12 @@
             final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
-            final RecordingCanvas c = node.start(1200, 200);
+            final RecordingCanvas c = node.startRecording(1200, 200);
             Canvas.freeTextLayoutCaches();
             state.resumeTiming();
 
             layout.draw(c);
+            node.endRecording();
         }
     }
 
@@ -310,11 +314,12 @@
             final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
-            final RecordingCanvas c = node.start(1200, 200);
+            final RecordingCanvas c = node.startRecording(1200, 200);
             Canvas.freeTextLayoutCaches();
             state.resumeTiming();
 
             layout.draw(c);
+            node.endRecording();
         }
     }
 
@@ -328,10 +333,11 @@
                     mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
-            final RecordingCanvas c = node.start(1200, 200);
+            final RecordingCanvas c = node.startRecording(1200, 200);
             state.resumeTiming();
 
             layout.draw(c);
+            node.endRecording();
         }
     }
 
@@ -345,10 +351,11 @@
                     mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
-            final RecordingCanvas c = node.start(1200, 200);
+            final RecordingCanvas c = node.startRecording(1200, 200);
             state.resumeTiming();
 
             layout.draw(c);
+            node.endRecording();
         }
     }
 
@@ -362,11 +369,12 @@
                     mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
-            final RecordingCanvas c = node.start(1200, 200);
+            final RecordingCanvas c = node.startRecording(1200, 200);
             Canvas.freeTextLayoutCaches();
             state.resumeTiming();
 
             layout.draw(c);
+            node.endRecording();
         }
     }
 
@@ -380,11 +388,12 @@
                     mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT);
             final StaticLayout layout =
                     StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
-            final RecordingCanvas c = node.start(1200, 200);
+            final RecordingCanvas c = node.startRecording(1200, 200);
             Canvas.freeTextLayoutCaches();
             state.resumeTiming();
 
             layout.draw(c);
+            node.endRecording();
         }
     }
 
diff --git a/api/current.txt b/api/current.txt
index cc38630..f27e83c 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -42607,6 +42607,7 @@
     field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
     field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
+    field public static final java.lang.String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool";
     field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
     field public static final java.lang.String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool";
     field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
@@ -51428,6 +51429,99 @@
 
 package android.view.textclassifier {
 
+  public final class ConversationActions implements android.os.Parcelable {
+    ctor public ConversationActions(java.util.List<android.view.textclassifier.ConversationActions.ConversationAction>);
+    method public int describeContents();
+    method public java.util.List<android.view.textclassifier.ConversationActions.ConversationAction> getConversationActions();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions> CREATOR;
+    field public static final java.lang.String HINT_FOR_IN_APP = "in_app";
+    field public static final java.lang.String HINT_FOR_NOTIFICATION = "notification";
+    field public static final java.lang.String TYPE_CALL_PHONE = "call_phone";
+    field public static final java.lang.String TYPE_CREATE_REMINDER = "create_reminder";
+    field public static final java.lang.String TYPE_OPEN_URL = "open_url";
+    field public static final java.lang.String TYPE_SEND_EMAIL = "send_email";
+    field public static final java.lang.String TYPE_SEND_SMS = "send_sms";
+    field public static final java.lang.String TYPE_SHARE_LOCATION = "share_location";
+    field public static final java.lang.String TYPE_TEXT_REPLY = "text_reply";
+    field public static final java.lang.String TYPE_TRACK_FLIGHT = "track_flight";
+    field public static final java.lang.String TYPE_VIEW_CALENDAR = "view_calendar";
+    field public static final java.lang.String TYPE_VIEW_MAP = "view_map";
+  }
+
+  public static final class ConversationActions.ConversationAction implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.app.RemoteAction getAction();
+    method public float getConfidenceScore();
+    method public android.os.Bundle getExtras();
+    method public java.lang.CharSequence getTextReply();
+    method public java.lang.String getType();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.ConversationAction> CREATOR;
+  }
+
+  public static final class ConversationActions.ConversationAction.Builder {
+    ctor public ConversationActions.ConversationAction.Builder(java.lang.String);
+    method public android.view.textclassifier.ConversationActions.ConversationAction build();
+    method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setAction(android.app.RemoteAction);
+    method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setConfidenceScore(float);
+    method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setExtras(android.os.Bundle);
+    method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setTextReply(java.lang.CharSequence);
+  }
+
+  public static final class ConversationActions.Message implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.app.Person getAuthor();
+    method public android.os.Bundle getExtras();
+    method public java.lang.CharSequence getText();
+    method public java.time.ZonedDateTime getTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR;
+  }
+
+  public static final class ConversationActions.Message.Builder {
+    ctor public ConversationActions.Message.Builder();
+    method public android.view.textclassifier.ConversationActions.Message build();
+    method public android.view.textclassifier.ConversationActions.Message.Builder setAuthor(android.app.Person);
+    method public android.view.textclassifier.ConversationActions.Message.Builder setComposeTime(java.time.ZonedDateTime);
+    method public android.view.textclassifier.ConversationActions.Message.Builder setExtras(android.os.Bundle);
+    method public android.view.textclassifier.ConversationActions.Message.Builder setText(java.lang.CharSequence);
+  }
+
+  public static final class ConversationActions.Request implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.util.List<android.view.textclassifier.ConversationActions.Message> getConversation();
+    method public java.util.List<java.lang.String> getHints();
+    method public int getMaxSuggestions();
+    method public android.view.textclassifier.ConversationActions.TypeConfig getTypeConfig();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Request> CREATOR;
+  }
+
+  public static final class ConversationActions.Request.Builder {
+    ctor public ConversationActions.Request.Builder(java.util.List<android.view.textclassifier.ConversationActions.Message>);
+    method public android.view.textclassifier.ConversationActions.Request build();
+    method public android.view.textclassifier.ConversationActions.Request.Builder setHints(java.util.List<java.lang.String>);
+    method public android.view.textclassifier.ConversationActions.Request.Builder setMaxSuggestions(int);
+    method public android.view.textclassifier.ConversationActions.Request.Builder setTypeConfig(android.view.textclassifier.ConversationActions.TypeConfig);
+  }
+
+  public static final class ConversationActions.TypeConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.util.Collection<java.lang.String> resolveTypes(java.util.Collection<java.lang.String>);
+    method public boolean shouldIncludeTypesFromTextClassifier();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.TypeConfig> CREATOR;
+  }
+
+  public static final class ConversationActions.TypeConfig.Builder {
+    ctor public ConversationActions.TypeConfig.Builder();
+    method public android.view.textclassifier.ConversationActions.TypeConfig build();
+    method public android.view.textclassifier.ConversationActions.TypeConfig.Builder includeTypesFromTextClassifier(boolean);
+    method public android.view.textclassifier.ConversationActions.TypeConfig.Builder setExcludedTypes(java.util.Collection<java.lang.String>);
+    method public android.view.textclassifier.ConversationActions.TypeConfig.Builder setIncludedTypes(java.util.Collection<java.lang.String>);
+  }
+
   public final class SelectionEvent implements android.os.Parcelable {
     method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int);
     method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification);
@@ -51482,6 +51576,7 @@
     method public float getConfidenceScore(java.lang.String);
     method public java.lang.String getEntity(int);
     method public int getEntityCount();
+    method public android.os.Bundle getExtras();
     method public deprecated android.graphics.drawable.Drawable getIcon();
     method public java.lang.String getId();
     method public deprecated android.content.Intent getIntent();
@@ -51497,6 +51592,7 @@
     method public android.view.textclassifier.TextClassification.Builder addAction(android.app.RemoteAction);
     method public android.view.textclassifier.TextClassification build();
     method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float);
+    method public android.view.textclassifier.TextClassification.Builder setExtras(android.os.Bundle);
     method public deprecated android.view.textclassifier.TextClassification.Builder setIcon(android.graphics.drawable.Drawable);
     method public android.view.textclassifier.TextClassification.Builder setId(java.lang.String);
     method public deprecated android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
@@ -51509,6 +51605,7 @@
     method public int describeContents();
     method public android.os.LocaleList getDefaultLocales();
     method public int getEndIndex();
+    method public android.os.Bundle getExtras();
     method public java.time.ZonedDateTime getReferenceTime();
     method public int getStartIndex();
     method public java.lang.CharSequence getText();
@@ -51520,6 +51617,7 @@
     ctor public TextClassification.Request.Builder(java.lang.CharSequence, int, int);
     method public android.view.textclassifier.TextClassification.Request build();
     method public android.view.textclassifier.TextClassification.Request.Builder setDefaultLocales(android.os.LocaleList);
+    method public android.view.textclassifier.TextClassification.Request.Builder setExtras(android.os.Bundle);
     method public android.view.textclassifier.TextClassification.Request.Builder setReferenceTime(java.time.ZonedDateTime);
   }
 
@@ -51564,6 +51662,7 @@
     method public default int getMaxGenerateLinksTextLength();
     method public default boolean isDestroyed();
     method public default void onSelectionEvent(android.view.textclassifier.SelectionEvent);
+    method public default android.view.textclassifier.ConversationActions suggestConversationActions(android.view.textclassifier.ConversationActions.Request);
     method public default android.view.textclassifier.TextSelection suggestSelection(android.view.textclassifier.TextSelection.Request);
     method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList);
     field public static final java.lang.String HINT_TEXT_IS_EDITABLE = "android.text_is_editable";
diff --git a/api/system-current.txt b/api/system-current.txt
index 0d7cad12..b1cbfa6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -871,9 +871,11 @@
     method public int getAppStandbyBucket(java.lang.String);
     method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets();
     method public void registerAppUsageObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, android.app.PendingIntent);
+    method public void registerUsageSessionObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit, android.app.PendingIntent, android.app.PendingIntent);
     method public void setAppStandbyBucket(java.lang.String, int);
     method public void setAppStandbyBuckets(java.util.Map<java.lang.String, java.lang.Integer>);
     method public void unregisterAppUsageObserver(int);
+    method public void unregisterUsageSessionObserver(int);
     method public void whitelistAppTemporarily(java.lang.String, long, android.os.UserHandle);
     field public static final java.lang.String EXTRA_OBSERVER_ID = "android.app.usage.extra.OBSERVER_ID";
     field public static final java.lang.String EXTRA_TIME_LIMIT = "android.app.usage.extra.TIME_LIMIT";
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 51d05bb..ca049b0 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -2540,10 +2540,17 @@
  * Binder stats will be reset every time the data is pulled. It means it can only be pulled by one
  * config on the device.
  *
- * Next tag: 14
+ * Next tag: 15
  */
 message BinderCalls {
+    // UID of the process responsible for the binder transaction. It will be set if the process
+    // executing the binder transaction attribute the transaction to another uid using
+    // Binder.setThreadWorkSource().
+    //
+    // If not set, the value will be -1.
     optional int32 uid = 1 [(is_uid) = true];
+    // UID of the process executing the binder transaction.
+    optional int32 direct_caller_uid = 14;
     // Fully qualified class name of the API call.
     //
     // This is a system server class name.
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 9713527..4d52263 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -51,4 +51,8 @@
     void registerAppUsageObserver(int observerId, in String[] packages, long timeLimitMs,
             in PendingIntent callback, String callingPackage);
     void unregisterAppUsageObserver(int observerId, String callingPackage);
+    void registerUsageSessionObserver(int sessionObserverId, in String[] observed, long timeLimitMs,
+            long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent,
+            in PendingIntent sessionEndCallbackIntent, String callingPackage);
+    void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
 }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index dbb00eb..6d7400e 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -595,7 +596,7 @@
      *                       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 the OBSERVE_APP_USAGE permission or
+     * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
      *                           is not the profile owner of this user.
      */
     @SystemApi
@@ -616,7 +617,7 @@
      * 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 the OBSERVE_APP_USAGE permission or is
+     * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and is
      *                           not the profile owner of this user.
      */
     @SystemApi
@@ -629,6 +630,81 @@
         }
     }
 
+    /**
+     * Register a usage session observer that receives a callback on the provided {@code
+     * limitReachedCallbackIntent} when the sum of usages of apps in the packages array exceeds
+     * the {@code timeLimit} specified within a usage session. After the {@code timeLimit} has
+     * been reached, the usage session observer will receive a callback on the provided {@code
+     * sessionEndCallbackIntent} when the usage session ends. Registering another session
+     * observer against a {@code sessionObserverId} that has already been registered will
+     * override the previous session observer.
+     *
+     * @param sessionObserverId 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 packages The list of packages to observe for foreground activity time. Cannot be null
+     *                 and must include at least one package.
+     * @param timeLimit The total time the set of apps can be used continuously before the {@code
+     *                  limitReachedCallbackIntent} is delivered. Must be at least one minute.
+     * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+     * @param sessionThresholdTime The time that can take place between usage sessions before the
+     *                             next session is considered a new session. Must be non-negative.
+     * @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
+     *                                   #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
+     *                                 sessionThresholdTime} has passed. The delivered Intent will
+     *                                 also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link
+     *                                 #EXTRA_TIME_USED}. Can be null.
+     * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+     *                           is not the profile owner of this user.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
+    public void registerUsageSessionObserver(int sessionObserverId, @NonNull String[] packages,
+            long timeLimit, @NonNull TimeUnit timeUnit, long sessionThresholdTime,
+            @NonNull TimeUnit sessionThresholdTimeUnit,
+            @NonNull PendingIntent limitReachedCallbackIntent,
+            @Nullable PendingIntent sessionEndCallbackIntent) {
+        try {
+            mService.registerUsageSessionObserver(sessionObserverId, packages,
+                    timeUnit.toMillis(timeLimit),
+                    sessionThresholdTimeUnit.toMillis(sessionThresholdTime),
+                    limitReachedCallbackIntent, sessionEndCallbackIntent,
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister the usage session observer specified by the {@code sessionObserverId}. This will
+     * only apply to any app session observer registered by this application. Unregistering an
+     * observer that was already unregistered or never registered will have no effect.
+     *
+     * @param sessionObserverId The id of the observer that was previously registered.
+     * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+     *                           is not the profile owner of this user.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
+    public void unregisterUsageSessionObserver(int sessionObserverId) {
+        try {
+            mService.unregisterUsageSessionObserver(sessionObserverId, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     public static String reasonToString(int standbyReason) {
         StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
index 7559cd5..b539414 100644
--- a/core/java/android/hardware/location/IContextHubClient.aidl
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -31,8 +31,8 @@
     void close();
 
     // Registers a PendingIntent with the client
-    boolean registerIntent(in PendingIntent intent, long nanoAppId);
+    boolean registerIntent(in PendingIntent pendingIntent, long nanoAppId);
 
     // Unregisters a PendingIntent from the client
-    boolean unregisterIntent(in PendingIntent intent);
+    boolean unregisterIntent(in PendingIntent pendingIntent);
 }
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index e270fc2..5447f59 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -811,14 +811,19 @@
      * packet needs to be subtracted from the root UID on the base interface both for tx
      * and rx traffic (http://b/12249687, http:/b/33681750).
      *
+     * As for eBPF, the per uid stats is collected by different hook, the rx packets on base
+     * interface will not be counted. Thus, the adjustment on root uid is only needed in tx
+     * direction.
+     *
      * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only
      * {@code ConcurrentHashMap}
      * @param baseTraffic Traffic on the base interfaces. Will be mutated.
      * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
      * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+     * @param useBpfStats True if eBPF is in use.
      */
     public static void apply464xlatAdjustments(NetworkStats baseTraffic,
-            NetworkStats stackedTraffic, Map<String, String> stackedIfaces) {
+            NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) {
         // Total 464xlat traffic to subtract from uid 0 on all base interfaces.
         // stackedIfaces may grow afterwards, but NetworkStats will just be resized automatically.
         final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size());
@@ -837,15 +842,20 @@
                 continue;
             }
             // Subtract any 464lat traffic seen for the root UID on the current base interface.
+            // However, for eBPF, the per uid stats is collected by different hook, the rx packets
+            // on base interface will not be counted. Thus, the adjustment on root uid is only
+            // needed in tx direction.
             adjust.iface = baseIface;
-            adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
+            if (!useBpfStats) {
+                adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
+                adjust.rxPackets = -entry.rxPackets;
+            }
             adjust.txBytes = -(entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
-            adjust.rxPackets = -entry.rxPackets;
             adjust.txPackets = -entry.txPackets;
             adjustments.combineValues(adjust);
 
-            // For 464xlat traffic, xt_qtaguid only counts the bytes of the native IPv4 packet sent
-            // on the stacked interface with prefix "v4-" and drops the IPv6 header size after
+            // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet
+            // sent on the stacked interface with prefix "v4-" and drops the IPv6 header size after
             // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes
             // difference for all packets (http://b/12249687, http:/b/33681750).
             entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA;
@@ -864,8 +874,8 @@
      * base and stacked traffic.
      * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
      */
-    public void apply464xlatAdjustments(Map<String, String> stackedIfaces) {
-        apply464xlatAdjustments(this, this, stackedIfaces);
+    public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) {
+        apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats);
     }
 
     /**
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 7af9db8..7f1082d 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -17,7 +17,6 @@
 package android.service.textclassifier;
 
 import android.Manifest;
-import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -98,8 +97,7 @@
             Preconditions.checkNotNull(request);
             Preconditions.checkNotNull(callback);
             TextClassifierService.this.onSuggestSelection(
-                    request.getText(), request.getStartIndex(), request.getEndIndex(),
-                    TextSelection.Options.from(sessionId, request), mCancellationSignal,
+                    sessionId, request, mCancellationSignal,
                     new Callback<TextSelection>() {
                         @Override
                         public void onSuccess(TextSelection result) {
@@ -132,8 +130,7 @@
             Preconditions.checkNotNull(request);
             Preconditions.checkNotNull(callback);
             TextClassifierService.this.onClassifyText(
-                    request.getText(), request.getStartIndex(), request.getEndIndex(),
-                    TextClassification.Options.from(sessionId, request), mCancellationSignal,
+                    sessionId, request, mCancellationSignal,
                     new Callback<TextClassification>() {
                         @Override
                         public void onSuccess(TextClassification result) {
@@ -164,7 +161,7 @@
             Preconditions.checkNotNull(request);
             Preconditions.checkNotNull(callback);
             TextClassifierService.this.onGenerateLinks(
-                    request.getText(), TextLinks.Options.from(sessionId, request),
+                    sessionId, request,
                     mCancellationSignal,
                     new Callback<TextLinks>() {
                         @Override
@@ -238,25 +235,6 @@
             @NonNull CancellationSignal cancellationSignal,
             @NonNull Callback<TextSelection> callback);
 
-    // TODO: Remove once apps can build against the latest sdk.
-    /** @hide */
-    public void onSuggestSelection(
-            @NonNull CharSequence text,
-            @IntRange(from = 0) int selectionStartIndex,
-            @IntRange(from = 0) int selectionEndIndex,
-            @Nullable TextSelection.Options options,
-            @NonNull CancellationSignal cancellationSignal,
-            @NonNull Callback<TextSelection> callback) {
-        final TextClassificationSessionId sessionId = options.getSessionId();
-        final TextSelection.Request request = options.getRequest() != null
-                ? options.getRequest()
-                : new TextSelection.Request.Builder(
-                        text, selectionStartIndex, selectionEndIndex)
-                        .setDefaultLocales(options.getDefaultLocales())
-                        .build();
-        onSuggestSelection(sessionId, request, cancellationSignal, callback);
-    }
-
     /**
      * Classifies the specified text and returns a {@link TextClassification} object that can be
      * used to generate a widget for handling the classified text.
@@ -272,26 +250,6 @@
             @NonNull CancellationSignal cancellationSignal,
             @NonNull Callback<TextClassification> callback);
 
-    // TODO: Remove once apps can build against the latest sdk.
-    /** @hide */
-    public void onClassifyText(
-            @NonNull CharSequence text,
-            @IntRange(from = 0) int startIndex,
-            @IntRange(from = 0) int endIndex,
-            @Nullable TextClassification.Options options,
-            @NonNull CancellationSignal cancellationSignal,
-            @NonNull Callback<TextClassification> callback) {
-        final TextClassificationSessionId sessionId = options.getSessionId();
-        final TextClassification.Request request = options.getRequest() != null
-                ? options.getRequest()
-                : new TextClassification.Request.Builder(
-                        text, startIndex, endIndex)
-                        .setDefaultLocales(options.getDefaultLocales())
-                        .setReferenceTime(options.getReferenceTime())
-                        .build();
-        onClassifyText(sessionId, request, cancellationSignal, callback);
-    }
-
     /**
      * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with
      * links information.
@@ -307,23 +265,6 @@
             @NonNull CancellationSignal cancellationSignal,
             @NonNull Callback<TextLinks> callback);
 
-    // TODO: Remove once apps can build against the latest sdk.
-    /** @hide */
-    public void onGenerateLinks(
-            @NonNull CharSequence text,
-            @Nullable TextLinks.Options options,
-            @NonNull CancellationSignal cancellationSignal,
-            @NonNull Callback<TextLinks> callback) {
-        final TextClassificationSessionId sessionId = options.getSessionId();
-        final TextLinks.Request request = options.getRequest() != null
-                ? options.getRequest()
-                : new TextLinks.Request.Builder(text)
-                        .setDefaultLocales(options.getDefaultLocales())
-                        .setEntityConfig(options.getEntityConfig())
-                        .build();
-        onGenerateLinks(sessionId, request, cancellationSignal, callback);
-    }
-
     /**
      * Writes the selection event.
      * This is called when a selection event occurs. e.g. user changed selection; or smart selection
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
new file mode 100644
index 0000000..5fcf227
--- /dev/null
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -0,0 +1,779 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.textclassifier;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.app.Person;
+import android.app.RemoteAction;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.SpannedString;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Represents a list of actions suggested by a {@link TextClassifier} on a given conversation.
+ *
+ * @see TextClassifier#suggestConversationActions(Request)
+ */
+public final class ConversationActions implements Parcelable {
+
+    public static final Creator<ConversationActions> CREATOR =
+            new Creator<ConversationActions>() {
+                @Override
+                public ConversationActions createFromParcel(Parcel in) {
+                    return new ConversationActions(in);
+                }
+
+                @Override
+                public ConversationActions[] newArray(int size) {
+                    return new ConversationActions[size];
+                }
+            };
+
+    /** @hide */
+    @Retention(SOURCE)
+    @StringDef(
+            value = {
+                    TYPE_VIEW_CALENDAR,
+                    TYPE_VIEW_MAP,
+                    TYPE_TRACK_FLIGHT,
+                    TYPE_OPEN_URL,
+                    TYPE_SEND_SMS,
+                    TYPE_CALL_PHONE,
+                    TYPE_SEND_EMAIL,
+                    TYPE_TEXT_REPLY,
+                    TYPE_CREATE_REMINDER,
+                    TYPE_SHARE_LOCATION
+            },
+            prefix = "TYPE_")
+    public @interface ActionType {}
+
+    /**
+     * Indicates an action to view a calendar at a specified time.
+     */
+    public static final String TYPE_VIEW_CALENDAR = "view_calendar";
+    /**
+     * Indicates an action to view the map at a specified location.
+     */
+    public static final String TYPE_VIEW_MAP = "view_map";
+    /**
+     * Indicates an action to track a flight.
+     */
+    public static final String TYPE_TRACK_FLIGHT = "track_flight";
+    /**
+     * Indicates an action to open an URL.
+     */
+    public static final String TYPE_OPEN_URL = "open_url";
+    /**
+     * Indicates an action to send a SMS.
+     */
+    public static final String TYPE_SEND_SMS = "send_sms";
+    /**
+     * Indicates an action to call a phone number.
+     */
+    public static final String TYPE_CALL_PHONE = "call_phone";
+    /**
+     * Indicates an action to send an email.
+     */
+    public static final String TYPE_SEND_EMAIL = "send_email";
+    /**
+     * Indicates an action to reply with a text message.
+     */
+    public static final String TYPE_TEXT_REPLY = "text_reply";
+    /**
+     * Indicates an action to create a reminder.
+     */
+    public static final String TYPE_CREATE_REMINDER = "create_reminder";
+    /**
+     * Indicates an action to reply with a location.
+     */
+    public static final String TYPE_SHARE_LOCATION = "share_location";
+
+    /** @hide */
+    @Retention(SOURCE)
+    @StringDef(
+            value = {
+                    HINT_FOR_NOTIFICATION,
+                    HINT_FOR_IN_APP,
+            },
+            prefix = "HINT_")
+    public @interface Hint {}
+    /**
+     * To indicate the generated actions will be used within the app.
+     */
+    public static final String HINT_FOR_IN_APP = "in_app";
+    /**
+     * To indicate the generated actions will be used for notification.
+     */
+    public static final String HINT_FOR_NOTIFICATION = "notification";
+
+    private List<ConversationAction> mConversationActions;
+
+    /** Constructs a {@link ConversationActions} object. */
+    public ConversationActions(@NonNull List<ConversationAction> conversationActions) {
+        mConversationActions =
+                Collections.unmodifiableList(Preconditions.checkNotNull(conversationActions));
+    }
+
+    private ConversationActions(Parcel in) {
+        mConversationActions =
+                Collections.unmodifiableList(in.createTypedArrayList(ConversationAction.CREATOR));
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeTypedList(mConversationActions);
+    }
+
+    /** Returns an immutable list of {@link ConversationAction} objects. */
+    @NonNull
+    public List<ConversationAction> getConversationActions() {
+        return mConversationActions;
+    }
+
+    /** Represents the action suggested by a {@link TextClassifier} on a given conversation. */
+    public static final class ConversationAction implements Parcelable {
+
+        public static final Creator<ConversationAction> CREATOR =
+                new Creator<ConversationAction>() {
+                    @Override
+                    public ConversationAction createFromParcel(Parcel in) {
+                        return new ConversationAction(in);
+                    }
+
+                    @Override
+                    public ConversationAction[] newArray(int size) {
+                        return new ConversationAction[size];
+                    }
+                };
+
+        @NonNull
+        @ActionType
+        private final String mType;
+        @NonNull
+        private final CharSequence mTextReply;
+        @Nullable
+        private final RemoteAction mAction;
+
+        @FloatRange(from = 0, to = 1)
+        private final float mScore;
+
+        @NonNull
+        private final Bundle mExtras;
+
+        private ConversationAction(
+                @NonNull String type,
+                @Nullable RemoteAction action,
+                @Nullable CharSequence textReply,
+                float score,
+                @NonNull Bundle extras) {
+            mType = Preconditions.checkNotNull(type);
+            mAction = action;
+            mTextReply = textReply;
+            mScore = score;
+            mExtras = Preconditions.checkNotNull(extras);
+        }
+
+        private ConversationAction(Parcel in) {
+            mType = in.readString();
+            mAction = in.readParcelable(null);
+            mTextReply = in.readCharSequence();
+            mScore = in.readFloat();
+            mExtras = in.readBundle();
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeString(mType);
+            parcel.writeParcelable(mAction, flags);
+            parcel.writeCharSequence(mTextReply);
+            parcel.writeFloat(mScore);
+            parcel.writeBundle(mExtras);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @NonNull
+        @ActionType
+        /** Returns the type of this action, for example, {@link #TYPE_VIEW_CALENDAR}. */
+        public String getType() {
+            return mType;
+        }
+
+        @Nullable
+        /**
+         * Returns a RemoteAction object, which contains the icon, label and a PendingIntent, for
+         * the specified action type.
+         */
+        public RemoteAction getAction() {
+            return mAction;
+        }
+
+        /**
+         * Returns the confidence score for the specified action. The value ranges from 0 (low
+         * confidence) to 1 (high confidence).
+         */
+        @FloatRange(from = 0, to = 1)
+        public float getConfidenceScore() {
+            return mScore;
+        }
+
+        /**
+         * Returns the text reply that could be sent as a reply to the given conversation.
+         * <p>
+         * This is only available when the type of the action is {@link #TYPE_TEXT_REPLY}.
+         */
+        @Nullable
+        public CharSequence getTextReply() {
+            return mTextReply;
+        }
+
+        /**
+         * Returns the extended data related to this conversation action.
+         *
+         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+         * prefer to hold a reference to the returned bundle rather than frequently calling this
+         * method.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras.deepCopy();
+        }
+
+        /** Builder class to construct {@link ConversationAction}. */
+        public static final class Builder {
+            @Nullable
+            @ActionType
+            private String mType;
+            @Nullable
+            private RemoteAction mAction;
+            @Nullable
+            private CharSequence mTextReply;
+            private float mScore;
+            @Nullable
+            private Bundle mExtras;
+
+            public Builder(@NonNull @ActionType String actionType) {
+                mType = Preconditions.checkNotNull(actionType);
+            }
+
+            /**
+             * Sets an action that may be performed on the given conversation.
+             */
+            @NonNull
+            public Builder setAction(@Nullable RemoteAction action) {
+                mAction = action;
+                return this;
+            }
+
+            /**
+             * Sets a text reply that may be performed on the given conversation.
+             */
+            @NonNull
+            public Builder setTextReply(@Nullable CharSequence textReply) {
+                mTextReply = textReply;
+                return this;
+            }
+
+            /** Sets the confident score. */
+            @NonNull
+            public Builder setConfidenceScore(@FloatRange(from = 0, to = 1) float score) {
+                mScore = score;
+                return this;
+            }
+
+            /**
+             * Sets the extended data for the conversation action object.
+             */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /** Builds the {@link ConversationAction} object. */
+            @NonNull
+            public ConversationAction build() {
+                return new ConversationAction(
+                        mType,
+                        mAction,
+                        mTextReply,
+                        mScore,
+                        mExtras == null ? Bundle.EMPTY : mExtras.deepCopy());
+            }
+        }
+    }
+
+    /** Represents a message in the conversation. */
+    public static final class Message implements Parcelable {
+        @Nullable
+        private final Person mAuthor;
+        @Nullable
+        private final ZonedDateTime mComposeTime;
+        @Nullable
+        private final CharSequence mText;
+        @NonNull
+        private final Bundle mExtras;
+
+        private Message(
+                @Nullable Person author,
+                @Nullable ZonedDateTime composeTime,
+                @Nullable CharSequence text,
+                @NonNull Bundle bundle) {
+            mAuthor = author;
+            mComposeTime = composeTime;
+            mText = text;
+            mExtras = Preconditions.checkNotNull(bundle);
+        }
+
+        private Message(Parcel in) {
+            mAuthor = in.readParcelable(null);
+            mComposeTime =
+                    in.readInt() == 0
+                            ? null
+                            : ZonedDateTime.parse(
+                                    in.readString(), DateTimeFormatter.ISO_ZONED_DATE_TIME);
+            mText = in.readCharSequence();
+            mExtras = in.readBundle();
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeParcelable(mAuthor, flags);
+            parcel.writeInt(mComposeTime != null ? 1 : 0);
+            if (mComposeTime != null) {
+                parcel.writeString(mComposeTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+            }
+            parcel.writeCharSequence(mText);
+            parcel.writeBundle(mExtras);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Creator<Message> CREATOR =
+                new Creator<Message>() {
+                    @Override
+                    public Message createFromParcel(Parcel in) {
+                        return new Message(in);
+                    }
+
+                    @Override
+                    public Message[] newArray(int size) {
+                        return new Message[size];
+                    }
+                };
+
+        /** Returns the person that composed the message. */
+        @Nullable
+        public Person getAuthor() {
+            return mAuthor;
+        }
+
+        /** Returns the compose time of the message. */
+        @Nullable
+        public ZonedDateTime getTime() {
+            return mComposeTime;
+        }
+
+        /** Returns the text of the message. */
+        @Nullable
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Returns the extended data related to this conversation action.
+         *
+         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+         * prefer to hold a reference to the returned bundle rather than frequently calling this
+         * method.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras.deepCopy();
+        }
+
+        /** Builder class to construct a {@link Message} */
+        public static final class Builder {
+            @Nullable
+            private Person mAuthor;
+            @Nullable
+            private ZonedDateTime mComposeTime;
+            @Nullable
+            private CharSequence mText;
+            @Nullable
+            private Bundle mExtras;
+
+            /** Sets the person who composed this message. */
+            @NonNull
+            public Builder setAuthor(@Nullable Person author) {
+                mAuthor = author;
+                return this;
+            }
+
+            /** Sets the text of this message */
+            @NonNull
+            public Builder setText(@Nullable CharSequence text) {
+                mText = text;
+                return this;
+            }
+
+            /** Sets the compose time of this message */
+            @NonNull
+            public Builder setComposeTime(@Nullable ZonedDateTime composeTime) {
+                mComposeTime = composeTime;
+                return this;
+            }
+
+            /** Sets a set of extended data to the message. */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle bundle) {
+                this.mExtras = bundle;
+                return this;
+            }
+
+            /** Builds the {@link Message} object. */
+            @NonNull
+            public Message build() {
+                return new Message(
+                        mAuthor,
+                        mComposeTime,
+                        mText == null ? null : new SpannedString(mText),
+                        mExtras == null ? new Bundle() : mExtras.deepCopy());
+            }
+        }
+    }
+
+    /** Configuration object for specifying what action types to identify. */
+    public static final class TypeConfig implements Parcelable {
+        @NonNull
+        @ActionType
+        private final Set<String> mExcludedTypes;
+        @NonNull
+        @ActionType
+        private final Set<String> mIncludedTypes;
+        private final boolean mIncludeTypesFromTextClassifier;
+
+        private TypeConfig(
+                @NonNull Set<String> includedTypes,
+                @NonNull Set<String> excludedTypes,
+                boolean includeTypesFromTextClassifier) {
+            mIncludedTypes = Preconditions.checkNotNull(includedTypes);
+            mExcludedTypes = Preconditions.checkNotNull(excludedTypes);
+            mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
+        }
+
+        private TypeConfig(Parcel in) {
+            mIncludedTypes = new ArraySet<>(in.createStringArrayList());
+            mExcludedTypes = new ArraySet<>(in.createStringArrayList());
+            mIncludeTypesFromTextClassifier = in.readByte() != 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeStringList(new ArrayList<>(mIncludedTypes));
+            parcel.writeStringList(new ArrayList<>(mExcludedTypes));
+            parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0));
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Creator<TypeConfig> CREATOR =
+                new Creator<TypeConfig>() {
+                    @Override
+                    public TypeConfig createFromParcel(Parcel in) {
+                        return new TypeConfig(in);
+                    }
+
+                    @Override
+                    public TypeConfig[] newArray(int size) {
+                        return new TypeConfig[size];
+                    }
+                };
+
+        /**
+         * Returns a final list of types that the text classifier should look for.
+         *
+         * <p>NOTE: This method is intended for use by a text classifier.
+         *
+         * @param defaultTypes types the text classifier thinks should be included before factoring
+         *    in the included/excluded types given by the client.
+         */
+        @NonNull
+        public Collection<String> resolveTypes(@Nullable Collection<String> defaultTypes) {
+            Set<String> types = new ArraySet<>();
+            if (mIncludeTypesFromTextClassifier && defaultTypes != null) {
+                types.addAll(defaultTypes);
+            }
+            types.addAll(mIncludedTypes);
+            types.removeAll(mExcludedTypes);
+            return Collections.unmodifiableCollection(types);
+        }
+
+        /**
+         * Return whether the client allows the text classifier to include its own list of default
+         * types. If this function returns {@code true}, the text classifier can consider specifying
+         * a default list of entity types in {@link #resolveTypes(Collection)}.
+         *
+         * <p>NOTE: This method is intended for use by a text classifier.
+         *
+         * @see #resolveTypes(Collection)
+         */
+        public boolean shouldIncludeTypesFromTextClassifier() {
+            return mIncludeTypesFromTextClassifier;
+        }
+
+        /** Builder class to construct the {@link TypeConfig} object. */
+        public static final class Builder {
+            @Nullable
+            private Collection<String> mExcludedTypes;
+            @Nullable
+            private Collection<String> mIncludedTypes;
+            private boolean mIncludeTypesFromTextClassifier = true;
+
+            /**
+             * Sets a collection of types that are explicitly included, for example, {@link
+             * #TYPE_VIEW_CALENDAR}.
+             */
+            @NonNull
+            public Builder setIncludedTypes(
+                    @Nullable @ActionType Collection<String> includedTypes) {
+                mIncludedTypes = includedTypes;
+                return this;
+            }
+
+            /**
+             * Sets a collection of types that are explicitly excluded, for example, {@link
+             * #TYPE_VIEW_CALENDAR}.
+             */
+            @NonNull
+            public Builder setExcludedTypes(
+                    @Nullable @ActionType Collection<String> excludedTypes) {
+                mExcludedTypes = excludedTypes;
+                return this;
+            }
+
+            /**
+             * Specifies whether or not to include the types suggested by the text classifier. By
+             * default, it is included.
+             */
+            @NonNull
+            public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) {
+                mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
+                return this;
+            }
+
+            /**
+             * Combines all of the options that have been set and returns a new {@link TypeConfig}
+             * object.
+             */
+            @NonNull
+            public TypeConfig build() {
+                return new TypeConfig(
+                        mIncludedTypes == null
+                                ? Collections.emptySet()
+                                : new ArraySet<>(mIncludedTypes),
+                        mExcludedTypes == null
+                                ? Collections.emptySet()
+                                : new ArraySet<>(mExcludedTypes),
+                        mIncludeTypesFromTextClassifier);
+            }
+        }
+    }
+
+    /**
+     * A request object for generating conversation action suggestions.
+     *
+     * @see TextClassifier#suggestConversationActions(Request)
+     */
+    public static final class Request implements Parcelable {
+        @NonNull
+        private final List<Message> mConversation;
+        @NonNull
+        private final TypeConfig mTypeConfig;
+        private final int mMaxSuggestions;
+        @NonNull
+        @Hint
+        private final List<String> mHints;
+
+        private Request(
+                @NonNull List<Message> conversation,
+                @NonNull TypeConfig typeConfig,
+                int maxSuggestions,
+                @Nullable @Hint List<String> hints) {
+            mConversation = Preconditions.checkNotNull(conversation);
+            mTypeConfig = Preconditions.checkNotNull(typeConfig);
+            mMaxSuggestions = maxSuggestions;
+            mHints = hints;
+        }
+
+        private Request(Parcel in) {
+            List<Message> conversation = new ArrayList<>();
+            in.readParcelableList(conversation, null);
+            mConversation = Collections.unmodifiableList(conversation);
+            mTypeConfig = in.readParcelable(null);
+            mMaxSuggestions = in.readInt();
+            List<String> hints = new ArrayList<>();
+            in.readStringList(hints);
+            mHints = Collections.unmodifiableList(hints);
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeParcelableList(mConversation, flags);
+            parcel.writeParcelable(mTypeConfig, flags);
+            parcel.writeInt(mMaxSuggestions);
+            parcel.writeStringList(mHints);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Creator<Request> CREATOR =
+                new Creator<Request>() {
+                    @Override
+                    public Request createFromParcel(Parcel in) {
+                        return new Request(in);
+                    }
+
+                    @Override
+                    public Request[] newArray(int size) {
+                        return new Request[size];
+                    }
+                };
+
+        /** Returns the type config. */
+        @NonNull
+        public TypeConfig getTypeConfig() {
+            return mTypeConfig;
+        }
+
+        /** Returns an immutable list of messages that make up the conversation. */
+        @NonNull
+        public List<Message> getConversation() {
+            return mConversation;
+        }
+
+        /**
+         * Return the maximal number of suggestions the caller wants, value 0 means no restriction.
+         */
+        @IntRange(from = 0)
+        public int getMaxSuggestions() {
+            return mMaxSuggestions;
+        }
+
+        /** Returns an immutable list of hints */
+        @Nullable
+        @Hint
+        public List<String> getHints() {
+            return mHints;
+        }
+
+        /** Builder object to construct the {@link Request} object. */
+        public static final class Builder {
+            @NonNull
+            private List<Message> mConversation;
+            @Nullable
+            private TypeConfig mTypeConfig;
+            private int mMaxSuggestions;
+            @Nullable
+            @Hint
+            private List<String> mHints;
+
+            /**
+             * Constructs a builder.
+             *
+             * @param conversation the conversation that the text classifier is going to generate
+             *     actions for.
+             */
+            public Builder(@NonNull List<Message> conversation) {
+                mConversation = Preconditions.checkNotNull(conversation);
+            }
+
+            /**
+             * Sets the hints to help text classifier to generate actions. It could be used to help
+             * text classifier to infer what types of actions the caller may be interested in.
+             */
+            public Builder setHints(@Nullable @Hint List<String> hints) {
+                mHints = hints;
+                return this;
+            }
+
+            /** Sets the type config. */
+            @NonNull
+            public Builder setTypeConfig(@Nullable TypeConfig typeConfig) {
+                mTypeConfig = typeConfig;
+                return this;
+            }
+
+            /** Sets the maximum number of suggestions you want.
+             * <p>
+             * Value 0 means no restriction.
+             */
+            @NonNull
+            public Builder setMaxSuggestions(@IntRange(from = 0) int maxSuggestions) {
+                mMaxSuggestions = Preconditions.checkArgumentNonnegative(maxSuggestions);
+                return this;
+            }
+
+            /** Builds the {@link Request} object. */
+            @NonNull
+            public Request build() {
+                return new Request(
+                        Collections.unmodifiableList(mConversation),
+                        mTypeConfig == null ? new TypeConfig.Builder().build() : mTypeConfig,
+                        mMaxSuggestions,
+                        mHints == null
+                                ? Collections.emptyList()
+                                : Collections.unmodifiableList(mHints));
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 9511a9e..f6c3d77 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -33,6 +33,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.os.Bundle;
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -126,6 +127,7 @@
     @NonNull private final List<RemoteAction> mActions;
     @NonNull private final EntityConfidence mEntityConfidence;
     @Nullable private final String mId;
+    @NonNull private final Bundle mExtras;
 
     private TextClassification(
             @Nullable String text,
@@ -135,7 +137,8 @@
             @Nullable OnClickListener legacyOnClickListener,
             @NonNull List<RemoteAction> actions,
             @NonNull Map<String, Float> entityConfidence,
-            @Nullable String id) {
+            @Nullable String id,
+            @NonNull Bundle extras) {
         mText = text;
         mLegacyIcon = legacyIcon;
         mLegacyLabel = legacyLabel;
@@ -144,6 +147,7 @@
         mActions = Collections.unmodifiableList(actions);
         mEntityConfidence = new EntityConfidence(entityConfidence);
         mId = id;
+        mExtras = extras;
     }
 
     /**
@@ -255,6 +259,18 @@
         return mId;
     }
 
+    /**
+     * Returns the extended data.
+     *
+     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+     * prefer to hold a reference to the returned bundle rather than frequently calling this
+     * method.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras.deepCopy();
+    }
+
     @Override
     public String toString() {
         return String.format(Locale.US,
@@ -359,6 +375,7 @@
         @Nullable private Intent mLegacyIntent;
         @Nullable private OnClickListener mLegacyOnClickListener;
         @Nullable private String mId;
+        @Nullable private Bundle mExtras;
 
         /**
          * Sets the classified text.
@@ -471,12 +488,22 @@
         }
 
         /**
+         * Sets the extended data.
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
          * Builds and returns a {@link TextClassification} object.
          */
         @NonNull
         public TextClassification build() {
             return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent,
-                    mLegacyOnClickListener, mActions, mEntityConfidence, mId);
+                    mLegacyOnClickListener, mActions, mEntityConfidence, mId,
+                    mExtras == null ? Bundle.EMPTY : mExtras.deepCopy());
         }
     }
 
@@ -490,18 +517,21 @@
         private final int mEndIndex;
         @Nullable private final LocaleList mDefaultLocales;
         @Nullable private final ZonedDateTime mReferenceTime;
+        @NonNull private final Bundle mExtras;
 
         private Request(
                 CharSequence text,
                 int startIndex,
                 int endIndex,
                 LocaleList defaultLocales,
-                ZonedDateTime referenceTime) {
+                ZonedDateTime referenceTime,
+                Bundle extras) {
             mText = text;
             mStartIndex = startIndex;
             mEndIndex = endIndex;
             mDefaultLocales = defaultLocales;
             mReferenceTime = referenceTime;
+            mExtras = extras;
         }
 
         /**
@@ -548,6 +578,18 @@
         }
 
         /**
+         * Returns the extended data.
+         *
+         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
+         * prefer to hold a reference to the returned bundle rather than frequently calling this
+         * method.
+         */
+        @NonNull
+        public Bundle getExtras() {
+            return mExtras.deepCopy();
+        }
+
+        /**
          * A builder for building TextClassification requests.
          */
         public static final class Builder {
@@ -555,6 +597,7 @@
             private final CharSequence mText;
             private final int mStartIndex;
             private final int mEndIndex;
+            private Bundle mExtras;
 
             @Nullable private LocaleList mDefaultLocales;
             @Nullable private ZonedDateTime mReferenceTime;
@@ -602,11 +645,23 @@
             }
 
             /**
+             * Sets the extended data.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setExtras(@Nullable Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
              * Builds and returns the request object.
              */
             @NonNull
             public Request build() {
-                return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime);
+                return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime,
+                        mExtras == null ? Bundle.EMPTY : mExtras.deepCopy());
             }
         }
 
@@ -628,6 +683,7 @@
             if (mReferenceTime != null) {
                 dest.writeString(mReferenceTime.toString());
             }
+            dest.writeBundle(mExtras);
         }
 
         public static final Parcelable.Creator<Request> CREATOR =
@@ -649,6 +705,7 @@
             mEndIndex = in.readInt();
             mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in);
             mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse(in.readString());
+            mExtras = in.readBundle();
         }
     }
 
@@ -664,6 +721,7 @@
         dest.writeTypedList(mActions);
         mEntityConfidence.writeToParcel(dest, flags);
         dest.writeString(mId);
+        dest.writeBundle(mExtras);
     }
 
     public static final Parcelable.Creator<TextClassification> CREATOR =
@@ -695,6 +753,7 @@
         mLegacyIntent = null; // mLegacyIntent is not parcelled.
         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
         mId = in.readString();
+        mExtras = in.readBundle();
     }
 
     // Best effort attempt to try to load a drawable from the provided icon.
@@ -717,67 +776,4 @@
         }
         return null;
     }
-
-    // TODO: Remove once apps can build against the latest sdk.
-    /**
-     * Optional input parameters for generating TextClassification.
-     * @hide
-     */
-    public static final class Options {
-
-        @Nullable private final TextClassificationSessionId mSessionId;
-        @Nullable private final Request mRequest;
-        @Nullable private LocaleList mDefaultLocales;
-        @Nullable private ZonedDateTime mReferenceTime;
-
-        public Options() {
-            this(null, null);
-        }
-
-        private Options(
-                @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
-            mSessionId = sessionId;
-            mRequest = request;
-        }
-
-        /** Helper to create Options from a Request. */
-        public static Options from(TextClassificationSessionId sessionId, Request request) {
-            final Options options = new Options(sessionId, request);
-            options.setDefaultLocales(request.getDefaultLocales());
-            options.setReferenceTime(request.getReferenceTime());
-            return options;
-        }
-
-        /** @param defaultLocales ordered list of locale preferences. */
-        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
-            mDefaultLocales = defaultLocales;
-            return this;
-        }
-
-        /** @param referenceTime refrence time used for interpreting relatives dates */
-        public Options setReferenceTime(@Nullable ZonedDateTime referenceTime) {
-            mReferenceTime = referenceTime;
-            return this;
-        }
-
-        @Nullable
-        public LocaleList getDefaultLocales() {
-            return mDefaultLocales;
-        }
-
-        @Nullable
-        public ZonedDateTime getReferenceTime() {
-            return mReferenceTime;
-        }
-
-        @Nullable
-        public Request getRequest() {
-            return mRequest;
-        }
-
-        @Nullable
-        public TextClassificationSessionId getSessionId() {
-            return mSessionId;
-        }
-    }
 }
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 2e92f14..e675744 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -325,6 +325,17 @@
     }
 
     /**
+     * Suggests and returns a list of actions according to the given conversation.
+     */
+    @WorkerThread
+    default ConversationActions suggestConversationActions(
+            @NonNull ConversationActions.Request request) {
+        Preconditions.checkNotNull(request);
+        Utils.checkMainThread();
+        return new ConversationActions(Collections.emptyList());
+    }
+
+    /**
      * Reports a selection event.
      *
      * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 1cac3ed..b31438f 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -20,7 +20,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.LocaleList;
 import android.os.Parcel;
@@ -29,8 +28,6 @@
 import android.text.method.MovementMethod;
 import android.text.style.ClickableSpan;
 import android.text.style.URLSpan;
-import android.text.util.Linkify;
-import android.text.util.Linkify.LinkifyMask;
 import android.view.View;
 import android.view.textclassifier.TextClassifier.EntityType;
 import android.widget.TextView;
@@ -634,125 +631,4 @@
             return new TextLinks(mFullText, mLinks);
         }
     }
-
-    // TODO: Remove once apps can build against the latest sdk.
-    /**
-     * Optional input parameters for generating TextLinks.
-     * @hide
-     */
-    public static final class Options {
-
-        @Nullable private final TextClassificationSessionId mSessionId;
-        @Nullable private final Request mRequest;
-        @Nullable private LocaleList mDefaultLocales;
-        @Nullable private TextClassifier.EntityConfig mEntityConfig;
-        private boolean mLegacyFallback;
-
-        private @ApplyStrategy int mApplyStrategy;
-        private Function<TextLink, TextLinkSpan> mSpanFactory;
-
-        private String mCallingPackageName;
-
-        @UnsupportedAppUsage
-        public Options() {
-            this(null, null);
-        }
-
-        private Options(
-                @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
-            mSessionId = sessionId;
-            mRequest = request;
-        }
-
-        /** Helper to create Options from a Request. */
-        public static Options from(TextClassificationSessionId sessionId, Request request) {
-            final Options options = new Options(sessionId, request);
-            options.setDefaultLocales(request.getDefaultLocales());
-            options.setEntityConfig(request.getEntityConfig());
-            return options;
-        }
-
-        /** Returns a new options object based on the specified link mask. */
-        public static Options fromLinkMask(@LinkifyMask int mask) {
-            final List<String> entitiesToFind = new ArrayList<>();
-
-            if ((mask & Linkify.WEB_URLS) != 0) {
-                entitiesToFind.add(TextClassifier.TYPE_URL);
-            }
-            if ((mask & Linkify.EMAIL_ADDRESSES) != 0) {
-                entitiesToFind.add(TextClassifier.TYPE_EMAIL);
-            }
-            if ((mask & Linkify.PHONE_NUMBERS) != 0) {
-                entitiesToFind.add(TextClassifier.TYPE_PHONE);
-            }
-            if ((mask & Linkify.MAP_ADDRESSES) != 0) {
-                entitiesToFind.add(TextClassifier.TYPE_ADDRESS);
-            }
-
-            return new Options().setEntityConfig(
-                    TextClassifier.EntityConfig.createWithEntityList(entitiesToFind));
-        }
-
-        /** @param defaultLocales ordered list of locale preferences. */
-        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
-            mDefaultLocales = defaultLocales;
-            return this;
-        }
-
-        /** @param entityConfig definition of which entity types to look for. */
-        public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) {
-            mEntityConfig = entityConfig;
-            return this;
-        }
-
-        /** @param applyStrategy strategy to use when resolving conflicts. */
-        public Options setApplyStrategy(@ApplyStrategy int applyStrategy) {
-            checkValidApplyStrategy(applyStrategy);
-            mApplyStrategy = applyStrategy;
-            return this;
-        }
-
-        /** @param spanFactory factory for converting TextLink to TextLinkSpan. */
-        public Options setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
-            mSpanFactory = spanFactory;
-            return this;
-        }
-
-        @Nullable
-        public LocaleList getDefaultLocales() {
-            return mDefaultLocales;
-        }
-
-        @Nullable
-        public TextClassifier.EntityConfig getEntityConfig() {
-            return mEntityConfig;
-        }
-
-        @ApplyStrategy
-        public int getApplyStrategy() {
-            return mApplyStrategy;
-        }
-
-        @Nullable
-        public Function<TextLink, TextLinkSpan> getSpanFactory() {
-            return mSpanFactory;
-        }
-
-        @Nullable
-        public Request getRequest() {
-            return mRequest;
-        }
-
-        @Nullable
-        public TextClassificationSessionId getSessionId() {
-            return mSessionId;
-        }
-
-        private static void checkValidApplyStrategy(int applyStrategy) {
-            if (applyStrategy != APPLY_STRATEGY_IGNORE && applyStrategy != APPLY_STRATEGY_REPLACE) {
-                throw new IllegalArgumentException(
-                        "Invalid apply strategy. See TextLinks.ApplyStrategy for options.");
-            }
-        }
-    }
 }
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index a4550af..52d01ea 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -375,56 +375,4 @@
         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
         mId = in.readString();
     }
-
-
-    // TODO: Remove once apps can build against the latest sdk.
-    /**
-     * Optional input parameters for generating TextSelection.
-     * @hide
-     */
-    public static final class Options {
-
-        @Nullable private final TextClassificationSessionId mSessionId;
-        @Nullable private final Request mRequest;
-        @Nullable private LocaleList mDefaultLocales;
-        private boolean mDarkLaunchAllowed;
-
-        public Options() {
-            this(null, null);
-        }
-
-        private Options(
-                @Nullable TextClassificationSessionId sessionId, @Nullable Request request) {
-            mSessionId = sessionId;
-            mRequest = request;
-        }
-
-        /** Helper to create Options from a Request. */
-        public static Options from(TextClassificationSessionId sessionId, Request request) {
-            final Options options = new Options(sessionId, request);
-            options.setDefaultLocales(request.getDefaultLocales());
-            return options;
-        }
-
-        /** @param defaultLocales ordered list of locale preferences. */
-        public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
-            mDefaultLocales = defaultLocales;
-            return this;
-        }
-
-        @Nullable
-        public LocaleList getDefaultLocales() {
-            return mDefaultLocales;
-        }
-
-        @Nullable
-        public Request getRequest() {
-            return mRequest;
-        }
-
-        @Nullable
-        public TextClassificationSessionId getSessionId() {
-            return mSessionId;
-        }
-    }
 }
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index d1c2799..0a7cff6 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -113,11 +113,12 @@
 
     /**
      * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
-     * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
+     * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map, boolean)
      */
     public static void apply464xlatAdjustments(NetworkStats baseTraffic,
-            NetworkStats stackedTraffic) {
-        NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces);
+            NetworkStats stackedTraffic, boolean useBpfStats) {
+        NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces,
+                useBpfStats);
     }
 
     @VisibleForTesting
@@ -263,7 +264,7 @@
 
         // No locking here: apply464xlatAdjustments behaves fine with an add-only ConcurrentHashMap.
         // TODO: remove this and only apply adjustments in NetworkStatsService.
-        stats.apply464xlatAdjustments(sStackedIfaces);
+        stats.apply464xlatAdjustments(sStackedIfaces, mUseBpfStats);
 
         return stats;
     }
diff --git a/core/java/com/android/internal/os/ProcStatsUtil.java b/core/java/com/android/internal/os/ProcStatsUtil.java
index 0651975..3d4df89 100644
--- a/core/java/com/android/internal/os/ProcStatsUtil.java
+++ b/core/java/com/android/internal/os/ProcStatsUtil.java
@@ -20,6 +20,8 @@
 import android.os.StrictMode;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.ByteArrayOutputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -27,7 +29,8 @@
 /**
  * Utility functions for reading {@code proc} files
  */
-final class ProcStatsUtil {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+public final class ProcStatsUtil {
 
     private static final String TAG = "ProcStatsUtil";
 
@@ -48,8 +51,9 @@
      *
      * @param path path of the file to read
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
     @Nullable
-    static String readNullSeparatedFile(String path) {
+    public static String readNullSeparatedFile(String path) {
         String contents = readSingleLineProcFile(path);
         if (contents == null) {
             return null;
@@ -72,8 +76,9 @@
      *
      * @param path path of the file to read
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
     @Nullable
-    static String readSingleLineProcFile(String path) {
+    public static String readSingleLineProcFile(String path) {
         return readTerminatedProcFile(path, (byte) '\n');
     }
 
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 98b7b5d..65213c0 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -216,6 +216,11 @@
      */
     native protected static void nativeUnmountStorageOnInit();
 
+    private static void callPostForkSystemServerHooks() {
+        // SystemServer specific post fork hooks run before child post fork hooks.
+        VM_HOOKS.postForkSystemServer();
+    }
+
     private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer,
             boolean isZygote, String instructionSet) {
         VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet);
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3e04bb3..4e20e29 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -84,6 +84,7 @@
 static const char kIsolatedStorage[] = "persist.sys.isolated_storage";
 static const char kZygoteClassName[] = "com/android/internal/os/Zygote";
 static jclass gZygoteClass;
+static jmethodID gCallPostForkSystemServerHooks;
 static jmethodID gCallPostForkChildHooks;
 
 static bool g_is_security_enforced = true;
@@ -886,6 +887,18 @@
   // Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers).
   UnsetChldSignalHandler();
 
+  if (is_system_server) {
+    env->CallStaticVoidMethod(gZygoteClass, gCallPostForkSystemServerHooks);
+    if (env->ExceptionCheck()) {
+      fail_fn("Error calling post fork system server hooks.");
+    }
+    // TODO(oth): Remove hardcoded label here (b/117874058).
+    static const char* kSystemServerLabel = "u:r:system_server:s0";
+    if (selinux_android_setcon(kSystemServerLabel) != 0) {
+      fail_fn(CREATE_ERROR("selinux_android_setcon(%s)", kSystemServerLabel));
+    }
+  }
+
   env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags,
                             is_system_server, is_child_zygote, instructionSet);
   if (env->ExceptionCheck()) {
@@ -1181,6 +1194,9 @@
 
 int register_com_android_internal_os_Zygote(JNIEnv* env) {
   gZygoteClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, kZygoteClassName));
+  gCallPostForkSystemServerHooks = GetStaticMethodIDOrDie(env, gZygoteClass,
+                                                          "callPostForkSystemServerHooks",
+                                                          "()V");
   gCallPostForkChildHooks = GetStaticMethodIDOrDie(env, gZygoteClass, "callPostForkChildHooks",
                                                    "(IZZLjava/lang/String;)V");
 
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
index de863d7..91a5440 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -29,6 +29,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.os.Bundle;
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
@@ -48,6 +49,13 @@
 @RunWith(AndroidJUnit4.class)
 public class TextClassificationTest {
 
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+
     public Icon generateTestIcon(int width, int height, int colorValue) {
         final int numPixels = width * height;
         final int[] colors = new int[numPixels];
@@ -89,6 +97,7 @@
                 .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
                 .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
                 .setId(id)
+                .setExtras(BUNDLE)
                 .build();
 
         // Parcel and unparcel
@@ -119,6 +128,9 @@
         assertEquals(TextClassifier.TYPE_ADDRESS, result.getEntity(1));
         assertEquals(0.7f, result.getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f);
         assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f);
+
+        // Extras
+        assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
     }
 
     @Test
@@ -182,6 +194,7 @@
                 new TextClassification.Request.Builder(text, 0, text.length())
                         .setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY))
                         .setReferenceTime(referenceTime)
+                        .setExtras(BUNDLE)
                         .build();
 
         // Parcel and unparcel.
@@ -197,5 +210,6 @@
         assertEquals(referenceTime, result.getReferenceTime());
         assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
         assertEquals(referenceTime, result.getReferenceTime());
+        assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
new file mode 100644
index 0000000..489e164
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProcStatsUtilTest {
+
+    private File mProcDirectory;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getContext();
+        mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtils.deleteContents(mProcDirectory);
+    }
+
+    @Test
+    public void testReadNullSeparatedFile_empty() throws IOException {
+        assertEquals(
+                "",
+                runReadNullSeparatedFile(""));
+    }
+
+    @Test
+    public void testReadNullSeparatedFile_simple() throws IOException {
+        assertEquals(
+                "abc def ghi",
+                runReadNullSeparatedFile("abc\0def\0ghi"));
+    }
+
+    @Test
+    public void testReadNullSeparatedFile_trailingNulls() throws IOException {
+        assertEquals(
+                "abc",
+                runReadNullSeparatedFile("abc\0\0\0\0"));
+    }
+
+    @Test
+    public void testReadNullSeparatedFile_doubleNullEnds() throws IOException {
+        assertEquals(
+                "abc",
+                runReadNullSeparatedFile("abc\0\0def"));
+    }
+
+    @Test
+    public void testReadSingleLineProcFile_simple() throws IOException {
+        assertEquals(
+                "abc",
+                runReadSingleLineProcFile("abc"));
+    }
+
+    @Test
+    public void testReadSingleLineProcFile_empty() throws IOException {
+        assertEquals(
+                "",
+                runReadSingleLineProcFile(""));
+    }
+
+    @Test
+    public void testReadSingleLineProcFile_newLine() throws IOException {
+        assertEquals(
+                "abc",
+                runReadSingleLineProcFile("abc\ndef"));
+    }
+
+    @Test
+    public void testReadSingleLineProcFile_doubleNewLine() throws IOException {
+        assertEquals(
+                "abc",
+                runReadSingleLineProcFile("abc\n\ndef"));
+    }
+
+    @Test
+    public void testReadTerminatedProcFile_simple() throws IOException {
+        assertEquals(
+                "abc",
+                runReadTerminatedProcFile("abc\0", (byte) '\0'));
+    }
+
+    @Test
+    public void testReadTerminatedProcFile_withExtra() throws IOException {
+        assertEquals(
+                "123",
+                runReadTerminatedProcFile("123\0extra", (byte) '\0'));
+    }
+
+    @Test
+    public void testReadTerminatedProcFile_noTerminator() throws IOException {
+        assertEquals(
+                "noterm",
+                runReadTerminatedProcFile("noterm", (byte) '\0'));
+    }
+
+    @Test
+    public void testReadTerminatedProcFile_newLineTerm() throws IOException {
+        assertEquals(
+                "123",
+                runReadTerminatedProcFile("123\n456", (byte) '\n'));
+    }
+
+    @Test
+    public void testReadTerminatedProcFile_normalCharTerm() throws IOException {
+        assertEquals(
+                "abc",
+                runReadTerminatedProcFile("abcdef", (byte) 'd'));
+    }
+
+    @Test
+    public void testReadTerminatedProcFile_largeUnterminated() throws IOException {
+        String longString = new String(new char[10000]).replace('\0', 'a');
+        assertEquals(
+                longString,
+                runReadTerminatedProcFile(longString, (byte) '\0'));
+    }
+
+    @Test
+    public void testReadTerminatedProcFile_largeTerminated() throws IOException {
+        String longString = new String(new char[10000]).replace('\0', 'a');
+        assertEquals(
+                longString,
+                runReadTerminatedProcFile(longString + "\0", (byte) '\0'));
+    }
+
+    @Test
+    public void testReadTerminatedProcFile_largeExtra() throws IOException {
+        String longString = new String(new char[10000]).replace('\0', 'a');
+        assertEquals(
+                longString,
+                runReadTerminatedProcFile(longString + "\0abc", (byte) '\0'));
+    }
+
+    private String runReadNullSeparatedFile(String fileContents) throws IOException {
+        File tempFile = File.createTempFile("null-separated-file", null, mProcDirectory);
+        Files.write(tempFile.toPath(), fileContents.getBytes());
+        String result = ProcStatsUtil.readNullSeparatedFile(tempFile.toString());
+        Files.delete(tempFile.toPath());
+        return result;
+    }
+
+    private String runReadSingleLineProcFile(String fileContents) throws IOException {
+        File tempFile = File.createTempFile("single-line-proc-file", null, mProcDirectory);
+        Files.write(tempFile.toPath(), fileContents.getBytes());
+        String result = ProcStatsUtil.readSingleLineProcFile(tempFile.toString());
+        Files.delete(tempFile.toPath());
+        return result;
+    }
+
+    private String runReadTerminatedProcFile(
+            String fileContents, byte terminator) throws IOException {
+        File tempFile = File.createTempFile("terminated-proc-file", null, mProcDirectory);
+        Files.write(tempFile.toPath(), fileContents.getBytes());
+        String result = ProcStatsUtil.readTerminatedProcFile(tempFile.toString(), terminator);
+        Files.delete(tempFile.toPath());
+        return result;
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
new file mode 100644
index 0000000..f2a531f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProcTimeInStateReaderTest {
+
+    private File mProcDirectory;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getContext();
+        mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtils.deleteContents(mProcDirectory);
+    }
+
+    @Test
+    public void testSimple() throws IOException {
+        Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state");
+        Files.write(initialTimeInStateFile, "1 2\n3 4\n5 6\n7 8\n".getBytes());
+        ProcTimeInStateReader reader = new ProcTimeInStateReader(initialTimeInStateFile);
+
+        assertArrayEquals(
+                "Reported frequencies are correct",
+                new long[]{1, 3, 5, 7},
+                reader.getFrequenciesKhz());
+        assertArrayEquals(
+                "Reported usage times are correct",
+                new long[]{20, 40, 60, 80},
+                reader.getUsageTimesMillis(initialTimeInStateFile));
+    }
+
+    @Test
+    public void testDifferentFile() throws IOException {
+        Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state");
+        Files.write(initialTimeInStateFile, "1 2\n3 4\n5 6\n7 8\n".getBytes());
+        ProcTimeInStateReader reader = new ProcTimeInStateReader(initialTimeInStateFile);
+
+        Path timeInStateFile = mProcDirectory.toPath().resolve("time-in-state");
+        Files.write(timeInStateFile, "1 20\n3 40\n5 60\n7 80\n".getBytes());
+        assertArrayEquals(
+                "Reported usage times are correct",
+                new long[]{200, 400, 600, 800},
+                reader.getUsageTimesMillis(timeInStateFile));
+    }
+
+    @Test
+    public void testWrongLength() throws IOException {
+        Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state");
+        Files.write(initialTimeInStateFile, "1 2\n3 4\n5 6\n7 8\n".getBytes());
+        ProcTimeInStateReader reader = new ProcTimeInStateReader(initialTimeInStateFile);
+
+        Path timeInStateFile = mProcDirectory.toPath().resolve("time-in-state");
+        Files.write(timeInStateFile, "1 2\n3 4\n5 6\n".getBytes());
+        assertNull(reader.getUsageTimesMillis(timeInStateFile));
+    }
+
+    @Test
+    public void testEmptyInitialFails() throws IOException {
+        Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state");
+        Files.write(initialTimeInStateFile, "".getBytes());
+        try {
+            new ProcTimeInStateReader(initialTimeInStateFile);
+            fail("Instantiation didn't fail with empty initial time_in_state file");
+        } catch (IOException ignored) {
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 3efe655..6f78651 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -48,9 +48,6 @@
  * </p>
  */
 public class MeasuredText {
-    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
-            MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
-
     private long mNativePtr;
     private @NonNull char[] mChars;
 
@@ -166,6 +163,9 @@
      * Note: The appendStyle and appendReplacementRun should be called to cover the text length.
      */
     public static class Builder {
+        private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
+
         private long mNativePtr;
 
         private final @NonNull char[] mText;
diff --git a/packages/SettingsLib/HelpUtils/res/values-hi/strings.xml b/packages/SettingsLib/HelpUtils/res/values-hi/strings.xml
new file mode 100644
index 0000000..cfc6961
--- /dev/null
+++ b/packages/SettingsLib/HelpUtils/res/values-hi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="help_feedback_label" msgid="4550436169116444686">"सहायता और सुझाव"</string>
+</resources>
diff --git a/packages/SettingsLib/SearchWidget/res/values-or/strings.xml b/packages/SettingsLib/SearchWidget/res/values-or/strings.xml
new file mode 100644
index 0000000..3523037
--- /dev/null
+++ b/packages/SettingsLib/SearchWidget/res/values-or/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="search_menu" msgid="1604061903696928905">"ସର୍ଚ୍ଚ ସେଟିଙ୍ଗ"</string>
+</resources>
diff --git a/packages/SettingsLib/SearchWidget/res/values-ur/strings.xml b/packages/SettingsLib/SearchWidget/res/values-ur/strings.xml
new file mode 100644
index 0000000..8b62fd4
--- /dev/null
+++ b/packages/SettingsLib/SearchWidget/res/values-ur/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="search_menu" msgid="1604061903696928905">"ترتیبات تلاش کریں"</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 5c11db1..83c4b8e 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -240,7 +240,7 @@
     <string name="wifi_unmetered_label" msgid="6124098729457992931">"بدون قياس"</string>
     <string name="select_logd_size_title" msgid="7433137108348553508">"أحجام ذاكرة التخزين المؤقت للتسجيل"</string>
     <string name="select_logd_size_dialog_title" msgid="1206769310236476760">"حدد أحجامًا أكبر لكل ذاكرة تخزين مؤقت للتسجيل"</string>
-    <string name="dev_logpersist_clear_warning_title" msgid="684806692440237967">"هل تريد محو سعة التخزين الدائمة للمسجِّل؟"</string>
+    <string name="dev_logpersist_clear_warning_title" msgid="684806692440237967">"هل تريد محو مساحة التخزين الدائمة للمسجِّل؟"</string>
     <string name="dev_logpersist_clear_warning_message" msgid="2256582531342994562">"عندما نتوقف عن رصد أي أخطاء باستخدام المسجِّل الدائم مرة أخرى، يتعين علينا محو بيانات المسجِّل الموجودة على جهازك."</string>
     <string name="select_logpersist_title" msgid="7530031344550073166">"تخزين بيانات المسجِّل باستمرار على الجهاز"</string>
     <string name="select_logpersist_dialog_title" msgid="4003400579973269060">"تحديد مخازن السجلات المؤقتة المراد تخزينها باستمرار على الجهاز"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 72dd8cf..5638947 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -446,6 +446,6 @@
     <string name="alarm_template_far" msgid="3779172822607461675">"Data: <xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="zen_mode_duration_settings_title" msgid="229547412251222757">"Durada"</string>
     <string name="zen_mode_duration_always_prompt_title" msgid="6478923750878945501">"Pregunta sempre"</string>
-    <string name="zen_mode_forever" msgid="2704305038191592967">"Fins que no ho desactivi"</string>
+    <string name="zen_mode_forever" msgid="2704305038191592967">"Fins que no ho desactivis"</string>
     <string name="time_unit_just_now" msgid="6363336622778342422">"Ara mateix"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 5a11e72..5da253c 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -317,7 +317,7 @@
     <string name="show_all_anrs" msgid="4924885492787069007">"‏הצגת מקרי ANR ברקע"</string>
     <string name="show_all_anrs_summary" msgid="6636514318275139826">"הצגת תיבת דו-שיח של \'אפליקציה לא מגיבה\' עבור אפליקציות שפועלות ברקע"</string>
     <string name="show_notification_channel_warnings" msgid="1399948193466922683">"אזהרות לגבי ערוץ הודעות"</string>
-    <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"הצגת אזהרה כשאפליקציה שולחת הודעה ללא ערוץ חוקי"</string>
+    <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"הצגת אזהרה כשאפליקציה שולחת התראה ללא ערוץ חוקי"</string>
     <string name="force_allow_on_external" msgid="3215759785081916381">"אילוץ הרשאת אפליקציות באחסון חיצוני"</string>
     <string name="force_allow_on_external_summary" msgid="3640752408258034689">"מאפשר כתיבה של כל אפליקציה באחסון חיצוני, ללא התחשבות בערכי המניפסט"</string>
     <string name="force_resizable_activities" msgid="8615764378147824985">"אלץ יכולת קביעת גודל של הפעילויות"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index df122e2..d693077 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -415,7 +415,7 @@
     <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Kiwango maalum (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
     <string name="content_description_menu_button" msgid="8182594799812351266">"Menyu"</string>
     <string name="retail_demo_reset_message" msgid="118771671364131297">"Weka nenosiri ili urejeshe mipangilio ya kiwandani ikiwa katika hali ya onyesho."</string>
-    <string name="retail_demo_reset_next" msgid="8356731459226304963">"Inayofuata"</string>
+    <string name="retail_demo_reset_next" msgid="8356731459226304963">"Endelea"</string>
     <string name="retail_demo_reset_title" msgid="696589204029930100">"Nenosiri linahitajika"</string>
     <string name="active_input_method_subtypes" msgid="3596398805424733238">"Mbinu zinazotumika"</string>
     <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"Tumia lugha za mfumo"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index a842229..3152e65 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -127,4 +127,17 @@
     default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
             int state, int bluetoothProfile) {
     }
+
+    /**
+     * Called when ACL connection state is changed. It listens to
+     * {@link android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED} and {@link
+     * android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED}
+     *
+     * @param cachedDevice Bluetooth device that changed
+     * @param state        the Bluetooth device connection state, the possible values are:
+     *                     {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED},
+     *                     {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED}
+     */
+    default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 022bf69..2b7babd0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -119,6 +119,10 @@
         addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,
                 new AudioModeChangedHandler());
 
+        // ACL connection changed broadcasts
+        addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler());
+        addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler());
+
         registerAdapterIntentReceiver();
     }
 
@@ -236,6 +240,15 @@
         }
     }
 
+    private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice,
+            int state) {
+        synchronized (mCallbacks) {
+            for (BluetoothCallback callback : mCallbacks) {
+                callback.onAclConnectionStateChanged(activeDevice, state);
+            }
+        }
+    }
+
     @VisibleForTesting
     void addHandler(String action, Handler handler) {
         mHandlerMap.put(action, handler);
@@ -447,6 +460,32 @@
         }
     }
 
+    private class AclStateChangedHandler implements Handler {
+        @Override
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            final String action = intent.getAction();
+            if (action == null) {
+                Log.w(TAG, "AclStateChangedHandler: action is null");
+                return;
+            }
+            final CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
+            final int state;
+            switch (action) {
+                case BluetoothDevice.ACTION_ACL_CONNECTED:
+                    state = BluetoothAdapter.STATE_CONNECTED;
+                    break;
+                case BluetoothDevice.ACTION_ACL_DISCONNECTED:
+                    state = BluetoothAdapter.STATE_DISCONNECTED;
+                    break;
+                default:
+                    Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
+                    return;
+
+            }
+            dispatchAclStateChanged(activeDevice, state);
+        }
+    }
+
     private class AudioModeChangedHandler implements Handler {
 
         @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 020234c6..c147d5e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -19,7 +19,10 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
@@ -50,6 +53,8 @@
     private BluetoothCallback mBluetoothCallback;
     @Mock
     private CachedBluetoothDevice mCachedBluetoothDevice;
+    @Mock
+    private BluetoothDevice mBluetoothDevice;
 
     private Context mContext;
     private Intent mIntent;
@@ -62,6 +67,7 @@
 
         mBluetoothEventManager = new BluetoothEventManager(mLocalAdapter,
                 mCachedDeviceManager, mContext, /* handler= */ null, /* userHandle= */ null);
+        when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
     }
 
     @Test
@@ -126,4 +132,28 @@
         verify(mBluetoothCallback).onProfileConnectionStateChanged(mCachedBluetoothDevice,
                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
     }
+
+    @Test
+    public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() {
+        mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+        mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
+        mContext.sendBroadcast(mIntent);
+
+        verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice,
+                BluetoothAdapter.STATE_DISCONNECTED);
+    }
+
+    @Test
+    public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() {
+        mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
+        mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
+        mContext.sendBroadcast(mIntent);
+
+        verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice,
+                BluetoothAdapter.STATE_CONNECTED);
+    }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index bf4374a..bca3530 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -116,6 +116,7 @@
         public boolean isTransient = false;
         public String expandedAccessibilityClassName;
         public SlashState slash;
+        public boolean handlesLongClick = true;
 
         public boolean copyTo(State other) {
             if (other == null) throw new IllegalArgumentException();
@@ -133,7 +134,8 @@
                     || !Objects.equals(other.state, state)
                     || !Objects.equals(other.isTransient, isTransient)
                     || !Objects.equals(other.dualTarget, dualTarget)
-                    || !Objects.equals(other.slash, slash);
+                    || !Objects.equals(other.slash, slash)
+                    || !Objects.equals(other.handlesLongClick, handlesLongClick);
             other.icon = icon;
             other.iconSupplier = iconSupplier;
             other.label = label;
@@ -146,6 +148,7 @@
             other.dualTarget = dualTarget;
             other.isTransient = isTransient;
             other.slash = slash != null ? slash.copy() : null;
+            other.handlesLongClick = handlesLongClick;
             return changed;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index d42127e..0638998 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -194,6 +194,7 @@
         }
 
         setClickable(state.state != Tile.STATE_UNAVAILABLE);
+        setLongClickable(state.handlesLongClick);
         mIcon.setIcon(state, allowAnimations);
         setContentDescription(state.contentDescription);
 
@@ -287,10 +288,14 @@
                 info.setText(label);
                 info.setChecked(b);
                 info.setCheckable(true);
-                info.addAction(
-                        new AccessibilityNodeInfo.AccessibilityAction(
-                                AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.getId(),
-                                getResources().getString(R.string.accessibility_long_click_tile)));
+                if (isLongClickable()) {
+                    info.addAction(
+                            new AccessibilityNodeInfo.AccessibilityAction(
+                                    AccessibilityNodeInfo.AccessibilityAction
+                                            .ACTION_LONG_CLICK.getId(),
+                                    getResources().getString(
+                                            R.string.accessibility_long_click_tile)));
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index f2ead1c..d7ac253 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager;
 import android.content.Intent;
-import android.graphics.drawable.Drawable;
 import android.provider.MediaStore;
 import android.service.quicksettings.Tile;
 import android.widget.Switch;
@@ -50,7 +49,9 @@
 
     @Override
     public BooleanState newTileState() {
-        return new BooleanState();
+        BooleanState state = new BooleanState();
+        state.handlesLongClick = false;
+        return state;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index c560301..df99a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -124,7 +125,8 @@
 
     // Dismiss action to be launched when we stop dozing or the keyguard is gone.
     private DismissWithActionRequest mPendingWakeupAction;
-    private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+    private final KeyguardMonitorImpl mKeyguardMonitor =
+            (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class);
     private final NotificationMediaManager mMediaManager =
             Dependency.get(NotificationMediaManager.class);
 
@@ -202,6 +204,8 @@
     public void show(Bundle options) {
         mShowing = true;
         mStatusBarWindowController.setKeyguardShowing(true);
+        mKeyguardMonitor.notifyKeyguardState(
+                mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());
         reset(true /* hideBouncerWhenShowing */);
         StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
             StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
@@ -424,6 +428,8 @@
      */
     public void hide(long startTime, long fadeoutDuration) {
         mShowing = false;
+        mKeyguardMonitor.notifyKeyguardState(
+                mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());
         launchPendingWakeupAction();
 
         if (KeyguardUpdateMonitor.getInstance(mContext).needsSlowUnlockTransition()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index ebd9e77..6eba914 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -457,13 +457,12 @@
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
         final long identityToken = Binder.clearCallingIdentity();
         try {
             connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId,
                     viewIdResName, partialInteractiveRegion, interactionId, callback, mFetchFlags,
                     interrogatingPid, interrogatingTid, spec);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+            return mSecurityPolicy.computeValidReportedPackages(
                     connection.getPackageName(), connection.getUid());
         } catch (RemoteException re) {
             if (DEBUG) {
@@ -514,13 +513,12 @@
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
         final long identityToken = Binder.clearCallingIdentity();
         try {
             connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId,
                     text, partialInteractiveRegion, interactionId, callback, mFetchFlags,
                     interrogatingPid, interrogatingTid, spec);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+            return mSecurityPolicy.computeValidReportedPackages(
                     connection.getPackageName(), connection.getUid());
         } catch (RemoteException re) {
             if (DEBUG) {
@@ -571,13 +569,12 @@
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
         final long identityToken = Binder.clearCallingIdentity();
         try {
             connection.getRemote().findAccessibilityNodeInfoByAccessibilityId(
                     accessibilityNodeId, partialInteractiveRegion, interactionId, callback,
                     mFetchFlags | flags, interrogatingPid, interrogatingTid, spec, arguments);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+            return mSecurityPolicy.computeValidReportedPackages(
                     connection.getPackageName(), connection.getUid());
         } catch (RemoteException re) {
             if (DEBUG) {
@@ -628,13 +625,12 @@
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
         final long identityToken = Binder.clearCallingIdentity();
         try {
             connection.getRemote().findFocus(accessibilityNodeId, focusType,
                     partialInteractiveRegion, interactionId, callback, mFetchFlags,
                     interrogatingPid, interrogatingTid, spec);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+            return mSecurityPolicy.computeValidReportedPackages(
                     connection.getPackageName(), connection.getUid());
         } catch (RemoteException re) {
             if (DEBUG) {
@@ -684,13 +680,12 @@
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
-        final int callingUid = Binder.getCallingUid();
         final long identityToken = Binder.clearCallingIdentity();
         try {
             connection.getRemote().focusSearch(accessibilityNodeId, direction,
                     partialInteractiveRegion, interactionId, callback, mFetchFlags,
                     interrogatingPid, interrogatingTid, spec);
-            return mSecurityPolicy.computeValidReportedPackages(callingUid,
+            return mSecurityPolicy.computeValidReportedPackages(
                     connection.getPackageName(), connection.getUid());
         } catch (RemoteException re) {
             if (DEBUG) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 097405a..e89b951 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -348,11 +348,24 @@
     }
 
     boolean getBindInstantServiceAllowed(int userId) {
-        return  mSecurityPolicy.getBindInstantServiceAllowed(userId);
+        final UserState userState = getUserState(userId);
+        if (userState == null) return false;
+        return userState.getBindInstantServiceAllowed();
     }
 
     void setBindInstantServiceAllowed(int userId, boolean allowed) {
-        mSecurityPolicy.setBindInstantServiceAllowed(userId, allowed);
+        UserState userState;
+        synchronized (mLock) {
+            userState = getUserState(userId);
+            if (userState == null) {
+                if (!allowed) {
+                    return;
+                }
+                userState = new UserState(userId);
+                mUserStates.put(userId, userState);
+            }
+        }
+        userState.setBindInstantServiceAllowed(allowed);
     }
 
     private void registerBroadcastReceivers() {
@@ -1316,7 +1329,7 @@
                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
-        if (userState.mBindInstantServiceAllowed) {
+        if (userState.getBindInstantServiceAllowed()) {
             flags |= PackageManager.MATCH_INSTANT;
         }
 
@@ -3158,9 +3171,15 @@
             return packageNames[0];
         }
 
-        String[] computeValidReportedPackages(int callingUid,
-                String targetPackage, int targetUid) {
-            if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
+        /**
+         * Get a list of package names an app may report, including any widget packages it owns.
+         *
+         * @param targetPackage The known valid target package
+         * @param targetUid The uid of the target app
+         * @return
+         */
+        String[] computeValidReportedPackages(String targetPackage, int targetUid) {
+            if (UserHandle.getAppId(targetUid) == Process.SYSTEM_UID) {
                 // Empty array means any package is Okay
                 return EmptyArray.STRING;
             }
@@ -3184,32 +3203,6 @@
             return uidPackages;
         }
 
-        private boolean getBindInstantServiceAllowed(int userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
-                    "getBindInstantServiceAllowed");
-            UserState state = mUserStates.get(userId);
-            return (state != null) && state.mBindInstantServiceAllowed;
-        }
-
-        private void setBindInstantServiceAllowed(int userId, boolean allowed) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
-                    "setBindInstantServiceAllowed");
-            UserState state = mUserStates.get(userId);
-            if (state == null) {
-                if (!allowed) {
-                    return;
-                }
-                state = new UserState(userId);
-                mUserStates.put(userId, state);
-            }
-            if (state.mBindInstantServiceAllowed != allowed) {
-                state.mBindInstantServiceAllowed = allowed;
-                onUserStateChangedLocked(state);
-            }
-        }
-
         public void clearWindowsLocked() {
             List<WindowInfo> windows = Collections.emptyList();
             final int activeWindowId = mActiveWindowId;
@@ -3819,7 +3812,7 @@
         public boolean mUserMinimumUiTimeoutEnabled;
         public int mUserMinimumUiTimeout;
 
-        public boolean mBindInstantServiceAllowed;
+        private boolean mBindInstantServiceAllowed;
 
         public UserState(int userId) {
             mUserId = userId;
@@ -4051,6 +4044,24 @@
                     Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0)
                     & SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0;
         }
+
+        public boolean getBindInstantServiceAllowed() {
+            synchronized (mLock) {
+                return mBindInstantServiceAllowed;
+            }
+        }
+
+        public void setBindInstantServiceAllowed(boolean allowed) {
+            synchronized (mLock) {
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+                        "setBindInstantServiceAllowed");
+                if (allowed) {
+                    mBindInstantServiceAllowed = allowed;
+                    onUserStateChangedLocked(this);
+                }
+            }
+        }
     }
 
     private final class AccessibilityContentObserver extends ContentObserver {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 2396ded..86132a8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -92,7 +92,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
-            if (userState.mBindInstantServiceAllowed) {
+            if (userState.getBindInstantServiceAllowed()) {
                 flags |= Context.BIND_ALLOW_INSTANT;
             }
             if (mService == null && mContext.bindServiceAsUser(
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index a72470d..05293b5 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -373,7 +373,8 @@
         return getOrCreateStack(windowingMode, activityType, onTop);
     }
 
-    private int getNextStackId() {
+    @VisibleForTesting
+    int getNextStackId() {
         return sNextFreeStackId++;
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 026c5cc..0049e22 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -48,41 +48,7 @@
 
 import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
 import static com.android.server.am.ActivityDisplay.POSITION_TOP;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ALL;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_APP;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_STATES;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_APP;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_SAVED_STATE;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_STATES;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_USER_LEAVING;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
-import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 import static com.android.server.am.ActivityStack.ActivityState.DESTROYED;
 import static com.android.server.am.ActivityStack.ActivityState.DESTROYING;
 import static com.android.server.am.ActivityStack.ActivityState.FINISHING;
@@ -102,8 +68,42 @@
 import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
-
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ALL;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_APP;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_STATES;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_APP;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_SAVED_STATE;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_STATES;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_USER_LEAVING;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityTaskManagerService.H.FIRST_ACTIVITY_STACK_MSG;
+import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
+import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+
 import static java.lang.Integer.MAX_VALUE;
 
 import android.app.Activity;
@@ -1807,13 +1807,6 @@
             return false;
         }
 
-        final ActivityRecord top = topRunningActivityLocked();
-        if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) {
-            // Shouldn't be visible if you don't have any running activities, not starting one, and
-            // not the top stack on display.
-            return false;
-        }
-
         final ActivityDisplay display = getDisplay();
         boolean gotSplitScreenStack = false;
         boolean gotOpaqueSplitScreenPrimary = false;
@@ -1822,9 +1815,16 @@
         final boolean isAssistantType = isActivityTypeAssistant();
         for (int i = display.getChildCount() - 1; i >= 0; --i) {
             final ActivityStack other = display.getChildAt(i);
+            final boolean hasRunningActivities = other.topRunningActivityLocked() != null;
             if (other == this) {
-                // Should be visible if there is no other stack occluding it.
-                return true;
+                // Should be visible if there is no other stack occluding it, unless it doesn't
+                // have any running activities, not starting one and not home stack.
+                return hasRunningActivities || isInStackLocked(starting) != null
+                        || isActivityTypeHome();
+            }
+
+            if (!hasRunningActivities) {
+                continue;
             }
 
             final int otherWindowingMode = other.getWindowingMode();
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
index 1d0ab8f..60f70c7 100644
--- a/services/core/java/com/android/server/location/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -16,12 +16,15 @@
 
 package com.android.server.location;
 
+import android.Manifest;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
 import android.hardware.contexthub.V1_0.IContexthub;
 import android.hardware.contexthub.V1_0.Result;
 import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
@@ -30,7 +33,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 
 /**
  * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle
@@ -69,14 +72,15 @@
     private final short mHostEndPointId;
 
     /*
-     * The remote callback interface for this client.
+     * The remote callback interface for this client. This will be set to null whenever the
+     * client connection is closed (either explicitly or via binder death).
      */
-    private final IContextHubClientCallback mCallbackInterface;
+    private IContextHubClientCallback mCallbackInterface = null;
 
     /*
-     * false if the connection has been closed by the client, true otherwise.
+     * True if the client is still registered with the Context Hub Service, false otherwise.
      */
-    private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true);
+    private boolean mRegistered = true;
 
     /*
      * Internal interface used to invoke client callbacks.
@@ -85,6 +89,74 @@
         void accept(IContextHubClientCallback callback) throws RemoteException;
     }
 
+    /*
+     * The PendingIntent registered with this client.
+     */
+    private final PendingIntentRequest mPendingIntentRequest = new PendingIntentRequest();
+
+    /*
+     * Helper class to manage registered PendingIntent requests from the client.
+     */
+    private class PendingIntentRequest {
+        /*
+         * The PendingIntent object to request, null if there is no open request.
+         */
+        private PendingIntent mPendingIntent;
+
+        /*
+         * The ID of the nanoapp the request is for, invalid if there is no open request.
+         */
+        private long mNanoAppId;
+
+        PendingIntentRequest() {}
+
+        PendingIntentRequest(PendingIntent pendingIntent, long nanoAppId) {
+            mPendingIntent = pendingIntent;
+            mNanoAppId = nanoAppId;
+        }
+
+        public long getNanoAppId() {
+            return mNanoAppId;
+        }
+
+        public PendingIntent getPendingIntent() {
+            return mPendingIntent;
+        }
+
+        public boolean hasPendingIntent() {
+            return mPendingIntent != null;
+        }
+
+        public void clear() {
+            mPendingIntent = null;
+        }
+
+        public boolean register(PendingIntent pendingIntent, long nanoAppId) {
+            boolean success = false;
+            if (hasPendingIntent()) {
+                Log.e(TAG, "Failed to register PendingIntent: registered PendingIntent exists");
+            } else {
+                mNanoAppId = nanoAppId;
+                mPendingIntent = pendingIntent;
+                success = true;
+            }
+
+            return success;
+        }
+
+        public boolean unregister(PendingIntent pendingIntent) {
+            boolean success = false;
+            if (!hasPendingIntent() || !mPendingIntent.equals(pendingIntent)) {
+                Log.e(TAG, "Failed to unregister PendingIntent: PendingIntent is not registered");
+            } else {
+                mPendingIntent = null;
+                success = true;
+            }
+
+            return success;
+        }
+    }
+
     /* package */ ContextHubClientBroker(
             Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
             ContextHubInfo contextHubInfo, short hostEndPointId,
@@ -102,8 +174,10 @@
      *
      * @throws RemoteException if the client has already died
      */
-    /* package */ void attachDeathRecipient() throws RemoteException {
-        mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+    /* package */ synchronized void attachDeathRecipient() throws RemoteException {
+        if (mCallbackInterface != null) {
+            mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+        }
     }
 
     /**
@@ -118,9 +192,13 @@
         ContextHubServiceUtil.checkPermissions(mContext);
 
         int result;
-        if (mConnectionOpen.get()) {
-            ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(
-                    mHostEndPointId, message);
+        IContextHubClientCallback callback = null;
+        synchronized (this) {
+            callback = mCallbackInterface;
+        }
+        if (callback != null) {
+            ContextHubMsg messageToNanoApp =
+                    ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
 
             int contextHubId = mAttachedContextHubInfo.getId();
             try {
@@ -139,24 +217,37 @@
     }
 
     /**
-     * @param intent the intent to register
-     * @param nanoAppId the ID of the nanoapp to send events for
+     * @param pendingIntent the intent to register
+     * @param nanoAppId     the ID of the nanoapp to send events for
      * @return true on success, false otherwise
      */
     @Override
-    public boolean registerIntent(PendingIntent intent, long nanoAppId) {
-        // TODO: Implement this
-        return false;
+    public boolean registerIntent(PendingIntent pendingIntent, long nanoAppId) {
+        ContextHubServiceUtil.checkPermissions(mContext);
+
+        boolean success = false;
+        synchronized (this) {
+            if (mCallbackInterface == null) {
+                Log.e(TAG, "Failed to register PendingIntent: client connection is closed");
+            } else {
+                success = mPendingIntentRequest.register(pendingIntent, nanoAppId);
+            }
+        }
+
+        return success;
     }
 
     /**
-     * @param intent the intent to unregister
+     * @param pendingIntent the intent to unregister
      * @return true on success, false otherwise
      */
     @Override
-    public boolean unregisterIntent(PendingIntent intent) {
-        // TODO: Implement this
-        return false;
+    public boolean unregisterIntent(PendingIntent pendingIntent) {
+        ContextHubServiceUtil.checkPermissions(mContext);
+
+        synchronized (this) {
+            return mPendingIntentRequest.unregister(pendingIntent);
+        }
     }
 
     /**
@@ -164,8 +255,15 @@
      */
     @Override
     public void close() {
-        if (mConnectionOpen.getAndSet(false)) {
-            mClientManager.unregisterClient(mHostEndPointId);
+        synchronized (this) {
+            if (mCallbackInterface != null) {
+                mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
+                mCallbackInterface = null;
+            }
+            if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
+                mClientManager.unregisterClient(mHostEndPointId);
+                mRegistered = false;
+            }
         }
     }
 
@@ -197,7 +295,12 @@
      * @param message the message that came from a nanoapp
      */
     /* package */ void sendMessageToClient(NanoAppMessage message) {
-        invokeCallbackConcurrent(callback -> callback.onMessageFromNanoApp(message));
+        invokeCallback(callback -> callback.onMessageFromNanoApp(message));
+
+        Supplier<Intent> supplier =
+                () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId())
+                        .putExtra(ContextHubManager.EXTRA_MESSAGE, message);
+        sendPendingIntent(supplier);
     }
 
     /**
@@ -206,7 +309,8 @@
      * @param nanoAppId the ID of the nanoapp that was loaded.
      */
     /* package */ void onNanoAppLoaded(long nanoAppId) {
-        invokeCallbackConcurrent(callback -> callback.onNanoAppLoaded(nanoAppId));
+        invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId));
+        sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId));
     }
 
     /**
@@ -215,14 +319,16 @@
      * @param nanoAppId the ID of the nanoapp that was unloaded.
      */
     /* package */ void onNanoAppUnloaded(long nanoAppId) {
-        invokeCallbackConcurrent(callback -> callback.onNanoAppUnloaded(nanoAppId));
+        invokeCallback(callback -> callback.onNanoAppUnloaded(nanoAppId));
+        sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_UNLOADED, nanoAppId));
     }
 
     /**
      * Notifies the client of a hub reset event if the connection is open.
      */
     /* package */ void onHubReset() {
-        invokeCallbackConcurrent(callback -> callback.onHubReset());
+        invokeCallback(callback -> callback.onHubReset());
+        sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_HUB_RESET));
     }
 
     /**
@@ -232,7 +338,12 @@
      * @param abortCode the nanoapp specific abort code
      */
     /* package */ void onNanoAppAborted(long nanoAppId, int abortCode) {
-        invokeCallbackConcurrent(callback -> callback.onNanoAppAborted(nanoAppId, abortCode));
+        invokeCallback(callback -> callback.onNanoAppAborted(nanoAppId, abortCode));
+
+        Supplier<Intent> supplier =
+                () -> createIntent(ContextHubManager.EVENT_NANOAPP_ABORTED, nanoAppId)
+                        .putExtra(ContextHubManager.EXTRA_NANOAPP_ABORT_CODE, abortCode);
+        sendPendingIntent(supplier);
     }
 
     /**
@@ -240,8 +351,8 @@
      *
      * @param consumer the consumer specifying the callback to invoke
      */
-    private void invokeCallbackConcurrent(CallbackConsumer consumer) {
-        if (mConnectionOpen.get()) {
+    private synchronized void invokeCallback(CallbackConsumer consumer) {
+        if (mCallbackInterface != null) {
             try {
                 consumer.accept(mCallbackInterface);
             } catch (RemoteException e) {
@@ -250,4 +361,53 @@
             }
         }
     }
+
+    /**
+     * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE extra field
+     *
+     * @param eventType the ContextHubManager.Event type describing the event
+     * @return the Intent object
+     */
+    private Intent createIntent(int eventType) {
+        Intent intent = new Intent();
+        intent.putExtra(ContextHubManager.EXTRA_EVENT_TYPE, eventType);
+        intent.putExtra(ContextHubManager.EXTRA_CONTEXT_HUB_INFO, mAttachedContextHubInfo);
+        return intent;
+    }
+
+    /**
+     * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE and the
+     * ContextHubManager.EXTRA_NANOAPP_ID extra fields
+     *
+     * @param eventType the ContextHubManager.Event type describing the event
+     * @param nanoAppId the ID of the nanoapp this event is for
+     * @return the Intent object
+     */
+    private Intent createIntent(int eventType, long nanoAppId) {
+        Intent intent = createIntent(eventType);
+        intent.putExtra(ContextHubManager.EXTRA_NANOAPP_ID, nanoAppId);
+        return intent;
+    }
+
+    /**
+     * Sends an intent to any existing PendingIntent
+     *
+     * @param supplier method to create the extra Intent
+     */
+    private synchronized void sendPendingIntent(Supplier<Intent> supplier) {
+        if (mPendingIntentRequest.hasPendingIntent()) {
+            Intent intent = supplier.get();
+            try {
+                mPendingIntentRequest.getPendingIntent().send(
+                        mContext, 0 /* code */, intent, null /* onFinished */, null /* Handler */,
+                        Manifest.permission.LOCATION_HARDWARE /* requiredPermission */,
+                        null /* options */);
+            } catch (PendingIntent.CanceledException e) {
+                // The PendingIntent is no longer valid
+                Log.w(TAG, "PendingIntent has been canceled, unregistering from client"
+                        + " (host endpoint ID " + mHostEndPointId + ")");
+                mPendingIntentRequest.clear();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 6d3a3b6..dc3bfbc 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -1627,7 +1627,8 @@
         // fold tethering stats and operations into uid snapshot
         final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID);
         tetherSnapshot.filter(UID_ALL, ifaces, TAG_ALL);
-        NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot);
+        NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot,
+                mUseBpfTrafficStats);
         uidSnapshot.combineAllValues(tetherSnapshot);
 
         final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
@@ -1637,7 +1638,8 @@
         final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID);
         if (vtStats != null) {
             vtStats.filter(UID_ALL, ifaces, TAG_ALL);
-            NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats);
+            NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats,
+                    mUseBpfTrafficStats);
             uidSnapshot.combineAllValues(vtStats);
         }
 
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 7b35b91..465a2cf 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -1052,6 +1052,7 @@
             e.writeLong(callStat.maxRequestSizeBytes);
             e.writeLong(callStat.recordedCallCount);
             e.writeInt(callStat.screenInteractive ? 1 : 0);
+            e.writeInt(callStat.callingUid);
             pulledData.add(e);
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java
index 0da5742..59c4067 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java
@@ -60,8 +60,7 @@
     public void testLastFocusedStackIsUpdatedWhenMovingStack() {
         // Create a stack at bottom.
         final ActivityDisplay display = mSupervisor.getDefaultDisplay();
-        final ActivityStack stack = display.createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, !ON_TOP);
+        final ActivityStack stack = new StackBuilder(mSupervisor).setOnTop(!ON_TOP).build();
         final ActivityStack prevFocusedStack = display.getFocusedStack();
 
         stack.moveToFront("moveStackToFront");
@@ -140,16 +139,14 @@
      */
     @Test
     public void testTopRunningActivity() {
-        // Create stack to hold focus.
         final ActivityDisplay display = mSupervisor.getDefaultDisplay();
-        final ActivityStack emptyStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
-                ACTIVITY_TYPE_STANDARD, true /* onTop */);
-
         final KeyguardController keyguard = mSupervisor.getKeyguardController();
-        final ActivityStack stack = mSupervisor.getDefaultDisplay().createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
-                .setStack(stack).build();
+        final ActivityStack stack = new StackBuilder(mSupervisor).build();
+        final ActivityRecord activity = stack.getTopActivity();
+
+        // Create empty stack on top.
+        final ActivityStack emptyStack =
+                new StackBuilder(mSupervisor).setCreateActivity(false).build();
 
         // Make sure the top running activity is not affected when keyguard is not locked.
         assertTopRunningActivity(activity, display);
@@ -159,8 +156,8 @@
         assertEquals(activity, display.topRunningActivity());
         assertNull(display.topRunningActivity(true /* considerKeyguardState */));
 
-        // Change focus to stack with activity.
-        stack.moveToFront("focusChangeToTestStack");
+        // Move stack with activity to top.
+        stack.moveToFront("testStackToFront");
         assertEquals(stack, display.getFocusedStack());
         assertEquals(activity, display.topRunningActivity());
         assertNull(display.topRunningActivity(true /* considerKeyguardState */));
@@ -175,11 +172,10 @@
         // Ensure the show when locked activity is returned.
         assertTopRunningActivity(showWhenLockedActivity, display);
 
-        // Change focus back to empty stack.
-        emptyStack.moveToFront("focusChangeToEmptyStack");
-        assertEquals(emptyStack, display.getFocusedStack());
-        // If there is no running activity in focused stack, the running activity in next focusable
-        // stack should be returned.
+        // Move empty stack to front. The running activity in focusable stack which below the
+        // empty stack should be returned.
+        emptyStack.moveToFront("emptyStackToFront");
+        assertEquals(stack, display.getFocusedStack());
         assertTopRunningActivity(showWhenLockedActivity, display);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index ffc7fa2..1015008 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -79,10 +79,9 @@
         super.setUp();
 
         setupActivityTaskManagerService();
-        mStack = mSupervisor.getDefaultDisplay().createStack(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
-        mActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        mStack = new StackBuilder(mSupervisor).build();
+        mTask = mStack.getChildAt(0);
+        mActivity = mTask.getTopActivity();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index 2c993d3..bdceec7 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -359,15 +359,12 @@
     public void testFindTaskToMoveToFrontWhenRecentsOnTop() throws Exception {
         // Create stack/task on default display.
         final ActivityDisplay display = mSupervisor.getDefaultDisplay();
-        final ActivityStack targetStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
-                ACTIVITY_TYPE_STANDARD, false /* onTop */);
-        final TaskRecord targetTask = new TaskBuilder(mSupervisor).setStack(targetStack).build();
+        final TestActivityStack targetStack = new StackBuilder(mSupervisor).setOnTop(false).build();
+        final TaskRecord targetTask = targetStack.getChildAt(0);
 
         // Create Recents on top of the display.
-        final ActivityStack stack = display.createStack(WINDOWING_MODE_FULLSCREEN,
-                ACTIVITY_TYPE_RECENTS, true /* onTop */);
-        final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build();
-        new ActivityBuilder(mService).setTask(task).build();
+        final ActivityStack stack =
+                new StackBuilder(mSupervisor).setActivityType(ACTIVITY_TYPE_RECENTS).build();
 
         final String reason = "findTaskToMoveToFront";
         mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 53f67af..ea33de74 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -26,8 +26,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
+import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.am.ActivityStack.ActivityState.DESTROYING;
-import static com.android.server.am.ActivityStack.ActivityState.FINISHING;
 import static com.android.server.am.ActivityStack.ActivityState.PAUSED;
 import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
 import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
@@ -361,14 +361,12 @@
                 translucentStack.topRunningActivityLocked();
         topRunningTranslucentActivity.finishing = true;
 
-        // Home shouldn't be visible since its activity is marked as finishing and it isn't the top
-        // of the stack list.
-        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        // Home stack should be visible even there are no running activities.
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
         // Home should be visible if we are starting an activity within it.
         assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */));
-        // The translucent stack should be visible since it is the top of the stack list even though
-        // it has its activity marked as finishing.
-        assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+        // The translucent stack shouldn't be visible since its activity marked as finishing.
+        assertFalse(translucentStack.shouldBeVisible(null /* starting */));
     }
 
     @Test
@@ -672,9 +670,10 @@
 
         // There is still an activity1 in stack1 so the activity2 should be added to finishing list
         // that will be destroyed until idle.
+        stack2.getTopActivity().visible = true;
         final ActivityRecord activity2 = finishCurrentActivity(stack2);
-        assertEquals(FINISHING, activity2.getState());
-        assertTrue(mSupervisor.mFinishingActivities.contains(activity2));
+        assertEquals(STOPPING, activity2.getState());
+        assertTrue(mSupervisor.mStoppingActivities.contains(activity2));
 
         // The display becomes empty. Since there is no next activity to be idle, the activity
         // should be destroyed immediately with updating configuration to restore original state.
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 094241e..adf861b 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -39,14 +39,6 @@
 
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
-import android.content.pm.PackageManagerInternal;
-import com.android.server.uri.UriGrantsManagerInternal;
-import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.server.wm.DisplayWindowController;
-
-import org.junit.Rule;
-import org.mockito.invocation.InvocationOnMock;
-
 import android.app.IApplicationThread;
 import android.content.ComponentName;
 import android.content.Context;
@@ -54,6 +46,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
@@ -73,7 +66,10 @@
 import com.android.server.AppOpsService;
 import com.android.server.AttributeCache;
 import com.android.server.ServiceThread;
+import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.AppWindowContainerController;
+import com.android.server.wm.DisplayWindowController;
 import com.android.server.wm.PinnedStackWindowController;
 import com.android.server.wm.RootWindowContainerController;
 import com.android.server.wm.StackWindowController;
@@ -83,7 +79,9 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
 
 import java.io.File;
 import java.util.List;
@@ -608,23 +606,9 @@
         @Override
         <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
                 int stackId, boolean onTop) {
-            if (windowingMode == WINDOWING_MODE_PINNED) {
-                return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop) {
-                    @Override
-                    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-                        return new Rect(50, 50, 100, 100);
-                    }
-
-                    @Override
-                    PinnedStackWindowController createStackWindowController(int displayId,
-                            boolean onTop, Rect outBounds) {
-                        return mock(PinnedStackWindowController.class);
-                    }
-                };
-            } else {
-                return (T) new TestActivityStack(
-                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
-            }
+            return new StackBuilder(mSupervisor).setDisplay(this)
+                    .setWindowingMode(windowingMode).setActivityType(activityType)
+                    .setStackId(stackId).setOnTop(onTop).setCreateActivity(false).build();
         }
 
         @Override
@@ -677,8 +661,19 @@
         private int mSupportsSplitScreen = SUPPORTS_SPLIT_SCREEN_UNSET;
 
         TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
-                int windowingMode, int activityType, boolean onTop) {
+                int windowingMode, int activityType, boolean onTop, boolean createActivity) {
             super(display, stackId, supervisor, windowingMode, activityType, onTop);
+            if (createActivity) {
+                new ActivityBuilder(mService).setCreateTask(true).setStack(this).build();
+                if (onTop) {
+                    // We move the task to front again in order to regain focus after activity
+                    // added to the stack. Or {@link ActivityDisplay#mPreferredTopFocusableStack}
+                    // could be other stacks (e.g. home stack).
+                    moveToFront("createActivityStack");
+                } else {
+                    moveToBack("createActivityStack", null);
+                }
+            }
         }
 
         @Override
@@ -750,4 +745,71 @@
                                  ActivityOptions options) {
         }
     }
+
+    protected static class StackBuilder {
+        private final ActivityStackSupervisor mSupervisor;
+        private ActivityDisplay mDisplay;
+        private int mStackId = -1;
+        private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
+        private int mActivityType = ACTIVITY_TYPE_STANDARD;
+        private boolean mOnTop = true;
+        private boolean mCreateActivity = true;
+
+        StackBuilder(ActivityStackSupervisor supervisor) {
+            mSupervisor = supervisor;
+            mDisplay = mSupervisor.getDefaultDisplay();
+        }
+
+        StackBuilder setWindowingMode(int windowingMode) {
+            mWindowingMode = windowingMode;
+            return this;
+        }
+
+        StackBuilder setActivityType(int activityType) {
+            mActivityType = activityType;
+            return this;
+        }
+
+        StackBuilder setStackId(int stackId) {
+            mStackId = stackId;
+            return this;
+        }
+
+        StackBuilder setDisplay(ActivityDisplay display) {
+            mDisplay = display;
+            return this;
+        }
+
+        StackBuilder setOnTop(boolean onTop) {
+            mOnTop = onTop;
+            return this;
+        }
+
+        StackBuilder setCreateActivity(boolean createActivity) {
+            mCreateActivity = createActivity;
+            return this;
+        }
+
+        <T extends ActivityStack> T build() {
+            final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId();
+            if (mWindowingMode == WINDOWING_MODE_PINNED) {
+                return (T) new PinnedActivityStack(mDisplay, stackId, mSupervisor, mOnTop) {
+                    @Override
+                    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
+                        return new Rect(50, 50, 100, 100);
+                    }
+
+                    @Override
+                    PinnedStackWindowController createStackWindowController(int displayId,
+                            boolean onTop, Rect outBounds) {
+                        return mock(PinnedStackWindowController.class);
+                    }
+                };
+            } else {
+                return (T) new TestActivityStack(mDisplay, stackId, mSupervisor, mWindowingMode,
+                        mActivityType, mOnTop, mCreateActivity);
+            }
+        }
+
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index 27e8c63..129b835 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -883,7 +883,7 @@
 
         MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) {
             super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN,
-                    ACTIVITY_TYPE_STANDARD, true);
+                    ACTIVITY_TYPE_STANDARD, true /* onTop */, false /* createActivity */);
             mDisplay = display;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
index aa3046f..849a411 100644
--- a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
@@ -16,9 +16,7 @@
 
 package com.android.server.am;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -30,7 +28,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
-import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
@@ -72,8 +69,8 @@
 
         final int numStacks = 2;
         for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
-            final ActivityStack stack = new TestActivityStack(display, stackIndex, mSupervisor,
-                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true);
+            final ActivityStack stack =
+                    new StackBuilder(mSupervisor).setCreateActivity(false).build();
             display.addChild(stack, POSITION_BOTTOM);
         }
 
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 047addd..793d6b0 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -61,6 +61,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 MAX_OBSERVER_PER_UID = 10;
     private static final long MIN_TIME_LIMIT = 4_000L;
@@ -77,7 +78,8 @@
             PKG_GAME1, PKG_GAME2
     };
 
-    private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+    private CountDownLatch mLimitReachedLatch = new CountDownLatch(1);
+    private CountDownLatch mSessionEndLatch = new CountDownLatch(1);
 
     private AppTimeLimitController mController;
 
@@ -85,18 +87,24 @@
 
     private long mUptimeMillis;
 
-    AppTimeLimitController.OnLimitReachedListener mListener
-            = new AppTimeLimitController.OnLimitReachedListener() {
+    AppTimeLimitController.TimeLimitCallbackListener mListener =
+            new AppTimeLimitController.TimeLimitCallbackListener() {
+                @Override
+                public void onLimitReached(int observerId, int userId, long timeLimit,
+                        long timeElapsed,
+                        PendingIntent callbackIntent) {
+                    mLimitReachedLatch.countDown();
+                }
 
-        @Override
-        public void onLimitReached(int observerId, int userId, long timeLimit, long timeElapsed,
-                PendingIntent callbackIntent) {
-            mCountDownLatch.countDown();
-        }
-    };
+                @Override
+                public void onSessionEnd(int observerId, int userId, long timeElapsed,
+                        PendingIntent callbackIntent) {
+                    mSessionEndLatch.countDown();
+                }
+            };
 
     class MyAppTimeLimitController extends AppTimeLimitController {
-        MyAppTimeLimitController(AppTimeLimitController.OnLimitReachedListener listener,
+        MyAppTimeLimitController(AppTimeLimitController.TimeLimitCallbackListener listener,
                 Looper looper) {
             super(listener, looper);
         }
@@ -107,7 +115,12 @@
         }
 
         @Override
-        protected long getObserverPerUidLimit() {
+        protected long getAppUsageObserverPerUidLimit() {
+            return MAX_OBSERVER_PER_UID;
+        }
+
+        @Override
+        protected long getUsageSessionObserverPerUidLimit() {
             return MAX_OBSERVER_PER_UID;
         }
 
@@ -129,188 +142,551 @@
         mThread.quit();
     }
 
-    /** Verify observer is added */
+    /** Verify app usage observer is added */
     @Test
-    public void testAddObserver() {
-        addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
-        addObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
-        assertTrue("Observer wasn't added", hasObserver(OBS_ID2));
-        assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
+    public void testAppUsageObserver_AddObserver() {
+        addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+        addAppUsageObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID2));
+        assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
     }
 
-    /** Verify observer is removed */
+    /** Verify usage session observer is added */
     @Test
-    public void testRemoveObserver() {
-        addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
-        mController.removeObserver(UID, OBS_ID1, USER_ID);
-        assertFalse("Observer wasn't removed", hasObserver(OBS_ID1));
+    public void testUsageSessionObserver_AddObserver() {
+        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+        addUsageSessionObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_1_MIN);
+        assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2));
+        assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+    }
+
+    /** Verify app usage observer is removed */
+    @Test
+    public void testAppUsageObserver_RemoveObserver() {
+        addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+        mController.removeAppUsageObserver(UID, OBS_ID1, USER_ID);
+        assertFalse("Observer wasn't removed", hasAppUsageObserver(UID, OBS_ID1));
+    }
+
+    /** Verify usage session observer is removed */
+    @Test
+    public void testUsageSessionObserver_RemoveObserver() {
+        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+        mController.removeUsageSessionObserver(UID, OBS_ID1, USER_ID);
+        assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
     }
 
     /** Re-adding an observer should result in only one copy */
     @Test
-    public void testObserverReAdd() {
-        addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
-        addObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+    public void testAppUsageObserver_ObserverReAdd() {
+        addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+        addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
         assertTrue("Observer wasn't added",
-                mController.getObserverGroup(OBS_ID1, USER_ID).timeLimit == TIME_10_MIN);
-        mController.removeObserver(UID, OBS_ID1, USER_ID);
-        assertFalse("Observer wasn't removed", hasObserver(OBS_ID1));
+                mController.getAppUsageGroup(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+        mController.removeAppUsageObserver(UID, OBS_ID1, USER_ID);
+        assertFalse("Observer wasn't removed", hasAppUsageObserver(UID, OBS_ID1));
+    }
+
+    /** Re-adding an observer should result in only one copy */
+    @Test
+    public void testUsageSessionObserver_ObserverReAdd() {
+        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_10_MIN, TIME_1_MIN);
+        assertTrue("Observer wasn't added",
+                mController.getSessionUsageGroup(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+        mController.removeUsageSessionObserver(UID, OBS_ID1, USER_ID);
+        assertFalse("Observer wasn't removed", hasUsageSessionObserver(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);
+        assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+        assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+
+        AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID,
+                OBS_ID1);
+        AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID,
+                OBS_ID1);
+
+        // Verify data still intact
+        assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs());
+        assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs());
     }
 
     /** Verify that usage across different apps within a group are added up */
     @Test
-    public void testAccumulation() throws Exception {
+    public void testAppUsageObserver_Accumulation() throws Exception {
         setTime(0L);
-        addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        moveToForeground(PKG_SOC1);
+        addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        startUsage(PKG_SOC1);
         // Add 10 mins
         setTime(TIME_10_MIN);
-        moveToBackground(PKG_SOC1);
+        stopUsage(PKG_SOC1);
 
-        long timeRemaining = mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining;
+        AppTimeLimitController.UsageGroup group = mController.getAppUsageGroup(UID, OBS_ID1);
+
+        long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
         assertEquals(TIME_10_MIN * 2, timeRemaining);
 
-        moveToForeground(PKG_SOC1);
+        startUsage(PKG_SOC1);
         setTime(TIME_10_MIN * 2);
-        moveToBackground(PKG_SOC1);
+        stopUsage(PKG_SOC1);
 
-        timeRemaining = mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining;
+        timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
         assertEquals(TIME_10_MIN, timeRemaining);
 
         setTime(TIME_30_MIN);
 
-        assertFalse(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS));
+        assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
 
         // Add a different package in the group
-        moveToForeground(PKG_GAME1);
+        startUsage(PKG_GAME1);
         setTime(TIME_30_MIN + TIME_10_MIN);
-        moveToBackground(PKG_GAME1);
+        stopUsage(PKG_GAME1);
 
-        assertEquals(0, mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining);
-        assertTrue(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS));
+        assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+        assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+    }
+
+    /** Verify that usage across different apps within a group are added up */
+    @Test
+    public void testUsageSessionObserver_Accumulation() throws Exception {
+        setTime(0L);
+        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        startUsage(PKG_SOC1);
+        // Add 10 mins
+        setTime(TIME_10_MIN);
+        stopUsage(PKG_SOC1);
+
+        AppTimeLimitController.UsageGroup group = mController.getSessionUsageGroup(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 testTimeoutOtherApp() throws Exception {
+    public void testAppUsageObserver_TimeoutOtherApp() throws Exception {
         setTime(0L);
-        addObserver(OBS_ID1, GROUP1, 4_000L);
-        moveToForeground(PKG_SOC2);
-        assertFalse(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        addAppUsageObserver(OBS_ID1, GROUP1, 4_000L);
+        startUsage(PKG_SOC2);
+        assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
         setTime(6_000L);
-        moveToBackground(PKG_SOC2);
-        assertFalse(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_SOC2);
+        assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+    }
+
+    /** Verify that time limit does not get triggered due to a different app */
+    @Test
+    public void testUsageSessionObserver_TimeoutOtherApp() throws Exception {
+        setTime(0L);
+        addUsageSessionObserver(OBS_ID1, GROUP1, 4_000L, 1_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 testTimeout() throws Exception {
+    public void testAppUsageObserver_Timeout() throws Exception {
         setTime(0L);
-        addObserver(OBS_ID1, GROUP1, 4_000L);
-        moveToForeground(PKG_SOC1);
+        addAppUsageObserver(OBS_ID1, GROUP1, 4_000L);
+        startUsage(PKG_SOC1);
         setTime(6_000L);
-        assertTrue(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS));
-        moveToBackground(PKG_SOC1);
+        assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_SOC1);
         // Verify that the observer was removed
-        assertFalse(hasObserver(OBS_ID1));
+        assertFalse(hasAppUsageObserver(UID, OBS_ID1));
+    }
+
+    /** Verify the timeout message is delivered at the right time */
+    @Test
+    public void testUsageSessionObserver_Timeout() throws Exception {
+        setTime(0L);
+        addUsageSessionObserver(OBS_ID1, GROUP1, 4_000L, 1_000L);
+        startUsage(PKG_SOC1);
+        setTime(6_000L);
+        assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_SOC1);
+        // Usage has stopped, Session should end in a second. Verify session end occurs in a second
+        // (+/- 100ms, which is hopefully not too slim a margin)
+        assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+        assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+        // Verify that the observer was not removed
+        assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
     }
 
     /** If an app was already running, make sure it is partially counted towards the time limit */
     @Test
-    public void testAlreadyRunning() throws Exception {
+    public void testAppUsageObserver_AlreadyRunning() throws Exception {
         setTime(TIME_10_MIN);
-        moveToForeground(PKG_GAME1);
+        startUsage(PKG_GAME1);
         setTime(TIME_30_MIN);
-        addObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
         setTime(TIME_30_MIN + TIME_10_MIN);
-        moveToBackground(PKG_GAME1);
-        assertFalse(mCountDownLatch.await(1000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_GAME1);
+        assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
 
-        moveToForeground(PKG_GAME2);
+        startUsage(PKG_GAME2);
         setTime(TIME_30_MIN + TIME_30_MIN);
-        moveToBackground(PKG_GAME2);
-        assertTrue(mCountDownLatch.await(1000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_GAME2);
+        assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
         // Verify that the observer was removed
-        assertFalse(hasObserver(OBS_ID2));
+        assertFalse(hasAppUsageObserver(UID, OBS_ID2));
+    }
+
+    /** If an app was already running, make sure it is partially counted towards the time limit */
+    @Test
+    public void testUsageSessionObserver_AlreadyRunning() throws Exception {
+        setTime(TIME_10_MIN);
+        startUsage(PKG_GAME1);
+        setTime(TIME_30_MIN);
+        addUsageSessionObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_1_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 removed
+        assertTrue(hasUsageSessionObserver(UID, OBS_ID2));
     }
 
     /** If watched app is already running, verify the timeout callback happens at the right time */
     @Test
-    public void testAlreadyRunningTimeout() throws Exception {
+    public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception {
         setTime(0);
-        moveToForeground(PKG_SOC1);
+        startUsage(PKG_SOC1);
         setTime(TIME_10_MIN);
         // 10 second time limit
-        addObserver(OBS_ID1, GROUP_SOC, 10_000L);
+        addAppUsageObserver(OBS_ID1, GROUP_SOC, 10_000L);
         setTime(TIME_10_MIN + 5_000L);
         // Shouldn't call back in 6 seconds
-        assertFalse(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS));
+        assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
         setTime(TIME_10_MIN + 10_000L);
         // Should call back by 11 seconds (6 earlier + 5 now)
-        assertTrue(mCountDownLatch.await(5_000L, TimeUnit.MILLISECONDS));
+        assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
         // Verify that the observer was removed
-        assertFalse(hasObserver(OBS_ID1));
+        assertFalse(hasAppUsageObserver(UID, OBS_ID1));
     }
 
-    /** Verify that App Time Limit Controller will limit the number of observerIds */
+    /** If watched app is already running, verify the timeout callback happens at the right time */
     @Test
-    public void testMaxObserverLimit() throws Exception {
+    public void testUsageSessionObserver_AlreadyRunningTimeout() throws Exception {
+        setTime(0);
+        startUsage(PKG_SOC1);
+        setTime(TIME_10_MIN);
+        // 10 second time limit
+        addUsageSessionObserver(OBS_ID1, GROUP_SOC, 10_000L, 1_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));
+        stopUsage(PKG_SOC1);
+        // Usage has stopped, Session should end in a second. Verify session end occurs in a second
+        // (+/- 100ms, which is hopefully not too slim a margin)
+        assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+        assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+        // Verify that the observer was removed
+        assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+    }
+
+    /**
+     * Verify that App Time Limit Controller will limit the number of observerIds for app usage
+     * observers
+     */
+    @Test
+    public void testAppUsageObserver_MaxObserverLimit() throws Exception {
         boolean receivedException = false;
         int ANOTHER_UID = UID + 1;
-        addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        addObserver(OBS_ID2, GROUP1, TIME_30_MIN);
-        addObserver(OBS_ID3, GROUP1, TIME_30_MIN);
-        addObserver(OBS_ID4, GROUP1, TIME_30_MIN);
-        addObserver(OBS_ID5, GROUP1, TIME_30_MIN);
-        addObserver(OBS_ID6, GROUP1, TIME_30_MIN);
-        addObserver(OBS_ID7, GROUP1, TIME_30_MIN);
-        addObserver(OBS_ID8, GROUP1, TIME_30_MIN);
-        addObserver(OBS_ID9, GROUP1, TIME_30_MIN);
-        addObserver(OBS_ID10, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID2, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID3, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID4, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID6, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID7, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID8, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID9, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID10, GROUP1, TIME_30_MIN);
         // Readding an observer should not cause an IllegalStateException
-        addObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+        addAppUsageObserver(OBS_ID5, GROUP1, TIME_30_MIN);
         // Adding an observer for a different uid shouldn't cause an IllegalStateException
-        mController.addObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
+        mController.addAppUsageObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
         try {
-            addObserver(OBS_ID11, GROUP1, TIME_30_MIN);
+            addAppUsageObserver(OBS_ID11, GROUP1, TIME_30_MIN);
         } catch (IllegalStateException ise) {
             receivedException = true;
         }
         assertTrue("Should have caused an IllegalStateException", receivedException);
     }
 
-    /** Verify that addObserver minimum time limit is one minute */
+    /**
+     * Verify that App Time Limit Controller will limit the number of observerIds for usage session
+     * observers
+     */
     @Test
-    public void testMinimumTimeLimit() throws Exception {
+    public void testUsageSessionObserver_MaxObserverLimit() throws Exception {
+        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        boolean receivedException = false;
+        int ANOTHER_UID = UID + 1;
+        addUsageSessionObserver(OBS_ID2, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID3, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID4, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID6, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID7, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID8, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID9, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        addUsageSessionObserver(OBS_ID10, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        // Readding an observer should not cause an IllegalStateException
+        addUsageSessionObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        // Adding an observer for a different uid shouldn't cause an IllegalStateException
+        mController.addUsageSessionObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, TIME_1_MIN,
+                null, null, USER_ID);
+        try {
+            addUsageSessionObserver(OBS_ID11, GROUP1, TIME_30_MIN, TIME_1_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 {
         boolean receivedException = false;
         // adding an observer with a one minute time limit should not cause an exception
-        addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
+        addAppUsageObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
         try {
-            addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
+            addAppUsageObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
         } catch (IllegalArgumentException iae) {
             receivedException = true;
         }
         assertTrue("Should have caused an IllegalArgumentException", receivedException);
     }
 
-    private void moveToForeground(String packageName) {
-        mController.moveToForeground(packageName, "class", USER_ID);
+    /** Verify that addUsageSessionObserver minimum time limit is one minute */
+    @Test
+    public void testUsageSessionObserver_MinimumTimeLimit() throws Exception {
+        boolean receivedException = false;
+        // test also for session observers
+        addUsageSessionObserver(OBS_ID10, GROUP1, MIN_TIME_LIMIT, TIME_1_MIN);
+        try {
+            addUsageSessionObserver(OBS_ID10, GROUP1, MIN_TIME_LIMIT - 1, TIME_1_MIN);
+        } catch (IllegalArgumentException iae) {
+            receivedException = true;
+        }
+        assertTrue("Should have caused an IllegalArgumentException", receivedException);
     }
 
-    private void moveToBackground(String packageName) {
-        mController.moveToBackground(packageName, "class", USER_ID);
+    /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+    @Test
+    public void testAppUsageObserver_ConcurrentUsage() throws Exception {
+        setTime(0L);
+        addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+        AppTimeLimitController.UsageGroup group = mController.getAppUsageGroup(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));
     }
 
-    private void addObserver(int observerId, String[] packages, long timeLimit) {
-        mController.addObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+    /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+    @Test
+    public void testUsageSessionObserver_ConcurrentUsage() throws Exception {
+        setTime(0L);
+        addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+        AppTimeLimitController.UsageGroup group = mController.getSessionUsageGroup(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));
     }
 
-    /** Is there still an observer by that id */
-    private boolean hasObserver(int observerId) {
-        return mController.getObserverGroup(observerId, USER_ID) != null;
+    /** Verify that a session will continue if usage starts again within the session threshold */
+    @Test
+    public void testUsageSessionObserver_ContinueSession() throws Exception {
+        setTime(0L);
+        addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 2_000L);
+        startUsage(PKG_SOC1);
+        setTime(6_000L);
+        stopUsage(PKG_SOC1);
+        // Wait momentarily, Session should not end
+        assertFalse(mSessionEndLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+        setTime(7_000L);
+        startUsage(PKG_SOC1);
+        setTime(10_500L);
+        stopUsage(PKG_SOC1);
+        // Total usage time has not reached the limit. Time limit callback should not fire yet
+        assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+        setTime(10_600L);
+        startUsage(PKG_SOC1);
+        setTime(12_000L);
+        assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_SOC1);
+        // Usage has stopped, Session should end in 2 seconds. Verify session end occurs
+        // (+/- 100ms, which is hopefully not too slim a margin)
+        assertFalse(mSessionEndLatch.await(1_900L, TimeUnit.MILLISECONDS));
+        assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+        // Verify that the observer was not removed
+        assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+    }
+
+    /** Verify that a new session will start if next usage starts after the session threshold */
+    @Test
+    public void testUsageSessionObserver_NewSession() throws Exception {
+        setTime(0L);
+        addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 1_000L);
+        startUsage(PKG_SOC1);
+        setTime(6_000L);
+        stopUsage(PKG_SOC1);
+        // Wait for longer than the session threshold. Session end callback should not be triggered
+        // because the usage timelimit hasn't been triggered.
+        assertFalse(mSessionEndLatch.await(1_500L, TimeUnit.MILLISECONDS));
+
+        setTime(7_500L);
+        // This should be the start of a new session
+        startUsage(PKG_SOC1);
+        setTime(16_000L);
+        stopUsage(PKG_SOC1);
+        // Total usage has exceed the timelimit, but current session time has not
+        assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+        setTime(16_100L);
+        startUsage(PKG_SOC1);
+        setTime(18_000L);
+        assertTrue(mLimitReachedLatch.await(2000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_SOC1);
+        // Usage has stopped, Session should end in 2 seconds. Verify session end occurs
+        // (+/- 100ms, which is hopefully not too slim a margin)
+        assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+        assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+        // Verify that the observer was not removed
+        assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+    }
+
+    /** Verify that the callbacks will be triggered for multiple sessions */
+    @Test
+    public void testUsageSessionObserver_RepeatSessions() throws Exception {
+        setTime(0L);
+        addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 1_000L);
+        startUsage(PKG_SOC1);
+        setTime(9_000L);
+        stopUsage(PKG_SOC1);
+        // Stutter usage here, to reduce real world time needed trigger limit reached callback
+        startUsage(PKG_SOC1);
+        setTime(11_000L);
+        assertTrue(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_SOC1);
+        // Usage has stopped, Session should end in 1 seconds. Verify session end occurs
+        // (+/- 100ms, which is hopefully not too slim a margin)
+        assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+        assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+
+        // Rearm the countdown latches
+        mLimitReachedLatch = new CountDownLatch(1);
+        mSessionEndLatch = new CountDownLatch(1);
+
+        // New session start
+        setTime(20_000L);
+        startUsage(PKG_SOC1);
+        setTime(29_000L);
+        stopUsage(PKG_SOC1);
+        startUsage(PKG_SOC1);
+        setTime(31_000L);
+        assertTrue(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS));
+        stopUsage(PKG_SOC1);
+        assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+        assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+        assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+    }
+
+    private void startUsage(String packageName) {
+        mController.noteUsageStart(packageName, USER_ID);
+    }
+
+    private void stopUsage(String packageName) {
+        mController.noteUsageStop(packageName, USER_ID);
+    }
+
+    private void addAppUsageObserver(int observerId, String[] packages, long timeLimit) {
+        mController.addAppUsageObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+    }
+
+    private void addUsageSessionObserver(int observerId, String[] packages, long timeLimit,
+            long sessionThreshold) {
+        mController.addUsageSessionObserver(UID, observerId, packages, timeLimit, sessionThreshold,
+                null, 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;
+    }
+
+    /** Is there still an usage session observer by that id */
+    private boolean hasUsageSessionObserver(int uid, int observerId) {
+        return mController.getSessionUsageGroup(uid, observerId) != null;
     }
 
     private void setTime(long time) {
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 5916b04c..eaaf9b2 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -22,17 +22,16 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
-import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -57,72 +56,432 @@
 
     private final MyHandler mHandler;
 
-    private OnLimitReachedListener mListener;
+    private TimeLimitCallbackListener mListener;
 
     private static final long MAX_OBSERVER_PER_UID = 1000;
 
     private static final long ONE_MINUTE = 60_000L;
 
+    /** Collection of data for each user that has reported usage */
     @GuardedBy("mLock")
     private final SparseArray<UserData> mUsers = new SparseArray<>();
 
-    private static class UserData {
+    /**
+     * Collection of data for each app that is registering observers
+     * WARNING: Entries are currently not removed, based on the assumption there are a small
+     *          fixed number of apps on device that can register observers.
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<ObserverAppData> mObserverApps = new SparseArray<>();
+
+    private class UserData {
         /** userId of the user */
-        private @UserIdInt int userId;
+        private @UserIdInt
+        int userId;
 
-        /** The app that is currently in the foreground */
-        private String currentForegroundedPackage;
+        /** Set of the currently active entities */
+        private final ArraySet<String> currentlyActive = new ArraySet<>();
 
-        /** The time when the current app came to the foreground */
-        private long currentForegroundedTime;
-
-        /** Map from package name for quick lookup */
-        private ArrayMap<String, ArrayList<TimeLimitGroup>> packageMap = new ArrayMap<>();
-
-        /** Map of observerId to details of the time limit group */
-        private SparseArray<TimeLimitGroup> groups = new SparseArray<>();
-
-        /** Map of the number of observerIds registered by uid */
-        private SparseIntArray observerIdCounts = new SparseIntArray();
+        /** Map from entity name for quick lookup */
+        private final ArrayMap<String, ArrayList<UsageGroup>> observedMap = new ArrayMap<>();
 
         private UserData(@UserIdInt int userId) {
             this.userId = userId;
         }
+
+        @GuardedBy("mLock")
+        boolean isActive(String[] entities) {
+            // TODO: Consider using a bloom filter here if number of actives becomes large
+            final int size = entities.length;
+            for (int i = 0; i < size; i++) {
+                if (currentlyActive.contains(entities[i])) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @GuardedBy("mLock")
+        void addUsageGroup(UsageGroup group) {
+            final int size = group.mObserved.length;
+            for (int i = 0; i < size; i++) {
+                ArrayList<UsageGroup> list = observedMap.get(group.mObserved[i]);
+                if (list == null) {
+                    list = new ArrayList<>();
+                    observedMap.put(group.mObserved[i], list);
+                }
+                list.add(group);
+            }
+        }
+
+        @GuardedBy("mLock")
+        void removeUsageGroup(UsageGroup group) {
+            final int size = group.mObserved.length;
+            for (int i = 0; i < size; i++) {
+                final ArrayList<UsageGroup> list = observedMap.get(group.mObserved[i]);
+                if (list != null) {
+                    list.remove(group);
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        void dump(PrintWriter pw) {
+            pw.print(" userId=");
+            pw.println(userId);
+            pw.print(" Currently Active:");
+            final int nActive = currentlyActive.size();
+            for (int i = 0; i < nActive; i++) {
+                pw.print(currentlyActive.valueAt(i));
+                pw.print(", ");
+            }
+            pw.println();
+            pw.print(" Observed Entities:");
+            final int nEntities = currentlyActive.size();
+            for (int i = 0; i < nEntities; i++) {
+                pw.print(observedMap.keyAt(i));
+                pw.print(", ");
+            }
+            pw.println();
+        }
+    }
+
+
+    private class ObserverAppData {
+        /** uid of the observing app */
+        private int uid;
+
+        /** Map of observerId to details of the time limit group */
+        SparseArray<AppUsageGroup> appUsageGroups = new SparseArray<>();
+
+        /** Map of observerId to details of the time limit group */
+        SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>();
+
+        private ObserverAppData(int uid) {
+            this.uid = uid;
+        }
+
+        @GuardedBy("mLock")
+        void removeAppUsageGroup(int observerId) {
+            appUsageGroups.remove(observerId);
+        }
+
+        @GuardedBy("mLock")
+        void removeSessionUsageGroup(int observerId) {
+            sessionUsageGroups.remove(observerId);
+        }
+
+
+        @GuardedBy("mLock")
+        void dump(PrintWriter pw) {
+            pw.print(" uid=");
+            pw.println(uid);
+            pw.println("    App Usage Groups:");
+            final int nAppUsageGroups = appUsageGroups.size();
+            for (int i = 0; i < nAppUsageGroups; i++) {
+                appUsageGroups.valueAt(i).dump(pw);
+                pw.println();
+            }
+            pw.println("    Session Usage Groups:");
+            final int nSessionUsageGroups = appUsageGroups.size();
+            for (int i = 0; i < nSessionUsageGroups; i++) {
+                sessionUsageGroups.valueAt(i).dump(pw);
+                pw.println();
+            }
+        }
     }
 
     /**
      * Listener interface for being informed when an app group's time limit is reached.
      */
-    public interface OnLimitReachedListener {
+    public interface TimeLimitCallbackListener {
         /**
          * Time limit for a group, keyed by the observerId, has been reached.
-         * @param observerId The observerId of the group whose limit was reached
-         * @param userId The userId
-         * @param timeLimit The original time limit in milliseconds
-         * @param timeElapsed How much time was actually spent on apps in the group, in milliseconds
+         *
+         * @param observerId     The observerId of the group whose limit was reached
+         * @param userId         The userId
+         * @param timeLimit      The original time limit in milliseconds
+         * @param timeElapsed    How much time was actually spent on apps in the group, in
+         *                       milliseconds
          * @param callbackIntent The PendingIntent to send when the limit is reached
          */
         public void onLimitReached(int observerId, @UserIdInt int userId, long timeLimit,
                 long timeElapsed, PendingIntent callbackIntent);
+
+        /**
+         * Session ended for a group, keyed by the observerId, after limit was reached.
+         *
+         * @param observerId     The observerId of the group whose limit was reached
+         * @param userId         The userId
+         * @param timeElapsed    How much time was actually spent on apps in the group, in
+         *                       milliseconds
+         * @param callbackIntent The PendingIntent to send when the limit is reached
+         */
+        public void onSessionEnd(int observerId, @UserIdInt int userId, long timeElapsed,
+                PendingIntent callbackIntent);
     }
 
-    static class TimeLimitGroup {
-        int requestingUid;
-        int observerId;
-        String[] packages;
-        long timeLimit;
-        long timeRequested;
-        long timeRemaining;
-        PendingIntent callbackIntent;
-        String currentPackage;
-        long timeCurrentPackageStarted;
-        int userId;
+    abstract class UsageGroup {
+        protected int mObserverId;
+        protected String[] mObserved;
+        protected long mTimeLimitMs;
+        protected long mUsageTimeMs;
+        protected int mActives;
+        protected long mLastKnownUsageTimeMs;
+        protected WeakReference<UserData> mUserRef;
+        protected WeakReference<ObserverAppData> mObserverAppRef;
+        protected PendingIntent mLimitReachedCallback;
+
+        UsageGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed,
+                long timeLimitMs, PendingIntent limitReachedCallback) {
+            mUserRef = new WeakReference<>(user);
+            mObserverAppRef = new WeakReference<>(observerApp);
+            mObserverId = observerId;
+            mObserved = observed;
+            mTimeLimitMs = timeLimitMs;
+            mLimitReachedCallback = limitReachedCallback;
+        }
+
+        @GuardedBy("mLock")
+        public long getTimeLimitMs() { return mTimeLimitMs; }
+
+        @GuardedBy("mLock")
+        public long getUsageTimeMs() { return mUsageTimeMs; }
+
+        @GuardedBy("mLock")
+        public void remove() {
+            UserData user = mUserRef.get();
+            if (user != null) {
+                user.removeUsageGroup(this);
+            }
+            // Clear the callback, so any racy inflight message will do nothing
+            mLimitReachedCallback = null;
+        }
+
+        @GuardedBy("mLock")
+        void noteUsageStart(long startTimeMs) {
+            noteUsageStart(startTimeMs, startTimeMs);
+        }
+
+        @GuardedBy("mLock")
+        void noteUsageStart(long startTimeMs, long currentTimeMs) {
+            if (mActives++ == 0) {
+                mLastKnownUsageTimeMs = startTimeMs;
+                final long timeRemaining =
+                        mTimeLimitMs - mUsageTimeMs + currentTimeMs - startTimeMs;
+                if (timeRemaining > 0) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Posting timeout for " + mObserverId + " for "
+                                + timeRemaining + "ms");
+                    }
+                    postCheckTimeoutLocked(this, timeRemaining);
+                }
+            } else {
+                if (mActives > mObserved.length) {
+                    // Try to get to a sane state and log the issue
+                    mActives = mObserved.length;
+                    final UserData user = mUserRef.get();
+                    if (user == null) return;
+                    final Object[] array = user.currentlyActive.toArray();
+                    Slog.e(TAG,
+                            "Too many noted usage starts! Observed entities: " + Arrays.toString(
+                                    mObserved) + "   Active Entities: " + Arrays.toString(array));
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        void noteUsageStop(long stopTimeMs) {
+            if (--mActives == 0) {
+                final boolean limitNotCrossed = mUsageTimeMs < mTimeLimitMs;
+                mUsageTimeMs += stopTimeMs - mLastKnownUsageTimeMs;
+                if (limitNotCrossed && mUsageTimeMs >= mTimeLimitMs) {
+                    // Crossed the limit
+                    if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + mObserverId);
+                    postInformLimitReachedListenerLocked(this);
+                }
+                cancelCheckTimeoutLocked(this);
+            } else {
+                if (mActives < 0) {
+                    // Try to get to a sane state and log the issue
+                    mActives = 0;
+                    final UserData user = mUserRef.get();
+                    if (user == null) return;
+                    final Object[] array = user.currentlyActive.toArray();
+                    Slog.e(TAG,
+                            "Too many noted usage stops! Observed entities: " + Arrays.toString(
+                                    mObserved) + "   Active Entities: " + Arrays.toString(array));
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        void checkTimeout(long currentTimeMs) {
+            final UserData user = mUserRef.get();
+            if (user == null) return;
+
+            long timeRemainingMs = mTimeLimitMs - mUsageTimeMs;
+
+            if (DEBUG) Slog.d(TAG, "checkTimeout timeRemaining=" + timeRemainingMs);
+
+            // Already reached the limit, no need to report again
+            if (timeRemainingMs <= 0) return;
+
+            if (DEBUG) {
+                Slog.d(TAG, "checkTimeout");
+            }
+
+            // Double check that at least one entity in this group is currently active
+            if (user.isActive(mObserved)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "checkTimeout group is active");
+                }
+                final long timeUsedMs = currentTimeMs - mLastKnownUsageTimeMs;
+                if (timeRemainingMs <= timeUsedMs) {
+                    if (DEBUG) Slog.d(TAG, "checkTimeout : Time limit reached");
+                    // Hit the limit, set timeRemaining to zero to avoid checking again
+                    mUsageTimeMs += timeUsedMs;
+                    mLastKnownUsageTimeMs = currentTimeMs;
+                    AppTimeLimitController.this.postInformLimitReachedListenerLocked(this);
+                } else {
+                    if (DEBUG) Slog.d(TAG, "checkTimeout : Some more time remaining");
+                    AppTimeLimitController.this.postCheckTimeoutLocked(this,
+                            timeRemainingMs - timeUsedMs);
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        public void onLimitReached() {
+            UserData user = mUserRef.get();
+            if (user == null) return;
+            if (mListener != null) {
+                mListener.onLimitReached(mObserverId, user.userId, mTimeLimitMs, mUsageTimeMs,
+                        mLimitReachedCallback);
+            }
+        }
+
+        @GuardedBy("mLock")
+        void dump(PrintWriter pw) {
+            pw.print("        Group id=");
+            pw.print(mObserverId);
+            pw.print(" timeLimit=");
+            pw.print(mTimeLimitMs);
+            pw.print(" used=");
+            pw.print(mUsageTimeMs);
+            pw.print(" lastKnownUsage=");
+            pw.print(mLastKnownUsageTimeMs);
+            pw.print(" mActives=");
+            pw.print(mActives);
+            pw.print(" observed=");
+            pw.print(Arrays.toString(mObserved));
+        }
     }
 
+    class AppUsageGroup extends UsageGroup {
+        public AppUsageGroup(UserData user, ObserverAppData observerApp, int observerId,
+                String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
+            super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+        }
+
+        @Override
+        @GuardedBy("mLock")
+        public void remove() {
+            super.remove();
+            ObserverAppData observerApp = mObserverAppRef.get();
+            if (observerApp != null) {
+                observerApp.removeAppUsageGroup(mObserverId);
+            }
+        }
+
+        @Override
+        @GuardedBy("mLock")
+        public void onLimitReached() {
+            super.onLimitReached();
+            // Unregister since the limit has been met and observer was informed.
+            remove();
+        }
+    }
+
+    class SessionUsageGroup extends UsageGroup {
+        private long mLastUsageEndTimeMs;
+        private long mNewSessionThresholdMs;
+        private PendingIntent mSessionEndCallback;
+
+        public SessionUsageGroup(UserData user, ObserverAppData observerApp, int observerId,
+                String[] observed, long timeLimitMs, PendingIntent limitReachedCallback,
+                long newSessionThresholdMs, PendingIntent sessionEndCallback) {
+            super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+            this.mNewSessionThresholdMs = newSessionThresholdMs;
+            this.mSessionEndCallback = sessionEndCallback;
+        }
+
+        @Override
+        @GuardedBy("mLock")
+        public void remove() {
+            super.remove();
+            ObserverAppData observerApp = mObserverAppRef.get();
+            if (observerApp != null) {
+                observerApp.removeSessionUsageGroup(mObserverId);
+            }
+            // Clear the callback, so any racy inflight messages will do nothing
+            mSessionEndCallback = null;
+        }
+
+        @Override
+        @GuardedBy("mLock")
+        public void noteUsageStart(long startTimeMs, long currentTimeMs) {
+            if (mActives == 0) {
+                if (startTimeMs - mLastUsageEndTimeMs > mNewSessionThresholdMs) {
+                    // New session has started, clear usage time.
+                    mUsageTimeMs = 0;
+                }
+                AppTimeLimitController.this.cancelInformSessionEndListener(this);
+            }
+            super.noteUsageStart(startTimeMs, currentTimeMs);
+        }
+
+        @Override
+        @GuardedBy("mLock")
+        public void noteUsageStop(long stopTimeMs) {
+            super.noteUsageStop(stopTimeMs);
+            if (mActives == 0) {
+                mLastUsageEndTimeMs = stopTimeMs;
+                if (mUsageTimeMs >= mTimeLimitMs) {
+                    // Usage has ended. Schedule the session end callback to be triggered once
+                    // the new session threshold has been reached
+                    AppTimeLimitController.this.postInformSessionEndListenerLocked(this,
+                            mNewSessionThresholdMs);
+                }
+
+            }
+        }
+
+        @GuardedBy("mLock")
+        public void onSessionEnd() {
+            UserData user = mUserRef.get();
+            if (user == null) return;
+            if (mListener != null) {
+                mListener.onSessionEnd(mObserverId, user.userId, mUsageTimeMs, mSessionEndCallback);
+            }
+        }
+
+        @Override
+        @GuardedBy("mLock")
+        void dump(PrintWriter pw) {
+            super.dump(pw);
+            pw.print(" lastUsageEndTime=");
+            pw.print(mLastUsageEndTimeMs);
+            pw.print(" newSessionThreshold=");
+            pw.print(mNewSessionThresholdMs);
+        }
+    }
+
+
     private class MyHandler extends Handler {
-
         static final int MSG_CHECK_TIMEOUT = 1;
-        static final int MSG_INFORM_LISTENER = 2;
+        static final int MSG_INFORM_LIMIT_REACHED_LISTENER = 2;
+        static final int MSG_INFORM_SESSION_END = 3;
 
         MyHandler(Looper looper) {
             super(looper);
@@ -132,10 +491,19 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_CHECK_TIMEOUT:
-                    checkTimeout((TimeLimitGroup) msg.obj);
+                    synchronized (mLock) {
+                        ((UsageGroup) msg.obj).checkTimeout(getUptimeMillis());
+                    }
                     break;
-                case MSG_INFORM_LISTENER:
-                    informListener((TimeLimitGroup) msg.obj);
+                case MSG_INFORM_LIMIT_REACHED_LISTENER:
+                    synchronized (mLock) {
+                        ((UsageGroup) msg.obj).onLimitReached();
+                    }
+                    break;
+                case MSG_INFORM_SESSION_END:
+                    synchronized (mLock) {
+                        ((SessionUsageGroup) msg.obj).onSessionEnd();
+                    }
                     break;
                 default:
                     super.handleMessage(msg);
@@ -144,7 +512,7 @@
         }
     }
 
-    public AppTimeLimitController(OnLimitReachedListener listener, Looper looper) {
+    public AppTimeLimitController(TimeLimitCallbackListener listener, Looper looper) {
         mHandler = new MyHandler(looper);
         mListener = listener;
     }
@@ -157,7 +525,13 @@
 
     /** Overrideable for testing purposes */
     @VisibleForTesting
-    protected long getObserverPerUidLimit() {
+    protected long getAppUsageObserverPerUidLimit() {
+        return MAX_OBSERVER_PER_UID;
+    }
+
+    /** Overrideable for testing purposes */
+    @VisibleForTesting
+    protected long getUsageSessionObserverPerUidLimit() {
         return MAX_OBSERVER_PER_UID;
     }
 
@@ -167,6 +541,21 @@
         return ONE_MINUTE;
     }
 
+    @VisibleForTesting
+    AppUsageGroup getAppUsageGroup(int observerAppUid, int observerId) {
+        synchronized (mLock) {
+            return getOrCreateObserverAppDataLocked(observerAppUid).appUsageGroups.get(observerId);
+        }
+    }
+
+    @VisibleForTesting
+    SessionUsageGroup getSessionUsageGroup(int observerAppUid, int observerId) {
+        synchronized (mLock) {
+            return getOrCreateObserverAppDataLocked(observerAppUid).sessionUsageGroups.get(
+                    observerId);
+        }
+    }
+
     /** Returns an existing UserData object for the given userId, or creates one */
     @GuardedBy("mLock")
     private UserData getOrCreateUserDataLocked(int userId) {
@@ -178,6 +567,17 @@
         return userData;
     }
 
+    /** Returns an existing ObserverAppData object for the given uid, or creates one */
+    @GuardedBy("mLock")
+    private ObserverAppData getOrCreateObserverAppDataLocked(int uid) {
+        ObserverAppData appData = mObserverApps.get(uid);
+        if (appData == null) {
+            appData = new ObserverAppData(uid);
+            mObserverApps.put(uid, appData);
+        }
+        return appData;
+    }
+
     /** Clean up data if user is removed */
     public void onUserRemoved(int userId) {
         synchronized (mLock) {
@@ -187,300 +587,219 @@
     }
 
     /**
-     * Registers an observer with the given details. Existing observer with the same observerId
-     * is removed.
+     * Check if group has any currently active entities.
      */
-    public void addObserver(int requestingUid, int observerId, String[] packages, long timeLimit,
-            PendingIntent callbackIntent, @UserIdInt int userId) {
+    @GuardedBy("mLock")
+    private void noteActiveLocked(UserData user, UsageGroup group, long currentTimeMs) {
+        // TODO: Consider using a bloom filter here if number of actives becomes large
+        final int size = group.mObserved.length;
+        for (int i = 0; i < size; i++) {
+            if (user.currentlyActive.contains(group.mObserved[i])) {
+                // Entity is currently active. Start group's usage.
+                group.noteUsageStart(currentTimeMs);
+            }
+        }
+    }
 
+    /**
+     * Registers an app usage observer with the given details.
+     * Existing app usage observer with the same observerId will be removed.
+     */
+    public void addAppUsageObserver(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);
-            removeObserverLocked(user, requestingUid, observerId, /*readding =*/ true);
-
-            final int observerIdCount = user.observerIdCounts.get(requestingUid, 0);
-            if (observerIdCount >= getObserverPerUidLimit()) {
-                throw new IllegalStateException(
-                        "Too many observers added by uid " + requestingUid);
+            ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+            AppUsageGroup group = observerApp.appUsageGroups.get(observerId);
+            if (group != null) {
+                // Remove previous app usage group associated with observerId
+                observerApp.appUsageGroups.get(observerId).remove();
             }
-            user.observerIdCounts.put(requestingUid, observerIdCount + 1);
 
-            TimeLimitGroup group = new TimeLimitGroup();
-            group.observerId = observerId;
-            group.callbackIntent = callbackIntent;
-            group.packages = packages;
-            group.timeLimit = timeLimit;
-            group.timeRemaining = group.timeLimit;
-            group.timeRequested = getUptimeMillis();
-            group.requestingUid = requestingUid;
-            group.timeCurrentPackageStarted = -1L;
-            group.userId = userId;
-
-            user.groups.append(observerId, group);
-
-            addGroupToPackageMapLocked(user, packages, group);
+            final int observerIdCount = observerApp.appUsageGroups.size();
+            if (observerIdCount >= getAppUsageObserverPerUidLimit()) {
+                throw new IllegalStateException(
+                        "Too many app usage observers added by uid " + requestingUid);
+            }
+            group = new AppUsageGroup(user, observerApp, observerId, observed, timeLimit,
+                    callbackIntent);
+            observerApp.appUsageGroups.append(observerId, group);
 
             if (DEBUG) {
-                Slog.d(TAG, "addObserver " + packages + " for " + timeLimit);
+                Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
             }
-            // Handle the case where a target package is already in the foreground when observer
-            // is added.
-            if (user.currentForegroundedPackage != null && inPackageList(group.packages,
-                    user.currentForegroundedPackage)) {
-                group.timeCurrentPackageStarted = group.timeRequested;
-                group.currentPackage = user.currentForegroundedPackage;
-                if (group.timeRemaining > 0) {
-                    postCheckTimeoutLocked(group, group.timeRemaining);
-                }
-            }
+
+            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
+     * @param observerId    The unique observer id for this user
+     * @param userId        The user id of the observer
      */
-    public void removeObserver(int requestingUid, int observerId, @UserIdInt int userId) {
+    public void removeAppUsageObserver(int requestingUid, int observerId, @UserIdInt int userId) {
+        synchronized (mLock) {
+            ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+            observerApp.appUsageGroups.get(observerId).remove();
+        }
+    }
+
+
+    /**
+     * Registers a usage session observer with the given details.
+     * Existing usage session observer with the same observerId will be removed.
+     */
+    public void addUsageSessionObserver(int requestingUid, int observerId, String[] observed,
+            long timeLimit, long sessionThresholdTime,
+            PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
+            @UserIdInt int userId) {
+        if (timeLimit < getMinTimeLimit()) {
+            throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
+        }
         synchronized (mLock) {
             UserData user = getOrCreateUserDataLocked(userId);
-            removeObserverLocked(user, requestingUid, observerId, /*readding =*/ false);
+            ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+            SessionUsageGroup group = observerApp.sessionUsageGroups.get(observerId);
+            if (group != null) {
+                // Remove previous app usage group associated with observerId
+                observerApp.sessionUsageGroups.get(observerId).remove();
+            }
+
+            final int observerIdCount = observerApp.sessionUsageGroups.size();
+            if (observerIdCount >= getUsageSessionObserverPerUidLimit()) {
+                throw new IllegalStateException(
+                        "Too many app usage observers added by uid " + requestingUid);
+            }
+            group = new SessionUsageGroup(user, observerApp, observerId, observed, timeLimit,
+                    limitReachedCallbackIntent, sessionThresholdTime, sessionEndCallbackIntent);
+            observerApp.sessionUsageGroups.append(observerId, group);
+
+            user.addUsageGroup(group);
+            noteActiveLocked(user, group, getUptimeMillis());
         }
     }
 
-    @VisibleForTesting
-    TimeLimitGroup getObserverGroup(int observerId, int userId) {
+    /**
+     * 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 removeUsageSessionObserver(int requestingUid, int observerId,
+            @UserIdInt int userId) {
         synchronized (mLock) {
-            return getOrCreateUserDataLocked(userId).groups.get(observerId);
+            ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+            observerApp.sessionUsageGroups.get(observerId).remove();
         }
     }
 
-    private static boolean inPackageList(String[] packages, String packageName) {
-        return ArrayUtils.contains(packages, packageName);
+    /**
+     * Called when an entity becomes active.
+     *
+     * @param name   The entity that became active
+     * @param userId The user
+     */
+    public void noteUsageStart(String name, int userId) throws IllegalArgumentException {
+        synchronized (mLock) {
+            UserData user = getOrCreateUserDataLocked(userId);
+            if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became active");
+            if (user.currentlyActive.contains(name)) {
+                throw new IllegalArgumentException(
+                        "Unable to start usage for " + name + ", already in use");
+            }
+            final long currentTime = getUptimeMillis();
+
+            // Add to the list of active entities
+            user.currentlyActive.add(name);
+
+            ArrayList<UsageGroup> groups = user.observedMap.get(name);
+            if (groups == null) return;
+
+            final int size = groups.size();
+            for (int i = 0; i < size; i++) {
+                UsageGroup group = groups.get(i);
+                group.noteUsageStart(currentTime);
+            }
+        }
+    }
+
+    /**
+     * Called when an entity becomes inactive.
+     *
+     * @param name   The entity that became inactive
+     * @param userId The user
+     */
+    public void noteUsageStop(String name, int userId) throws IllegalArgumentException {
+        synchronized (mLock) {
+            UserData user = getOrCreateUserDataLocked(userId);
+            if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became inactive");
+            if (!user.currentlyActive.remove(name)) {
+                throw new IllegalArgumentException(
+                        "Unable to stop usage for " + name + ", not in use");
+            }
+            final long currentTime = getUptimeMillis();
+
+            // Check if any of the groups need to watch for this entity
+            ArrayList<UsageGroup> groups = user.observedMap.get(name);
+            if (groups == null) return;
+
+            final int size = groups.size();
+            for (int i = 0; i < size; i++) {
+                UsageGroup group = groups.get(i);
+                group.noteUsageStop(currentTime);
+            }
+        }
     }
 
     @GuardedBy("mLock")
-    private void removeObserverLocked(UserData user, int requestingUid, int observerId,
-            boolean readding) {
-        TimeLimitGroup group = user.groups.get(observerId);
-        if (group != null && group.requestingUid == requestingUid) {
-            removeGroupFromPackageMapLocked(user, group);
-            user.groups.remove(observerId);
-            mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
-            final int observerIdCount = user.observerIdCounts.get(requestingUid);
-            if (observerIdCount <= 1 && !readding) {
-                user.observerIdCounts.delete(requestingUid);
-            } else {
-                user.observerIdCounts.put(requestingUid, observerIdCount - 1);
-            }
-        }
-    }
-
-    /**
-     * Called when an app has moved to the foreground.
-     * @param packageName The app that is foregrounded
-     * @param className The className of the activity
-     * @param userId The user
-     */
-    public void moveToForeground(String packageName, String className, int userId) {
-        synchronized (mLock) {
-            UserData user = getOrCreateUserDataLocked(userId);
-            if (DEBUG) Slog.d(TAG, "Setting mCurrentForegroundedPackage to " + packageName);
-            // Note the current foreground package
-            user.currentForegroundedPackage = packageName;
-            user.currentForegroundedTime = getUptimeMillis();
-
-            // Check if any of the groups need to watch for this package
-            maybeWatchForPackageLocked(user, packageName, user.currentForegroundedTime);
-        }
-    }
-
-    /**
-     * Called when an app is sent to the background.
-     *
-     * @param packageName
-     * @param className
-     * @param userId
-     */
-    public void moveToBackground(String packageName, String className, int userId) {
-        synchronized (mLock) {
-            UserData user = getOrCreateUserDataLocked(userId);
-            if (!TextUtils.equals(user.currentForegroundedPackage, packageName)) {
-                Slog.w(TAG, "Eh? Last foregrounded package = " + user.currentForegroundedPackage
-                        + " and now backgrounded = " + packageName);
-                return;
-            }
-            final long stopTime = getUptimeMillis();
-
-            // Add up the usage time to all groups that contain the package
-            ArrayList<TimeLimitGroup> groups = user.packageMap.get(packageName);
-            if (groups != null) {
-                final int size = groups.size();
-                for (int i = 0; i < size; i++) {
-                    final TimeLimitGroup group = groups.get(i);
-                    // Don't continue to send
-                    if (group.timeRemaining <= 0) continue;
-
-                    final long startTime = Math.max(user.currentForegroundedTime,
-                            group.timeRequested);
-                    long diff = stopTime - startTime;
-                    group.timeRemaining -= diff;
-                    if (group.timeRemaining <= 0) {
-                        if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + group.observerId);
-                        postInformListenerLocked(group);
-                    }
-                    // Reset indicators that observer was added when package was already fg
-                    group.currentPackage = null;
-                    group.timeCurrentPackageStarted = -1L;
-                    mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
-                }
-            }
-            user.currentForegroundedPackage = null;
-        }
-    }
-
-    private void postInformListenerLocked(TimeLimitGroup group) {
-        mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MSG_INFORM_LISTENER,
+    private void postInformLimitReachedListenerLocked(UsageGroup group) {
+        mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MSG_INFORM_LIMIT_REACHED_LISTENER,
                 group));
     }
 
-    /**
-     * Inform the observer and unregister it, as the limit has been reached.
-     * @param group the observed group
-     */
-    private void informListener(TimeLimitGroup group) {
-        if (mListener != null) {
-            mListener.onLimitReached(group.observerId, group.userId, group.timeLimit,
-                    group.timeLimit - group.timeRemaining, group.callbackIntent);
-        }
-        // Unregister since the limit has been met and observer was informed.
-        synchronized (mLock) {
-            UserData user = getOrCreateUserDataLocked(group.userId);
-            removeObserverLocked(user, group.requestingUid, group.observerId, false);
-        }
-    }
-
-    /** Check if any of the groups care about this package and set up delayed messages */
     @GuardedBy("mLock")
-    private void maybeWatchForPackageLocked(UserData user, String packageName, long uptimeMillis) {
-        ArrayList<TimeLimitGroup> groups = user.packageMap.get(packageName);
-        if (groups == null) return;
-
-        final int size = groups.size();
-        for (int i = 0; i < size; i++) {
-            TimeLimitGroup group = groups.get(i);
-            if (group.timeRemaining > 0) {
-                group.timeCurrentPackageStarted = uptimeMillis;
-                group.currentPackage = packageName;
-                if (DEBUG) {
-                    Slog.d(TAG, "Posting timeout for " + packageName + " for "
-                            + group.timeRemaining + "ms");
-                }
-                postCheckTimeoutLocked(group, group.timeRemaining);
-            }
-        }
+    private void postInformSessionEndListenerLocked(SessionUsageGroup group, long timeout) {
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_INFORM_SESSION_END, group),
+                timeout);
     }
 
-    private void addGroupToPackageMapLocked(UserData user, String[] packages,
-            TimeLimitGroup group) {
-        for (int i = 0; i < packages.length; i++) {
-            ArrayList<TimeLimitGroup> list = user.packageMap.get(packages[i]);
-            if (list == null) {
-                list = new ArrayList<>();
-                user.packageMap.put(packages[i], list);
-            }
-            list.add(group);
-        }
+    @GuardedBy("mLock")
+    private void cancelInformSessionEndListener(SessionUsageGroup group) {
+        mHandler.removeMessages(MyHandler.MSG_INFORM_SESSION_END, group);
     }
 
-    /**
-     * Remove the group reference from the package to group mapping, which is 1 to many.
-     * @param group The group to remove from the package map.
-     */
-    private void removeGroupFromPackageMapLocked(UserData user, TimeLimitGroup group) {
-        final int mapSize = user.packageMap.size();
-        for (int i = 0; i < mapSize; i++) {
-            ArrayList<TimeLimitGroup> list = user.packageMap.valueAt(i);
-            list.remove(group);
-        }
-    }
-
-    private void postCheckTimeoutLocked(TimeLimitGroup group, long timeout) {
+    @GuardedBy("mLock")
+    private void postCheckTimeoutLocked(UsageGroup group, long timeout) {
         mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_CHECK_TIMEOUT, group),
                 timeout);
     }
 
-    /**
-     * See if the given group has reached the timeout if the current foreground app is included
-     * and it exceeds the time remaining.
-     * @param group the group of packages to check
-     */
-    void checkTimeout(TimeLimitGroup group) {
-        // For each package in the group, check if any of the currently foregrounded apps are adding
-        // up to hit the limit and inform the observer
-        synchronized (mLock) {
-            UserData user = getOrCreateUserDataLocked(group.userId);
-            // This group doesn't exist anymore, nothing to see here.
-            if (user.groups.get(group.observerId) != group) return;
-
-            if (DEBUG) Slog.d(TAG, "checkTimeout timeRemaining=" + group.timeRemaining);
-
-            // Already reached the limit, no need to report again
-            if (group.timeRemaining <= 0) return;
-
-            if (DEBUG) {
-                Slog.d(TAG, "checkTimeout foregroundedPackage="
-                        + user.currentForegroundedPackage);
-            }
-
-            if (inPackageList(group.packages, user.currentForegroundedPackage)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "checkTimeout package in foreground="
-                            + user.currentForegroundedPackage);
-                }
-                if (group.timeCurrentPackageStarted < 0) {
-                    Slog.w(TAG, "startTime was not set correctly for " + group);
-                }
-                final long timeInForeground = getUptimeMillis() - group.timeCurrentPackageStarted;
-                if (group.timeRemaining <= timeInForeground) {
-                    if (DEBUG) Slog.d(TAG, "checkTimeout : Time limit reached");
-                    // Hit the limit, set timeRemaining to zero to avoid checking again
-                    group.timeRemaining -= timeInForeground;
-                    postInformListenerLocked(group);
-                    // Reset
-                    group.timeCurrentPackageStarted = -1L;
-                    group.currentPackage = null;
-                } else {
-                    if (DEBUG) Slog.d(TAG, "checkTimeout : Some more time remaining");
-                    postCheckTimeoutLocked(group, group.timeRemaining - timeInForeground);
-                }
-            }
-        }
+    @GuardedBy("mLock")
+    private void cancelCheckTimeoutLocked(UsageGroup group) {
+        mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
     }
 
     void dump(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("\n  App Time Limits");
-            int nUsers = mUsers.size();
+            final int nUsers = mUsers.size();
             for (int i = 0; i < nUsers; i++) {
-                UserData user = mUsers.valueAt(i);
-                pw.print("   User "); pw.println(user.userId);
-                int nGroups = user.groups.size();
-                for (int j = 0; j < nGroups; j++) {
-                    TimeLimitGroup group = user.groups.valueAt(j);
-                    pw.print("    Group id="); pw.print(group.observerId);
-                    pw.print(" timeLimit="); pw.print(group.timeLimit);
-                    pw.print(" remaining="); pw.print(group.timeRemaining);
-                    pw.print(" currentPackage="); pw.print(group.currentPackage);
-                    pw.print(" timeCurrentPkgStarted="); pw.print(group.timeCurrentPackageStarted);
-                    pw.print(" packages="); pw.println(Arrays.toString(group.packages));
-                }
-                pw.println();
-                pw.print("    currentForegroundedPackage=");
-                pw.println(user.currentForegroundedPackage);
+                pw.print("   User ");
+                mUsers.valueAt(i).dump(pw);
+            }
+            pw.println();
+            final int nObserverApps = mObserverApps.size();
+            for (int i = 0; i < nObserverApps; i++) {
+                pw.print("   Observer App ");
+                mObserverApps.valueAt(i).dump(pw);
             }
         }
     }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index dd1ddfa..2621252 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -165,16 +165,36 @@
         mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
 
         mAppTimeLimit = new AppTimeLimitController(
-                (observerId, userId, timeLimit, timeElapsed, callbackIntent) -> {
-                    Intent intent = new Intent();
-                    intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
-                    intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit);
-                    intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
-                    try {
-                        callbackIntent.send(getContext(), 0, intent);
-                    } catch (PendingIntent.CanceledException e) {
-                        Slog.w(TAG, "Couldn't deliver callback: "
-                                + callbackIntent);
+                new AppTimeLimitController.TimeLimitCallbackListener() {
+                    @Override
+                    public void onLimitReached(int observerId, int userId, long timeLimit,
+                            long timeElapsed, PendingIntent callbackIntent) {
+                        if (callbackIntent == null) return;
+                        Intent intent = new Intent();
+                        intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
+                        intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit);
+                        intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
+                        try {
+                            callbackIntent.send(getContext(), 0, intent);
+                        } catch (PendingIntent.CanceledException e) {
+                            Slog.w(TAG, "Couldn't deliver callback: "
+                                    + callbackIntent);
+                        }
+                    }
+
+                    @Override
+                    public void onSessionEnd(int observerId, int userId, long timeElapsed,
+                            PendingIntent callbackIntent) {
+                        if (callbackIntent == null) return;
+                        Intent intent = new Intent();
+                        intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
+                        intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
+                        try {
+                            callbackIntent.send(getContext(), 0, intent);
+                        } catch (PendingIntent.CanceledException e) {
+                            Slog.w(TAG, "Couldn't deliver callback: "
+                                    + callbackIntent);
+                        }
                     }
                 }, mHandler.getLooper());
 
@@ -412,12 +432,18 @@
             mAppStandby.reportEvent(event, elapsedRealtime, userId);
             switch (event.mEventType) {
                 case Event.MOVE_TO_FOREGROUND:
-                    mAppTimeLimit.moveToForeground(event.getPackageName(), event.getClassName(),
-                            userId);
+                    try {
+                        mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
+                    } catch (IllegalArgumentException iae) {
+                        Slog.e(TAG, "Failed to note usage start", iae);
+                    }
                     break;
                 case Event.MOVE_TO_BACKGROUND:
-                    mAppTimeLimit.moveToBackground(event.getPackageName(), event.getClassName(),
-                            userId);
+                    try {
+                        mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
+                    } catch (IllegalArgumentException iae) {
+                        Slog.e(TAG, "Failed to note usage stop", iae);
+                    }
                     break;
             }
         }
@@ -1151,16 +1177,70 @@
                 Binder.restoreCallingIdentity(token);
             }
         }
+
+        @Override
+        public void registerUsageSessionObserver(int sessionObserverId, String[] observed,
+                long timeLimitMs, long sessionThresholdTimeMs,
+                PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
+                String callingPackage) {
+            if (!hasObserverPermission(callingPackage)) {
+                throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+            }
+
+            if (observed == null || observed.length == 0) {
+                throw new IllegalArgumentException("Must specify at least one observed entity");
+            }
+            if (limitReachedCallbackIntent == null) {
+                throw new NullPointerException("limitReachedCallbackIntent can't be null");
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(callingUid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                UsageStatsService.this.registerUsageSessionObserver(callingUid, sessionObserverId,
+                        observed, timeLimitMs, sessionThresholdTimeMs, limitReachedCallbackIntent,
+                        sessionEndCallbackIntent, userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) {
+            if (!hasObserverPermission(callingPackage)) {
+                throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+            }
+
+            final int callingUid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(callingUid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                UsageStatsService.this.unregisterUsageSessionObserver(callingUid, sessionObserverId, userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
     }
 
     void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
             long timeLimitMs, PendingIntent callbackIntent, int userId) {
-        mAppTimeLimit.addObserver(callingUid, observerId, packages, timeLimitMs, callbackIntent,
+        mAppTimeLimit.addAppUsageObserver(callingUid, observerId, packages, timeLimitMs, callbackIntent,
                 userId);
     }
 
     void unregisterAppUsageObserver(int callingUid, int observerId, int userId) {
-        mAppTimeLimit.removeObserver(callingUid, observerId, userId);
+        mAppTimeLimit.removeAppUsageObserver(callingUid, observerId, userId);
+    }
+
+    void registerUsageSessionObserver(int callingUid, int observerId, String[] observed,
+            long timeLimitMs, long sessionThresholdTime, PendingIntent limitReachedCallbackIntent,
+            PendingIntent sessionEndCallbackIntent, int userId) {
+        mAppTimeLimit.addUsageSessionObserver(callingUid, observerId, observed, timeLimitMs,
+                sessionThresholdTime, limitReachedCallbackIntent, sessionEndCallbackIntent, userId);
+    }
+
+    void unregisterUsageSessionObserver(int callingUid, int sessionObserverId, int userId) {
+        mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 66918eb7..f4b5f86 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2231,6 +2231,13 @@
     public static final String KEY_CALL_WAITING_OVER_UT_WARNING_BOOL =
             "call_waiting_over_ut_warning_bool";
 
+    /**
+     * Flag indicating whether to support "Network default" option in Caller ID settings for Calling
+     * Line Identification Restriction (CLIR).
+     */
+    public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL =
+            "support_clir_network_default_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -2579,6 +2586,7 @@
         sDefaults.putBoolean(KEY_CALL_BARRING_OVER_UT_WARNING_BOOL, false);
         sDefaults.putBoolean(KEY_CALLER_ID_OVER_UT_WARNING_BOOL, false);
         sDefaults.putBoolean(KEY_CALL_WAITING_OVER_UT_WARNING_BOOL, false);
+        sDefaults.putBoolean(KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL, true);
     }
 
     /**
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 0459667..0ba18ee 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -43,6 +43,7 @@
 import android.net.INetworkPolicyManager;
 import android.net.NetworkCapabilities;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -781,8 +782,13 @@
         IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
             @Override
             public void onSubscriptionsChanged() {
-                if (DBG) log("onOpportunisticSubscriptionsChanged callback received.");
-                mExecutor.execute(() -> onOpportunisticSubscriptionsChanged());
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    if (DBG) log("onOpportunisticSubscriptionsChanged callback received.");
+                    mExecutor.execute(() -> onOpportunisticSubscriptionsChanged());
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
         };
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 203b7b1..f484d1f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -41,6 +41,7 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.BatteryStats;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PersistableBundle;
@@ -1311,10 +1312,9 @@
      * Returns the unique device ID, for example, the IMEI for GSM and the MEID
      * or ESN for CDMA phones. Return null if device ID is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
+     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+     * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+     * that owns a managed profile on the device; for more details see <a
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
      * access is deprecated and will be removed in a future release.
      *
@@ -1322,7 +1322,7 @@
      * MEID for CDMA.
      */
     @Deprecated
-    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @SuppressAutoDoc // No support for device / profile owner.
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getDeviceId() {
         try {
@@ -1341,10 +1341,9 @@
      * Returns the unique device ID of a subscription, for example, the IMEI for
      * GSM and the MEID for CDMA phones. Return null if device ID is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
+     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+     * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+     * that owns a managed profile on the device; for more details see <a
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
      * access is deprecated and will be removed in a future release.
      *
@@ -1354,7 +1353,7 @@
      * MEID for CDMA.
      */
     @Deprecated
-    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @SuppressAutoDoc // No support for device / profile owner.
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getDeviceId(int slotIndex) {
         // FIXME this assumes phoneId == slotIndex
@@ -1374,14 +1373,13 @@
      * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
      * available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
+     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+     * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+     * that owns a managed profile on the device; for more details see <a
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
      * access is deprecated and will be removed in a future release.
      */
-    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @SuppressAutoDoc // No support for device / profile owner.
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getImei() {
         return getImei(getSlotIndex());
@@ -1391,16 +1389,15 @@
      * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
      * available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
+     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+     * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+     * that owns a managed profile on the device; for more details see <a
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
      * access is deprecated and will be removed in a future release.
      *
      * @param slotIndex of which IMEI is returned
      */
-    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @SuppressAutoDoc // No support for device / profile owner.
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getImei(int slotIndex) {
         ITelephony telephony = getITelephony();
@@ -1445,14 +1442,13 @@
     /**
      * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
+     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+     * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+     * that owns a managed profile on the device; for more details see <a
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
      * access is deprecated and will be removed in a future release.
      */
-    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @SuppressAutoDoc // No support for device / profile owner.
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getMeid() {
         return getMeid(getSlotIndex());
@@ -1461,16 +1457,15 @@
     /**
      * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
      *
-     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
-     * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier
-     * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a
-     * managed profile on the device; for more details see <a
+     * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the
+     * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app
+     * that owns a managed profile on the device; for more details see <a
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
      * access is deprecated and will be removed in a future release.
      *
      * @param slotIndex of which MEID is returned
      */
-    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @SuppressAutoDoc // No support for device / profile owner.
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getMeid(int slotIndex) {
         ITelephony telephony = getITelephony();
@@ -2968,7 +2963,7 @@
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
      * access is deprecated and will be removed in a future release.
      */
-    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getSimSerialNumber() {
          return getSimSerialNumber(getSubId());
@@ -3130,7 +3125,7 @@
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner
      * access is deprecated and will be removed in a future release.
      */
-    @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getSubscriberId() {
         return getSubscriberId(getSubId());
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index 56f0c09..eb6be65 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -188,13 +188,6 @@
         if (checkReadDeviceIdentifiers(context, pid, uid, callingPackage)) {
             return true;
         }
-        // Calling packages with carrier privileges will also have access to device identifiers, but
-        // this may be removed in a future release.
-        if (SubscriptionManager.isValidSubscriptionId(subId) && getCarrierPrivilegeStatus(
-                TELEPHONY_SUPPLIER, subId, uid)
-                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-            return true;
-        }
         // else the calling package is not authorized to access the device identifiers; call
         // a central method to report the failure based on the target SDK and if the calling package
         // has the READ_PHONE_STATE permission or carrier privileges that were previously required
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index 8f18d07..d6dbf5a 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -39,6 +39,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.net.NetworkStats.Entry;
 import android.os.Process;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.filters.SmallTest;
@@ -785,7 +786,38 @@
         ArrayMap<String, String> stackedIface = new ArrayMap<>();
         stackedIface.put(v4Iface, baseIface);
 
-        NetworkStats.Entry otherEntry = new NetworkStats.Entry(
+        // Ipv4 traffic sent/received by an app on stacked interface.
+        final NetworkStats.Entry appEntry = new NetworkStats.Entry(
+                v4Iface, appUid, SET_DEFAULT, TAG_NONE,
+                30501490  /* rxBytes */,
+                22401 /* rxPackets */,
+                876235 /* txBytes */,
+                13805 /* txPackets */,
+                0 /* operations */);
+
+        // Traffic measured for the root uid on the base interface if eBPF is in use.
+        // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation
+        // overhead (20 bytes per packet), only for TX traffic.
+        final NetworkStats.Entry ebpfRootUidEntry = new NetworkStats.Entry(
+                baseIface, rootUid, SET_DEFAULT, TAG_NONE,
+                163577 /* rxBytes */,
+                187 /* rxPackets */,
+                1169942 /* txBytes */,
+                13902 /* txPackets */,
+                0 /* operations */);
+
+        // Traffic measured for the root uid on the base interface if xt_qtaguid is in use.
+        // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation
+        // overhead (20 bytes per packet), in both directions.
+        final NetworkStats.Entry xtRootUidEntry = new NetworkStats.Entry(
+                baseIface, rootUid, SET_DEFAULT, TAG_NONE,
+                31113087 /* rxBytes */,
+                22588 /* rxPackets */,
+                1169942 /* txBytes */,
+                13902 /* txPackets */,
+                0 /* operations */);
+
+        final NetworkStats.Entry otherEntry = new NetworkStats.Entry(
                 otherIface, appUid, SET_DEFAULT, TAG_NONE,
                 2600  /* rxBytes */,
                 2 /* rxPackets */,
@@ -793,39 +825,41 @@
                 3 /* txPackets */,
                 0 /* operations */);
 
-        NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(v4Iface, appUid, SET_DEFAULT, TAG_NONE,
-                        30501490  /* rxBytes */,
-                        22401 /* rxPackets */,
-                        876235 /* txBytes */,
-                        13805 /* txPackets */,
-                        0 /* operations */)
-                .addValues(baseIface, rootUid, SET_DEFAULT, TAG_NONE,
-                        31113087,
-                        22588,
-                        1169942,
-                        13902,
-                        0)
+        final NetworkStats statsXt = new NetworkStats(TEST_START, 3)
+                .addValues(appEntry)
+                .addValues(xtRootUidEntry)
                 .addValues(otherEntry);
 
-        stats.apply464xlatAdjustments(stackedIface);
+        final NetworkStats statsEbpf = new NetworkStats(TEST_START, 3)
+                .addValues(appEntry)
+                .addValues(ebpfRootUidEntry)
+                .addValues(otherEntry);
 
-        assertEquals(3, stats.size());
-        assertValues(stats, 0, v4Iface, appUid, SET_DEFAULT, TAG_NONE,
-                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+        statsXt.apply464xlatAdjustments(stackedIface, false);
+        statsEbpf.apply464xlatAdjustments(stackedIface, true);
+
+        assertEquals(3, statsXt.size());
+        assertEquals(3, statsEbpf.size());
+        final NetworkStats.Entry expectedAppUid = new NetworkStats.Entry(
+                v4Iface, appUid, SET_DEFAULT, TAG_NONE,
                 30949510,
                 22401,
                 1152335,
                 13805,
                 0);
-        assertValues(stats, 1, baseIface, 0, SET_DEFAULT, TAG_NONE,
-                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+        final NetworkStats.Entry expectedRootUid = new NetworkStats.Entry(
+                baseIface, 0, SET_DEFAULT, TAG_NONE,
                 163577,
                 187,
                 17607,
                 97,
                 0);
-        assertEquals(otherEntry, stats.getValues(2, null));
+        assertEquals(expectedAppUid, statsXt.getValues(0, null));
+        assertEquals(expectedRootUid, statsXt.getValues(1, null));
+        assertEquals(otherEntry, statsXt.getValues(2, null));
+        assertEquals(expectedAppUid, statsEbpf.getValues(0, null));
+        assertEquals(expectedRootUid, statsEbpf.getValues(1, null));
+        assertEquals(otherEntry, statsEbpf.getValues(2, null));
     }
 
     @Test
@@ -850,7 +884,7 @@
                 .addValues(secondEntry);
 
         // Empty map: no adjustment
-        stats.apply464xlatAdjustments(new ArrayMap<>());
+        stats.apply464xlatAdjustments(new ArrayMap<>(), false);
 
         assertEquals(2, stats.size());
         assertEquals(firstEntry, stats.getValues(0, null));