Merge "Add key-value mappers for improved safety."
diff --git a/api/current.txt b/api/current.txt
index f105bb8..de5a0b1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23397,6 +23397,7 @@
     method public int getStreamType();
     method public boolean getTimestamp(android.media.AudioTimestamp);
     method public int getUnderrunCount();
+    method public static boolean isDirectPlaybackSupported(android.media.AudioFormat, android.media.AudioAttributes);
     method public void pause() throws java.lang.IllegalStateException;
     method public void play() throws java.lang.IllegalStateException;
     method public void registerStreamEventCallback(java.util.concurrent.Executor, android.media.AudioTrack.StreamEventCallback);
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 56dc4c1..bacb991 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -1639,10 +1639,6 @@
 Landroid/widget/QuickContactBadge$QueryHandler;-><init>(Landroid/widget/QuickContactBadge;Landroid/content/ContentResolver;)V
 Landroid/widget/RelativeLayout$DependencyGraph$Node;-><init>()V
 Landroid/widget/ScrollBarDrawable;-><init>()V
-Lcom/android/i18n/phonenumbers/Phonenumber$PhoneNumber$CountryCodeSource;->values()[Lcom/android/i18n/phonenumbers/Phonenumber$PhoneNumber$CountryCodeSource;
-Lcom/android/i18n/phonenumbers/PhoneNumberUtil$MatchType;->values()[Lcom/android/i18n/phonenumbers/PhoneNumberUtil$MatchType;
-Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberFormat;->values()[Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberFormat;
-Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberType;->values()[Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberType;
 Lcom/android/ims/ImsCall;->deflect(Ljava/lang/String;)V
 Lcom/android/ims/ImsCall;->isMultiparty()Z
 Lcom/android/ims/ImsCall;->reject(I)V
diff --git a/core/java/android/annotation/UnsupportedAppUsage.java b/core/java/android/annotation/UnsupportedAppUsage.java
index 65e3f25..ac3daaf 100644
--- a/core/java/android/annotation/UnsupportedAppUsage.java
+++ b/core/java/android/annotation/UnsupportedAppUsage.java
@@ -26,16 +26,32 @@
 import java.lang.annotation.Target;
 
 /**
- * Indicates that a class member, that is not part of the SDK, is used by apps.
- * Since the member is not part of the SDK, such use is not supported.
+ * Indicates that this non-SDK interface is used by apps. A non-SDK interface is a
+ * class member (field or method) that is not part of the public SDK. Since the
+ * member is not part of the SDK, usage by apps is not supported.
  *
- * <p>This annotation acts as a heads up that changing a given method or field
+ * <h2>If you are an Android App developer</h2>
+ *
+ * This annotation indicates that you may be able to access the member, but that
+ * this access is discouraged and not supported by Android. If there is a value
+ * for {@link #maxTargetSdk()} on the annotation, access will be restricted based
+ * on the {@code targetSdkVersion} value set in your manifest.
+ *
+ * <p>Fields and methods annotated with this are likely to be restricted, changed
+ * or removed in future Android releases. If you rely on these members for
+ * functionality that is not otherwise supported by Android, consider filing a
+ * <a href="http://g.co/dev/appcompat">feature request</a>.
+ *
+ * <h2>If you are an Android OS developer</h2>
+ *
+ * This annotation acts as a heads up that changing a given method or field
  * may affect apps, potentially breaking them when the next Android version is
  * released. In some cases, for members that are heavily used, this annotation
  * may imply restrictions on changes to the member.
  *
  * <p>This annotation also results in access to the member being permitted by the
- * runtime, with a warning being generated in debug builds.
+ * runtime, with a warning being generated in debug builds. Which apps can access
+ * the member is determined by the value of {@link #maxTargetSdk()}.
  *
  * <p>For more details, see go/UnsupportedAppUsage.
  *
diff --git a/core/java/com/android/internal/os/AppIdToPackageMap.java b/core/java/com/android/internal/os/AppIdToPackageMap.java
new file mode 100644
index 0000000..65aa989
--- /dev/null
+++ b/core/java/com/android/internal/os/AppIdToPackageMap.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+
+import android.app.AppGlobals;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Maps AppIds to their package names. */
+public final class AppIdToPackageMap {
+    private final Map<Integer, String> mAppIdToPackageMap;
+
+    @VisibleForTesting
+    public AppIdToPackageMap(Map<Integer, String> appIdToPackageMap) {
+        mAppIdToPackageMap = appIdToPackageMap;
+    }
+
+    /** Creates a new {@link AppIdToPackageMap} for currently installed packages. */
+    public static AppIdToPackageMap getSnapshot() {
+        List<PackageInfo> packages;
+        try {
+            packages = AppGlobals.getPackageManager()
+                    .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE,
+                            UserHandle.USER_SYSTEM).getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        final Map<Integer, String> map = new HashMap<>();
+        for (PackageInfo pkg : packages) {
+            final int uid = pkg.applicationInfo.uid;
+            if (pkg.sharedUserId != null && map.containsKey(uid)) {
+                // Use sharedUserId string as package name if there are collisions
+                map.put(uid, "shared:" + pkg.sharedUserId);
+            } else {
+                map.put(uid, pkg.packageName);
+            }
+        }
+        return new AppIdToPackageMap(map);
+    }
+
+    /** Maps the AppId to a package name. */
+    public String mapAppId(int appId) {
+        String pkgName = mAppIdToPackageMap.get(appId);
+        return pkgName == null ? String.valueOf(appId) : pkgName;
+    }
+
+    /** Maps the UID to a package name. */
+    public String mapUid(int uid) {
+        final int appId = UserHandle.getAppId(uid);
+        final String pkgName = mAppIdToPackageMap.get(appId);
+        final String uidStr = UserHandle.formatUid(uid);
+        return pkgName == null ? uidStr : pkgName + '/' + uidStr;
+    }
+}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index ff34036..afed31d 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -22,7 +22,6 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.ThreadLocalWorkSource;
-import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.Pair;
@@ -356,14 +355,13 @@
     }
 
     /** Writes the collected statistics to the supplied {@link PrintWriter}.*/
-    public void dump(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap, boolean verbose) {
+    public void dump(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) {
         synchronized (mLock) {
-            dumpLocked(pw, appIdToPkgNameMap, verbose);
+            dumpLocked(pw, packageMap, verbose);
         }
     }
 
-    private void dumpLocked(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap,
-            boolean verbose) {
+    private void dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) {
         long totalCallsCount = 0;
         long totalRecordedCallsCount = 0;
         long totalCpuTime = 0;
@@ -397,9 +395,9 @@
         for (ExportedCallStat e : exportedCallStats) {
             sb.setLength(0);
             sb.append("    ")
-                    .append(uidToString(e.callingUid, appIdToPkgNameMap))
+                    .append(packageMap.mapUid(e.callingUid))
                     .append(',')
-                    .append(uidToString(e.workSourceUid, appIdToPkgNameMap))
+                    .append(packageMap.mapUid(e.workSourceUid))
                     .append(',').append(e.className)
                     .append('#').append(e.methodName)
                     .append(',').append(e.screenInteractive)
@@ -420,7 +418,7 @@
         final List<UidEntry> summaryEntries = verbose ? entries
                 : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9);
         for (UidEntry entry : summaryEntries) {
-            String uidStr = uidToString(entry.workSourceUid, appIdToPkgNameMap);
+            String uidStr = packageMap.mapUid(entry.workSourceUid);
             pw.println(String.format("  %10d %3.0f%% %8d %8d %s",
                     entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime,
                     entry.recordedCallCount, entry.callCount, uidStr));
@@ -448,13 +446,6 @@
         }
     }
 
-    private static String uidToString(int uid, Map<Integer, String> pkgNameMap) {
-        final int appId = UserHandle.getAppId(uid);
-        final String pkgName = pkgNameMap == null ? null : pkgNameMap.get(appId);
-        final String uidStr = UserHandle.formatUid(uid);
-        return pkgName == null ? uidStr : pkgName + '/' + uidStr;
-    }
-
     protected long getThreadTimeMicro() {
         return SystemClock.currentThreadTimeMicro();
     }
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 7ebb2b2..516093e 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -479,6 +479,24 @@
 }
 
 // ----------------------------------------------------------------------------
+static jboolean
+android_media_AudioTrack_is_direct_output_supported(JNIEnv *env, jobject thiz,
+                                             jint encoding, jint sampleRate,
+                                             jint channelMask, jint channelIndexMask,
+                                             jint contentType, jint usage, jint flags) {
+    audio_config_base_t config = {};
+    audio_attributes_t attributes = {};
+    config.format = static_cast<audio_format_t>(audioFormatToNative(encoding));
+    config.sample_rate = static_cast<uint32_t>(sampleRate);
+    config.channel_mask = nativeChannelMaskFromJavaChannelMasks(channelMask, channelIndexMask);
+    attributes.content_type = static_cast<audio_content_type_t>(contentType);
+    attributes.usage = static_cast<audio_usage_t>(usage);
+    attributes.flags = static_cast<audio_flags_mask_t>(flags);
+    // ignore source and tags attributes as they don't affect querying whether output is supported
+    return AudioTrack::isDirectOutputSupported(config, attributes);
+}
+
+// ----------------------------------------------------------------------------
 static void
 android_media_AudioTrack_start(JNIEnv *env, jobject thiz)
 {
@@ -1297,6 +1315,9 @@
 // ----------------------------------------------------------------------------
 static const JNINativeMethod gMethods[] = {
     // name,              signature,     funcPtr
+    {"native_is_direct_output_supported",
+                             "(IIIIIII)Z",
+                                         (void *)android_media_AudioTrack_is_direct_output_supported},
     {"native_start",         "()V",      (void *)android_media_AudioTrack_start},
     {"native_stop",          "()V",      (void *)android_media_AudioTrack_stop},
     {"native_pause",         "()V",      (void *)android_media_AudioTrack_pause},
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 5726d9a..c7a6b68 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -51,7 +51,7 @@
   SET_ALWAYS_ON_VPN_PACKAGE = 26;
   SET_PERMITTED_INPUT_METHODS = 27;
   SET_PERMITTED_ACCESSIBILITY_SERVICES = 28;
-  SET_SCREEN_CAPTURE_DISABLE = 29;
+  SET_SCREEN_CAPTURE_DISABLED = 29;
   SET_CAMERA_DISABLED = 30;
   QUERY_SUMMARY_FOR_USER = 31;
   QUERY_SUMMARY = 32;
@@ -64,7 +64,7 @@
   SET_ORGANIZATION_COLOR = 39;
   SET_PROFILE_NAME = 40;
   SET_USER_ICON = 41;
-  SET_DEVICE_OWNER_LOCKSCREEN_INFO = 42;
+  SET_DEVICE_OWNER_LOCK_SCREEN_INFO = 42;
   SET_SHORT_SUPPORT_MESSAGE = 43;
   SET_LONG_SUPPORT_MESSAGE = 44;
   SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED = 45;
@@ -139,4 +139,6 @@
   SET_GLOBAL_SETTING = 111;
   PM_IS_INSTALLER_DEVICE_OWNER_OR_AFFILIATED_PROFILE_OWNER = 112;
   PM_UNINSTALL = 113;
+  WIFI_SERVICE_ADD_NETWORK_SUGGESTIONS = 114;
+  WIFI_SERVICE_ADD_OR_UPDATE_NETWORK = 115;
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index e261819..97942a8 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -484,7 +484,7 @@
         bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
 
         PrintWriter pw = new PrintWriter(new StringWriter());
-        bcs.dump(pw, new HashMap<>(), true);
+        bcs.dump(pw, new AppIdToPackageMap(new HashMap<>()), true);
     }
 
     @Test
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index e4d4795..5e77fdf 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -995,6 +995,35 @@
         }
     }
 
+    /**
+     * Returns whether direct playback of an audio format with the provided attributes is
+     * currently supported on the system.
+     * <p>Direct playback means that the audio stream is not resampled or downmixed
+     * by the framework. Checking for direct support can help the app select the representation
+     * of audio content that most closely matches the capabilities of the device and peripherials
+     * (e.g. A/V receiver) connected to it. Note that the provided stream can still be re-encoded
+     * or mixed with other streams, if needed.
+     * <p>Also note that this query only provides information about the support of an audio format.
+     * It does not indicate whether the resources necessary for the playback are available
+     * at that instant.
+     * @param format a non-null {@link AudioFormat} instance describing the format of
+     *   the audio data.
+     * @param attributes a non-null {@link AudioAttributes} instance.
+     * @return true if the given audio format can be played directly.
+     */
+    public static boolean isDirectPlaybackSupported(@NonNull AudioFormat format,
+            @NonNull AudioAttributes attributes) {
+        if (format == null) {
+            throw new IllegalArgumentException("Illegal null AudioFormat argument");
+        }
+        if (attributes == null) {
+            throw new IllegalArgumentException("Illegal null AudioAttributes argument");
+        }
+        return native_is_direct_output_supported(format.getEncoding(), format.getSampleRate(),
+                format.getChannelMask(), format.getChannelIndexMask(),
+                attributes.getContentType(), attributes.getUsage(), attributes.getFlags());
+    }
+
     // mask of all the positional channels supported, however the allowed combinations
     // are further restricted by the matching left/right rule and
     // AudioSystem.OUT_CHANNEL_COUNT_MAX
@@ -3328,6 +3357,9 @@
     // Native methods called from the Java side
     //--------------------
 
+    private static native boolean native_is_direct_output_supported(int encoding, int sampleRate,
+            int channelMask, int channelIndexMask, int contentType, int usage, int flags);
+
     // post-condition: mStreamType is overwritten with a value
     //     that reflects the audio attributes (e.g. an AudioAttributes object with a usage of
     //     AudioAttributes.USAGE_MEDIA will map to AudioManager.STREAM_MUSIC
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index 0cad5af..133d8ba 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -27,20 +27,15 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.IPackageManager;
-import android.database.ContentObserver;
 import android.ext.services.notification.AgingHelper.Callback;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Environment;
-import android.os.Handler;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationAssistantService;
 import android.service.notification.NotificationStats;
@@ -92,8 +87,6 @@
         PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL);
     }
 
-    private float mDismissToViewRatioLimit;
-    private int mStreakLimit;
     private SmartActionsHelper mSmartActionsHelper;
     private NotificationCategorizer mNotificationCategorizer;
     private AgingHelper mAgingHelper;
@@ -107,7 +100,11 @@
     private Ranking mFakeRanking = null;
     private AtomicFile mFile = null;
     private IPackageManager mPackageManager;
-    protected SettingsObserver mSettingsObserver;
+
+    @VisibleForTesting
+    protected AssistantSettings.Factory mSettingsFactory = AssistantSettings.FACTORY;
+    @VisibleForTesting
+    protected AssistantSettings mSettings;
 
     public Assistant() {
     }
@@ -118,7 +115,8 @@
         // Contexts are correctly hooked up by the creation step, which is required for the observer
         // to be hooked up/initialized.
         mPackageManager = ActivityThread.getPackageManager();
-        mSettingsObserver = new SettingsObserver(mHandler);
+        mSettings = mSettingsFactory.createAndRegister(mHandler,
+                getApplicationContext().getContentResolver(), getUserId(), this::updateThresholds);
         mSmartActionsHelper = new SmartActionsHelper();
         mNotificationCategorizer = new NotificationCategorizer();
         mAgingHelper = new AgingHelper(getContext(),
@@ -216,11 +214,11 @@
         if (!isForCurrentUser(sbn)) {
             return null;
         }
-        NotificationEntry entry = new NotificationEntry(
-                ActivityThread.getPackageManager(), sbn, channel);
+        NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel);
         ArrayList<Notification.Action> actions =
-                mSmartActionsHelper.suggestActions(this, entry);
-        ArrayList<CharSequence> replies = mSmartActionsHelper.suggestReplies(this, entry);
+                mSmartActionsHelper.suggestActions(this, entry, mSettings);
+        ArrayList<CharSequence> replies =
+                mSmartActionsHelper.suggestReplies(this, entry, mSettings);
         return createEnqueuedNotificationAdjustment(entry, actions, replies);
     }
 
@@ -239,8 +237,7 @@
         if (!smartReplies.isEmpty()) {
             signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies);
         }
-        if (Settings.Secure.getInt(getContentResolver(),
-                Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) == 1) {
+        if (mSettings.mNewInterruptionModel) {
             if (mNotificationCategorizer.shouldSilence(entry)) {
                 final int importance = entry.getImportance() < IMPORTANCE_LOW
                         ? entry.getImportance() : IMPORTANCE_LOW;
@@ -460,6 +457,11 @@
     }
 
     @VisibleForTesting
+    public void setSmartActionsHelper(SmartActionsHelper smartActionsHelper) {
+        mSmartActionsHelper = smartActionsHelper;
+    }
+
+    @VisibleForTesting
     public ChannelImpressions getImpressions(String key) {
         synchronized (mkeyToImpressions) {
             return mkeyToImpressions.get(key);
@@ -475,10 +477,20 @@
 
     private ChannelImpressions createChannelImpressionsWithThresholds() {
         ChannelImpressions impressions = new ChannelImpressions();
-        impressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit);
+        impressions.updateThresholds(mSettings.mDismissToViewRatioLimit, mSettings.mStreakLimit);
         return impressions;
     }
 
+    private void updateThresholds() {
+        // Update all existing channel impression objects with any new limits/thresholds.
+        synchronized (mkeyToImpressions) {
+            for (ChannelImpressions channelImpressions: mkeyToImpressions.values()) {
+                channelImpressions.updateThresholds(
+                        mSettings.mDismissToViewRatioLimit, mSettings.mStreakLimit);
+            }
+        }
+    }
+
     protected final class AgingCallback implements Callback {
         @Override
         public void sendAdjustment(String key, int newImportance) {
@@ -495,51 +507,4 @@
         }
     }
 
-    /**
-     * Observer for updates on blocking helper threshold values.
-     */
-    protected final class SettingsObserver extends ContentObserver {
-        private final Uri STREAK_LIMIT_URI =
-                Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT);
-        private final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
-                Settings.Global.getUriFor(
-                        Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
-
-        public SettingsObserver(Handler handler) {
-            super(handler);
-            ContentResolver resolver = getApplicationContext().getContentResolver();
-            resolver.registerContentObserver(
-                    DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, getUserId());
-            resolver.registerContentObserver(STREAK_LIMIT_URI, false, this, getUserId());
-
-            // Update all uris on creation.
-            update(null);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            update(uri);
-        }
-
-        private void update(Uri uri) {
-            ContentResolver resolver = getApplicationContext().getContentResolver();
-            if (uri == null || DISMISS_TO_VIEW_RATIO_LIMIT_URI.equals(uri)) {
-                mDismissToViewRatioLimit = Settings.Global.getFloat(
-                        resolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
-                        ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT);
-            }
-            if (uri == null || STREAK_LIMIT_URI.equals(uri)) {
-                mStreakLimit = Settings.Global.getInt(
-                        resolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
-                        ChannelImpressions.DEFAULT_STREAK_LIMIT);
-            }
-
-            // Update all existing channel impression objects with any new limits/thresholds.
-            synchronized (mkeyToImpressions) {
-                for (ChannelImpressions channelImpressions: mkeyToImpressions.values()) {
-                    channelImpressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit);
-                }
-            }
-        }
-    }
 }
diff --git a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
new file mode 100644
index 0000000..39a1676
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java
@@ -0,0 +1,140 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ext.services.notification;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Observes the settings for {@link Assistant}.
+ */
+final class AssistantSettings extends ContentObserver {
+    public static Factory FACTORY = AssistantSettings::createAndRegister;
+    private static final boolean DEFAULT_GENERATE_REPLIES = true;
+    private static final boolean DEFAULT_GENERATE_ACTIONS = true;
+    private static final int DEFAULT_NEW_INTERRUPTION_MODEL_INT = 1;
+
+    private static final Uri STREAK_LIMIT_URI =
+            Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT);
+    private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI =
+            Settings.Global.getUriFor(
+                    Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT);
+    private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI =
+            Settings.Global.getUriFor(
+                    Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
+    private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI =
+            Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL);
+
+    private static final String KEY_GENERATE_REPLIES = "generate_replies";
+    private static final String KEY_GENERATE_ACTIONS = "generate_actions";
+
+    private final KeyValueListParser mParser = new KeyValueListParser(',');
+    private final ContentResolver mResolver;
+    private final int mUserId;
+
+    @VisibleForTesting
+    protected final Runnable mOnUpdateRunnable;
+
+    // Actuall configuration settings.
+    float mDismissToViewRatioLimit;
+    int mStreakLimit;
+    boolean mGenerateReplies = DEFAULT_GENERATE_REPLIES;
+    boolean mGenerateActions = DEFAULT_GENERATE_ACTIONS;
+    boolean mNewInterruptionModel;
+
+    private AssistantSettings(Handler handler, ContentResolver resolver, int userId,
+            Runnable onUpdateRunnable) {
+        super(handler);
+        mResolver = resolver;
+        mUserId = userId;
+        mOnUpdateRunnable = onUpdateRunnable;
+    }
+
+    private static AssistantSettings createAndRegister(
+            Handler handler, ContentResolver resolver, int userId, Runnable onUpdateRunnable) {
+        AssistantSettings assistantSettings =
+                new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
+        assistantSettings.register();
+        return assistantSettings;
+    }
+
+    /**
+     * Creates an instance but doesn't register it as an observer.
+     */
+    @VisibleForTesting
+    protected static AssistantSettings createForTesting(
+            Handler handler, ContentResolver resolver, int userId, Runnable onUpdateRunnable) {
+        return new AssistantSettings(handler, resolver, userId, onUpdateRunnable);
+    }
+
+    private void register() {
+        mResolver.registerContentObserver(
+                DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId);
+        mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId);
+        mResolver.registerContentObserver(
+                SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId);
+
+        // Update all uris on creation.
+        update(null);
+    }
+
+    @Override
+    public void onChange(boolean selfChange, Uri uri) {
+        update(uri);
+    }
+
+    private void update(Uri uri) {
+        if (uri == null || DISMISS_TO_VIEW_RATIO_LIMIT_URI.equals(uri)) {
+            mDismissToViewRatioLimit = Settings.Global.getFloat(
+                    mResolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
+                    ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT);
+        }
+        if (uri == null || STREAK_LIMIT_URI.equals(uri)) {
+            mStreakLimit = Settings.Global.getInt(
+                    mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
+                    ChannelImpressions.DEFAULT_STREAK_LIMIT);
+        }
+        if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) {
+            mParser.setString(
+                    Settings.Global.getString(mResolver,
+                            Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+            mGenerateReplies =
+                    mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES);
+            mGenerateActions =
+                    mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS);
+        }
+        if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) {
+            int mNewInterruptionModelInt = Settings.Secure.getInt(
+                    mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL,
+                    DEFAULT_NEW_INTERRUPTION_MODEL_INT);
+            mNewInterruptionModel = mNewInterruptionModelInt == 1;
+        }
+
+        mOnUpdateRunnable.run();
+    }
+
+    public interface Factory {
+        AssistantSettings createAndRegister(Handler handler, ContentResolver resolver, int userId,
+                Runnable onUpdateRunnable);
+    }
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 892267b..6f2b6c9 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -69,8 +69,11 @@
      * from notification text / message, we can replace most of the code here by consuming that API.
      */
     @NonNull
-    ArrayList<Notification.Action> suggestActions(
-            @Nullable Context context, @NonNull NotificationEntry entry) {
+    ArrayList<Notification.Action> suggestActions(@Nullable Context context,
+            @NonNull NotificationEntry entry, @NonNull AssistantSettings settings) {
+        if (!settings.mGenerateActions) {
+            return EMPTY_ACTION_LIST;
+        }
         if (!isEligibleForActionAdjustment(entry)) {
             return EMPTY_ACTION_LIST;
         }
@@ -86,8 +89,11 @@
                 getMostSalientActionText(entry.getNotification()), MAX_SMART_ACTIONS);
     }
 
-    ArrayList<CharSequence> suggestReplies(
-            @Nullable Context context, @NonNull NotificationEntry entry) {
+    ArrayList<CharSequence> suggestReplies(@Nullable Context context,
+            @NonNull NotificationEntry entry, @NonNull AssistantSettings settings) {
+        if (!settings.mGenerateReplies) {
+            return EMPTY_REPLY_LIST;
+        }
         if (!isEligibleForReplyAdjustment(entry)) {
             return EMPTY_REPLY_LIST;
         }
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
new file mode 100644
index 0000000..fd23f2b
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java
@@ -0,0 +1,162 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ext.services.notification;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.testing.TestableContext;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class AssistantSettingsTest {
+    private static final int USER_ID = 5;
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getContext(), null);
+
+    @Mock Runnable mOnUpdateRunnable;
+
+    private ContentResolver mResolver;
+    private AssistantSettings mAssistantSettings;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mResolver = mContext.getContentResolver();
+        Handler handler = new Handler(Looper.getMainLooper());
+
+        // To bypass real calls to global settings values, set the Settings values here.
+        Settings.Global.putFloat(mResolver,
+                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
+        Settings.Global.putInt(mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
+                "generate_replies=true,generate_actions=true");
+        Settings.Secure.putInt(mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
+
+        mAssistantSettings = AssistantSettings.createForTesting(
+                handler, mResolver, USER_ID, mOnUpdateRunnable);
+    }
+
+    @Test
+    public void testGenerateRepliesDisabled() {
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
+                "generate_replies=false");
+
+        // Notify for the settings values we updated.
+        mAssistantSettings.onChange(false,
+                Settings.Global.getUriFor(
+                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+
+
+        assertFalse(mAssistantSettings.mGenerateReplies);
+    }
+
+    @Test
+    public void testGenerateRepliesEnabled() {
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_replies=true");
+
+        // Notify for the settings values we updated.
+        mAssistantSettings.onChange(false,
+                Settings.Global.getUriFor(
+                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+
+        assertTrue(mAssistantSettings.mGenerateReplies);
+    }
+
+    @Test
+    public void testGenerateActionsDisabled() {
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=false");
+
+        // Notify for the settings values we updated.
+        mAssistantSettings.onChange(false,
+                Settings.Global.getUriFor(
+                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+
+        assertTrue(mAssistantSettings.mGenerateReplies);
+    }
+
+    @Test
+    public void testGenerateActionsEnabled() {
+        Settings.Global.putString(mResolver,
+                Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=true");
+
+        // Notify for the settings values we updated.
+        mAssistantSettings.onChange(false,
+                Settings.Global.getUriFor(
+                        Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS));
+
+        assertTrue(mAssistantSettings.mGenerateReplies);
+    }
+
+    @Test
+    public void testStreakLimit() {
+        verify(mOnUpdateRunnable, never()).run();
+
+        // Update settings value.
+        int newStreakLimit = 4;
+        Settings.Global.putInt(mResolver,
+                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, newStreakLimit);
+
+        // Notify for the settings value we updated.
+        mAssistantSettings.onChange(false, Settings.Global.getUriFor(
+                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT));
+
+        assertEquals(newStreakLimit, mAssistantSettings.mStreakLimit);
+        verify(mOnUpdateRunnable).run();
+    }
+
+    @Test
+    public void testDismissToViewRatioLimit() {
+        verify(mOnUpdateRunnable, never()).run();
+
+        // Update settings value.
+        float newDismissToViewRatioLimit = 3f;
+        Settings.Global.putFloat(mResolver,
+                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
+                newDismissToViewRatioLimit);
+
+        // Notify for the settings value we updated.
+        mAssistantSettings.onChange(false, Settings.Global.getUriFor(
+                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT));
+
+        assertEquals(newDismissToViewRatioLimit, mAssistantSettings.mDismissToViewRatioLimit, 1e-6);
+        verify(mOnUpdateRunnable).run();
+    }
+}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
index 2eb005a..0a95b83 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
@@ -33,13 +33,11 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.os.Build;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -86,8 +84,7 @@
 
     @Mock INotificationManager mNoMan;
     @Mock AtomicFile mFile;
-    @Mock
-    IPackageManager mPackageManager;
+    @Mock IPackageManager mPackageManager;
 
     Assistant mAssistant;
     Application mApplication;
@@ -108,20 +105,26 @@
                 new Intent("android.service.notification.NotificationAssistantService");
         startIntent.setPackage("android.ext.services");
 
-        // To bypass real calls to global settings values, set the Settings values here.
-        Settings.Global.putFloat(mContext.getContentResolver(),
-                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f);
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2);
         mApplication = (Application) InstrumentationRegistry.getInstrumentation().
                 getTargetContext().getApplicationContext();
         // Force the test to use the correct application instead of trying to use a mock application
         setApplication(mApplication);
-        bindService(startIntent);
+
+        setupService();
         mAssistant = getService();
+
+        // Override the AssistantSettings factory.
+        mAssistant.mSettingsFactory = AssistantSettings::createForTesting;
+
+        bindService(startIntent);
+
+        mAssistant.mSettings.mDismissToViewRatioLimit = 0.8f;
+        mAssistant.mSettings.mStreakLimit = 2;
+        mAssistant.mSettings.mNewInterruptionModel = true;
         mAssistant.setNoMan(mNoMan);
         mAssistant.setFile(mFile);
         mAssistant.setPackageManager(mPackageManager);
+
         ApplicationInfo info = mock(ApplicationInfo.class);
         when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt()))
                 .thenReturn(info);
@@ -408,6 +411,8 @@
         mAssistant.writeXml(serializer);
 
         Assistant assistant = new Assistant();
+        // onCreate is not invoked, so settings won't be initialised, unless we do it here.
+        assistant.mSettings = mAssistant.mSettings;
         assistant.readXml(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())));
 
         assertEquals(ci1, assistant.getImpressions(key1));
@@ -417,8 +422,6 @@
 
     @Test
     public void testSettingsProviderUpdate() {
-        ContentResolver resolver = mApplication.getContentResolver();
-
         // Set up channels
         String key = mAssistant.getKey("pkg1", 1, "channel1");
         ChannelImpressions ci = new ChannelImpressions();
@@ -435,19 +438,11 @@
         assertEquals(false, ci.shouldTriggerBlock());
 
         // Update settings values.
-        float newDismissToViewRatioLimit = 0f;
-        int newStreakLimit = 0;
-        Settings.Global.putFloat(resolver,
-                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
-                newDismissToViewRatioLimit);
-        Settings.Global.putInt(resolver,
-                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, newStreakLimit);
+        mAssistant.mSettings.mDismissToViewRatioLimit = 0f;
+        mAssistant.mSettings.mStreakLimit = 0;
 
         // Notify for the settings values we updated.
-        mAssistant.mSettingsObserver.onChange(false, Settings.Global.getUriFor(
-                Settings.Global.BLOCKING_HELPER_STREAK_LIMIT));
-        mAssistant.mSettingsObserver.onChange(false, Settings.Global.getUriFor(
-                Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT));
+        mAssistant.mSettings.mOnUpdateRunnable.run();
 
         // With the new threshold, the blocking helper should be triggered.
         assertEquals(true, ci.shouldTriggerBlock());
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index cc17b25..0126e7e5 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -18,6 +18,7 @@
         "SettingsLibSettingsSpinner",
         "SettingsLayoutPreference",
         "ActionButtonsPreference",
+        "SettingsLibEntityHeaderWidgets",
     ],
 
     // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
new file mode 100644
index 0000000..3ca4ecd
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
@@ -0,0 +1,14 @@
+android_library {
+    name: "SettingsLibEntityHeaderWidgets",
+
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+
+    static_libs: [
+          "androidx.annotation_annotation",
+          "SettingsLibAppPreference"
+    ],
+
+    sdk_version: "system_current",
+    min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml b/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml
new file mode 100644
index 0000000..4b9f1ab
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.widget">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
new file mode 100644
index 0000000..9f30eda
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
@@ -0,0 +1,64 @@
+<?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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/app_entities_header"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="24dp"
+    android:paddingEnd="8dp"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/header_title"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:gravity="center"
+        android:textAppearance="@style/AppEntitiesHeader.Text.HeaderTitle"/>
+
+    <LinearLayout
+        android:id="@+id/all_apps_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp"
+        android:gravity="center">
+
+        <include
+            android:id="@+id/app1_view"
+            layout="@layout/app_view"/>
+
+        <include
+            android:id="@+id/app2_view"
+            layout="@layout/app_view"/>
+
+        <include
+            android:id="@+id/app3_view"
+            layout="@layout/app_view"/>
+
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/header_details"
+        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:gravity="center"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml
new file mode 100644
index 0000000..fcafa31
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml
@@ -0,0 +1,50 @@
+<?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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="wrap_content"
+    android:layout_weight="1"
+    android:layout_marginEnd="16dp"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/app_icon"
+        android:layout_width="@dimen/secondary_app_icon_size"
+        android:layout_height="@dimen/secondary_app_icon_size"
+        android:layout_marginBottom="12dp"/>
+
+    <TextView
+        android:id="@+id/app_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="2dp"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="@style/AppEntitiesHeader.Text.Title"/>
+
+    <TextView
+        android:id="@+id/app_summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="@style/AppEntitiesHeader.Text.Summary"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml b/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml
new file mode 100644
index 0000000..0eefd4b
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml
@@ -0,0 +1,38 @@
+<?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>
+    <style name="AppEntitiesHeader.Text"
+           parent="@android:style/TextAppearance.Material.Subhead">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="AppEntitiesHeader.Text.HeaderTitle">
+        <item name="android:textSize">14sp</item>
+    </style>
+
+    <style name="AppEntitiesHeader.Text.Title">
+        <item name="android:textSize">16sp</item>
+    </style>
+
+    <style name="AppEntitiesHeader.Text.Summary"
+           parent="@android:style/TextAppearance.Material.Body1">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textSize">14sp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
new file mode 100644
index 0000000..8ccf89f
--- /dev/null
+++ b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * This is used to initialize view which was inflated
+ * from {@link R.xml.app_entities_header.xml}.
+ *
+ * <p>The view looks like below.
+ *
+ * <pre>
+ * --------------------------------------------------------------
+ * |                     Header title                           |
+ * --------------------------------------------------------------
+ * |    App1 icon       |   App2 icon        |   App3 icon      |
+ * |    App1 title      |   App2 title       |   App3 title     |
+ * |    App1 summary    |   App2 summary     |   App3 summary   |
+ * |-------------------------------------------------------------
+ * |                     Header details                         |
+ * --------------------------------------------------------------
+ * </pre>
+ *
+ * <p>How to use AppEntitiesHeaderController?
+ *
+ * <p>1. Add a {@link LayoutPreference} in layout XML file.
+ * <pre>
+ * &lt;com.android.settingslib.widget.LayoutPreference
+ *        android:key="app_entities_header"
+ *        android:layout="@layout/app_entities_header"/&gt;
+ * </pre>
+ *
+ * <p>2. Use AppEntitiesHeaderController to call below methods, then you can initialize
+ * view of <code>app_entities_header</code>.
+ *
+ * <pre>
+ *
+ * View headerView = ((LayoutPreference) screen.findPreference("app_entities_header"))
+ *         .findViewById(R.id.app_entities_header);
+ *
+ * AppEntitiesHeaderController.newInstance(context, headerView)
+ *         .setHeaderTitleRes(R.string.xxxxx)
+ *         .setHeaderDetailsRes(R.string.xxxxx)
+ *         .setHeaderDetailsClickListener(onClickListener)
+ *         .setAppEntity(0, icon, "app title", "app summary")
+ *         .setAppEntity(1, icon, "app title", "app summary")
+ *         .setAppEntity(2, icon, "app title", "app summary")
+ *         .apply();
+ * </pre>
+ */
+public class AppEntitiesHeaderController {
+
+    private static final String TAG = "AppEntitiesHeaderCtl";
+
+    @VisibleForTesting
+    static final int MAXIMUM_APPS = 3;
+
+    private final Context mContext;
+    private final TextView mHeaderTitleView;
+    private final Button mHeaderDetailsView;
+
+    private final AppEntity[] mAppEntities;
+    private final View[] mAppEntityViews;
+    private final ImageView[] mAppIconViews;
+    private final TextView[] mAppTitleViews;
+    private final TextView[] mAppSummaryViews;
+
+    private int mHeaderTitleRes;
+    private int mHeaderDetailsRes;
+    private View.OnClickListener mDetailsOnClickListener;
+
+    /**
+     * Creates a new instance of the controller.
+     *
+     * @param context the Context the view is running in
+     * @param appEntitiesHeaderView view was inflated from <code>app_entities_header</code>
+     */
+    public static AppEntitiesHeaderController newInstance(@NonNull Context context,
+            @NonNull View appEntitiesHeaderView) {
+        return new AppEntitiesHeaderController(context, appEntitiesHeaderView);
+    }
+
+    private AppEntitiesHeaderController(Context context, View appEntitiesHeaderView) {
+        mContext = context;
+        mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title);
+        mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details);
+
+        mAppEntities = new AppEntity[MAXIMUM_APPS];
+        mAppIconViews = new ImageView[MAXIMUM_APPS];
+        mAppTitleViews = new TextView[MAXIMUM_APPS];
+        mAppSummaryViews = new TextView[MAXIMUM_APPS];
+
+        mAppEntityViews = new View[]{
+                appEntitiesHeaderView.findViewById(R.id.app1_view),
+                appEntitiesHeaderView.findViewById(R.id.app2_view),
+                appEntitiesHeaderView.findViewById(R.id.app3_view)
+        };
+
+        // Initialize view in advance, so we won't take too much time to do it when controller is
+        // binding view.
+        for (int index = 0; index < MAXIMUM_APPS; index++) {
+            final View appView = mAppEntityViews[index];
+            mAppIconViews[index] = (ImageView) appView.findViewById(R.id.app_icon);
+            mAppTitleViews[index] = (TextView) appView.findViewById(R.id.app_title);
+            mAppSummaryViews[index] = (TextView) appView.findViewById(R.id.app_summary);
+        }
+    }
+
+    /**
+     * Set the text resource for app entities header title.
+     */
+    public AppEntitiesHeaderController setHeaderTitleRes(@StringRes int titleRes) {
+        mHeaderTitleRes = titleRes;
+        return this;
+    }
+
+    /**
+     * Set the text resource for app entities header details.
+     */
+    public AppEntitiesHeaderController setHeaderDetailsRes(@StringRes int detailsRes) {
+        mHeaderDetailsRes = detailsRes;
+        return this;
+    }
+
+    /**
+     * Register a callback to be invoked when header details view is clicked.
+     */
+    public AppEntitiesHeaderController setHeaderDetailsClickListener(
+            @Nullable View.OnClickListener clickListener) {
+        mDetailsOnClickListener = clickListener;
+        return this;
+    }
+
+    /**
+     * Set an app entity at a specified position view.
+     *
+     * @param index the index at which the specified view is to be inserted
+     * @param icon the icon of app entity
+     * @param titleRes the title of app entity
+     * @param summaryRes the summary of app entity
+     * @return this {@code AppEntitiesHeaderController} object
+     */
+    public AppEntitiesHeaderController setAppEntity(int index, @NonNull Drawable icon,
+            @Nullable CharSequence titleRes, @Nullable CharSequence summaryRes) {
+        final AppEntity appEntity = new AppEntity(icon, titleRes, summaryRes);
+        mAppEntities[index] = appEntity;
+        return this;
+    }
+
+    /**
+     * Remove an app entity at a specified position view.
+     *
+     * @param index the index at which the specified view is to be removed
+     * @return this {@code AppEntitiesHeaderController} object
+     */
+    public AppEntitiesHeaderController removeAppEntity(int index) {
+        mAppEntities[index] = null;
+        return this;
+    }
+
+    /**
+     * Clear all app entities in app entities header.
+     *
+     * @return this {@code AppEntitiesHeaderController} object
+     */
+    public AppEntitiesHeaderController clearAllAppEntities() {
+        for (int index = 0; index < MAXIMUM_APPS; index++) {
+            removeAppEntity(index);
+        }
+        return this;
+    }
+
+    /**
+     * Done mutating app entities header, rebinds everything.
+     */
+    public void apply() {
+        bindHeaderTitleView();
+        bindHeaderDetailsView();
+
+        // Rebind all apps view
+        for (int index = 0; index < MAXIMUM_APPS; index++) {
+            bindAppEntityView(index);
+        }
+    }
+
+    private void bindHeaderTitleView() {
+        CharSequence titleText = "";
+        try {
+            titleText = mContext.getText(mHeaderTitleRes);
+        } catch (Resources.NotFoundException e) {
+            Log.e(TAG, "Resource of header title can't not be found!", e);
+        }
+        mHeaderTitleView.setText(titleText);
+        mHeaderTitleView.setVisibility(
+                TextUtils.isEmpty(titleText) ? View.GONE : View.VISIBLE);
+    }
+
+    private void bindHeaderDetailsView() {
+        CharSequence detailsText = "";
+        try {
+            detailsText = mContext.getText(mHeaderDetailsRes);
+        } catch (Resources.NotFoundException e) {
+            Log.e(TAG, "Resource of header details can't not be found!", e);
+        }
+        mHeaderDetailsView.setText(detailsText);
+        mHeaderDetailsView.setVisibility(
+                TextUtils.isEmpty(detailsText) ? View.GONE : View.VISIBLE);
+        mHeaderDetailsView.setOnClickListener(mDetailsOnClickListener);
+    }
+
+    private void bindAppEntityView(int index) {
+        final AppEntity appEntity = mAppEntities[index];
+        mAppEntityViews[index].setVisibility(appEntity != null ? View.VISIBLE : View.GONE);
+
+        if (appEntity != null) {
+            mAppIconViews[index].setImageDrawable(appEntity.icon);
+
+            mAppTitleViews[index].setVisibility(
+                    TextUtils.isEmpty(appEntity.title) ? View.INVISIBLE : View.VISIBLE);
+            mAppTitleViews[index].setText(appEntity.title);
+
+            mAppSummaryViews[index].setVisibility(
+                    TextUtils.isEmpty(appEntity.summary) ? View.INVISIBLE : View.VISIBLE);
+            mAppSummaryViews[index].setText(appEntity.summary);
+        }
+    }
+
+    private static class AppEntity {
+        public final Drawable icon;
+        public final CharSequence title;
+        public final CharSequence summary;
+
+        AppEntity(Drawable appIcon, CharSequence appTitle, CharSequence appSummary) {
+            icon = appIcon;
+            title = appTitle;
+            summary = appSummary;
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
new file mode 100644
index 0000000..c3bc8da
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class AppEntitiesHeaderControllerTest {
+
+    private static final CharSequence TITLE = "APP_TITLE";
+    private static final CharSequence SUMMARY = "APP_SUMMARY";
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    private Context mContext;
+    private Drawable mIcon;
+    private View mAppEntitiesHeaderView;
+    private AppEntitiesHeaderController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mAppEntitiesHeaderView = LayoutInflater.from(mContext).inflate(
+                R.layout.app_entities_header, null /* root */);
+        mIcon = mContext.getDrawable(R.drawable.ic_menu);
+        mController = AppEntitiesHeaderController.newInstance(mContext,
+                mAppEntitiesHeaderView);
+    }
+
+    @Test
+    public void assert_amountOfMaximumAppsAreThree() {
+        assertThat(AppEntitiesHeaderController.MAXIMUM_APPS).isEqualTo(3);
+    }
+
+    @Test
+    public void setHeaderTitleRes_setTextRes_shouldSetToTitleView() {
+        mController.setHeaderTitleRes(R.string.expand_button_title).apply();
+        final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_title);
+
+        assertThat(view.getText()).isEqualTo(mContext.getText(R.string.expand_button_title));
+    }
+
+    @Test
+    public void setHeaderDetailsRes_setTextRes_shouldSetToDetailsView() {
+        mController.setHeaderDetailsRes(R.string.expand_button_title).apply();
+        final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+
+        assertThat(view.getText()).isEqualTo(mContext.getText(R.string.expand_button_title));
+    }
+
+    @Test
+    public void setHeaderDetailsClickListener_setClickListener_detailsViewAttachClickListener() {
+        mController.setHeaderDetailsClickListener(v -> {
+        }).apply();
+        final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+
+        assertThat(view.hasOnClickListeners()).isTrue();
+    }
+
+    @Test
+    public void setAppEntity_indexLessThanZero_shouldThrowArrayIndexOutOfBoundsException() {
+        thrown.expect(ArrayIndexOutOfBoundsException.class);
+
+        mController.setAppEntity(-1, mIcon, TITLE, SUMMARY);
+    }
+
+    @Test
+    public void asetAppEntity_indexGreaterThanMaximum_shouldThrowArrayIndexOutOfBoundsException() {
+        thrown.expect(ArrayIndexOutOfBoundsException.class);
+
+        mController.setAppEntity(AppEntitiesHeaderController.MAXIMUM_APPS + 1, mIcon, TITLE,
+                SUMMARY);
+    }
+
+    @Test
+    public void setAppEntity_addAppToIndex0_shouldShowAppView1() {
+        mController.setAppEntity(0, mIcon, TITLE, SUMMARY).apply();
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final ImageView appIconView = app1View.findViewById(R.id.app_icon);
+        final TextView appTitle = app1View.findViewById(R.id.app_title);
+        final TextView appSummary = app1View.findViewById(R.id.app_summary);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView.getDrawable()).isNotNull();
+        assertThat(appTitle.getText()).isEqualTo(TITLE);
+        assertThat(appSummary.getText()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void setAppEntity_addAppToIndex1_shouldShowAppView2() {
+        mController.setAppEntity(1, mIcon, TITLE, SUMMARY).apply();
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+        final ImageView appIconView = app2View.findViewById(R.id.app_icon);
+        final TextView appTitle = app2View.findViewById(R.id.app_title);
+        final TextView appSummary = app2View.findViewById(R.id.app_summary);
+
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView.getDrawable()).isNotNull();
+        assertThat(appTitle.getText()).isEqualTo(TITLE);
+        assertThat(appSummary.getText()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void setAppEntity_addAppToIndex2_shouldShowAppView3() {
+        mController.setAppEntity(2, mIcon, TITLE, SUMMARY).apply();
+        final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
+        final ImageView appIconView = app3View.findViewById(R.id.app_icon);
+        final TextView appTitle = app3View.findViewById(R.id.app_title);
+        final TextView appSummary = app3View.findViewById(R.id.app_summary);
+
+        assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(appIconView.getDrawable()).isNotNull();
+        assertThat(appTitle.getText()).isEqualTo(TITLE);
+        assertThat(appSummary.getText()).isEqualTo(SUMMARY);
+    }
+
+    @Test
+    public void removeAppEntity_removeIndex0_shouldNotShowAppView1() {
+        mController.setAppEntity(0, mIcon, TITLE, SUMMARY)
+                .setAppEntity(1, mIcon, TITLE, SUMMARY).apply();
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+
+        mController.removeAppEntity(0).apply();
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void clearAllAppEntities_shouldNotShowAllAppViews() {
+        mController.setAppEntity(0, mIcon, TITLE, SUMMARY)
+                .setAppEntity(1, mIcon, TITLE, SUMMARY)
+                .setAppEntity(2, mIcon, TITLE, SUMMARY).apply();
+        final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
+        final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
+        final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
+
+        assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
+
+        mController.clearAllAppEntities().apply();
+        assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
+        assertThat(app2View.getVisibility()).isEqualTo(View.GONE);
+        assertThat(app3View.getVisibility()).isEqualTo(View.GONE);
+    }
+}
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b6c9b8c..cb860ae 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -361,6 +361,7 @@
     <!-- The height of the qs customize header. Should be (28dp - qs_tile_margin_top_bottom). -->
     <dimen name="qs_customize_header_min_height">40dp</dimen>
     <dimen name="qs_tile_margin_top">18dp</dimen>
+    <dimen name="qs_tile_background_size">40dp</dimen>
     <dimen name="qs_quick_tile_size">48dp</dimen>
     <!-- Maximum width of quick quick settings panel. Defaults to MATCH_PARENT-->
     <dimen name="qs_quick_layout_width">-1px</dimen>
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 3a96595d..32fd2dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -19,14 +19,19 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
+import android.graphics.Path;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.PathShape;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.PathParser;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -46,6 +51,7 @@
 public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
 
     private static final String TAG = "QSTileBaseView";
+    private static final int ICON_MASK_ID = com.android.internal.R.string.config_icon_mask;
     private final H mHandler = new H();
     private final int[] mLocInScreen = new int[2];
     private final FrameLayout mIconFrame;
@@ -62,6 +68,7 @@
     private final int mColorInactive;
     private final int mColorDisabled;
     private int mCircleColor;
+    private int mBgSize;
 
     public QSTileBaseView(Context context, QSIconView icon) {
         this(context, icon, false);
@@ -71,15 +78,23 @@
         super(context);
         // Default to Quick Tile padding, and QSTileView will specify its own padding.
         int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
-
         mIconFrame = new FrameLayout(context);
         mIconFrame.setForegroundGravity(Gravity.CENTER);
         int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
         addView(mIconFrame, new LayoutParams(size, size));
         mBg = new ImageView(getContext());
+        Path path = new Path(PathParser.createPathFromPathData(
+                context.getResources().getString(ICON_MASK_ID)));
+        float pathSize = AdaptiveIconDrawable.MASK_SIZE;
+        PathShape p = new PathShape(path, pathSize, pathSize);
+        ShapeDrawable d = new ShapeDrawable(p);
+        int bgSize = context.getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size);
+        d.setIntrinsicHeight(bgSize);
+        d.setIntrinsicWidth(bgSize);
         mBg.setScaleType(ScaleType.FIT_CENTER);
-        mBg.setImageResource(R.drawable.ic_qs_circle);
-        mIconFrame.addView(mBg);
+        mBg.setImageDrawable(d);
+        mIconFrame.addView(mBg, ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
         mIcon = icon;
         FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -107,7 +122,7 @@
         setFocusable(true);
     }
 
-    public View getBgCicle() {
+    public View getBgCircle() {
         return mBg;
     }
 
@@ -303,6 +318,7 @@
 
     private class H extends Handler {
         private static final int STATE_CHANGED = 1;
+
         public H() {
             super(Looper.getMainLooper());
         }
@@ -314,4 +330,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index 11a2fc9..13925ba 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -19,7 +19,6 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
-import android.app.AppGlobals;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -28,7 +27,6 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.ThreadLocalWorkSource;
 import android.os.UserHandle;
@@ -39,6 +37,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AppIdToPackageMap;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderInternal;
@@ -48,9 +47,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 public class BinderCallsStatsService extends Binder {
 
@@ -82,11 +79,11 @@
             mAppIdWhitelist = createAppidWhitelist(context);
         }
 
-        public void dump(PrintWriter pw, Map<Integer, String> appIdToPackageName) {
+        public void dump(PrintWriter pw, AppIdToPackageMap packageMap) {
             pw.println("AppIds of apps that can set the work source:");
             final ArraySet<Integer> whitelist = mAppIdWhitelist;
             for (Integer appId : whitelist) {
-                pw.println("\t- " + appIdToPackageName.getOrDefault(appId, String.valueOf(appId)));
+                pw.println("\t- " + packageMap.mapAppId(appId));
             }
         }
 
@@ -361,7 +358,7 @@
                     pw.println("Detailed tracking disabled");
                     return;
                 } else if ("--dump-worksource-provider".equals(arg)) {
-                    mWorkSourceProvider.dump(pw, getAppIdToPackagesMap());
+                    mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot());
                     return;
                 } else if ("-h".equals(arg)) {
                     pw.println("binder_calls_stats commands:");
@@ -377,28 +374,6 @@
                 }
             }
         }
-        mBinderCallsStats.dump(pw, getAppIdToPackagesMap(), verbose);
-    }
-
-    private Map<Integer, String> getAppIdToPackagesMap() {
-        List<PackageInfo> packages;
-        try {
-            packages = AppGlobals.getPackageManager()
-                    .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                            UserHandle.USER_SYSTEM).getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        Map<Integer,String> map = new HashMap<>();
-        for (PackageInfo pkg : packages) {
-            String name = pkg.packageName;
-            int uid = pkg.applicationInfo.uid;
-            // Use sharedUserId string as package name if there are collisions
-            if (pkg.sharedUserId != null && map.containsKey(uid)) {
-                name = "shared:" + pkg.sharedUserId;
-            }
-            map.put(uid, name);
-        }
-        return map;
+        mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), verbose);
     }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 14503f9..eda9fe1 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -902,6 +902,7 @@
         // Listen to package add and removal events for all users.
         intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         intentFilter.addDataScheme("package");
         mContext.registerReceiverAsUser(
@@ -4203,12 +4204,46 @@
         mPermissionMonitor.onPackageAdded(packageName, uid);
     }
 
-    private void onPackageRemoved(String packageName, int uid) {
+    private void onPackageReplaced(String packageName, int uid) {
+        if (TextUtils.isEmpty(packageName) || uid < 0) {
+            Slog.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
+            return;
+        }
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mVpns) {
+            final Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                return;
+            }
+            // Legacy always-on VPN won't be affected since the package name is not set.
+            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
+                Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user "
+                        + userId);
+                vpn.startAlwaysOnVpn();
+            }
+        }
+    }
+
+    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
         if (TextUtils.isEmpty(packageName) || uid < 0) {
             Slog.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
             return;
         }
         mPermissionMonitor.onPackageRemoved(uid);
+
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mVpns) {
+            final Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                return;
+            }
+            // Legacy always-on VPN won't be affected since the package name is not set.
+            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
+                Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
+                        + userId);
+                vpn.setAlwaysOnPackage(null, false);
+            }
+        }
     }
 
     private void onUserUnlocked(int userId) {
@@ -4245,8 +4280,12 @@
                 onUserUnlocked(userId);
             } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                 onPackageAdded(packageName, uid);
+            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+                onPackageReplaced(packageName, uid);
             } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                onPackageRemoved(packageName, uid);
+                final boolean isReplacing = intent.getBooleanExtra(
+                        Intent.EXTRA_REPLACING, false);
+                onPackageRemoved(packageName, uid, isReplacing);
             }
         }
     };
diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java
index fa3baba..cee98c1 100644
--- a/services/core/java/com/android/server/LooperStatsService.java
+++ b/services/core/java/com/android/server/LooperStatsService.java
@@ -31,6 +31,7 @@
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
+import com.android.internal.os.AppIdToPackageMap;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.CachedDeviceState;
 import com.android.internal.os.LooperStats;
@@ -92,6 +93,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        AppIdToPackageMap packageMap = AppIdToPackageMap.getSnapshot();
         pw.print("Start time: ");
         pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStats.getStartTimeMillis()));
         pw.print("On battery time (ms): ");
@@ -121,7 +123,7 @@
         pw.println(header);
         for (LooperStats.ExportedEntry entry : entries) {
             pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
-                    entry.workSourceUid,
+                    packageMap.mapUid(entry.workSourceUid),
                     entry.threadName,
                     entry.handlerClassName,
                     entry.messageName,
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index b7ed2f9..602aedb 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -206,45 +206,6 @@
     // Handle of the user initiating VPN.
     private final int mUserHandle;
 
-    // Listen to package removal and change events (update/uninstall) for this user
-    private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final Uri data = intent.getData();
-            final String packageName = data == null ? null : data.getSchemeSpecificPart();
-            if (packageName == null) {
-                return;
-            }
-
-            synchronized (Vpn.this) {
-                // Avoid race where always-on package has been unset
-                if (!packageName.equals(getAlwaysOnPackage())) {
-                    return;
-                }
-
-                final String action = intent.getAction();
-                Log.i(TAG, "Received broadcast " + action + " for always-on VPN package "
-                        + packageName + " in user " + mUserHandle);
-
-                switch(action) {
-                    case Intent.ACTION_PACKAGE_REPLACED:
-                        // Start vpn after app upgrade
-                        startAlwaysOnVpn();
-                        break;
-                    case Intent.ACTION_PACKAGE_REMOVED:
-                        final boolean isPackageRemoved = !intent.getBooleanExtra(
-                                Intent.EXTRA_REPLACING, false);
-                        if (isPackageRemoved) {
-                            setAlwaysOnPackage(null, false);
-                        }
-                        break;
-                }
-            }
-        }
-    };
-
-    private boolean mIsPackageIntentReceiverRegistered = false;
-
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
             @UserIdInt int userHandle) {
         this(looper, context, netService, userHandle, new SystemServices(context));
@@ -500,7 +461,6 @@
             // Prepare this app. The notification will update as a side-effect of updateState().
             prepareInternal(packageName);
         }
-        maybeRegisterPackageChangeReceiverLocked(packageName);
         setVpnForcedLocked(mLockdown);
         return true;
     }
@@ -509,31 +469,6 @@
         return packageName == null || VpnConfig.LEGACY_VPN.equals(packageName);
     }
 
-    private void unregisterPackageChangeReceiverLocked() {
-        if (mIsPackageIntentReceiverRegistered) {
-            mContext.unregisterReceiver(mPackageIntentReceiver);
-            mIsPackageIntentReceiverRegistered = false;
-        }
-    }
-
-    private void maybeRegisterPackageChangeReceiverLocked(String packageName) {
-        // Unregister IntentFilter listening for previous always-on package change
-        unregisterPackageChangeReceiverLocked();
-
-        if (!isNullOrLegacyVpn(packageName)) {
-            mIsPackageIntentReceiverRegistered = true;
-
-            IntentFilter intentFilter = new IntentFilter();
-            // Protected intent can only be sent by system. No permission required in register.
-            intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-            intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            intentFilter.addDataScheme("package");
-            intentFilter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL);
-            mContext.registerReceiverAsUser(
-                    mPackageIntentReceiver, UserHandle.of(mUserHandle), intentFilter, null, null);
-        }
-    }
-
     /**
      * @return the package name of the VPN controller responsible for always-on VPN,
      *         or {@code null} if none is set or always-on VPN is controlled through
@@ -1302,7 +1237,6 @@
         setLockdown(false);
         mAlwaysOn = false;
 
-        unregisterPackageChangeReceiverLocked();
         // Quit any active connections
         agentDisconnect();
     }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
index 6e08949..26e8270 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java
@@ -16,13 +16,17 @@
 
 package com.android.server.locksettings.recoverablekeystore.certificate;
 
-import static javax.xml.xpath.XPathConstants.NODESET;
-
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -40,7 +44,6 @@
 import java.security.cert.CertPathValidator;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertStore;
-import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.CollectionCertStoreParameters;
@@ -58,15 +61,6 @@
 
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
 
 /** Utility functions related to parsing and validating public-key certificates. */
 public final class CertUtils {
@@ -167,50 +161,63 @@
     static List<String> getXmlNodeContents(@MustExist int mustExist, Element rootNode,
             String... nodeTags)
             throws CertParsingException {
-        String expression = String.join("/", nodeTags);
-
-        XPath xPath = XPathFactory.newInstance().newXPath();
-        NodeList nodeList;
-        try {
-            nodeList = (NodeList) xPath.compile(expression).evaluate(rootNode, NODESET);
-        } catch (XPathExpressionException e) {
-            throw new CertParsingException(e);
+        if (nodeTags.length == 0) {
+            throw new CertParsingException("The tag list must not be empty");
         }
 
-        switch (mustExist) {
-            case MUST_EXIST_UNENFORCED:
-                break;
-
-            case MUST_EXIST_EXACTLY_ONE:
-                if (nodeList.getLength() != 1) {
-                    throw new CertParsingException(
-                            "The XML file must contain exactly one node with the path "
-                                    + expression);
-                }
-                break;
-
-            case MUST_EXIST_AT_LEAST_ONE:
-                if (nodeList.getLength() == 0) {
-                    throw new CertParsingException(
-                            "The XML file must contain at least one node with the path "
-                                    + expression);
-                }
-                break;
-
-            default:
-                throw new UnsupportedOperationException(
-                        "This value of MustExist is not supported: " + mustExist);
+        // Go down through all the intermediate node tags (except the last tag for the leaf nodes).
+        // Note that this implementation requires that at most one path exists for the given
+        // intermediate node tags.
+        Element parent = rootNode;
+        for (int i = 0; i < nodeTags.length - 1; i++) {
+            String tag = nodeTags[i];
+            List<Element> children = getXmlDirectChildren(parent, tag);
+            if ((children.size() == 0 && mustExist != MUST_EXIST_UNENFORCED)
+                    || children.size() > 1) {
+                throw new CertParsingException(
+                        "The XML file must contain exactly one path with the tag " + tag);
+            }
+            if (children.size() == 0) {
+                return new ArrayList<>();
+            }
+            parent = children.get(0);
         }
 
+        // Then collect the contents of the leaf nodes.
+        List<Element> leafs = getXmlDirectChildren(parent, nodeTags[nodeTags.length - 1]);
+        if (mustExist == MUST_EXIST_EXACTLY_ONE && leafs.size() != 1) {
+            throw new CertParsingException(
+                    "The XML file must contain exactly one node with the path "
+                            + String.join("/", nodeTags));
+        }
+        if (mustExist == MUST_EXIST_AT_LEAST_ONE && leafs.size() == 0) {
+            throw new CertParsingException(
+                    "The XML file must contain at least one node with the path "
+                            + String.join("/", nodeTags));
+        }
         List<String> result = new ArrayList<>();
-        for (int i = 0; i < nodeList.getLength(); i++) {
-            Node node = nodeList.item(i);
+        for (Element leaf : leafs) {
             // Remove whitespaces and newlines.
-            result.add(node.getTextContent().replaceAll("\\s", ""));
+            result.add(leaf.getTextContent().replaceAll("\\s", ""));
         }
         return result;
     }
 
+    /** Get the direct child nodes with a given tag. */
+    private static List<Element> getXmlDirectChildren(Element parent, String tag) {
+        // Cannot use Element.getElementsByTagName because it will return all descendant elements
+        // with the tag name, i.e. not only the direct child nodes.
+        List<Element> children = new ArrayList<>();
+        NodeList childNodes = parent.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node node = childNodes.item(i);
+            if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(tag)) {
+                children.add((Element) node);
+            }
+        }
+        return children;
+    }
+
     /**
      * Decodes a base64-encoded string.
      *
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 3a74ab5..36b7269 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,6 +16,11 @@
 
 package com.android.server.pm.dex;
 
+import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -26,9 +31,9 @@
 import android.os.Build;
 import android.os.FileUtils;
 import android.os.RemoteException;
-import android.os.storage.StorageManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.storage.StorageManager;
 import android.provider.Settings.Global;
 import android.util.Log;
 import android.util.Slog;
@@ -48,18 +53,14 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.zip.ZipEntry;
 
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
-import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
-import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
-
 /**
  * This class keeps track of how dex files are used.
  * Every time it gets a notification about a dex file being loaded it tracks
@@ -89,6 +90,12 @@
     // encode and save the dex usage data.
     private final PackageDexUsage mPackageDexUsage;
 
+    // PackageDynamicCodeLoading handles recording of dynamic code loading -
+    // which is similar to PackageDexUsage but records a different aspect of the data.
+    // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't
+    // record class loaders or ISAs.)
+    private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;
+
     private final IPackageManager mPackageManager;
     private final PackageDexOptimizer mPackageDexOptimizer;
     private final Object mInstallLock;
@@ -126,14 +133,15 @@
 
     public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
             Installer installer, Object installLock, Listener listener) {
-      mContext = context;
-      mPackageCodeLocationsCache = new HashMap<>();
-      mPackageDexUsage = new PackageDexUsage();
-      mPackageManager = pms;
-      mPackageDexOptimizer = pdo;
-      mInstaller = installer;
-      mInstallLock = installLock;
-      mListener = listener;
+        mContext = context;
+        mPackageCodeLocationsCache = new HashMap<>();
+        mPackageDexUsage = new PackageDexUsage();
+        mPackageDynamicCodeLoading = new PackageDynamicCodeLoading();
+        mPackageManager = pms;
+        mPackageDexOptimizer = pdo;
+        mInstaller = installer;
+        mInstallLock = installLock;
+        mListener = listener;
     }
 
     public void systemReady() {
@@ -207,7 +215,6 @@
                 Slog.i(TAG, loadingAppInfo.packageName +
                         " uses unsupported class loader in " + classLoaderNames);
             }
-            return;
         }
 
         int dexPathIndex = 0;
@@ -236,15 +243,24 @@
                     continue;
                 }
 
-                // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
-                // or UsedByOtherApps), record will return true and we trigger an async write
-                // to disk to make sure we don't loose the data in case of a reboot.
+                if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath,
+                        PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
+                        loadingAppInfo.packageName)) {
+                    mPackageDynamicCodeLoading.maybeWriteAsync();
+                }
 
-                String classLoaderContext = classLoaderContexts[dexPathIndex];
-                if (mPackageDexUsage.record(searchResult.mOwningPackageName,
-                        dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
-                        loadingAppInfo.packageName, classLoaderContext)) {
-                    mPackageDexUsage.maybeWriteAsync();
+                if (classLoaderContexts != null) {
+
+                    // Record dex file usage. If the current usage is a new pattern (e.g. new
+                    // secondary, or UsedByOtherApps), record will return true and we trigger an
+                    // async write to disk to make sure we don't loose the data in case of a reboot.
+
+                    String classLoaderContext = classLoaderContexts[dexPathIndex];
+                    if (mPackageDexUsage.record(searchResult.mOwningPackageName,
+                            dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
+                            loadingAppInfo.packageName, classLoaderContext)) {
+                        mPackageDexUsage.maybeWriteAsync();
+                    }
                 }
             } else {
                 // If we can't find the owner of the dex we simply do not track it. The impact is
@@ -268,8 +284,8 @@
             loadInternal(existingPackages);
         } catch (Exception e) {
             mPackageDexUsage.clear();
-            Slog.w(TAG, "Exception while loading package dex usage. " +
-                    "Starting with a fresh state.", e);
+            mPackageDynamicCodeLoading.clear();
+            Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e);
         }
     }
 
@@ -311,15 +327,24 @@
      * all usage information for the package will be removed.
      */
     public void notifyPackageDataDestroyed(String packageName, int userId) {
-        boolean updated = userId == UserHandle.USER_ALL
-            ? mPackageDexUsage.removePackage(packageName)
-            : mPackageDexUsage.removeUserPackage(packageName, userId);
         // In case there was an update, write the package use info to disk async.
-        // Note that we do the writing here and not in PackageDexUsage in order to be
+        // Note that we do the writing here and not in the lower level classes in order to be
         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
         // multiple updates in PackageDexUsage before writing it).
-        if (updated) {
-            mPackageDexUsage.maybeWriteAsync();
+        if (userId == UserHandle.USER_ALL) {
+            if (mPackageDexUsage.removePackage(packageName)) {
+                mPackageDexUsage.maybeWriteAsync();
+            }
+            if (mPackageDynamicCodeLoading.removePackage(packageName)) {
+                mPackageDynamicCodeLoading.maybeWriteAsync();
+            }
+        } else {
+            if (mPackageDexUsage.removeUserPackage(packageName, userId)) {
+                mPackageDexUsage.maybeWriteAsync();
+            }
+            if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) {
+                mPackageDynamicCodeLoading.maybeWriteAsync();
+            }
         }
     }
 
@@ -388,8 +413,23 @@
             }
         }
 
-        mPackageDexUsage.read();
-        mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths);
+        try {
+            mPackageDexUsage.read();
+            mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths);
+        } catch (Exception e) {
+            mPackageDexUsage.clear();
+            Slog.w(TAG, "Exception while loading package dex usage. "
+                    + "Starting with a fresh state.", e);
+        }
+
+        try {
+            mPackageDynamicCodeLoading.read();
+            mPackageDynamicCodeLoading.syncData(packageToUsersMap);
+        } catch (Exception e) {
+            mPackageDynamicCodeLoading.clear();
+            Slog.w(TAG, "Exception while loading package dynamic code usage. "
+                    + "Starting with a fresh state.", e);
+        }
     }
 
     /**
@@ -415,10 +455,16 @@
      * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
      * to access the package use.
      */
+    @VisibleForTesting
     /*package*/ boolean hasInfoOnPackage(String packageName) {
         return mPackageDexUsage.getPackageUseInfo(packageName) != null;
     }
 
+    @VisibleForTesting
+    /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
+        return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
+    }
+
     /**
      * Perform dexopt on with the given {@code options} on the secondary dex files.
      * @return true if all secondary dex files were processed successfully (compiled or skipped
@@ -652,7 +698,7 @@
             // to load dex files through it.
             try {
                 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
-                if (dexPathReal != dexPath) {
+                if (!dexPath.equals(dexPathReal)) {
                     Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
                             dexPath + " dexPathReal=" + dexPathReal);
                 }
@@ -675,6 +721,7 @@
      */
     public void writePackageDexUsageNow() {
         mPackageDexUsage.writeNow();
+        mPackageDynamicCodeLoading.writeNow();
     }
 
     private void registerSettingObserver() {
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index c2cb861..f74aa1d 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -96,7 +96,7 @@
      * @param ownerUserId the user id which runs the code loading the file
      * @param loadingPackageName the package performing the load
      * @return whether new information has been recorded
-     * @throw IllegalArgumentException if clearly invalid information is detected
+     * @throws IllegalArgumentException if clearly invalid information is detected
      */
     boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId,
             String loadingPackageName) {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 7683172..aca9702 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -1797,7 +1797,7 @@
         // focus). Also if there is an active pinned stack - we always want to notify it about
         // task stack changes, because its positioning may depend on it.
         if (mStackSupervisor.mAppVisibilitiesChangedSinceLastPause
-                || getDisplay().hasPinnedStack()) {
+                || (getDisplay() != null && getDisplay().hasPinnedStack())) {
             mService.getTaskChangeNotificationController().notifyTaskStackChanged();
             mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
         }
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index a335fa2..5b20af3 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -219,7 +219,7 @@
     }
 
     private boolean updateStateLw(final int state) {
-        if (state != mState) {
+        if (mWin != null && state != mState) {
             mState = state;
             if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state));
             mHandler.post(new Runnable() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index dad7b93..fd07cb0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -18,10 +18,14 @@
 
 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile;
+import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -41,6 +45,10 @@
 
 import com.android.server.pm.Installer;
 
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.PathClassLoader;
+import dalvik.system.VMRuntime;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -50,10 +58,6 @@
 import org.mockito.junit.MockitoRule;
 import org.mockito.quality.Strictness;
 
-import dalvik.system.DelegateLastClassLoader;
-import dalvik.system.PathClassLoader;
-import dalvik.system.VMRuntime;
-
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -129,6 +133,9 @@
 
         // Package is not used by others, so we should get nothing back.
         assertNoUseInfo(mFooUser0);
+
+        // A package loading its own code is not stored as DCL.
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -140,6 +147,8 @@
         PackageUseInfo pui = getPackageUseInfo(mBarUser0);
         assertIsUsedByOtherApps(mBarUser0, pui, true);
         assertTrue(pui.getDexUseInfoMap().isEmpty());
+
+        assertHasDclInfo(mBarUser0, mFooUser0, mBarUser0.getBaseAndSplitDexPaths());
     }
 
     @Test
@@ -152,6 +161,8 @@
         assertIsUsedByOtherApps(mFooUser0, pui, false);
         assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
+
+        assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
     }
 
     @Test
@@ -164,6 +175,8 @@
         assertIsUsedByOtherApps(mBarUser0, pui, false);
         assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0);
+
+        assertHasDclInfo(mBarUser0, mFooUser0, barSecondaries);
     }
 
     @Test
@@ -200,9 +213,10 @@
     }
 
     @Test
-    public void testPackageUseInfoNotFound() {
+    public void testNoNotify() {
         // Assert we don't get back data we did not previously record.
         assertNoUseInfo(mFooUser0);
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -210,6 +224,7 @@
         // Notifying with an invalid ISA should be ignored.
         notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0);
         assertNoUseInfo(mInvalidIsa);
+        assertNoDclInfo(mInvalidIsa);
     }
 
     @Test
@@ -218,6 +233,7 @@
         // register in DexManager#load should be ignored.
         notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0);
         assertNoUseInfo(mDoesNotExist);
+        assertNoDclInfo(mDoesNotExist);
     }
 
     @Test
@@ -226,6 +242,8 @@
         // Request should be ignored.
         notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1);
         assertNoUseInfo(mBarUser1);
+
+        assertNoDclInfo(mBarUser1);
     }
 
     @Test
@@ -235,6 +253,10 @@
         // still check that nothing goes unexpected in DexManager.
         notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1);
         assertNoUseInfo(mBarUser1);
+        assertNoUseInfo(mFooUser0);
+
+        assertNoDclInfo(mBarUser1);
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -247,6 +269,7 @@
         // is trying to load something from it we should not find it.
         notifyDexLoad(mFooUser0, newSecondaries, mUser0);
         assertNoUseInfo(newPackage);
+        assertNoDclInfo(newPackage);
 
         // Notify about newPackage install and let mFoo load its dexes.
         mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0);
@@ -257,6 +280,7 @@
         assertIsUsedByOtherApps(newPackage, pui, false);
         assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0);
+        assertHasDclInfo(newPackage, mFooUser0, newSecondaries);
     }
 
     @Test
@@ -273,6 +297,7 @@
         assertIsUsedByOtherApps(newPackage, pui, false);
         assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0);
+        assertHasDclInfo(newPackage, newPackage, newSecondaries);
     }
 
     @Test
@@ -305,6 +330,7 @@
         // We shouldn't find yet the new split as we didn't notify the package update.
         notifyDexLoad(mFooUser0, newSplits, mUser0);
         assertNoUseInfo(mBarUser0);
+        assertNoDclInfo(mBarUser0);
 
         // Notify that bar is updated. splitSourceDirs will contain the updated path.
         mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(),
@@ -314,8 +340,8 @@
         // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers.
         notifyDexLoad(mFooUser0, newSplits, mUser0);
         PackageUseInfo pui = getPackageUseInfo(mBarUser0);
-        assertNotNull(pui);
         assertIsUsedByOtherApps(newSplits, pui, true);
+        assertHasDclInfo(mBarUser0, mFooUser0, newSplits);
     }
 
     @Test
@@ -326,11 +352,15 @@
 
         mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0);
 
-        // Bar should not be around since it was removed for all users.
+        // Data for user 1 should still be present
         PackageUseInfo pui = getPackageUseInfo(mBarUser1);
-        assertNotNull(pui);
         assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(),
                 /*isUsedByOtherApps*/false, mUser1);
+        assertHasDclInfo(mBarUser1, mBarUser1, mBarUser1.getSecondaryDexPaths());
+
+        // But not user 0
+        assertNoUseInfo(mBarUser0, mUser0);
+        assertNoDclInfo(mBarUser0, mUser0);
     }
 
     @Test
@@ -349,6 +379,8 @@
         PackageUseInfo pui = getPackageUseInfo(mFooUser0);
         assertIsUsedByOtherApps(mFooUser0, pui, true);
         assertTrue(pui.getDexUseInfoMap().isEmpty());
+
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -362,6 +394,7 @@
         // Foo should not be around since all its secondary dex info were deleted
         // and it is not used by other apps.
         assertNoUseInfo(mFooUser0);
+        assertNoDclInfo(mFooUser0);
     }
 
     @Test
@@ -374,6 +407,7 @@
 
         // Bar should not be around since it was removed for all users.
         assertNoUseInfo(mBarUser0);
+        assertNoDclInfo(mBarUser0);
     }
 
     @Test
@@ -383,6 +417,7 @@
         notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0);
         // The dex file should not be recognized as a package.
         assertFalse(mDexManager.hasInfoOnPackage(frameworkDex));
+        assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex));
     }
 
     @Test
@@ -395,6 +430,8 @@
         assertIsUsedByOtherApps(mFooUser0, pui, false);
         assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
         assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
+
+        assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
     }
 
     @Test
@@ -402,7 +439,12 @@
         List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths();
         notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0);
 
+        // We don't record the dex usage
         assertNoUseInfo(mBarUser0UnsupportedClassLoader);
+
+        // But we do record this as an intance of dynamic code loading
+        assertHasDclInfo(
+                mBarUser0UnsupportedClassLoader, mBarUser0UnsupportedClassLoader, secondaries);
     }
 
     @Test
@@ -414,6 +456,8 @@
         notifyDexLoad(mBarUser0, classLoaders, classPaths, mUser0);
 
         assertNoUseInfo(mBarUser0);
+
+        assertHasDclInfo(mBarUser0, mBarUser0, mBarUser0.getSecondaryDexPaths());
     }
 
     @Test
@@ -421,6 +465,7 @@
         notifyDexLoad(mBarUser0, null, mUser0);
 
         assertNoUseInfo(mBarUser0);
+        assertNoDclInfo(mBarUser0);
     }
 
     @Test
@@ -455,12 +500,14 @@
         notifyDexLoad(mBarUser0, secondaries, mUser0);
         PackageUseInfo pui = getPackageUseInfo(mBarUser0);
         assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0);
+        assertHasDclInfo(mBarUser0, mBarUser0, secondaries);
 
         // Record bar secondaries again with an unsupported class loader. This should not change the
         // context.
         notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0);
         pui = getPackageUseInfo(mBarUser0);
         assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0);
+        assertHasDclInfo(mBarUser0, mBarUser0, secondaries);
     }
 
     @Test
@@ -533,13 +580,53 @@
 
     private PackageUseInfo getPackageUseInfo(TestData testData) {
         assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName()));
-        return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
+        PackageUseInfo pui = mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
+        assertNotNull(pui);
+        return pui;
     }
 
     private void assertNoUseInfo(TestData testData) {
         assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName()));
     }
 
+    private void assertNoUseInfo(TestData testData, int userId) {
+        if (!mDexManager.hasInfoOnPackage(testData.getPackageName())) {
+            return;
+        }
+        PackageUseInfo pui = getPackageUseInfo(testData);
+        for (DexUseInfo dexUseInfo : pui.getDexUseInfoMap().values()) {
+            assertNotEquals(userId, dexUseInfo.getOwnerUserId());
+        }
+    }
+
+    private void assertNoDclInfo(TestData testData) {
+        assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()));
+    }
+
+    private void assertNoDclInfo(TestData testData, int userId) {
+        PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName());
+        if (info == null) {
+            return;
+        }
+
+        for (DynamicCodeFile fileInfo : info.mFileUsageMap.values()) {
+            assertNotEquals(userId, fileInfo.mUserId);
+        }
+    }
+
+    private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) {
+        PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName());
+        assertNotNull("No DCL data for owner " + owner.getPackageName(), info);
+        for (String path : paths) {
+            DynamicCodeFile fileInfo = info.mFileUsageMap.get(path);
+            assertNotNull("No DCL data for path " + path, fileInfo);
+            assertEquals(PackageDynamicCodeLoading.FILE_TYPE_DEX, fileInfo.mFileType);
+            assertEquals(owner.mUserId, fileInfo.mUserId);
+            assertTrue("No DCL data for loader " + loader.getPackageName(),
+                    fileInfo.mLoadingPackages.contains(loader.getPackageName()));
+        }
+    }
+
     private static PackageInfo getMockPackageInfo(String packageName, int userId) {
         PackageInfo pi = new PackageInfo();
         pi.packageName = packageName;
@@ -563,11 +650,13 @@
         private final PackageInfo mPackageInfo;
         private final String mLoaderIsa;
         private final String mClassLoader;
+        private final int mUserId;
 
         private TestData(String packageName, String loaderIsa, int userId, String classLoader) {
             mPackageInfo = getMockPackageInfo(packageName, userId);
             mLoaderIsa = loaderIsa;
             mClassLoader = classLoader;
+            mUserId = userId;
         }
 
         private TestData(String packageName, String loaderIsa, int userId) {
@@ -603,9 +692,7 @@
         List<String> getBaseAndSplitDexPaths() {
             List<String> paths = new ArrayList<>();
             paths.add(mPackageInfo.applicationInfo.sourceDir);
-            for (String split : mPackageInfo.applicationInfo.splitSourceDirs) {
-                paths.add(split);
-            }
+            Collections.addAll(paths, mPackageInfo.applicationInfo.splitSourceDirs);
             return paths;
         }
 
diff --git a/tools/hiddenapi/exclude.sh b/tools/hiddenapi/exclude.sh
index 2291e5a..4ffcf68 100755
--- a/tools/hiddenapi/exclude.sh
+++ b/tools/hiddenapi/exclude.sh
@@ -11,6 +11,7 @@
   android.system \
   com.android.bouncycastle \
   com.android.conscrypt \
+  com.android.i18n.phonenumbers \
   com.android.okhttp \
   com.sun \
   dalvik \