Merge "tether: Use new API: startTetheredHotspot to enable hotspot"
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..03af56d
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,13 @@
+BasedOnStyle: Google
+
+AccessModifierOffset: -4
+AlignOperands: false
+AllowShortFunctionsOnASingleLine: Inline
+AlwaysBreakBeforeMultilineStrings: false
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ConstructorInitializerIndentWidth: 6
+ContinuationIndentWidth: 8
+IndentWidth: 4
+PenaltyBreakBeforeFirstCallParameter: 100000
+SpacesBeforeTrailingComments: 1
diff --git a/Android.bp b/Android.bp
index 04b4e6e..5c99b8c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -217,7 +217,9 @@
         // TODO(b/146011398) package android.net.wifi is now split amongst 2 jars: framework.jar and
         // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
         // to a separate package.
-        "wifi/java/android/net/wifi/WifiNetworkScoreCache.java"
+        "wifi/java/android/net/wifi/WifiNetworkScoreCache.java",
+        "wifi/java/android/net/wifi/WifiCondManager.java",
+        "wifi/java/android/net/wifi/wificond/*.java",
     ],
 }
 
@@ -247,6 +249,7 @@
         ":framework-telephony-sources",
         ":framework-wifi-sources",
         ":framework-wifi-non-updatable-sources",
+	":libwificond_ipc_aidl",
         ":PacProcessor-aidl-sources",
         ":ProxyHandler-aidl-sources",
 
@@ -371,6 +374,9 @@
         "com.android.sysprop.apex",
         "PlatformProperties",
     ],
+    aidl: {
+        include_dirs: ["system/connectivity/wificond/aidl"],
+    },
     sdk_version: "core_platform",
     installable: false,
 }
@@ -804,8 +810,8 @@
 filegroup {
     name: "incremental_aidl",
     srcs: [
+        "core/java/android/os/incremental/IIncrementalManagerNative.aidl",
         "core/java/android/os/incremental/IIncrementalManager.aidl",
-        "core/java/android/os/incremental/IIncrementalServiceProxy.aidl",
         "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
         "core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
         "core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 8fac394..f94de29 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -256,6 +256,7 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/ext.jar)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/google/android/mms)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/*-service.jar)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/service-statsd.jar)
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
 # ******************************************************************
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 6831117..78e72bf 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,3 +1,13 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+# Only turn on clang-format check for the following subfolders.
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+               cmds/hid/
+               cmds/input/
+               libs/input/
+
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
 
diff --git a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
index c096cd2..4ed3b4e 100644
--- a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
@@ -95,7 +95,7 @@
 
         mTraceMarkParser.forAllSlices((key, slices) -> {
             for (TraceMarkSlice slice : slices) {
-                state.addExtraResult(key, (long) (slice.getDurarionInSeconds() * NANOS_PER_S));
+                state.addExtraResult(key, (long) (slice.getDurationInSeconds() * NANOS_PER_S));
             }
         });
 
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
index b075239..fe2b1f6 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -23,11 +23,15 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -140,6 +144,8 @@
     /** @see #addExtraResult(String, long) */
     private ArrayMap<String, ArrayList<Long>> mExtraResults;
 
+    private final List<Long> mTmpDurations = Arrays.asList(0L);
+
     // Statistics. These values will be filled when the benchmark has finished.
     // The computation needs double precision, but long int is fine for final reporting.
     private Stats mStats;
@@ -188,14 +194,25 @@
         if (duration < 0) {
             throw new RuntimeException("duration is negative: " + duration);
         }
+        mTmpDurations.set(0, duration);
+        return keepRunning(mTmpDurations);
+    }
+
+    /**
+     * Similar to the {@link #keepRunning(long)} but accepts a list of durations
+     */
+    public boolean keepRunning(List<Long> durations) {
         switch (mState) {
             case NOT_STARTED:
                 mState = WARMUP;
                 mWarmupStartTime = System.nanoTime();
                 return true;
             case WARMUP: {
+                if (ArrayUtils.isEmpty(durations)) {
+                    return true;
+                }
                 final long timeSinceStartingWarmup = System.nanoTime() - mWarmupStartTime;
-                ++mWarmupIterations;
+                mWarmupIterations += durations.size();
                 if (mWarmupIterations >= WARMUP_MIN_ITERATIONS
                         && timeSinceStartingWarmup >= mWarmupDurationNs) {
                     beginBenchmark(timeSinceStartingWarmup, mWarmupIterations);
@@ -203,7 +220,10 @@
                 return true;
             }
             case RUNNING: {
-                mResults.add(duration);
+                if (ArrayUtils.isEmpty(durations)) {
+                    return true;
+                }
+                mResults.addAll(durations);
                 final boolean keepRunning = mResults.size() < mMaxIterations;
                 if (!keepRunning) {
                     mStats = new Stats(mResults);
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
index 1afed3a..b15b6f6 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
@@ -40,6 +40,8 @@
 
     private final Predicate<TraceMarkLine> mTraceLineFilter;
 
+    private static final long MICROS_PER_SECOND = 1000L * 1000L;
+
     public TraceMarkParser(Predicate<TraceMarkLine> traceLineFilter) {
         mTraceLineFilter = traceLineFilter;
     }
@@ -116,13 +118,19 @@
         }
     }
 
+    public void reset() {
+        mSlicesMap.clear();
+        mDepthMap.clear();
+        mPendingStarts.clear();
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
         forAllSlices((key, slices) -> {
             double totalMs = 0;
             for (TraceMarkSlice s : slices) {
-                totalMs += s.getDurarionInSeconds() * 1000;
+                totalMs += s.getDurationInSeconds() * 1000;
             }
             sb.append(key).append(" count=").append(slices.size()).append(" avg=")
                     .append(totalMs / slices.size()).append("ms\n");
@@ -134,6 +142,10 @@
         return sb.toString();
     }
 
+    static double microsecondToSeconds(long ms) {
+        return (ms * 1.0d) / MICROS_PER_SECOND;
+    }
+
     public static class TraceMarkSlice {
         public final TraceMarkLine begin;
         public final TraceMarkLine end;
@@ -143,7 +155,11 @@
             this.end = end;
         }
 
-        public double getDurarionInSeconds() {
+        public double getDurationInSeconds() {
+            return microsecondToSeconds(end.timestamp - begin.timestamp);
+        }
+
+        public long getDurationInMicroseconds() {
             return end.timestamp - begin.timestamp;
         }
     }
@@ -164,7 +180,7 @@
         static final char SYNC_END = 'E';
 
         public final String taskPid;
-        public final double timestamp;
+        public final long timestamp; // in microseconds
         public final String name;
         public final boolean isAsync;
         public final boolean isBegin;
@@ -179,7 +195,7 @@
             if (timeBegin < 0) {
                 throw new IllegalArgumentException("Timestamp start not found");
             }
-            timestamp = Double.parseDouble(rawLine.substring(timeBegin, timeEnd));
+            timestamp = parseMicroseconds(rawLine.substring(timeBegin, timeEnd));
             isAsync = type == ASYNC_START || type == ASYNC_FINISH;
             isBegin = type == ASYNC_START || type == SYNC_BEGIN;
 
@@ -223,9 +239,29 @@
             return null;
         }
 
+        /**
+         * Parse the timestamp from atrace output, the format will be like:
+         * 84962.920719  where the decimal part will be always exactly 6 digits.
+         * ^^^^^ ^^^^^^
+         * |     |
+         * sec   microsec
+         */
+        static long parseMicroseconds(String line) {
+            int end = line.length();
+            long t = 0;
+            for (int i = 0; i < end; i++) {
+                char c = line.charAt(i);
+                if (c >= '0' && c <= '9') {
+                    t = t * 10 + (c - '0');
+                }
+            }
+            return t;
+        }
+
         @Override
         public String toString() {
-            return "TraceMarkLine{pid=" + taskPid + " time=" + timestamp + " name=" + name
+            return "TraceMarkLine{pid=" + taskPid + " time="
+                    + microsecondToSeconds(timestamp) + " name=" + name
                     + " async=" + isAsync + " begin=" + isBegin + "}";
         }
     }
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index d76a40e..5c46e1f 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -14,20 +14,17 @@
 
 apex {
     name: "com.android.os.statsd",
-
+    defaults: ["com.android.os.statsd-defaults"],
     manifest: "apex_manifest.json",
+}
 
-    // optional. if unspecified, a default one is auto-generated
-    //androidManifest: "AndroidManifest.xml",
-
+apex_defaults {
     // libc.so and libcutils.so are included in the apex
     // native_shared_libs: ["libc", "libcutils"],
     // binaries: ["vold"],
-    // java_libs: ["core-all"],
+    java_libs: ["service-statsd"],
     // prebuilts: ["my_prebuilt"],
-
-    compile_multilib: "both",
-
+    name: "com.android.os.statsd-defaults",
     key: "com.android.os.statsd.key",
     certificate: ":com.android.os.statsd.certificate",
 }
diff --git a/apex/statsd/aidl/android/os/IStatsManager.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
similarity index 99%
rename from apex/statsd/aidl/android/os/IStatsManager.aidl
rename to apex/statsd/aidl/android/os/IStatsd.aidl
index cc62f07..cffc6ce 100644
--- a/apex/statsd/aidl/android/os/IStatsManager.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -24,7 +24,7 @@
   * Binder interface to communicate with the statistics management service.
   * {@hide}
   */
-interface IStatsManager {
+interface IStatsd {
     /**
      * Tell the stats daemon that the android system server is up and running.
      */
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index f71d74f..f3a8989 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -1,5 +1,5 @@
 // Statsd Service jar, which will eventually be put in the statsd mainline apex.
-// service-statsd needs to be added to PRODUCT_SYSTEM_SERVER_JARS.
+// service-statsd needs to be added to PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS.
 // This jar will contain StatsCompanionService
 java_library {
     name: "service-statsd",
@@ -8,9 +8,9 @@
     srcs: [
         "java/**/*.java",
     ],
-
+    // TODO: link against the proper stubs (b/146084685).
     libs: [
-        "framework",
+        "framework-minus-apex",
         "services.core",
     ],
 }
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index 6fb3bc4..bc7716e 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -79,7 +79,7 @@
 import android.os.IBinder;
 import android.os.IPullAtomCallback;
 import android.os.IStatsCompanionService;
-import android.os.IStatsManager;
+import android.os.IStatsd;
 import android.os.IStoraged;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
@@ -268,7 +268,7 @@
     private final AlarmManager mAlarmManager;
     private final INetworkStatsService mNetworkStatsService;
     @GuardedBy("sStatsdLock")
-    private static IStatsManager sStatsd;
+    private static IStatsd sStatsd;
     private static final Object sStatsdLock = new Object();
 
     private final OnAlarmListener mAnomalyAlarmListener = new AnomalyAlarmListener();
@@ -2743,8 +2743,8 @@
      * Note: This should only be called from sayHiToStatsd. All other clients should use the cached
      * sStatsd with a null check.
      */
-    private static IStatsManager fetchStatsdService() {
-        return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+    private static IStatsd fetchStatsdService() {
+        return IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
     }
 
     public static final class Lifecycle extends SystemService {
diff --git a/apex/statsd/testing/Android.bp b/apex/statsd/testing/Android.bp
new file mode 100644
index 0000000..22e7301
--- /dev/null
+++ b/apex/statsd/testing/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+apex {
+    name: "test_com.android.os.statsd",
+    visibility: [
+        "//system/apex/tests",
+    ],
+    defaults: ["com.android.os.statsd-defaults"],
+    manifest: "test_manifest.json",
+    file_contexts: ":com.android.os.statsd-file_contexts",
+    // Test APEX, should never be installed
+    installable: false,
+}
diff --git a/apex/statsd/testing/test_manifest.json b/apex/statsd/testing/test_manifest.json
new file mode 100644
index 0000000..57343d3
--- /dev/null
+++ b/apex/statsd/testing/test_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.os.statsd",
+  "version": 2147483647
+}
diff --git a/api/current.txt b/api/current.txt
index 6b2f45a..b61917d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6739,6 +6739,7 @@
     method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
     method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
     method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
+    method @NonNull public java.util.Set<java.lang.String> getCrossProfilePackages(@NonNull android.content.ComponentName);
     method @NonNull public java.util.List<java.lang.String> getCrossProfileWidgetProviders(@NonNull android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
     method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
@@ -6856,6 +6857,7 @@
     method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
     method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
+    method public void setCrossProfilePackages(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
     method public void setDefaultSmsApplication(@NonNull android.content.ComponentName, @NonNull String);
     method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
     method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
@@ -25397,6 +25399,8 @@
     field public static final String KEY_OPERATING_RATE = "operating-rate";
     field public static final String KEY_OUTPUT_REORDER_DEPTH = "output-reorder-depth";
     field public static final String KEY_PCM_ENCODING = "pcm-encoding";
+    field public static final String KEY_PIXEL_ASPECT_RATIO_HEIGHT = "sar-height";
+    field public static final String KEY_PIXEL_ASPECT_RATIO_WIDTH = "sar-width";
     field public static final String KEY_PREPEND_HEADER_TO_SYNC_FRAMES = "prepend-sps-pps-to-idr-frames";
     field public static final String KEY_PRIORITY = "priority";
     field public static final String KEY_PROFILE = "profile";
@@ -25639,10 +25643,10 @@
   }
 
   public static interface MediaParser.OutputConsumer {
-    method public void onFormat(int, @NonNull android.media.MediaFormat);
     method public void onSampleCompleted(int, long, int, int, int, @Nullable android.media.MediaCodec.CryptoInfo);
     method public void onSampleData(int, @NonNull android.media.MediaParser.InputReader) throws java.io.IOException, java.lang.InterruptedException;
     method public void onSeekMap(@NonNull android.media.MediaParser.SeekMap);
+    method public void onTrackData(int, @NonNull android.media.MediaParser.TrackData);
     method public void onTracksFound(int);
   }
 
@@ -25663,6 +25667,11 @@
     method public void seekToPosition(long);
   }
 
+  public static final class MediaParser.TrackData {
+    field @Nullable public final android.media.DrmInitData drmInitData;
+    field @NonNull public final android.media.MediaFormat mediaFormat;
+  }
+
   public static final class MediaParser.UnrecognizedInputFormatException extends java.io.IOException {
   }
 
@@ -29211,6 +29220,7 @@
 
   public class NetworkRequest implements android.os.Parcelable {
     method public int describeContents();
+    method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
     method public boolean hasCapability(int);
     method public boolean hasTransport(int);
     method public void writeToParcel(android.os.Parcel, int);
@@ -45597,6 +45607,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot();
     method public int getActiveModemCount();
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
+    method @NonNull public static int[] getAllNetworkTypes();
     method public int getCallState();
     method public int getCardIdForDefaultEuicc();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig();
@@ -45969,6 +45980,7 @@
     field public static final int TYPE_MCX = 1024; // 0x400
     field public static final int TYPE_MMS = 2; // 0x2
     field public static final int TYPE_SUPL = 4; // 0x4
+    field public static final int TYPE_XCAP = 2048; // 0x800
   }
 
   public static class ApnSetting.Builder {
@@ -47278,7 +47290,7 @@
     field public static final long WEEK_IN_MILLIS = 604800000L; // 0x240c8400L
     field public static final String YEAR_FORMAT = "%Y";
     field public static final String YEAR_FORMAT_TWO_DIGITS = "%g";
-    field public static final long YEAR_IN_MILLIS = 31449600000L; // 0x7528ad000L
+    field @Deprecated public static final long YEAR_IN_MILLIS = 31449600000L; // 0x7528ad000L
     field @Deprecated public static final int[] sameMonthTable;
     field @Deprecated public static final int[] sameYearTable;
   }
@@ -48717,6 +48729,13 @@
     ctor public Base64OutputStream(java.io.OutputStream, int);
   }
 
+  public final class CloseGuard {
+    ctor public CloseGuard();
+    method public void close();
+    method public void open(@NonNull String);
+    method public void warnIfOpen();
+  }
+
   @Deprecated public final class Config {
     field @Deprecated public static final boolean DEBUG = false;
     field @Deprecated public static final boolean LOGD = true;
diff --git a/api/system-current.txt b/api/system-current.txt
old mode 100644
new mode 100755
index 6b51eab..b449b2e
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1305,6 +1305,7 @@
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
     method public int getUsageSource();
+    method @RequiresPermission(android.Manifest.permission.BIND_CARRIER_SERVICES) public void onCarrierPrivilegedAppsChanged();
     method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @Nullable android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
@@ -1423,15 +1424,24 @@
 
   public final class BluetoothDevice implements android.os.Parcelable {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess();
+    method public boolean cancelPairing();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getBatteryLevel();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getMessageAccessPermission();
     method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getPhonebookAccessPermission();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isBondingInitiatedLocally();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInSilenceMode();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMessageAccessPermission(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, @NonNull byte[]);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setPin(@Nullable String);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSimAccessPermission(int);
     field public static final int ACCESS_ALLOWED = 1; // 0x1
     field public static final int ACCESS_REJECTED = 2; // 0x2
     field public static final int ACCESS_UNKNOWN = 0; // 0x0
@@ -1466,7 +1476,9 @@
   }
 
   public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getActiveDevices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public long getHiSyncId(@Nullable android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
@@ -3410,6 +3422,14 @@
     field public static final int STATUS_OK = 0; // 0x0
   }
 
+  public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR;
+    field public final int end;
+    field public final int start;
+  }
+
   public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -3879,6 +3899,7 @@
     field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 64; // 0x40
     field public static final int FLAG_BYPASS_MUTE = 128; // 0x80
     field public static final int FLAG_HW_HOTWORD = 32; // 0x20
+    field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final int USAGE_CALL_ASSISTANT = 17; // 0x11
   }
 
   public static class AudioAttributes.Builder {
@@ -4198,8 +4219,11 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerDetector createSoundTriggerDetector(java.util.UUID, @NonNull android.media.soundtrigger.SoundTriggerDetector.Callback, @Nullable android.os.Handler);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID);
     method public int getDetectionServiceOperationsTimeout();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
   }
 
@@ -4356,6 +4380,7 @@
     method @Nullable public String onHardwareRemoved(android.media.tv.TvInputHardwareInfo);
     method @Nullable public android.media.tv.TvInputInfo onHdmiDeviceAdded(android.hardware.hdmi.HdmiDeviceInfo);
     method @Nullable public String onHdmiDeviceRemoved(android.hardware.hdmi.HdmiDeviceInfo);
+    method public void onHdmiDeviceUpdated(@NonNull android.hardware.hdmi.HdmiDeviceInfo);
   }
 
   public abstract static class TvInputService.RecordingSession {
@@ -5647,7 +5672,6 @@
     method public boolean isApMacRandomizationSupported();
     method public boolean isConnectedMacRandomizationSupported();
     method @Deprecated public boolean isDeviceToDeviceRttSupported();
-    method public boolean isDualBandSupported();
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean isDualModeSupported();
     method public boolean isPortableHotspotSupported();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
@@ -5660,10 +5684,10 @@
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerTrafficStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.TrafficStateCallback);
     method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void removeOnWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void restoreBackupData(@NonNull byte[]);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void restoreSoftApBackupData(@NonNull byte[]);
+    method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public android.net.wifi.SoftApConfiguration restoreSoftApBackupData(@NonNull byte[]);
     method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void restoreSupplicantBackupData(@NonNull byte[], @NonNull byte[]);
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveBackupData();
-    method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
@@ -6203,6 +6227,27 @@
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void noteWifiScanStoppedFromSource(@NonNull android.os.WorkSource);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void noteWifiState(int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void noteWifiSupplicantStateChanged(int, boolean);
+    field public static final int WIFI_STATE_OFF = 0; // 0x0
+    field public static final int WIFI_STATE_OFF_SCANNING = 1; // 0x1
+    field public static final int WIFI_STATE_ON_CONNECTED_P2P = 5; // 0x5
+    field public static final int WIFI_STATE_ON_CONNECTED_STA = 4; // 0x4
+    field public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6; // 0x6
+    field public static final int WIFI_STATE_ON_DISCONNECTED = 3; // 0x3
+    field public static final int WIFI_STATE_ON_NO_NETWORKS = 2; // 0x2
+    field public static final int WIFI_STATE_SOFT_AP = 7; // 0x7
+    field public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7; // 0x7
+    field public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6; // 0x6
+    field public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5; // 0x5
+    field public static final int WIFI_SUPPL_STATE_COMPLETED = 10; // 0xa
+    field public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1; // 0x1
+    field public static final int WIFI_SUPPL_STATE_DORMANT = 11; // 0xb
+    field public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8; // 0x8
+    field public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9; // 0x9
+    field public static final int WIFI_SUPPL_STATE_INACTIVE = 3; // 0x3
+    field public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2; // 0x2
+    field public static final int WIFI_SUPPL_STATE_INVALID = 0; // 0x0
+    field public static final int WIFI_SUPPL_STATE_SCANNING = 4; // 0x4
+    field public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12; // 0xc
   }
 
   public class Binder implements android.os.IBinder {
@@ -6594,6 +6639,21 @@
     field public static final int STATUS_WAITING_REBOOT = 5; // 0x5
   }
 
+  public class TelephonyServiceManager {
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyServiceRegisterer();
+  }
+
+  public static class TelephonyServiceManager.ServiceNotFoundException extends java.lang.Exception {
+    ctor public TelephonyServiceManager.ServiceNotFoundException(@NonNull String);
+  }
+
+  public final class TelephonyServiceManager.ServiceRegisterer {
+    method @Nullable public android.os.IBinder get();
+    method @NonNull public android.os.IBinder getOrThrow() throws android.os.TelephonyServiceManager.ServiceNotFoundException;
+    method public void register(@NonNull android.os.IBinder);
+    method @Nullable public android.os.IBinder tryGet();
+  }
+
   public class UpdateEngine {
     ctor public UpdateEngine();
     method public void applyPayload(String, long, long, String[]);
@@ -6885,7 +6945,7 @@
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
     method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
-    method @BinderThread public void onUpdateUserSensitive();
+    method @BinderThread public void onUpdateUserSensitivePermissionFlags();
     field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
   }
 
@@ -7059,7 +7119,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
     method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
-    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
     field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
     field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
@@ -7091,6 +7151,10 @@
     field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
   }
 
+  public static class DeviceConfig.BadConfigException extends java.lang.Exception {
+    ctor public DeviceConfig.BadConfigException();
+  }
+
   public static interface DeviceConfig.OnPropertiesChangedListener {
     method public void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
   }
@@ -7105,6 +7169,16 @@
     method @Nullable public String getString(@NonNull String, @Nullable String);
   }
 
+  public static final class DeviceConfig.Properties.Builder {
+    ctor public DeviceConfig.Properties.Builder(@NonNull String);
+    method @NonNull public android.provider.DeviceConfig.Properties build();
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setBoolean(@NonNull String, boolean);
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setFloat(@NonNull String, float);
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setInt(@NonNull String, int);
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setLong(@NonNull String, long);
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setString(@NonNull String, @Nullable String);
+  }
+
   public final class DocumentsContract {
     method public static boolean isManageMode(@NonNull android.net.Uri);
     method @NonNull public static android.net.Uri setManageMode(@NonNull android.net.Uri);
@@ -8095,16 +8169,6 @@
 
 }
 
-package android.service.sms {
-
-  public abstract class FinancialSmsService extends android.app.Service {
-    method public android.os.IBinder onBind(android.content.Intent);
-    method @Nullable public abstract android.database.CursorWindow onGetSmsMessages(@NonNull android.os.Bundle);
-    field public static final String ACTION_FINANCIAL_SERVICE_INTENT = "android.service.sms.action.FINANCIAL_SERVICE_INTENT";
-  }
-
-}
-
 package android.service.storage {
 
   public abstract class ExternalStorageService extends android.app.Service {
@@ -9491,6 +9555,7 @@
   }
 
   public class SubscriptionInfo implements android.os.Parcelable {
+    method public boolean areUiccApplicationsEnabled();
     method @Nullable public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
     method public int getProfileClass();
     method public boolean isGroupDisabled();
@@ -9530,6 +9595,7 @@
 
   public class TelephonyFrameworkInitializer {
     method public static void registerServiceWrappers();
+    method public static void setTelephonyServiceManager(@NonNull android.os.TelephonyServiceManager);
   }
 
   public final class TelephonyHistogram implements android.os.Parcelable {
@@ -9569,6 +9635,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCarrierPrivilegeStatus(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<java.lang.String> getCarrierPrivilegedPackagesForAllActiveSubscriptions();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierRestrictionRules getCarrierRestrictionRules();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn();
@@ -9610,6 +9677,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
@@ -9629,6 +9697,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
+    method public void requestModemActivityInfo(@NonNull android.os.ResultReceiver);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig();
@@ -9648,6 +9717,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int);
     method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPin(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String);
@@ -9657,6 +9727,7 @@
     method public void updateServiceLocation();
     method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String);
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED";
+    field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE";
     field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
     field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
     field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
diff --git a/api/test-current.txt b/api/test-current.txt
index efb8538..8dcc095 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -722,6 +722,7 @@
   public class Intent implements java.lang.Cloneable android.os.Parcelable {
     field @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
     field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
+    field public static final String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
     field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
   }
 
@@ -753,6 +754,7 @@
     method public void setEnableRollback(boolean);
     method @RequiresPermission("android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS") public void setGrantedRuntimePermissions(String[]);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex();
+    method public void setInstallerPackageName(@Nullable String);
     method public void setRequestDowngrade(boolean);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
   }
@@ -2452,6 +2454,16 @@
     method @Nullable public String getString(@NonNull String, @Nullable String);
   }
 
+  public static final class DeviceConfig.Properties.Builder {
+    ctor public DeviceConfig.Properties.Builder(@NonNull String);
+    method @NonNull public android.provider.DeviceConfig.Properties build();
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setBoolean(@NonNull String, boolean);
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setFloat(@NonNull String, float);
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setInt(@NonNull String, int);
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setLong(@NonNull String, long);
+    method @NonNull public android.provider.DeviceConfig.Properties.Builder setString(@NonNull String, @Nullable String);
+  }
+
   public final class MediaStore {
     method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
diff --git a/cmds/hid/jni/Android.bp b/cmds/hid/jni/Android.bp
index 095cfc6..2c07de0 100644
--- a/cmds/hid/jni/Android.bp
+++ b/cmds/hid/jni/Android.bp
@@ -5,6 +5,7 @@
 
     shared_libs: [
         "libandroid",
+        "libbase",
         "liblog",
         "libnativehelper",
     ],
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index d4fdf85..f3871d7 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -21,10 +21,11 @@
 #include <linux/uhid.h>
 
 #include <fcntl.h>
+#include <inttypes.h>
+#include <unistd.h>
 #include <cstdio>
 #include <cstring>
 #include <memory>
-#include <unistd.h>
 
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
@@ -33,6 +34,8 @@
 #include <android/looper.h>
 #include <android/log.h>
 
+#include <android-base/stringprintf.h>
+
 #define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
 #define  LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
 #define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
@@ -61,6 +64,14 @@
     }
 }
 
+static std::string toString(const std::vector<uint8_t>& data) {
+    std::string s = "";
+    for (uint8_t b : data) {
+        s += android::base::StringPrintf("%x ", b);
+    }
+    return s;
+}
+
 DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback) :
     mCallbackObject(env->NewGlobalRef(callback)) {
     env->GetJavaVM(&mJavaVM);
@@ -96,17 +107,17 @@
     return env;
 }
 
-Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
-        std::vector<uint8_t> descriptor, std::unique_ptr<DeviceCallback> callback) {
-
+std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
+                                     const std::vector<uint8_t>& descriptor,
+                                     std::unique_ptr<DeviceCallback> callback) {
     size_t size = descriptor.size();
     if (size > HID_MAX_DESCRIPTOR_SIZE) {
         LOGE("Received invalid hid report with descriptor size %zu, skipping", size);
         return nullptr;
     }
 
-    int fd = ::open(UHID_PATH, O_RDWR | O_CLOEXEC);
-    if (fd < 0) {
+    android::base::unique_fd fd(::open(UHID_PATH, O_RDWR | O_CLOEXEC));
+    if (!fd.ok()) {
         LOGE("Failed to open uhid: %s", strerror(errno));
         return nullptr;
     }
@@ -114,8 +125,7 @@
     struct uhid_event ev = {};
     ev.type = UHID_CREATE2;
     strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
-    memcpy(&ev.u.create2.rd_data, descriptor.data(),
-            size * sizeof(ev.u.create2.rd_data[0]));
+    memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0]));
     ev.u.create2.rd_size = size;
     ev.u.create2.bus = BUS_BLUETOOTH;
     ev.u.create2.vendor = vid;
@@ -126,7 +136,6 @@
     errno = 0;
     ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
     if (ret < 0 || ret != sizeof(ev)) {
-        ::close(fd);
         LOGE("Failed to create uhid node: %s", strerror(errno));
         return nullptr;
     }
@@ -134,21 +143,21 @@
     // Wait for the device to actually be created.
     ret = TEMP_FAILURE_RETRY(::read(fd, &ev, sizeof(ev)));
     if (ret < 0 || ev.type != UHID_START) {
-        ::close(fd);
         LOGE("uhid node failed to start: %s", strerror(errno));
         return nullptr;
     }
-    return new Device(id, fd, std::move(callback));
+    // using 'new' to access non-public constructor
+    return std::unique_ptr<Device>(new Device(id, std::move(fd), std::move(callback)));
 }
 
-Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback) :
-            mId(id), mFd(fd), mDeviceCallback(std::move(callback)) {
+Device::Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback)
+      : mId(id), mFd(std::move(fd)), mDeviceCallback(std::move(callback)) {
     ALooper* aLooper = ALooper_forThread();
     if (aLooper == NULL) {
         LOGE("Could not get ALooper, ALooper_forThread returned NULL");
         aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
     }
-    ALooper_addFd(aLooper, fd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents,
+    ALooper_addFd(aLooper, mFd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents,
                   reinterpret_cast<void*>(this));
 }
 
@@ -162,8 +171,14 @@
     struct uhid_event ev = {};
     ev.type = UHID_DESTROY;
     TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
-    ::close(mFd);
-    mFd = -1;
+}
+
+// Send event over the fd.
+static void writeEvent(int fd, struct uhid_event& ev, const char* messageType) {
+    ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
+    if (ret < 0 || ret != sizeof(ev)) {
+        LOGE("Failed to send uhid_event %s: %s", messageType, strerror(errno));
+    }
 }
 
 void Device::sendReport(const std::vector<uint8_t>& report) const {
@@ -176,10 +191,7 @@
     ev.type = UHID_INPUT2;
     ev.u.input2.size = report.size();
     memcpy(&ev.u.input2.data, report.data(), report.size() * sizeof(ev.u.input2.data[0]));
-    ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
-    if (ret < 0 || ret != sizeof(ev)) {
-        LOGE("Failed to send hid event: %s", strerror(errno));
-    }
+    writeEvent(mFd, ev, "UHID_INPUT2");
 }
 
 void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const {
@@ -190,10 +202,7 @@
     ev.u.get_report_reply.size = report.size();
     memcpy(&ev.u.get_report_reply.data, report.data(),
             report.size() * sizeof(ev.u.get_report_reply.data[0]));
-    ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
-    if (ret < 0 || ret != sizeof(ev)) {
-        LOGE("Failed to send hid event (UHID_GET_REPORT_REPLY): %s", strerror(errno));
-    }
+    writeEvent(mFd, ev, "UHID_GET_REPORT_REPLY");
 }
 
 int Device::handleEvents(int events) {
@@ -210,13 +219,31 @@
         return 0;
     }
 
-    if (ev.type == UHID_OPEN) {
-        mDeviceCallback->onDeviceOpen();
-    } else if (ev.type == UHID_GET_REPORT) {
-        mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum);
-    } else if (ev.type == UHID_SET_REPORT) {
-        LOGE("UHID_SET_REPORT is currently not supported");
-        return 0;
+    switch (ev.type) {
+        case UHID_OPEN: {
+            mDeviceCallback->onDeviceOpen();
+            break;
+        }
+        case UHID_GET_REPORT: {
+            mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum);
+            break;
+        }
+        case UHID_SET_REPORT: {
+            const struct uhid_set_report_req& set_report = ev.u.set_report;
+            if (set_report.size > UHID_DATA_MAX) {
+                LOGE("SET_REPORT contains too much data: size = %" PRIu16, set_report.size);
+                return 0;
+            }
+
+            std::vector<uint8_t> data(set_report.data, set_report.data + set_report.size);
+            LOGI("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id,
+                 set_report.rnum, toString(data).c_str());
+            break;
+        }
+        default: {
+            LOGI("Unhandled event type: %" PRIu32, ev.type);
+            break;
+        }
     }
 
     return 1;
@@ -250,9 +277,10 @@
 
     std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback));
 
-    uhid::Device* d = uhid::Device::open(
-            id, reinterpret_cast<const char*>(name.c_str()), vid, pid, desc, std::move(cb));
-    return reinterpret_cast<jlong>(d);
+    std::unique_ptr<uhid::Device> d =
+            uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, desc,
+                               std::move(cb));
+    return reinterpret_cast<jlong>(d.release());
 }
 
 static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray rawReport) {
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index 892c7cd..b0471ed 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -19,6 +19,8 @@
 
 #include <jni.h>
 
+#include <android-base/unique_fd.h>
+
 namespace android {
 namespace uhid {
 
@@ -39,10 +41,10 @@
 
 class Device {
 public:
-    static Device* open(int32_t id, const char* name, int32_t vid, int32_t pid,
-            std::vector<uint8_t> descriptor, std::unique_ptr<DeviceCallback> callback);
+    static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid,
+                                        const std::vector<uint8_t>& descriptor,
+                                        std::unique_ptr<DeviceCallback> callback);
 
-    Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback);
     ~Device();
 
     void sendReport(const std::vector<uint8_t>& report) const;
@@ -52,8 +54,9 @@
     int handleEvents(int events);
 
 private:
+    Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback);
     int32_t mId;
-    int mFd;
+    android::base::unique_fd mFd;
     std::unique_ptr<DeviceCallback> mDeviceCallback;
 };
 
diff --git a/cmds/media/Android.bp b/cmds/media/Android.bp
deleted file mode 100644
index 7879c53..0000000
--- a/cmds/media/Android.bp
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright 2013 The Android Open Source Project
-//
-
-java_binary {
-    name: "media",
-    wrapper: "media",
-    srcs: ["**/*.java"],
-}
diff --git a/cmds/media/MODULE_LICENSE_APACHE2 b/cmds/media/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/cmds/media/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/cmds/media/NOTICE b/cmds/media/NOTICE
deleted file mode 100644
index c5b1efa..0000000
--- a/cmds/media/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
-   Copyright (c) 2005-2008, 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.
-
-   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.
-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
diff --git a/cmds/media/media b/cmds/media/media
deleted file mode 100755
index 00c3915..0000000
--- a/cmds/media/media
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/system/bin/sh
-export CLASSPATH=/system/framework/media.jar
-exec app_process /system/bin com.android.commands.media.Media "$@"
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 3c5ad42..4d38ba0 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -272,7 +272,7 @@
             }
             return NO_ERROR;
         }
-        default: { return BnStatsManager::onTransact(code, data, reply, flags); }
+        default: { return BnStatsd::onTransact(code, data, reply, flags); }
     }
 }
 
@@ -862,13 +862,13 @@
     int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10);
     int options = 0;
     if (args[3] == "1") {
-        options = options | IStatsManager::FLAG_REQUIRE_STAGING;
+        options = options | IStatsd::FLAG_REQUIRE_STAGING;
     }
     if (args[4] == "1") {
-        options = options | IStatsManager::FLAG_ROLLBACK_ENABLED;
+        options = options | IStatsd::FLAG_ROLLBACK_ENABLED;
     }
     if (args[5] == "1") {
-        options = options | IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
+        options = options | IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
     }
     int32_t state = atoi(args[6].c_str());
     vector<int64_t> experimentIds;
@@ -1406,9 +1406,9 @@
     StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds);
 
     userid_t userId = multiuser_get_user_id(uid);
-    bool requiresStaging = options & IStatsManager::FLAG_REQUIRE_STAGING;
-    bool rollbackEnabled = options & IStatsManager::FLAG_ROLLBACK_ENABLED;
-    bool requiresLowLatencyMonitor = options & IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
+    bool requiresStaging = options & IStatsd::FLAG_REQUIRE_STAGING;
+    bool rollbackEnabled = options & IStatsd::FLAG_ROLLBACK_ENABLED;
+    bool requiresLowLatencyMonitor = options & IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
     LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled,
                    requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId);
     mProcessor->OnLogEvent(&event);
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 50b1014..9abf415 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -29,9 +29,9 @@
 
 #include <android/frameworks/stats/1.0/IStats.h>
 #include <android/frameworks/stats/1.0/types.h>
-#include <android/os/BnStatsManager.h>
+#include <android/os/BnStatsd.h>
 #include <android/os/IStatsCompanionService.h>
-#include <android/os/IStatsManager.h>
+#include <android/os/IStatsd.h>
 #include <binder/IResultReceiver.h>
 #include <binder/ParcelFileDescriptor.h>
 #include <utils/Looper.h>
@@ -52,7 +52,7 @@
 
 using android::hardware::Return;
 
-class StatsService : public BnStatsManager,
+class StatsService : public BnStatsd,
                      public IStats,
                      public IBinder::DeathRecipient {
 public:
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 49a8e2f3..36ce4e8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -112,6 +112,7 @@
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.TelephonyServiceManager;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.permission.IPermissionManager;
@@ -128,6 +129,7 @@
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.system.StructStat;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
@@ -3415,7 +3417,7 @@
     private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
         final int displayId;
         try {
-            displayId = ActivityTaskManager.getService().getActivityDisplayId(r.token);
+            displayId = ActivityTaskManager.getService().getDisplayId(r.token);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -7395,6 +7397,24 @@
                 super.remove(path);
             }
         }
+
+        @Override
+        public void rename(String oldPath, String newPath) throws ErrnoException {
+            try {
+                super.rename(oldPath, newPath);
+            } catch (ErrnoException e) {
+                if (e.errno == OsConstants.EXDEV && oldPath.startsWith("/storage/")) {
+                    Log.v(TAG, "Recovering failed rename " + oldPath + " to " + newPath);
+                    try {
+                        Files.move(new File(oldPath).toPath(), new File(newPath).toPath());
+                    } catch (IOException e2) {
+                        throw e;
+                    }
+                } else {
+                    throw e;
+                }
+            }
+        }
     }
 
     public static void main(String[] args) {
@@ -7414,6 +7434,9 @@
         final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
         TrustedCertificateStore.setDefaultUserDirectory(configDir);
 
+        // Call per-process mainline module initialization.
+        initializeMainlineModules();
+
         Process.setArgV0("<pre-initialized>");
 
         Looper.prepareMainLooper();
@@ -7448,6 +7471,14 @@
         throw new RuntimeException("Main thread loop unexpectedly exited");
     }
 
+    /**
+     * Call various initializer APIs in mainline modules that need to be called when each process
+     * starts.
+     */
+    public static void initializeMainlineModules() {
+        TelephonyFrameworkInitializer.setTelephonyServiceManager(new TelephonyServiceManager());
+    }
+
     private void purgePendingResources() {
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "purgePendingResources");
         nPurgePendingResources();
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 37136a1..e2b1b86 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -164,7 +164,7 @@
     boolean convertToTranslucent(in IBinder token, in Bundle options);
     void notifyActivityDrawn(in IBinder token);
     void reportActivityFullyDrawn(in IBinder token, boolean restoredFromBundle);
-    int getActivityDisplayId(in IBinder activityToken);
+    int getDisplayId(in IBinder activityToken);
     boolean isImmersive(in IBinder token);
     void setImmersive(in IBinder token, boolean immersive);
     boolean isTopActivityImmersive();
@@ -405,6 +405,14 @@
     void setDisablePreviewScreenshots(IBinder token, boolean disable);
 
     /**
+     * It should only be called from home activity to remove its outdated snapshot. The home
+     * snapshot is used to speed up entering home from screen off. If the content of home activity
+     * is significantly different from before taking the snapshot, then the home activity can use
+     * this method to avoid inconsistent transition.
+     */
+    void invalidateHomeTaskSnapshot(IBinder homeToken);
+
+    /**
      * Return the user id of last resumed activity.
      */
     int getLastResumedActivityUserId();
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 90cd51f..f6e9569 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -27,8 +27,8 @@
 import android.os.IPullAtomCallback;
 import android.os.IPullAtomResultReceiver;
 import android.os.IStatsCompanionService;
-import android.os.IStatsManager;
 import android.os.IStatsPullerCallback;
+import android.os.IStatsd;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.AndroidException;
@@ -56,7 +56,7 @@
     private final Context mContext;
 
     @GuardedBy("sLock")
-    private IStatsManager mService;
+    private IStatsd mService;
 
     @GuardedBy("sLock")
     private IStatsCompanionService mStatsCompanion;
@@ -129,7 +129,7 @@
     public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 // can throw IllegalArgumentException
                 service.addConfiguration(configKey, config, mContext.getOpPackageName());
             } catch (RemoteException e) {
@@ -166,7 +166,7 @@
     public void removeConfig(long configKey) throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 service.removeConfiguration(configKey, mContext.getOpPackageName());
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to connect to statsd when removing configuration");
@@ -227,7 +227,7 @@
             throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (pendingIntent != null) {
                     // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
                     IBinder intentSender = pendingIntent.getTarget().asBinder();
@@ -281,7 +281,7 @@
             throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (pendingIntent == null) {
                     service.removeDataFetchOperation(configKey, mContext.getOpPackageName());
                 } else {
@@ -319,7 +319,7 @@
             throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (pendingIntent == null) {
                     service.removeActiveConfigsChangedOperation(mContext.getOpPackageName());
                     return new long[0];
@@ -367,7 +367,7 @@
     public byte[] getReports(long configKey) throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 return service.getData(configKey, mContext.getOpPackageName());
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to connect to statsd when getting data");
@@ -404,7 +404,7 @@
     public byte[] getStatsMetadata() throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 return service.getMetadata(mContext.getOpPackageName());
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to connect to statsd when getting metadata");
@@ -439,7 +439,7 @@
             throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (service == null) {
                     if (DEBUG) {
                         Slog.d(TAG, "Failed to find statsd when getting experiment IDs");
@@ -476,7 +476,7 @@
             throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (callback == null) {
                     service.unregisterPullerCallback(atomTag, mContext.getOpPackageName());
                 } else {
@@ -660,11 +660,11 @@
     }
 
     @GuardedBy("sLock")
-    private IStatsManager getIStatsManagerLocked() throws StatsUnavailableException {
+    private IStatsd getIStatsdLocked() throws StatsUnavailableException {
         if (mService != null) {
             return mService;
         }
-        mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+        mService = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
         if (mService == null) {
             throw new StatsUnavailableException("could not be found");
         }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 601b658..bd948ec5 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -53,7 +53,9 @@
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayManager;
 import android.content.pm.CrossProfileApps;
+import android.content.pm.DataLoaderManager;
 import android.content.pm.ICrossProfileApps;
+import android.content.pm.IDataLoaderManager;
 import android.content.pm.IPackageManager;
 import android.content.pm.IShortcutService;
 import android.content.pm.LauncherApps;
@@ -118,6 +120,7 @@
 import android.net.lowpan.LowpanManager;
 import android.net.nsd.INsdManager;
 import android.net.nsd.NsdManager;
+import android.net.wifi.WifiCondManager;
 import android.net.wifi.WifiFrameworkInitializer;
 import android.nfc.NfcManager;
 import android.os.BatteryManager;
@@ -694,6 +697,14 @@
                 return new EthernetManager(ctx.getOuterContext(), service);
             }});
 
+        registerService(Context.WIFI_COND_SERVICE, WifiCondManager.class,
+                new CachedServiceFetcher<WifiCondManager>() {
+                    @Override
+                    public WifiCondManager createService(ContextImpl ctx) {
+                        return new WifiCondManager(ctx.getOuterContext());
+                    }
+                });
+
         registerService(Context.WINDOW_SERVICE, WindowManager.class,
                 new CachedServiceFetcher<WindowManager>() {
             @Override
@@ -1197,6 +1208,15 @@
                         return new BatteryStatsManager(
                                 IBatteryStats.Stub.asInterface(b));
                     }});
+        registerService(Context.DATA_LOADER_MANAGER_SERVICE, DataLoaderManager.class,
+                new CachedServiceFetcher<DataLoaderManager>() {
+                    @Override
+                    public DataLoaderManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(
+                                Context.DATA_LOADER_MANAGER_SERVICE);
+                        return new DataLoaderManager(IDataLoaderManager.Stub.asInterface(b));
+                    }});
         //CHECKSTYLE:ON IndentationCheck
 
         sInitializing = true;
diff --git a/core/java/android/app/TaskEmbedder.java b/core/java/android/app/TaskEmbedder.java
index 79d88fd..e5707bb 100644
--- a/core/java/android/app/TaskEmbedder.java
+++ b/core/java/android/app/TaskEmbedder.java
@@ -184,7 +184,7 @@
             return false;
         }
 
-        // Create a container surface to which the ActivityDisplay will be reparented
+        // Create a container surface to which the DisplayContent will be reparented
         final String name = "TaskEmbedder - " + Integer.toHexString(System.identityHashCode(this));
         mSurfaceControl = new SurfaceControl.Builder()
                 .setContainerLayer()
@@ -276,7 +276,7 @@
      * @see ActivityView.StateCallback
      * @see #startActivity(Intent)
      */
-    void setListener(TaskEmbedder.Listener listener) {
+    public void setListener(TaskEmbedder.Listener listener) {
         mListener = listener;
         if (mListener != null && isInitialized()) {
             mListener.onInitialized();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 915e457..ec981b2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2324,6 +2324,11 @@
 
     /**
      * Activity action: Starts the administrator to show policy compliance for the provisioning.
+     * This action is used any time that the administrator has an opportunity to show policy
+     * compliance before the end of setup wizard. This could happen as part of the admin-integrated
+     * provisioning flow (in which case this gets sent after {@link #ACTION_GET_PROVISIONING_MODE}),
+     * or it could happen during provisioning finalization if the administrator supports
+     * finalization during setup wizard.
      */
     public static final String ACTION_ADMIN_POLICY_COMPLIANCE =
             "android.app.action.ADMIN_POLICY_COMPLIANCE";
@@ -11085,6 +11090,53 @@
     }
 
     /**
+     * Sets the set of package names that are allowed to request user consent for cross-profile
+     * communication.
+     *
+     * <p>Assumes that the caller is a profile owner and is the given {@code admin}.
+     *
+     * <p>Previous calls are overridden by each subsequent call to this method.
+     *
+     * @param admin the {@link DeviceAdminReceiver} this request is associated with
+     * @param packageNames the new cross-profile package names
+     */
+    public void setCrossProfilePackages(
+            @NonNull ComponentName admin, @NonNull Set<String> packageNames) {
+        throwIfParentInstance("setCrossProfilePackages");
+        if (mService != null) {
+            try {
+                mService.setCrossProfilePackages(admin, new ArrayList<>(packageNames));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the set of package names that the admin has previously set as allowed to request user
+     * consent for cross-profile communication, via {@link
+     * #setCrossProfilePackages(ComponentName, Set)}.
+     *
+     * <p>Assumes that the caller is a profile owner and is the given {@code admin}.
+     *
+     * @param admin the {@link DeviceAdminReceiver} this request is associated with
+     * @return the set of package names the admin has previously set as allowed to request user
+     * consent for cross-profile communication, via {@link
+     * #setCrossProfilePackages(ComponentName, Set)}
+     */
+    public @NonNull Set<String> getCrossProfilePackages(@NonNull ComponentName admin) {
+        throwIfParentInstance("getCrossProfilePackages");
+        if (mService != null) {
+            try {
+                return new ArraySet<>(mService.getCrossProfilePackages(admin));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return Collections.emptySet();
+    }
+
+    /**
      * Returns whether the device is being used as a managed kiosk. These requirements are as
      * follows:
      * <ul>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 34246fa..ff1ecd5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -438,6 +438,9 @@
     boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle);
     List<String> getCrossProfileCalendarPackagesForUser(int userHandle);
 
+    void setCrossProfilePackages(in ComponentName admin, in List<String> packageNames);
+    List<String> getCrossProfilePackages(in ComponentName admin);
+
     boolean isManagedKiosk();
     boolean isUnattendedManagedKiosk();
 
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 099272d..4940976 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -597,6 +597,12 @@
     boolean isBackupServiceActive(int whichUser);
 
     /**
+    * Checks if the user is ready for backup or not.
+    * @param userId User id for which this operation should be performed.
+    */
+    boolean isUserReadyForBackup(int userId);
+
+    /**
      * Ask the framework which dataset, if any, the given package's data would be
      * restored from if we were to install it right now.
      *
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 471606da..55f92be 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -56,6 +56,7 @@
 
     public ManualTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
         mUtcTime = Objects.requireNonNull(utcTime);
+        Objects.requireNonNull(utcTime.getValue());
     }
 
     private static ManualTimeSuggestion createFromParcel(Parcel in) {
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index dd02af7..4a89a12 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -166,7 +166,12 @@
         }
 
         /** Returns the builder for call chaining. */
-        public Builder setUtcTime(TimestampedValue<Long> utcTime) {
+        public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) {
+            if (utcTime != null) {
+                // utcTime can be null, but the value it holds cannot.
+                Objects.requireNonNull(utcTime.getValue());
+            }
+
             mUtcTime = utcTime;
             return this;
         }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index fb5645a..419377c 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -1125,8 +1125,11 @@
 
     /**
      * Inform usage stats that the carrier privileged apps access rules have changed.
+     * <p> The caller must have {@link android.Manifest.permission#BIND_CARRIER_SERVICES} </p>
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BIND_CARRIER_SERVICES)
     public void onCarrierPrivilegedAppsChanged() {
         try {
             mService.onCarrierPrivilegedAppsChanged();
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 3f8cb62..8404705 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -847,7 +847,8 @@
         }
         synchronized (mLock) {
             if (sBluetoothLeScanner == null) {
-                sBluetoothLeScanner = new BluetoothLeScanner(mManagerService);
+                sBluetoothLeScanner = new BluetoothLeScanner(mManagerService, getOpPackageName(),
+                        getFeatureId());
             }
         }
         return sBluetoothLeScanner;
@@ -1637,6 +1638,15 @@
         return ActivityThread.currentOpPackageName();
     }
 
+    private String getFeatureId() {
+        // Workaround for legacy API for getting a BluetoothAdapter not
+        // passing a context
+        if (mContext != null) {
+            return mContext.getFeatureId();
+        }
+        return null;
+    }
+
     /**
      * Start the remote device discovery process.
      * <p>The discovery process usually involves an inquiry scan of about 12
@@ -1674,7 +1684,7 @@
         try {
             mServiceLock.readLock().lock();
             if (mService != null) {
-                return mService.startDiscovery(getOpPackageName());
+                return mService.startDiscovery(getOpPackageName(), getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 49187dc..323c7d1 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -17,6 +17,7 @@
 package android.bluetooth;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -33,8 +34,12 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.UUID;
 
 /**
@@ -771,6 +776,13 @@
     @UnsupportedAppUsage
     public static final String EXTRA_SDP_SEARCH_STATUS =
             "android.bluetooth.device.extra.SDP_SEARCH_STATUS";
+
+    /** @hide */
+    @IntDef(prefix = "ACCESS_", value = {ACCESS_UNKNOWN,
+            ACCESS_ALLOWED, ACCESS_REJECTED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AccessPermission{}
+
     /**
      * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
      * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
@@ -1096,15 +1108,14 @@
 
     /**
      * Get the most recent identified battery level of this Bluetooth device
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
      * @return Battery level in percents from 0 to 100, or {@link #BATTERY_LEVEL_UNKNOWN} if
      * Bluetooth is disabled, or device is disconnected, or does not have any battery reporting
      * service, or return value is invalid
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-    @UnsupportedAppUsage
     public int getBatteryLevel() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1187,8 +1198,15 @@
         return false;
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * Gets whether bonding was initiated locally
+     *
+     * @return true if bonding is initiated locally, false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
     public boolean isBondingInitiatedLocally() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1480,15 +1498,20 @@
         return false;
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
-    public boolean setPasskey(int passkey) {
-        //TODO(BT)
-        /*
-        try {
-            return sService.setPasskey(this, true, 4, passkey);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}*/
-        return false;
+    /**
+     * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
+     *
+     * @return true pin has been set false for error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setPin(@Nullable String pin) {
+        byte[] pinBytes = convertPinToBytes(pin);
+        if (pinBytes == null) {
+            return false;
+        }
+        return setPin(pinBytes);
     }
 
     /**
@@ -1511,22 +1534,18 @@
         return false;
     }
 
-    /** @hide */
-    public boolean setRemoteOutOfBandData() {
-        // TODO(BT)
-        /*
-        try {
-          return sService.setRemoteOutOfBandData(this);
-      } catch (RemoteException e) {Log.e(TAG, "", e);}*/
-        return false;
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage
-    public boolean cancelPairingUserInput() {
+    /**
+     * Cancels pairing to this device
+     *
+     * @return true if pairing cancelled successfully, false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean cancelPairing() {
         final IBluetooth service = sService;
         if (service == null) {
-            Log.e(TAG, "BT not enabled. Cannot create pairing user input");
+            Log.e(TAG, "BT not enabled. Cannot cancel pairing");
             return false;
         }
         try {
@@ -1537,17 +1556,6 @@
         return false;
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
-    public boolean isBluetoothDock() {
-        // TODO(BT)
-        /*
-        try {
-            return sService.isBluetoothDock(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}*/
-        return false;
-    }
-
     boolean isBluetoothEnabled() {
         boolean ret = false;
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -1558,13 +1566,14 @@
     }
 
     /**
-     * Requires {@link android.Manifest.permission#BLUETOOTH}.
+     * Gets whether the phonebook access is allowed for this bluetooth device
      *
      * @return Whether the phonebook access is allowed to this device. Can be {@link
      * #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}.
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
     public int getPhonebookAccessPermission() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1667,14 +1676,14 @@
     }
 
     /**
-     * Requires {@link android.Manifest.permission#BLUETOOTH}.
+     * Gets whether message access is allowed to this bluetooth device
      *
-     * @return Whether the message access is allowed to this device. Can be {@link #ACCESS_UNKNOWN},
-     * {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}.
+     * @return Whether the message access is allowed to this device.
      * @hide
      */
-    @UnsupportedAppUsage
-    public int getMessageAccessPermission() {
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @AccessPermission int getMessageAccessPermission() {
         final IBluetooth service = sService;
         if (service == null) {
             return ACCESS_UNKNOWN;
@@ -1689,15 +1698,18 @@
 
     /**
      * Sets whether the message access is allowed to this device.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
      *
-     * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link
-     * #ACCESS_REJECTED}.
+     * @param value is the value we are setting the message access permission to
      * @return Whether the value has been successfully set.
      * @hide
      */
-    @UnsupportedAppUsage
-    public boolean setMessageAccessPermission(int value) {
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setMessageAccessPermission(@AccessPermission int value) {
+        // Validates param value is one of the accepted constants
+        if (value != ACCESS_ALLOWED && value != ACCESS_REJECTED && value != ACCESS_UNKNOWN) {
+            throw new IllegalArgumentException(value + "is not a valid AccessPermission value");
+        }
         final IBluetooth service = sService;
         if (service == null) {
             return false;
@@ -1711,13 +1723,14 @@
     }
 
     /**
-     * Requires {@link android.Manifest.permission#BLUETOOTH}.
+     * Gets whether sim access is allowed for this bluetooth device
      *
-     * @return Whether the Sim access is allowed to this device. Can be {@link #ACCESS_UNKNOWN},
-     * {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}.
+     * @return Whether the Sim access is allowed to this device.
      * @hide
      */
-    public int getSimAccessPermission() {
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @AccessPermission int getSimAccessPermission() {
         final IBluetooth service = sService;
         if (service == null) {
             return ACCESS_UNKNOWN;
@@ -1732,14 +1745,14 @@
 
     /**
      * Sets whether the Sim access is allowed to this device.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
      *
      * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link
      * #ACCESS_REJECTED}.
      * @return Whether the value has been successfully set.
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setSimAccessPermission(int value) {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1970,7 +1983,7 @@
      * @return the pin code as a UTF-8 byte array, or null if it is an invalid Bluetooth pin.
      * @hide
      */
-    @UnsupportedAppUsage
+    @VisibleForTesting
     public static byte[] convertPinToBytes(String pin) {
         if (pin == null) {
             return null;
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index ead8429..b4521c6 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -335,9 +335,9 @@
      * is not active, it will be null on that position. Returns empty list on error.
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-    public List<BluetoothDevice> getActiveDevices() {
+    public @NonNull List<BluetoothDevice> getActiveDevices() {
         if (VDBG) log("getActiveDevices()");
         final IBluetoothHearingAid service = getService();
         try {
@@ -559,8 +559,9 @@
      * @return the CustomerId of the device
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-    public long getHiSyncId(BluetoothDevice device) {
+    public long getHiSyncId(@Nullable BluetoothDevice device) {
         if (VDBG) {
             log("getCustomerId(" + device + ")");
         }
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index adedff3..7ff6466 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -22,7 +22,9 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -60,22 +62,34 @@
      * @hide
      */
     public BluetoothManager(Context context) {
-        context = context.getApplicationContext();
-        if (context == null) {
-            throw new IllegalArgumentException(
-                    "context not associated with any application (using a mock context?)");
+        if (context.getFeatureId() == null) {
+            context = context.getApplicationContext();
+            if (context == null) {
+                throw new IllegalArgumentException(
+                        "context not associated with any application (using a mock context?)");
+            }
+
+            mAdapter = BluetoothAdapter.getDefaultAdapter();
+        } else {
+            IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+            if (b != null) {
+                mAdapter = new BluetoothAdapter(IBluetoothManager.Stub.asInterface(b));
+            } else {
+                Log.e(TAG, "Bluetooth binder is null");
+                mAdapter = null;
+            }
         }
-        // Legacy api - getDefaultAdapter does not take in the context
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        // Context is not initialized in constructor
         if (mAdapter != null) {
             mAdapter.setContext(context);
         }
     }
 
     /**
-     * Get the default BLUETOOTH Adapter for this device.
+     * Get the BLUETOOTH Adapter for this device.
      *
-     * @return the default BLUETOOTH Adapter
+     * @return the BLUETOOTH Adapter
      */
     public BluetoothAdapter getAdapter() {
         return mAdapter;
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index ac126ae..9a17346 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -21,7 +21,6 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
-import android.app.ActivityThread;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothGatt;
@@ -84,17 +83,25 @@
     private BluetoothAdapter mBluetoothAdapter;
     private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
 
+    private final String mOpPackageName;
+    private final String mFeatureId;
+
     /**
      * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
      *
      * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
+     * @param opPackageName The opPackageName of the context this object was created from
+     * @param featureId  The featureId of the context this object was created from
      * @hide
      */
-    public BluetoothLeScanner(IBluetoothManager bluetoothManager) {
+    public BluetoothLeScanner(IBluetoothManager bluetoothManager,
+            @NonNull String opPackageName, @Nullable String featureId) {
         mBluetoothManager = bluetoothManager;
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         mHandler = new Handler(Looper.getMainLooper());
         mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
+        mOpPackageName = opPackageName;
+        mFeatureId = featureId;
     }
 
     /**
@@ -246,8 +253,8 @@
                 wrapper.startRegistration();
             } else {
                 try {
-                    gatt.startScanForIntent(callbackIntent, settings, filters,
-                            ActivityThread.currentOpPackageName());
+                    gatt.startScanForIntent(callbackIntent, settings, filters, mOpPackageName,
+                            mFeatureId);
                 } catch (RemoteException e) {
                     return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
                 }
@@ -288,7 +295,7 @@
         IBluetoothGatt gatt;
         try {
             gatt = mBluetoothManager.getBluetoothGatt();
-            gatt.stopScanForIntent(callbackIntent, ActivityThread.currentOpPackageName());
+            gatt.stopScanForIntent(callbackIntent, mOpPackageName);
         } catch (RemoteException e) {
         }
     }
@@ -448,8 +455,7 @@
                         } else {
                             mScannerId = scannerId;
                             mBluetoothGatt.startScan(mScannerId, mSettings, mFilters,
-                                    mResultStorages,
-                                    ActivityThread.currentOpPackageName());
+                                    mResultStorages, mOpPackageName, mFeatureId);
                         }
                     } catch (RemoteException e) {
                         Log.e(TAG, "fail to start le scan: " + e);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7b580c3..85027d9 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3973,6 +3973,17 @@
     public static final String WIFI_SERVICE = "wifi";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.net.wifi.WifiCondManager} for handling management of the Wi-Fi control
+     * daemon.
+     *
+     * @see #getSystemService(String)
+     * @see android.net.wifi.WifiCondManager
+     * @hide
+     */
+    public static final String WIFI_COND_SERVICE = "wificond";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve a {@link
      * android.net.wifi.p2p.WifiP2pManager} for handling management of
      * Wi-Fi peer-to-peer connections.
@@ -4980,7 +4991,7 @@
      * {@link android.os.incremental.IncrementalManager}.
      * @hide
      */
-    public static final String INCREMENTAL_SERVICE = "incremental";
+    public static final String INCREMENTAL_SERVICE = "incremental_service";
 
     /**
      * Determine whether the given permission is allowed for a particular
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ca2de6a..47fb375 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1728,6 +1728,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static final String EXTRA_ORIGINATING_UID
             = "android.intent.extra.ORIGINATING_UID";
 
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f017aad..8f51435 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1770,7 +1770,8 @@
          * @param installerPackageName name of the installer package
          * {@hide}
          */
-        public void setInstallerPackageName(String installerPackageName) {
+        @TestApi
+        public void setInstallerPackageName(@Nullable String installerPackageName) {
             this.installerPackageName = installerPackageName;
         }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6e890ba..94af541 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -946,7 +946,8 @@
             INSTALL_REASON_POLICY,
             INSTALL_REASON_DEVICE_RESTORE,
             INSTALL_REASON_DEVICE_SETUP,
-            INSTALL_REASON_USER
+            INSTALL_REASON_USER,
+            INSTALL_REASON_ROLLBACK
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InstallReason {}
@@ -977,6 +978,13 @@
     public static final int INSTALL_REASON_USER = 4;
 
     /**
+     * Code indicating that the package installation was a rollback initiated by RollbackManager.
+     *
+     * @hide
+     */
+    public static final int INSTALL_REASON_ROLLBACK = 5;
+
+    /**
      * @hide
      */
     public static final int INSTALL_UNKNOWN = 0;
diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java
index 5364313..fc210b2 100644
--- a/core/java/android/content/pm/parsing/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java
@@ -768,6 +768,10 @@
         protected PatternMatcher[] uriPermissionPatterns;
         protected PathPermission[] pathPermissions;
 
+        public ParsedProvider(ParsedProvider other) {
+            this.setFrom(other);
+        }
+
         protected void setFrom(ParsedProvider other) {
             super.setFrom(other);
             this.exported = other.exported;
@@ -823,6 +827,10 @@
             return authority;
         }
 
+        public void setSyncable(boolean isSyncable) {
+            this.isSyncable = isSyncable;
+        }
+
         public boolean isSyncable() {
             return isSyncable;
         }
diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java
index af9ba8d..f2cf9a4 100644
--- a/core/java/android/content/pm/parsing/PackageInfoUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java
@@ -485,6 +485,7 @@
                 pg.backgroundRequestDetailResourceId
         );
         assignSharedFieldsForPackageItemInfo(pgi, pg);
+        pgi.descriptionRes = pg.descriptionRes;
         pgi.priority = pg.priority;
         pgi.requestRes = pg.requestRes;
         pgi.flags = pg.flags;
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 8c2a65f..cda0e98 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -44,9 +44,10 @@
     // Used by the staging manager to notify the RollbackManager that a session is
     // being staged. In the case of multi-package sessions, the specified sessionId
     // is that of the parent session.
+    // Returns the rollback id if rollback was enabled successfully, or -1 if not.
     //
     // NOTE: This call is synchronous.
-    boolean notifyStagedSession(int sessionId);
+    int notifyStagedSession(int sessionId);
 
     // Used by the staging manager to notify the RollbackManager of the apk
     // session for a staged session.
diff --git a/core/java/android/hardware/soundtrigger/ModelParams.aidl b/core/java/android/hardware/soundtrigger/ModelParams.aidl
new file mode 100644
index 0000000..d90dc81
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/ModelParams.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.soundtrigger;
+
+/**
+ * Model specific parameters to be used with parameter set and get APIs
+ * {@hide}
+ */
+@Backing(type="int")
+enum ModelParams {
+  /**
+   * Placeholder for invalid model parameter used for returning error or
+   * passing an invalid value.
+   */
+  INVALID = -1,
+  /**
+   * Controls the sensitivity threshold adjustment factor for a given model.
+   * Negative value corresponds to less sensitive model (high threshold) and
+   * a positive value corresponds to a more sensitive model (low threshold).
+   * Default value is 0.
+   */
+  THRESHOLD_FACTOR = 0,
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.aidl b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
index 325a9ad..94c4216 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
@@ -24,5 +24,6 @@
 parcelable SoundTrigger.KeyphraseRecognitionExtra;
 parcelable SoundTrigger.KeyphraseSoundModel;
 parcelable SoundTrigger.GenericSoundModel;
+parcelable SoundTrigger.ModelParamRange;
 parcelable SoundTrigger.ModuleProperties;
 parcelable SoundTrigger.RecognitionConfig;
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index b1134e1..86f3eec 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -567,6 +567,65 @@
         }
     }
 
+    /*****************************************************************************
+     * A ModelParamRange is a representation of supported parameter range for a
+     * given loaded model.
+     ****************************************************************************/
+    public static final class ModelParamRange implements Parcelable {
+
+        /**
+         * start of supported range inclusive
+         */
+        public final int start;
+
+        /**
+         * end of supported range inclusive
+         */
+        public final int end;
+
+        ModelParamRange(int start, int end) {
+            this.start = start;
+            this.end = end;
+        }
+
+        private ModelParamRange(@NonNull Parcel in) {
+            this.start = in.readInt();
+            this.end = in.readInt();
+        }
+
+        @NonNull
+        public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() {
+            @Override
+            @NonNull
+            public ModelParamRange createFromParcel(@NonNull Parcel in) {
+                return new ModelParamRange(in);
+            }
+
+            @Override
+            @NonNull
+            public ModelParamRange[] newArray(int size) {
+                return new ModelParamRange[size];
+            }
+        };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(start);
+            dest.writeInt(end);
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "ModelParamRange [start=" + start + ", end=" + end + "]";
+        }
+    }
+
     /**
      *  Modes for key phrase recognition
      */
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 9113548..b16ef5c 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -16,7 +16,9 @@
 
 package android.hardware.soundtrigger;
 
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -150,6 +152,57 @@
      */
     public native int getModelState(int soundModelHandle);
 
+    /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link SoundTriggerModule#isParameterSupported} should be checked first before calling this
+     * method.
+     *
+     * @param soundModelHandle handle of model to apply parameter
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    public native int setParameter(int soundModelHandle,
+            @ModelParams int modelParam, int value);
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See {@link ModelParams} for parameter default values.
+     * {@link SoundTriggerModule#isParameterSupported} should be checked first before
+     * calling this method. Otherwise, an exception can be thrown.
+     *
+     * @param soundModelHandle handle of model to get parameter
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     * @throws UnsupportedOperationException if hal or model do not support this API.
+     *         {@link SoundTriggerModule#isParameterSupported} should be checked first.
+     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+     *         {@link SoundTriggerModule#isParameterSupported} should be checked first.
+     */
+    public native int getParameter(int soundModelHandle,
+            @ModelParams int modelParam)
+            throws UnsupportedOperationException, IllegalArgumentException;
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link SoundTriggerModule#setParameter} or
+     * {@link SoundTriggerModule#getParameter}.
+     *
+     * @param soundModelHandle handle of model to get parameter
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @Nullable
+    public native ModelParamRange queryParameter(int soundModelHandle, @ModelParams int modelParam);
+
     private class NativeEventHandlerDelegate {
         private final Handler mHandler;
 
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index adc497a..2992127 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
@@ -461,6 +462,14 @@
         return networkCapabilities.hasTransport(transportType);
     }
 
+    /**
+     * @see Builder#setNetworkSpecifier(NetworkSpecifier)
+     */
+    @Nullable
+    public NetworkSpecifier getNetworkSpecifier() {
+        return networkCapabilities.getNetworkSpecifier();
+    }
+
     public String toString() {
         return "NetworkRequest [ " + type + " id=" + requestId +
                 (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") +
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9fed269..53ea936 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -17,6 +17,8 @@
 package android.os;
 
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
+import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
 
 import android.annotation.IntDef;
 import android.annotation.UnsupportedAppUsage;
@@ -24,6 +26,8 @@
 import android.app.job.JobParameters;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.os.BatteryStatsManager.WifiState;
+import android.os.BatteryStatsManager.WifiSupplState;
 import android.server.ServerProtoEnums;
 import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
 import android.service.batterystats.BatteryStatsServiceDumpProto;
@@ -2422,7 +2426,7 @@
 
     public static final int DATA_CONNECTION_OUT_OF_SERVICE = 0;
     public static final int DATA_CONNECTION_EMERGENCY_SERVICE =
-            TelephonyManager.MAX_NETWORK_TYPE + 1;
+            TelephonyManager.getAllNetworkTypes().length + 1;
     public static final int DATA_CONNECTION_OTHER = DATA_CONNECTION_EMERGENCY_SERVICE + 1;
 
 
@@ -2458,41 +2462,6 @@
      */
     public abstract Timer getPhoneDataConnectionTimer(int dataType);
 
-    public static final int WIFI_SUPPL_STATE_INVALID = 0;
-    public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
-    public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
-    public static final int WIFI_SUPPL_STATE_INACTIVE = 3;
-    public static final int WIFI_SUPPL_STATE_SCANNING = 4;
-    public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5;
-    public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6;
-    public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7;
-    public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8;
-    public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9;
-    public static final int WIFI_SUPPL_STATE_COMPLETED = 10;
-    public static final int WIFI_SUPPL_STATE_DORMANT = 11;
-    public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12;
-
-    public static final int NUM_WIFI_SUPPL_STATES = WIFI_SUPPL_STATE_UNINITIALIZED+1;
-
-    /** @hide */
-    @IntDef(flag = true, prefix = { "WIFI_SUPPL_STATE_" }, value = {
-            WIFI_SUPPL_STATE_INVALID,
-            WIFI_SUPPL_STATE_DISCONNECTED,
-            WIFI_SUPPL_STATE_INTERFACE_DISABLED,
-            WIFI_SUPPL_STATE_INACTIVE,
-            WIFI_SUPPL_STATE_SCANNING,
-            WIFI_SUPPL_STATE_AUTHENTICATING,
-            WIFI_SUPPL_STATE_ASSOCIATING,
-            WIFI_SUPPL_STATE_ASSOCIATED,
-            WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE,
-            WIFI_SUPPL_STATE_GROUP_HANDSHAKE,
-            WIFI_SUPPL_STATE_COMPLETED,
-            WIFI_SUPPL_STATE_DORMANT,
-            WIFI_SUPPL_STATE_UNINITIALIZED,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface WifiSupplState {}
-
     static final String[] WIFI_SUPPL_STATE_NAMES = {
         "invalid", "disconn", "disabled", "inactive", "scanning",
         "authenticating", "associating", "associated", "4-way-handshake",
@@ -2635,31 +2604,6 @@
     @UnsupportedAppUsage
     public abstract long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which);
 
-    public static final int WIFI_STATE_OFF = 0;
-    public static final int WIFI_STATE_OFF_SCANNING = 1;
-    public static final int WIFI_STATE_ON_NO_NETWORKS = 2;
-    public static final int WIFI_STATE_ON_DISCONNECTED = 3;
-    public static final int WIFI_STATE_ON_CONNECTED_STA = 4;
-    public static final int WIFI_STATE_ON_CONNECTED_P2P = 5;
-    public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6;
-    public static final int WIFI_STATE_SOFT_AP = 7;
-
-    public static final int NUM_WIFI_STATES = WIFI_STATE_SOFT_AP + 1;
-
-    /** @hide */
-    @IntDef(flag = true, prefix = { "WIFI_STATE_" }, value = {
-            WIFI_STATE_OFF,
-            WIFI_STATE_OFF_SCANNING,
-            WIFI_STATE_ON_NO_NETWORKS,
-            WIFI_STATE_ON_DISCONNECTED,
-            WIFI_STATE_ON_CONNECTED_STA,
-            WIFI_STATE_ON_CONNECTED_P2P,
-            WIFI_STATE_ON_CONNECTED_STA_P2P,
-            WIFI_STATE_SOFT_AP
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface WifiState {}
-
     static final String[] WIFI_STATE_NAMES = {
         "off", "scanning", "no_net", "disconn",
         "sta", "p2p", "sta_p2p", "soft_ap"
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index e5650ae..0545666 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +29,9 @@
 
 import com.android.internal.app.IBatteryStats;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This class provides an API surface for internal system components to report events that are
  * needed for battery usage/estimation and battery blaming for apps.
@@ -39,6 +43,116 @@
 @SystemApi
 @SystemService(Context.BATTERY_STATS_SERVICE)
 public class BatteryStatsManager {
+    /**
+     * Wifi states.
+     *
+     * @see #noteWifiState(int, String)
+     */
+    /**
+     * Wifi fully off.
+     */
+    public static final int WIFI_STATE_OFF = 0;
+    /**
+     * Wifi connectivity off, but scanning enabled.
+     */
+    public static final int WIFI_STATE_OFF_SCANNING = 1;
+    /**
+     * Wifi on, but no saved infrastructure networks to connect to.
+     */
+    public static final int WIFI_STATE_ON_NO_NETWORKS = 2;
+    /**
+     * Wifi on, but not connected to any infrastructure networks.
+     */
+    public static final int WIFI_STATE_ON_DISCONNECTED = 3;
+    /**
+     * Wifi on and connected to a infrastructure network.
+     */
+    public static final int WIFI_STATE_ON_CONNECTED_STA = 4;
+    /**
+     * Wifi on and connected to a P2P device, but no infrastructure connection to a network.
+     */
+    public static final int WIFI_STATE_ON_CONNECTED_P2P = 5;
+    /**
+     * Wifi on and connected to both a P2P device and infrastructure connection to a network.
+     */
+    public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6;
+    /**
+     * SoftAp/Hotspot turned on.
+     */
+    public static final int WIFI_STATE_SOFT_AP = 7;
+
+    /** @hide */
+    public static final int NUM_WIFI_STATES = WIFI_STATE_SOFT_AP + 1;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "WIFI_STATE_" }, value = {
+            WIFI_STATE_OFF,
+            WIFI_STATE_OFF_SCANNING,
+            WIFI_STATE_ON_NO_NETWORKS,
+            WIFI_STATE_ON_DISCONNECTED,
+            WIFI_STATE_ON_CONNECTED_STA,
+            WIFI_STATE_ON_CONNECTED_P2P,
+            WIFI_STATE_ON_CONNECTED_STA_P2P,
+            WIFI_STATE_SOFT_AP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WifiState {}
+
+    /**
+     * Wifi supplicant daemon states.
+     *
+     * @see android.net.wifi.SupplicantState for detailed description of states.
+     * @see #noteWifiSupplicantStateChanged(int)
+     */
+    /** @see android.net.wifi.SupplicantState#INVALID */
+    public static final int WIFI_SUPPL_STATE_INVALID = 0;
+    /** @see android.net.wifi.SupplicantState#DISCONNECTED*/
+    public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
+    /** @see android.net.wifi.SupplicantState#INTERFACE_DISABLED */
+    public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
+    /** @see android.net.wifi.SupplicantState#INACTIVE*/
+    public static final int WIFI_SUPPL_STATE_INACTIVE = 3;
+    /** @see android.net.wifi.SupplicantState#SCANNING*/
+    public static final int WIFI_SUPPL_STATE_SCANNING = 4;
+    /** @see android.net.wifi.SupplicantState#AUTHENTICATING */
+    public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5;
+    /** @see android.net.wifi.SupplicantState#ASSOCIATING */
+    public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6;
+    /** @see android.net.wifi.SupplicantState#ASSOCIATED */
+    public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7;
+    /** @see android.net.wifi.SupplicantState#FOUR_WAY_HANDSHAKE */
+    public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8;
+    /** @see android.net.wifi.SupplicantState#GROUP_HANDSHAKE */
+    public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9;
+    /** @see android.net.wifi.SupplicantState#COMPLETED */
+    public static final int WIFI_SUPPL_STATE_COMPLETED = 10;
+    /** @see android.net.wifi.SupplicantState#DORMANT */
+    public static final int WIFI_SUPPL_STATE_DORMANT = 11;
+    /** @see android.net.wifi.SupplicantState#UNINITIALIZED */
+    public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12;
+
+    /** @hide */
+    public static final int NUM_WIFI_SUPPL_STATES = WIFI_SUPPL_STATE_UNINITIALIZED + 1;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "WIFI_SUPPL_STATE_" }, value = {
+            WIFI_SUPPL_STATE_INVALID,
+            WIFI_SUPPL_STATE_DISCONNECTED,
+            WIFI_SUPPL_STATE_INTERFACE_DISABLED,
+            WIFI_SUPPL_STATE_INACTIVE,
+            WIFI_SUPPL_STATE_SCANNING,
+            WIFI_SUPPL_STATE_AUTHENTICATING,
+            WIFI_SUPPL_STATE_ASSOCIATING,
+            WIFI_SUPPL_STATE_ASSOCIATED,
+            WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE,
+            WIFI_SUPPL_STATE_GROUP_HANDSHAKE,
+            WIFI_SUPPL_STATE_COMPLETED,
+            WIFI_SUPPL_STATE_DORMANT,
+            WIFI_SUPPL_STATE_UNINITIALIZED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WifiSupplState {}
+
     private final IBatteryStats mBatteryStats;
 
     /** @hide */
@@ -91,7 +205,7 @@
      * @param accessPoint SSID of the network if wifi is connected to STA, else null.
      */
     @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
-    public void noteWifiState(@BatteryStats.WifiState int newWifiState,
+    public void noteWifiState(@WifiState int newWifiState,
             @Nullable String accessPoint) {
         try {
             mBatteryStats.noteWifiState(newWifiState, accessPoint);
@@ -224,7 +338,7 @@
      *                   authentication failure.
      */
     @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
-    public void noteWifiSupplicantStateChanged(@BatteryStats.WifiSupplState int newSupplState,
+    public void noteWifiSupplicantStateChanged(@WifiSupplState int newSupplState,
             boolean failedAuth) {
         try {
             mBatteryStats.noteWifiSupplicantStateChanged(newSupplState, failedAuth);
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index ee95fce..b0c2546 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -479,8 +479,14 @@
             // For now, avoid spamming the log by disabling after we've logged
             // about this interface at least once
             mWarnOnBlocking = false;
-            Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
-                    new Throwable());
+            if (Build.IS_USERDEBUG) {
+                // Log this as a WTF on userdebug builds.
+                Log.wtf(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
+                        new Throwable());
+            } else {
+                Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
+                        new Throwable());
+            }
         }
 
         final boolean tracingEnabled = Binder.isTracingEnabled();
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 6408f61..ebb2071 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -519,11 +519,12 @@
      * @param invokeWith null-ok the command to invoke with.
      * @param packageName null-ok the name of the package this process belongs to.
      * @param isTopApp whether the process starts for high priority application.
-     *
+     * @param disabledCompatChanges null-ok list of disabled compat changes for the process being
+     *                             started.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
      * @throws RuntimeException on fatal start failure
-     * 
+     *
      * {@hide}
      */
     public static ProcessStartResult start(@NonNull final String processClass,
@@ -539,11 +540,12 @@
                                            @Nullable String invokeWith,
                                            @Nullable String packageName,
                                            boolean isTopApp,
+                                           @Nullable long[] disabledCompatChanges,
                                            @Nullable String[] zygoteArgs) {
         return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
-                    /*useUsapPool=*/ true, isTopApp, zygoteArgs);
+                    /*useUsapPool=*/ true, isTopApp, disabledCompatChanges, zygoteArgs);
     }
 
     /** @hide */
@@ -559,11 +561,12 @@
                                                   @Nullable String appDataDir,
                                                   @Nullable String invokeWith,
                                                   @Nullable String packageName,
+                                                  @Nullable long[] disabledCompatChanges,
                                                   @Nullable String[] zygoteArgs) {
         return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
-                    /*useUsapPool=*/ false, /*isTopApp=*/ false, zygoteArgs);
+                    /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index bf9225a..3c0997b 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -140,7 +140,7 @@
 
     /**
      * Returns a reference to a service with the given name, or throws
-     * {@link NullPointerException} if none is found.
+     * {@link ServiceNotFoundException} if none is found.
      *
      * @hide
      */
diff --git a/core/java/android/os/TelephonyServiceManager.java b/core/java/android/os/TelephonyServiceManager.java
new file mode 100644
index 0000000..1211dd6
--- /dev/null
+++ b/core/java/android/os/TelephonyServiceManager.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the telephony
+ * service.
+ *
+ * <p>Only the telephony mainline module will be able to access an instance of this class.
+ *
+ * @hide
+ */
+@SystemApi
+public class TelephonyServiceManager {
+    /**
+     * @hide
+     */
+    public TelephonyServiceManager() {
+    }
+
+    /**
+     * A class that exposes the methods to register and obtain each system service.
+     */
+    public final class ServiceRegisterer {
+        private final String mServiceName;
+
+        /**
+         * @hide
+         */
+        public ServiceRegisterer(String serviceName) {
+            mServiceName = serviceName;
+        }
+
+        /**
+         * Register a system server binding object for a service.
+         */
+        public void register(@NonNull IBinder binder) {
+            ServiceManager.addService(mServiceName, binder);
+        }
+
+        /**
+         * Get the system server binding object for a service.
+         *
+         * <p>This blocks until the service instance is ready,
+         * or a timeout happens, in which case it returns null.
+         */
+        @Nullable
+        public IBinder get() {
+            return ServiceManager.getService(mServiceName);
+        }
+
+        /**
+         * Get the system server binding object for a service.
+         *
+         * <p>This blocks until the service instance is ready,
+         * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+         */
+        @NonNull
+        public IBinder getOrThrow() throws ServiceNotFoundException {
+            try {
+                return ServiceManager.getServiceOrThrow(mServiceName);
+            } catch (ServiceManager.ServiceNotFoundException e) {
+                throw new ServiceNotFoundException(mServiceName);
+            }
+        }
+
+        /**
+         * Get the system server binding object for a service. If the specified service is
+         * not available, it returns null.
+         */
+        @Nullable
+        public IBinder tryGet() {
+            return ServiceManager.checkService(mServiceName);
+        }
+    }
+
+    /**
+     * See {@link ServiceRegisterer#getOrThrow}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+        /**
+         * Constructor.
+         *
+         * @param name the name of the binder service that cannot be found.
+         *
+         */
+        public ServiceNotFoundException(@NonNull String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the "telephony" service.
+     */
+    @NonNull
+    public ServiceRegisterer getTelephonyServiceRegisterer() {
+        return new ServiceRegisterer("phone");
+    }
+
+
+// TODO: Add more services...
+//
+//    /**
+//     * Returns {@link ServiceRegisterer} for the "subscription" service.
+//     */
+//    @NonNull
+//    public ServiceRegisterer getSubscriptionServiceRegisterer() {
+//        return new ServiceRegisterer("isub");
+//    }
+//
+//    /**
+//     * Returns {@link ServiceRegisterer} for the "SMS" service.
+//     */
+//    @NonNull
+//    public ServiceRegisterer getSmsServiceRegisterer() {
+//        return new ServiceRegisterer("isms");
+//    }
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 137f537..d099629 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2391,19 +2391,11 @@
      */
     public @Nullable UserInfo createUser(@Nullable String name, @NonNull String userType,
             @UserInfoFlag int flags) {
-        UserInfo user = null;
         try {
-            user = mService.createUser(name, userType, flags);
-            // TODO: Keep this in sync with
-            // UserManagerService.LocalService.createUserEvenWhenDisallowed
-            if (user != null && !user.isAdmin() && !user.isDemo()) {
-                mService.setUserRestriction(DISALLOW_SMS, true, user.id);
-                mService.setUserRestriction(DISALLOW_OUTGOING_CALLS, true, user.id);
-            }
+            return mService.createUser(name, userType, flags);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
-        return user;
     }
 
     /**
@@ -2748,8 +2740,7 @@
     /**
      * Assigns admin privileges to the user, if such a user exists.
      *
-     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} and
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permissions.
+     * <p>Note that this does not alter the user's pre-existing user restrictions.
      *
      * @param userId the id of the user to become admin
      * @hide
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 907eae8..d17a5e0 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -306,6 +306,8 @@
      * @param appDataDir null-ok the data directory of the app.
      * @param invokeWith null-ok the command to invoke with.
      * @param packageName null-ok the name of the package this process belongs to.
+     * @param disabledCompatChanges null-ok list of disabled compat changes for the process being
+     *                             started.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      * @param isTopApp Whether the process starts for high priority application.
      *
@@ -325,6 +327,7 @@
                                                   @Nullable String packageName,
                                                   boolean useUsapPool,
                                                   boolean isTopApp,
+                                                  @Nullable long[] disabledCompatChanges,
                                                   @Nullable String[] zygoteArgs) {
         // TODO (chriswailes): Is there a better place to check this value?
         if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -335,7 +338,7 @@
             return startViaZygote(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
-                    packageName, useUsapPool, isTopApp, zygoteArgs);
+                    packageName, useUsapPool, isTopApp, disabledCompatChanges, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -535,6 +538,7 @@
      * that has its state cloned from this zygote process.
      * @param packageName null-ok the name of the package this process belongs to.
      * @param isTopApp Whether the process starts for high priority application.
+     * @param disabledCompatChanges a list of disabled compat changes for the process being started.
      * @param extraArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
      * @throws ZygoteStartFailedEx if process start failed for any reason
@@ -554,6 +558,7 @@
                                                       @Nullable String packageName,
                                                       boolean useUsapPool,
                                                       boolean isTopApp,
+                                                      @Nullable long[] disabledCompatChanges,
                                                       @Nullable String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
         ArrayList<String> argsForZygote = new ArrayList<>();
@@ -584,10 +589,10 @@
 
         // --setgroups is a comma-separated list
         if (gids != null && gids.length > 0) {
-            StringBuilder sb = new StringBuilder();
+            final StringBuilder sb = new StringBuilder();
             sb.append("--setgroups=");
 
-            int sz = gids.length;
+            final int sz = gids.length;
             for (int i = 0; i < sz; i++) {
                 if (i != 0) {
                     sb.append(',');
@@ -631,6 +636,21 @@
             argsForZygote.add(Zygote.START_AS_TOP_APP_ARG);
         }
 
+        if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("--disabled-compat-changes=");
+
+            int sz = disabledCompatChanges.length;
+            for (int i = 0; i < sz; i++) {
+                if (i != 0) {
+                    sb.append(',');
+                }
+                sb.append(disabledCompatChanges[i]);
+            }
+
+            argsForZygote.add(sb.toString());
+        }
+
         argsForZygote.add(processClass);
 
         if (extraArgs != null) {
@@ -1166,7 +1186,8 @@
                     gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
                     abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
                     true /* startChildZygote */, null /* packageName */,
-                    false /* useUsapPool */, false /* isTopApp */, extraArgs);
+                    false /* useUsapPool */, false /* isTopApp */,
+                    null /* disabledCompatChanges */, extraArgs);
         } catch (ZygoteStartFailedEx ex) {
             throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
         }
diff --git a/core/java/android/os/connectivity/WifiBatteryStats.java b/core/java/android/os/connectivity/WifiBatteryStats.java
index e9b3837..895d837 100644
--- a/core/java/android/os/connectivity/WifiBatteryStats.java
+++ b/core/java/android/os/connectivity/WifiBatteryStats.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.BatteryStats;
+import android.os.BatteryStatsManager;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -46,9 +47,9 @@
     private long mEnergyConsumedMaMillis = 0;
     private long mNumAppScanRequest = 0;
     private long[] mTimeInStateMillis =
-        new long[BatteryStats.NUM_WIFI_STATES];
+        new long[BatteryStatsManager.NUM_WIFI_STATES];
     private long[] mTimeInSupplicantStateMillis =
-        new long[BatteryStats.NUM_WIFI_SUPPL_STATES];
+        new long[BatteryStatsManager.NUM_WIFI_SUPPL_STATES];
     private long[] mTimeInRxSignalStrengthLevelMillis =
         new long[BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS];
     private long mMonitoredRailChargeConsumedMaMillis = 0;
@@ -369,7 +370,7 @@
     /** @hide */
     public void setTimeInStateMillis(long[] t) {
         mTimeInStateMillis = Arrays.copyOfRange(t, 0,
-                Math.min(t.length, BatteryStats.NUM_WIFI_STATES));
+                Math.min(t.length, BatteryStatsManager.NUM_WIFI_STATES));
         return;
     }
 
@@ -383,7 +384,7 @@
     /** @hide */
     public void setTimeInSupplicantStateMillis(long[] t) {
         mTimeInSupplicantStateMillis = Arrays.copyOfRange(
-                t, 0, Math.min(t.length, BatteryStats.NUM_WIFI_SUPPL_STATES));
+                t, 0, Math.min(t.length, BatteryStatsManager.NUM_WIFI_SUPPL_STATES));
         return;
     }
 
diff --git a/core/java/android/os/incremental/IIncrementalManager.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl
index d6446d4..f84d7ef 100644
--- a/core/java/android/os/incremental/IIncrementalManager.aidl
+++ b/core/java/android/os/incremental/IIncrementalManager.aidl
@@ -16,89 +16,22 @@
 
 package android.os.incremental;
 
+import android.os.incremental.IncrementalFileSystemControlParcel;
 import android.os.incremental.IncrementalDataLoaderParamsParcel;
+import android.content.pm.IDataLoaderStatusListener;
 
-/** @hide */
+/**
+ * Binder service to receive calls from native Incremental Service and handle Java tasks such as
+ * looking up data loader service package names, binding and talking to the data loader service.
+ * @hide
+ */
 interface IIncrementalManager {
-    /**
-     * A set of flags for the |createMode| parameters when creating a new Incremental storage.
-     */
-    const int CREATE_MODE_TEMPORARY_BIND = 1;
-    const int CREATE_MODE_PERMANENT_BIND = 2;
-    const int CREATE_MODE_CREATE = 4;
-    const int CREATE_MODE_OPEN_EXISTING = 8;
-
-    /**
-     * Opens or creates a storage given a target path and data loader params. Returns the storage ID.
-     */
-    int openStorage(in @utf8InCpp String path);
-    int createStorage(in @utf8InCpp String path, in IncrementalDataLoaderParamsParcel params, int createMode);
-    int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
-
-    /**
-     * Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
-     */
-    const int BIND_TEMPORARY = 0;
-    const int BIND_PERMANENT = 1;
-    int makeBindMount(int storageId, in @utf8InCpp String pathUnderStorage, in @utf8InCpp String targetFullPath, int bindType);
-
-    /**
-     * Deletes an existing bind mount on a path under a storage. Returns 0 on success, and -errno on failure.
-     */
-    int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath);
-
-    /**
-     * Creates a directory under a storage. The target directory is specified by its relative path under the storage.
-     */
-    int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage);
-
-    /**
-     * Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage.
-     * All the parent directories of the target directory will be created if they do not exist already.
-     */
-    int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage);
-
-    /**
-     * Creates a file under a storage, specifying its name, size and metadata.
-     */
-    int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata);
-
-    /**
-     * Creates a file under a storage. Content of the file is from a range inside another file.
-     * Both files are specified by relative paths under storage.
-     */
-    int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end);
-
-    /**
-     * Creates a hard link between two files in two storage instances.
-     * Source and dest specified by parent storage IDs and their relative paths under the storage.
-     * The source and dest storage instances should be in the same fs mount.
-     * Note: destStorageId can be the same as sourceStorageId.
-     */
-    int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage);
-
-    /**
-     * Deletes a hard link in a storage, specified by the relative path of the link target under storage.
-     */
-    int unlink(int storageId, in @utf8InCpp String pathUnderStorage);
-
-    /**
-     * Checks if a file's certain range is loaded. File is specified by relative file path under storage.
-     */
-    boolean isFileRangeLoaded(int storageId, in @utf8InCpp String pathUnderStorage, long start, long end);
-
-    /**
-     * Reads the metadata of a file. File is specified by relative path under storage.
-     */
-    byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage);
-
-    /**
-     * Starts loading data for a storage.
-     */
-    boolean startLoading(int storageId);
-
-    /**
-     * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
-     */
-    void deleteStorage(int storageId);
+    boolean prepareDataLoader(int mountId,
+        in IncrementalFileSystemControlParcel control,
+        in IncrementalDataLoaderParamsParcel params,
+        in IDataLoaderStatusListener listener);
+    boolean startDataLoader(int mountId);
+    void showHealthBlockedUI(int mountId);
+    void destroyDataLoader(int mountId);
+    void newFileForDataLoader(int mountId, long inode, in byte[] metadata);
 }
diff --git a/core/java/android/os/incremental/IIncrementalManagerNative.aidl b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
new file mode 100644
index 0000000..d9c7c6b
--- /dev/null
+++ b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+import android.os.incremental.IncrementalDataLoaderParamsParcel;
+
+/** @hide */
+interface IIncrementalManagerNative {
+    /**
+     * A set of flags for the |createMode| parameters when creating a new Incremental storage.
+     */
+    const int CREATE_MODE_TEMPORARY_BIND = 1;
+    const int CREATE_MODE_PERMANENT_BIND = 2;
+    const int CREATE_MODE_CREATE = 4;
+    const int CREATE_MODE_OPEN_EXISTING = 8;
+
+    /**
+     * Opens or creates a storage given a target path and data loader params. Returns the storage ID.
+     */
+    int openStorage(in @utf8InCpp String path);
+    int createStorage(in @utf8InCpp String path, in IncrementalDataLoaderParamsParcel params, int createMode);
+    int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
+
+    /**
+     * Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
+     */
+    const int BIND_TEMPORARY = 0;
+    const int BIND_PERMANENT = 1;
+    int makeBindMount(int storageId, in @utf8InCpp String pathUnderStorage, in @utf8InCpp String targetFullPath, int bindType);
+
+    /**
+     * Deletes an existing bind mount on a path under a storage. Returns 0 on success, and -errno on failure.
+     */
+    int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath);
+
+    /**
+     * Creates a directory under a storage. The target directory is specified by its relative path under the storage.
+     */
+    int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage);
+
+    /**
+     * Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage.
+     * All the parent directories of the target directory will be created if they do not exist already.
+     */
+    int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage);
+
+    /**
+     * Creates a file under a storage, specifying its name, size and metadata.
+     */
+    int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata);
+
+    /**
+     * Creates a file under a storage. Content of the file is from a range inside another file.
+     * Both files are specified by relative paths under storage.
+     */
+    int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end);
+
+    /**
+     * Creates a hard link between two files in two storage instances.
+     * Source and dest specified by parent storage IDs and their relative paths under the storage.
+     * The source and dest storage instances should be in the same fs mount.
+     * Note: destStorageId can be the same as sourceStorageId.
+     */
+    int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage);
+
+    /**
+     * Deletes a hard link in a storage, specified by the relative path of the link target under storage.
+     */
+    int unlink(int storageId, in @utf8InCpp String pathUnderStorage);
+
+    /**
+     * Checks if a file's certain range is loaded. File is specified by relative file path under storage.
+     */
+    boolean isFileRangeLoaded(int storageId, in @utf8InCpp String pathUnderStorage, long start, long end);
+
+    /**
+     * Reads the metadata of a file. File is specified by relative path under storage.
+     */
+    byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage);
+
+    /**
+     * Starts loading data for a storage.
+     */
+    boolean startLoading(int storageId);
+
+    /**
+     * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
+     */
+    void deleteStorage(int storageId);
+}
diff --git a/core/java/android/os/incremental/IIncrementalServiceProxy.aidl b/core/java/android/os/incremental/IIncrementalServiceProxy.aidl
deleted file mode 100644
index ffff52e..0000000
--- a/core/java/android/os/incremental/IIncrementalServiceProxy.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os.incremental;
-
-import android.os.incremental.IncrementalFileSystemControlParcel;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
-import android.content.pm.IDataLoaderStatusListener;
-
-/**
- * Binder service to receive calls from native Incremental Service and handle Java tasks such as
- * looking up data loader service package names, binding and talking to the data loader service.
- * @hide
- */
-interface IIncrementalServiceProxy {
-    boolean prepareDataLoader(int mountId,
-        in IncrementalFileSystemControlParcel control,
-        in IncrementalDataLoaderParamsParcel params,
-        in IDataLoaderStatusListener listener);
-    boolean startDataLoader(int mountId);
-    void showHealthBlockedUI(int mountId);
-    void destroyDataLoader(int mountId);
-    void newFileForDataLoader(int mountId, long inode, in byte[] metadata);
-}
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 5aabf86..c30f558 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -36,31 +36,28 @@
 import java.nio.file.Paths;
 
 /**
- * Provides operations to open or create an IncrementalStorage, using IIncrementalManager service.
- * Example Usage:
+ * Provides operations to open or create an IncrementalStorage, using IIncrementalManagerNative
+ * service. Example Usage:
  *
  * <blockquote><pre>
- * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
  * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
  * </pre></blockquote>
  *
  * @hide
  */
 @SystemService(Context.INCREMENTAL_SERVICE)
-public class IncrementalManager {
+public final class IncrementalManager {
     private static final String TAG = "IncrementalManager";
-    private final IIncrementalManager mService;
-    @GuardedBy("mStorages")
-    private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
 
     public static final int CREATE_MODE_TEMPORARY_BIND =
-            IIncrementalManager.CREATE_MODE_TEMPORARY_BIND;
+            IIncrementalManagerNative.CREATE_MODE_TEMPORARY_BIND;
     public static final int CREATE_MODE_PERMANENT_BIND =
-            IIncrementalManager.CREATE_MODE_PERMANENT_BIND;
+            IIncrementalManagerNative.CREATE_MODE_PERMANENT_BIND;
     public static final int CREATE_MODE_CREATE =
-            IIncrementalManager.CREATE_MODE_CREATE;
+            IIncrementalManagerNative.CREATE_MODE_CREATE;
     public static final int CREATE_MODE_OPEN_EXISTING =
-            IIncrementalManager.CREATE_MODE_OPEN_EXISTING;
+            IIncrementalManagerNative.CREATE_MODE_OPEN_EXISTING;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"CREATE_MODE_"}, value = {
@@ -72,8 +69,12 @@
     public @interface CreateMode {
     }
 
-    public IncrementalManager(@NonNull IIncrementalManager is) {
-        mService = is;
+    private final @Nullable IIncrementalManagerNative mNativeService;
+    @GuardedBy("mStorages")
+    private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
+
+    public IncrementalManager(IIncrementalManagerNative nativeService) {
+        mNativeService = nativeService;
     }
 
     /**
@@ -82,6 +83,7 @@
      * @param storageId The storage ID to identify the storage object.
      * @return IncrementalStorage object corresponding to storage ID.
      */
+    // TODO(b/136132412): remove this
     @Nullable
     public IncrementalStorage getStorage(int storageId) {
         synchronized (mStorages) {
@@ -95,8 +97,8 @@
      *
      * @param path                Absolute path to mount Incremental File System on.
      * @param params              IncrementalDataLoaderParams object to configure data loading.
-     * @param createMode          Mode for opening an old Incremental File System mount or
-     *                            creating a new mount.
+     * @param createMode          Mode for opening an old Incremental File System mount or creating
+     *                            a new mount.
      * @param autoStartDataLoader Set true to immediately start data loader after creating storage.
      * @return IncrementalStorage object corresponding to the mounted directory.
      */
@@ -105,11 +107,11 @@
             @NonNull IncrementalDataLoaderParams params, @CreateMode int createMode,
             boolean autoStartDataLoader) {
         try {
-            final int id = mService.createStorage(path, params.getData(), createMode);
+            final int id = mNativeService.createStorage(path, params.getData(), createMode);
             if (id < 0) {
                 return null;
             }
-            final IncrementalStorage storage = new IncrementalStorage(mService, id);
+            final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
             synchronized (mStorages) {
                 mStorages.put(id, storage);
             }
@@ -123,8 +125,8 @@
     }
 
     /**
-     * Opens an existing Incremental File System mounted directory and returns an
-     * IncrementalStorage object.
+     * Opens an existing Incremental File System mounted directory and returns an IncrementalStorage
+     * object.
      *
      * @param path Absolute target path that Incremental File System has been mounted on.
      * @return IncrementalStorage object corresponding to the mounted directory.
@@ -132,11 +134,11 @@
     @Nullable
     public IncrementalStorage openStorage(@NonNull String path) {
         try {
-            final int id = mService.openStorage(path);
+            final int id = mNativeService.openStorage(path);
             if (id < 0) {
                 return null;
             }
-            final IncrementalStorage storage = new IncrementalStorage(mService, id);
+            final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
             synchronized (mStorages) {
                 mStorages.put(id, storage);
             }
@@ -155,11 +157,12 @@
     public IncrementalStorage createStorage(@NonNull String path,
             @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
         try {
-            final int id = mService.createLinkedStorage(path, linkedStorage.getId(), createMode);
+            final int id = mNativeService.createLinkedStorage(
+                    path, linkedStorage.getId(), createMode);
             if (id < 0) {
                 return null;
             }
-            final IncrementalStorage storage = new IncrementalStorage(mService, id);
+            final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
             synchronized (mStorages) {
                 mStorages.put(id, storage);
             }
@@ -175,6 +178,7 @@
      * @param file Target file to search storage for.
      * @return Absolute path which is a bind-mount point of Incremental File System.
      */
+    @Nullable
     private Path getStoragePathForFile(File file) {
         File currentPath = new File(file.getParent());
         while (currentPath.getParent() != null) {
@@ -198,18 +202,19 @@
      *     </li>
      * </ol>
      *
-     * @param sourcePath   Absolute path to the source. Should be the same type as the destPath
-     *                     (file or dir). Expected to already exist and is an Incremental path.
-     * @param destPath     Absolute path to the destination.
-     * @throws IllegalArgumentException when 1) source does not exist,
-     *                     or 2) source and dest type mismatch (one is file and the other is dir),
-     *                     or 3) source path is not on Incremental File System,
-     * @throws IOException when 1) cannot find the root path of the Incremental storage of source,
-     *                     or 2) cannot retrieve the Incremental storage instance of the source,
-     *                     or 3) renaming a file, but dest is not on the same Incremental Storage,
-     *                     or 4) renaming a dir, dest dir does not exist but fails to be created.
-     *
-     * TODO(b/136132412): add unit tests
+     * @param sourcePath Absolute path to the source. Should be the same type as the destPath (file
+     *                   or dir). Expected to already exist and is an Incremental path.
+     * @param destPath   Absolute path to the destination.
+     * @throws IllegalArgumentException when 1) source does not exist, or 2) source and dest type
+     *                                  mismatch (one is file and the other is dir), or 3) source
+     *                                  path is not on Incremental File System,
+     * @throws IOException              when 1) cannot find the root path of the Incremental storage
+     *                                  of source, or 2) cannot retrieve the Incremental storage
+     *                                  instance of the source, or 3) renaming a file, but dest is
+     *                                  not on the same Incremental Storage, or 4) renaming a dir,
+     *                                  dest dir does not exist but fails to be created.
+     *                                  <p>
+     *                                  TODO(b/136132412): add unit tests
      */
     public void rename(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
         final File source = new File(sourcePath);
@@ -243,6 +248,7 @@
         if (storage == null) {
             throw new IOException("Failed to retrieve storage from Incremental Service.");
         }
+
         if (source.isFile()) {
             renameFile(storage, storagePath, source, dest);
         } else {
@@ -304,11 +310,11 @@
      */
     public void closeStorage(@NonNull String path) {
         try {
-            final int id = mService.openStorage(path);
+            final int id = mNativeService.openStorage(path);
             if (id < 0) {
                 return;
             }
-            mService.deleteStorage(id);
+            mNativeService.deleteStorage(id);
             synchronized (mStorages) {
                 mStorages.remove(id);
             }
@@ -321,7 +327,7 @@
      * Checks if path is mounted on Incremental File System.
      */
     public static boolean isIncrementalPath(@NonNull String path) {
-        // TODO(b/136132412): implement native method
+        // TODO(b/136132412): add jni implementation
         return false;
     }
 }
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index 2bf89ed..2750868 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -24,11 +24,11 @@
 import java.io.IOException;
 
 /**
- * Provides operations on an Incremental File System directory, using IncrementalService. Example
- * usage:
+ * Provides operations on an Incremental File System directory, using IncrementalServiceNative.
+ * Example usage:
  *
  * <blockquote><pre>
- * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
  * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
  * storage.makeDirectory("subdir");
  * </pre></blockquote>
@@ -38,10 +38,10 @@
 public final class IncrementalStorage {
     private static final String TAG = "IncrementalStorage";
     private final int mId;
-    private final IIncrementalManager mService;
+    private final IIncrementalManagerNative mService;
 
 
-    public IncrementalStorage(@NonNull IIncrementalManager is, int id) {
+    public IncrementalStorage(@NonNull IIncrementalManagerNative is, int id) {
         mService = is;
         mId = id;
     }
@@ -72,7 +72,7 @@
             throws IOException {
         try {
             int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
-                    IIncrementalManager.BIND_TEMPORARY);
+                    IIncrementalManagerNative.BIND_TEMPORARY);
             if (res < 0) {
                 throw new IOException("bind() failed with errno " + -res);
             }
@@ -103,7 +103,7 @@
             throws IOException {
         try {
             int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
-                    IIncrementalManager.BIND_PERMANENT);
+                    IIncrementalManagerNative.BIND_PERMANENT);
             if (res < 0) {
                 throw new IOException("bind() permanent failed with errno " + -res);
             }
@@ -274,7 +274,8 @@
             throw new IOException("moveDir() requires that destination dir already exists.");
         }
         try {
-            int res = mService.makeBindMount(mId, "", destPath, IIncrementalManager.BIND_PERMANENT);
+            int res = mService.makeBindMount(mId, "", destPath,
+                    IIncrementalManagerNative.BIND_PERMANENT);
             if (res < 0) {
                 throw new IOException("moveDir() failed at making bind mount, errno " + -res);
             }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2d70986d..62603fe 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -80,6 +80,7 @@
 import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.DataUnit;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -156,7 +157,8 @@
     /** {@hide} */
     public static final String PROP_FUSE = "persist.sys.fuse";
     /** {@hide} */
-    public static final String PROP_FUSE_SNAPSHOT = "sys.fuse_snapshot";
+    public static final String PROP_SETTINGS_FUSE = FeatureFlagUtils.PERSIST_PREFIX
+            + FeatureFlagUtils.SETTINGS_FUSE_FLAG;
 
 
     /** {@hide} */
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8f765fa..f914663 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -221,7 +221,7 @@
      * permission controller package.
      */
     @BinderThread
-    public void onUpdateUserSensitive() {
+    public void onUpdateUserSensitivePermissionFlags() {
         throw new AbstractMethodError("Must be overridden in implementing class");
     }
 
@@ -449,7 +449,7 @@
             public void updateUserSensitive(AndroidFuture callback) {
                 Preconditions.checkNotNull(callback, "callback cannot be null");
 
-                onUpdateUserSensitive();
+                onUpdateUserSensitivePermissionFlags();
                 callback.complete(null);
             }
         };
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 8bf723f..eb1684f 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -2916,6 +2916,8 @@
          */
         @Nullable
         public static String getLocalAccountName(@NonNull Context context) {
+            //  config_rawContactsLocalAccountName is defined in
+            //  platform/frameworks/base/core/res/res/values/config.xml
             return TextUtils.nullIfEmpty(context.getString(
                     com.android.internal.R.string.config_rawContactsLocalAccountName));
         }
@@ -2933,6 +2935,8 @@
          */
         @Nullable
         public static String getLocalAccountType(@NonNull Context context) {
+            //  config_rawContactsLocalAccountType is defined in
+            //  platform/frameworks/base/core/res/res/values/config.xml
             return TextUtils.nullIfEmpty(context.getString(
                     com.android.internal.R.string.config_rawContactsLocalAccountType));
         }
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 9dc6806..ef22d70 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -40,6 +40,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -578,11 +579,13 @@
      * none or all of this update is picked up, but never only part of it.
      *
      * @param properties the complete set of properties to set for a specific namespace.
+     * @throws BadConfigException if the provided properties are banned by RescueParty.
+     * @return True if the values were set, false otherwise.
      * @hide
      */
     @SystemApi
     @RequiresPermission(WRITE_DEVICE_CONFIG)
-    public static boolean setProperties(@NonNull Properties properties) {
+    public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
         ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
         return Settings.Config.setStrings(contentResolver, properties.getNamespace(),
                 properties.mMap);
@@ -732,19 +735,19 @@
         List<String> pathSegments = uri.getPathSegments();
         // pathSegments(0) is "config"
         final String namespace = pathSegments.get(1);
-        Map<String, String> propertyMap = new ArrayMap<>();
+        Properties.Builder propBuilder = new Properties.Builder(namespace);
         try {
             Properties allProperties = getProperties(namespace);
             for (int i = 2; i < pathSegments.size(); ++i) {
                 String key = pathSegments.get(i);
-                propertyMap.put(key, allProperties.getString(key, null));
+                propBuilder.setString(key, allProperties.getString(key, null));
             }
         } catch (SecurityException e) {
             // Silently failing to not crash binder or listener threads.
             Log.e(TAG, "OnPropertyChangedListener update failed: permission violation.");
             return;
         }
-        Properties properties = new Properties(namespace, propertyMap);
+        Properties properties = propBuilder.build();
 
         synchronized (sLock) {
             for (int i = 0; i < sListeners.size(); i++) {
@@ -799,6 +802,15 @@
     }
 
     /**
+     * Thrown by {@link #setProperties(Properties)} when a configuration is rejected. This
+     * happens if RescueParty has identified a bad configuration and reset the namespace.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static class BadConfigException extends Exception {}
+
+    /**
      * A mapping of properties to values, as well as a single namespace which they all belong to.
      *
      * @hide
@@ -808,6 +820,7 @@
     public static class Properties {
         private final String mNamespace;
         private final HashMap<String, String> mMap;
+        private Set<String> mKeyset;
 
         /**
          * Create a mapping of properties to values and the namespace they belong to.
@@ -838,7 +851,10 @@
          */
         @NonNull
         public Set<String> getKeyset() {
-            return mMap.keySet();
+            if (mKeyset == null) {
+                mKeyset = Collections.unmodifiableSet(mMap.keySet());
+            }
+            return mKeyset;
         }
 
         /**
@@ -933,5 +949,93 @@
                 return defaultValue;
             }
         }
+
+        /**
+         * Builder class for the construction of {@link Properties} objects.
+         */
+        public static final class Builder {
+            @NonNull
+            private final String mNamespace;
+            @NonNull
+            private final Map<String, String> mKeyValues = new HashMap<>();
+
+            /**
+             * Create a new Builders for the specified namespace.
+             * @param namespace non null namespace.
+             */
+            public Builder(@NonNull String namespace) {
+                mNamespace = namespace;
+            }
+
+            /**
+             * Add a new property with the specified key and value.
+             * @param name non null name of the property.
+             * @param value nullable string value of the property.
+             * @return this Builder object
+             */
+            @NonNull
+            public Builder setString(@NonNull String name, @Nullable String value) {
+                mKeyValues.put(name, value);
+                return this;
+            }
+
+            /**
+             * Add a new property with the specified key and value.
+             * @param name non null name of the property.
+             * @param value nullable string value of the property.
+             * @return this Builder object
+             */
+            @NonNull
+            public Builder setBoolean(@NonNull String name, boolean value) {
+                mKeyValues.put(name, Boolean.toString(value));
+                return this;
+            }
+
+            /**
+             * Add a new property with the specified key and value.
+             * @param name non null name of the property.
+             * @param value int value of the property.
+             * @return this Builder object
+             */
+            @NonNull
+            public Builder setInt(@NonNull String name, int value) {
+                mKeyValues.put(name, Integer.toString(value));
+                return this;
+            }
+
+            /**
+             * Add a new property with the specified key and value.
+             * @param name non null name of the property.
+             * @param value long value of the property.
+             * @return this Builder object
+             */
+            @NonNull
+            public Builder setLong(@NonNull String name, long value) {
+                mKeyValues.put(name, Long.toString(value));
+                return this;
+            }
+
+            /**
+             * Add a new property with the specified key and value.
+             * @param name non null name of the property.
+             * @param value float value of the property.
+             * @return this Builder object
+             */
+            @NonNull
+            public Builder setFloat(@NonNull String name, float value) {
+                mKeyValues.put(name, Float.toString(value));
+                return this;
+            }
+
+            /**
+             * Create a new {@link Properties} object.
+             * @return non null Properties.
+             */
+            @NonNull
+            public Properties build() {
+                return new Properties(mNamespace, mKeyValues);
+            }
+        }
     }
+
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 165c284..bd1eb21 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10312,6 +10312,19 @@
         public static final String ERROR_LOGCAT_PREFIX = "logcat_for_";
 
         /**
+         * Maximum number of bytes of a system crash/ANR/etc. report that
+         * ActivityManagerService should send to DropBox, as a prefix of the
+         * dropbox tag of the report type. For example,
+         * "max_error_bytes_for_system_server_anr" controls the maximum
+         * number of bytes captured with system server ANR reports.
+         * <p>
+         * Type: int (max size in bytes)
+         *
+         * @hide
+         */
+        public static final String MAX_ERROR_BYTES_PREFIX = "max_error_bytes_for_";
+
+        /**
          * The interval in minutes after which the amount of free storage left
          * on the device is logged to the event log
          *
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 70c8e5d..c287ae3 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -25,6 +25,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.ChangeId;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -1307,8 +1308,7 @@
              * Broadcast action: When SMS-MMS db is being created. If file-based encryption is
              * supported, this broadcast indicates creation of the db in credential-encrypted
              * storage. A boolean is specified in {@link #EXTRA_IS_INITIAL_CREATE} to indicate if
-             * this is the initial create of the db. Requires
-             * {@link android.Manifest.permission#READ_SMS} to receive.
+             * this is the initial create of the db.
              *
              * @see #EXTRA_IS_INITIAL_CREATE
              *
@@ -4032,6 +4032,16 @@
         @Retention(RetentionPolicy.SOURCE)
         public @interface Skip464XlatStatus {}
 
+        /**
+         * Compat framework change ID for the APN db read permission change.
+         *
+         * In API level 30 and beyond, accessing the APN database will require the
+         * {@link android.Manifest.permission#WRITE_APN_SETTINGS} permission. This change ID tracks
+         * apps that are affected because they don't hold this permission.
+         * @hide
+         */
+        @ChangeId
+        public static final long APN_READING_PERMISSION_CHANGE_ID = 124107808L;
     }
 
     /**
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 15ded8d..e4ba87c 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -94,6 +94,7 @@
     public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
     public static final int KM_TAG_ATTESTATION_ID_MANUFACTURER = KM_BYTES | 716;
     public static final int KM_TAG_ATTESTATION_ID_MODEL = KM_BYTES | 717;
+    public static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION = KM_BOOL | 720;
 
     public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
     public static final int KM_TAG_NONCE = KM_BYTES | 1001;
diff --git a/core/java/android/service/controls/BooleanAction.java b/core/java/android/service/controls/BooleanAction.java
index 8508c63..877f82e 100644
--- a/core/java/android/service/controls/BooleanAction.java
+++ b/core/java/android/service/controls/BooleanAction.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Bundle;
 import android.os.Parcel;
 
 /**
@@ -26,6 +27,8 @@
  */
 public final class BooleanAction extends ControlAction {
 
+    private static final String KEY_NEW_STATE = "key_new_state";
+
     private final boolean mNewState;
 
     /**
@@ -49,9 +52,9 @@
         mNewState = newValue;
     }
 
-    BooleanAction(Parcel in) {
-        super(in);
-        mNewState = in.readByte() == 1;
+    BooleanAction(Bundle b) {
+        super(b);
+        mNewState = b.getBoolean(KEY_NEW_STATE);
     }
 
     /**
@@ -72,17 +75,17 @@
         return ControlAction.TYPE_BOOLEAN;
     }
 
-
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeByte(mNewState ? (byte) 1 : (byte) 0);
+    protected Bundle getDataBundle() {
+        Bundle b =  super.getDataBundle();
+        b.putBoolean(KEY_NEW_STATE, mNewState);
+        return b;
     }
 
     public static final @NonNull Creator<BooleanAction> CREATOR = new Creator<BooleanAction>() {
         @Override
         public BooleanAction createFromParcel(Parcel source) {
-            return new BooleanAction(source);
+            return new BooleanAction(source.readBundle());
         }
 
         @Override
diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java
index a69408c..2c17e89 100644
--- a/core/java/android/service/controls/Control.java
+++ b/core/java/android/service/controls/Control.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Icon;
@@ -55,7 +56,7 @@
     private final @NonNull Icon mIcon;
     private final @NonNull CharSequence mTitle;
     private final @Nullable ColorStateList mTintColor;
-    private final @NonNull Intent mAppIntent;
+    private final @NonNull PendingIntent mAppIntent;
     private final @ControlTemplate.TemplateType int mPrimaryType;
 
     /**
@@ -64,14 +65,15 @@
      * @param title the user facing name of this control (e.g. "Bedroom thermostat").
      * @param tintColor the color to tint parts of the element UI. If {@code null} is passed, the
      *                  system accent color will be used.
-     * @param appIntent an intent linking to a page to interact with the corresponding device.
+     * @param appIntent a {@link PendingIntent} linking to a page to interact with the
+     *                  corresponding device.
      * @param primaryType the primary template for this type.
      */
     public Control(@NonNull String controlId,
             @NonNull Icon icon,
             @NonNull CharSequence title,
             @Nullable ColorStateList tintColor,
-            @NonNull Intent appIntent,
+            @NonNull PendingIntent appIntent,
             int primaryType) {
         Preconditions.checkNotNull(controlId);
         Preconditions.checkNotNull(icon);
@@ -94,7 +96,7 @@
         } else {
             mTintColor = null;
         }
-        mAppIntent = Intent.CREATOR.createFromParcel(in);
+        mAppIntent = PendingIntent.CREATOR.createFromParcel(in);
         mPrimaryType = in.readInt();
     }
 
@@ -119,7 +121,7 @@
     }
 
     @NonNull
-    public Intent getAppIntent() {
+    public PendingIntent getAppIntent() {
         return mAppIntent;
     }
 
@@ -175,17 +177,17 @@
         private Icon mIcon;
         private CharSequence mTitle = "";
         private ColorStateList mTintColor;
-        private @Nullable Intent mAppIntent;
+        private @Nullable PendingIntent mAppIntent;
         private @ControlTemplate.TemplateType int mPrimaryType = ControlTemplate.TYPE_NONE;
 
         /**
          * @param controlId the identifier for the {@link Control}.
          * @param icon the icon for the {@link Control}.
-         * @param appIntent the intent linking to the device Activity.
+         * @param appIntent the pending intent linking to the device Activity.
          */
         public Builder(@NonNull String controlId,
                 @NonNull Icon icon,
-                @NonNull Intent appIntent) {
+                @NonNull PendingIntent appIntent) {
             Preconditions.checkNotNull(controlId);
             Preconditions.checkNotNull(icon);
             Preconditions.checkNotNull(appIntent);
@@ -256,7 +258,7 @@
          * @return {@code this}
          */
         @NonNull
-        public Builder setAppIntent(@NonNull Intent appIntent) {
+        public Builder setAppIntent(@NonNull PendingIntent appIntent) {
             Preconditions.checkNotNull(appIntent);
             mAppIntent = appIntent;
             return this;
diff --git a/core/java/android/service/controls/ControlAction.java b/core/java/android/service/controls/ControlAction.java
index 8b75955..0a7c97c 100644
--- a/core/java/android/service/controls/ControlAction.java
+++ b/core/java/android/service/controls/ControlAction.java
@@ -16,9 +16,11 @@
 
 package android.service.controls;
 
+import android.annotation.CallSuper;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -36,25 +38,38 @@
  */
 public abstract class ControlAction implements Parcelable {
 
+    private static final String KEY_TEMPLATE_ID = "key_template_id";
+    private static final String KEY_CHALLENGE_VALUE = "key_challenge_value";
+
+    public static final ControlAction UNKNOWN_ACTION = new ControlAction() {
+
+        @Override
+        public int getActionType() {
+            return TYPE_UNKNOWN;
+        }
+    };
+
     /**
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
+            TYPE_UNKNOWN,
             TYPE_BOOLEAN,
             TYPE_FLOAT
     })
     public @interface ActionType {};
 
+    public static final @ActionType int TYPE_UNKNOWN = 0;
     /**
      * The identifier of {@link BooleanAction}.
      */
-    public static final @ActionType int TYPE_BOOLEAN = 0;
+    public static final @ActionType int TYPE_BOOLEAN = 1;
 
     /**
      * The identifier of {@link FloatAction}.
      */
-    public static final @ActionType int TYPE_FLOAT = 1;
+    public static final @ActionType int TYPE_FLOAT = 2;
 
     /**
      * @hide
@@ -120,13 +135,9 @@
     /**
      * @hide
      */
-    ControlAction(Parcel in) {
-        mTemplateId = in.readString();
-        if (in.readByte() == 1) {
-            mChallengeValue = in.readString();
-        } else {
-            mChallengeValue = null;
-        }
+    ControlAction(Bundle b) {
+        mTemplateId = b.getString(KEY_TEMPLATE_ID);
+        mChallengeValue = b.getString(KEY_CHALLENGE_VALUE);
     }
 
     /**
@@ -151,15 +162,24 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public final void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(getActionType());
-        dest.writeString(mTemplateId);
-        if (mChallengeValue != null) {
-            dest.writeByte((byte) 1);
-            dest.writeString(mChallengeValue);
-        } else {
-            dest.writeByte((byte) 0);
-        }
+        dest.writeBundle(getDataBundle());
+    }
+
+    /**
+     * Obtain a {@link Bundle} describing this object populated with data.
+     *
+     * Implementations in subclasses should populate the {@link Bundle} returned by
+     * {@link ControlAction}.
+     * @return a {@link Bundle} containing the data that represents this object.
+     */
+    @CallSuper
+    protected Bundle getDataBundle() {
+        Bundle b = new Bundle();
+        b.putString(KEY_TEMPLATE_ID, mTemplateId);
+        b.putString(KEY_CHALLENGE_VALUE, mChallengeValue);
+        return b;
     }
 
     public static final @NonNull Creator<ControlAction> CREATOR = new Creator<ControlAction>() {
@@ -175,6 +195,7 @@
         }
     };
 
+
     private static ControlAction createActionFromType(@ActionType int type, Parcel source) {
         switch(type) {
             case TYPE_BOOLEAN:
@@ -182,7 +203,8 @@
             case TYPE_FLOAT:
                 return FloatAction.CREATOR.createFromParcel(source);
             default:
-                return null;
+                source.readBundle();
+                return UNKNOWN_ACTION;
         }
     }
 
diff --git a/core/java/android/service/controls/ControlButton.java b/core/java/android/service/controls/ControlButton.java
index fed3115..969c0a7 100644
--- a/core/java/android/service/controls/ControlButton.java
+++ b/core/java/android/service/controls/ControlButton.java
@@ -29,29 +29,29 @@
  */
 public class ControlButton implements Parcelable {
 
-    private final boolean mActive;
+    private final boolean mChecked;
     private final @NonNull Icon mIcon;
     private final @NonNull CharSequence mContentDescription;
 
     /**
-     * @param active true if the button should be rendered as active.
+     * @param checked true if the button should be rendered as active.
      * @param icon icon to display in the button.
      * @param contentDescription content description for the button.
      */
-    public ControlButton(boolean active, @NonNull Icon icon,
+    public ControlButton(boolean checked, @NonNull Icon icon,
             @NonNull CharSequence contentDescription) {
         Preconditions.checkNotNull(icon);
         Preconditions.checkNotNull(contentDescription);
-        mActive = active;
+        mChecked = checked;
         mIcon = icon;
         mContentDescription = contentDescription;
     }
 
     /**
-     * Whether the button should be rendered in its active state.
+     * Whether the button should be rendered in a checked state.
      */
-    public boolean isActive() {
-        return mActive;
+    public boolean isChecked() {
+        return mChecked;
     }
 
     /**
@@ -78,13 +78,13 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeByte(mActive ? (byte) 1 : (byte) 0);
+        dest.writeByte(mChecked ? (byte) 1 : (byte) 0);
         mIcon.writeToParcel(dest, flags);
         dest.writeCharSequence(mContentDescription);
     }
 
     ControlButton(Parcel in) {
-        mActive = in.readByte() != 0;
+        mChecked = in.readByte() != 0;
         mIcon = Icon.CREATOR.createFromParcel(in);
         mContentDescription = in.readCharSequence();
     }
diff --git a/core/java/android/service/controls/ControlState.java b/core/java/android/service/controls/ControlState.java
index 804aef7..f2410a8 100644
--- a/core/java/android/service/controls/ControlState.java
+++ b/core/java/android/service/controls/ControlState.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Icon;
 import android.os.Parcel;
@@ -33,11 +34,11 @@
  * Current state for a {@link Control}.
  *
  * Collects information to render the current state of a {@link Control} as well as possible action
- * that can be performed on it. Some of the information may temporarily override the defaults
- * provided by the corresponding {@link Control}, while this state is being displayed.
- *
- * Additionally, this can be used to modify information related to the corresponding
- * {@link Control}.
+ * that can be performed on it.
+ * <p>
+ * Additionally, this object is used to modify elements from the {@link Control} such as icons,
+ * colors, names and intents. This information will last until it is again modified by a
+ * {@link ControlState}.
  * @hide
  */
 public final class ControlState implements Parcelable {
@@ -74,58 +75,81 @@
      */
     public static final int STATUS_DISABLED = 3;
 
-    private final @NonNull Control mControl;
+    private final @NonNull String mControlId;
     private final @Status int mStatus;
     private final @NonNull ControlTemplate mControlTemplate;
     private final @NonNull CharSequence mStatusText;
-    private final @Nullable Icon mOverrideIcon;
-    private final @Nullable ColorStateList mOverrideTint;
+    private final @Nullable CharSequence mTitle;
+    private final @Nullable PendingIntent mAppIntent;
+    private final @Nullable Icon mIcon;
+    private final @Nullable ColorStateList mTint;
 
     /**
-     * @param control the {@link Control} this state should be applied to. Can be used to
-     *                       update information about the {@link Control}
+     * @param controlId the identifier of the {@link Control} this object refers to.
      * @param status the current status of the {@link Control}.
-     * @param controlTemplate the template to be used to render the {@link Control}.
-     * @param statusText the text describing the current status.
-     * @param overrideIcon the icon to temporarily override the one provided in
-     *                     {@link Control#getIcon()}. Pass {@code null} to use the icon in
-     *                     {@link Control#getIcon()}.
-     * @param overrideTint the colors to temporarily override those provided in
-     *                            {@link Control#getTint()}. Pass {@code null} to use the colors in
-     *                            {@link Control#getTint()}.
+     * @param controlTemplate the template to be used to render the {@link Control}. This can be
+     *                        of a different
+     *                        {@link android.service.controls.ControlTemplate.TemplateType} than the
+     *                        one defined in {@link Control#getPrimaryType}
+     * @param statusText the user facing text describing the current status.
+     * @param title the title to replace the one set in the {@link Control} or set in the
+     *              last {@link ControlState}. Pass {@code null} to use the last value set for this
+     *              {@link Control}
+     * @param appIntent the {@link PendingIntent} to replace the one set in the {@link Control} or
+     *                  set in the last {@link ControlState}. Pass {@code null} to use the last
+     *                  value set for this {@link Control}.
+     * @param icon the icon to replace the one set in the {@link Control} or set in the last
+     *             {@link ControlState}. Pass {@code null} to use the last value set for this
+     *             {@link Control}.
+     * @param tint the colors to replace those set in the {@link Control} or set in the last
+     *             {@link ControlState}. Pass {@code null} to use the last value set for this
+     *             {@link Control}.
      */
-    public ControlState(@NonNull Control control,
+    public ControlState(@NonNull String controlId,
             int status,
             @NonNull ControlTemplate controlTemplate,
             @NonNull CharSequence statusText,
-            @Nullable Icon overrideIcon,
-            @Nullable ColorStateList overrideTint) {
-        Preconditions.checkNotNull(control);
+            @Nullable CharSequence title,
+            @Nullable PendingIntent appIntent,
+            @Nullable Icon icon,
+            @Nullable ColorStateList tint) {
+        Preconditions.checkNotNull(controlId);
         Preconditions.checkNotNull(controlTemplate);
         Preconditions.checkNotNull(statusText);
-
-        mControl = control;
+        mControlId = controlId;
         mStatus = status;
         mControlTemplate = controlTemplate;
-        mOverrideIcon = overrideIcon;
         mStatusText = statusText;
-        mOverrideTint = overrideTint;
+        mTitle = title;
+        mAppIntent = appIntent;
+        mIcon = icon;
+        mTint = tint;
     }
 
     ControlState(Parcel in) {
-        mControl = Control.CREATOR.createFromParcel(in);
+        mControlId = in.readString();
         mStatus = in.readInt();
         mControlTemplate = ControlTemplate.CREATOR.createFromParcel(in);
         mStatusText = in.readCharSequence();
         if (in.readByte() == 1) {
-            mOverrideIcon = Icon.CREATOR.createFromParcel(in);
+            mTitle = in.readCharSequence();
         } else {
-            mOverrideIcon = null;
+            mTitle = null;
         }
         if (in.readByte() == 1) {
-            mOverrideTint = ColorStateList.CREATOR.createFromParcel(in);
+            mAppIntent = PendingIntent.CREATOR.createFromParcel(in);
         } else {
-            mOverrideTint = null;
+            mAppIntent = null;
+        }
+        if (in.readByte() == 1) {
+            mIcon = Icon.CREATOR.createFromParcel(in);
+        } else {
+            mIcon = null;
+        }
+        if (in.readByte() == 1) {
+            mTint = ColorStateList.CREATOR.createFromParcel(in);
+        } else {
+            mTint = null;
         }
     }
 
@@ -134,6 +158,21 @@
         return 0;
     }
 
+    @NonNull
+    public String getControlId() {
+        return mControlId;
+    }
+
+    @Nullable
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    @Nullable
+    public PendingIntent getAppIntent() {
+        return mAppIntent;
+    }
+
     @Status
     public int getStatus() {
         return mStatus;
@@ -145,8 +184,8 @@
     }
 
     @Nullable
-    public Icon getOverrideIcon() {
-        return mOverrideIcon;
+    public Icon getIcon() {
+        return mIcon;
     }
 
     @NonNull
@@ -155,35 +194,35 @@
     }
 
     @Nullable
-    public ColorStateList getOverrideTint() {
-        return mOverrideTint;
-    }
-
-    @NonNull
-    public Control getControl() {
-        return mControl;
-    }
-
-    @NonNull
-    public String getControlId() {
-        return mControl.getControlId();
+    public ColorStateList getTint() {
+        return mTint;
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        mControl.writeToParcel(dest, flags);
+        dest.writeString(mControlId);
         dest.writeInt(mStatus);
         mControlTemplate.writeToParcel(dest, flags);
         dest.writeCharSequence(mStatusText);
-        if (mOverrideIcon != null) {
+        if (mTitle != null) {
             dest.writeByte((byte) 1);
-            mOverrideIcon.writeToParcel(dest, flags);
+            dest.writeCharSequence(mTitle);
         } else {
             dest.writeByte((byte) 0);
         }
-        if (mOverrideTint != null) {
+        if (mAppIntent != null) {
             dest.writeByte((byte) 1);
-            mOverrideTint.writeToParcel(dest, flags);
+            mAppIntent.writeToParcel(dest, flags);
+        }
+        if (mIcon != null) {
+            dest.writeByte((byte) 1);
+            mIcon.writeToParcel(dest, flags);
+        } else {
+            dest.writeByte((byte) 0);
+        }
+        if (mTint != null) {
+            dest.writeByte((byte) 1);
+            mTint.writeToParcel(dest, flags);
         } else {
             dest.writeByte((byte) 0);
         }
@@ -213,19 +252,22 @@
      * </ul>
      */
     public static class Builder {
-        private @NonNull Control mControl;
+        private @NonNull String mControlId;
         private @Status int mStatus = STATUS_OK;
         private @NonNull ControlTemplate mControlTemplate = ControlTemplate.NO_TEMPLATE;
         private @NonNull CharSequence mStatusText = "";
-        private @Nullable Icon mOverrideIcon;
-        private @Nullable ColorStateList mOverrideTint;
+        private @Nullable CharSequence mTitle;
+        private @Nullable PendingIntent mAppIntent;
+        private @Nullable Icon mIcon;
+        private @Nullable ColorStateList mTint;
 
         /**
-         * @param control the {@link Control} that the resulting {@link ControlState} refers to.
+         * @param controlId the identifier of the {@link Control} that the resulting
+         *                  {@link ControlState} refers to.
          */
-        public Builder(@NonNull Control control) {
-            Preconditions.checkNotNull(control);
-            mControl = control;
+        public Builder(@NonNull String controlId) {
+            Preconditions.checkNotNull(controlId);
+            mControlId = controlId;
         }
 
         /**
@@ -234,21 +276,24 @@
          */
         public Builder(@NonNull ControlState controlState) {
             Preconditions.checkNotNull(controlState);
-            mControl = controlState.mControl;
+            mControlId = controlState.mControlId;
+            mStatus = controlState.mStatus;
             mControlTemplate = controlState.mControlTemplate;
-            mOverrideIcon = controlState.mOverrideIcon;
             mStatusText = controlState.mStatusText;
-            mOverrideTint = controlState.mOverrideTint;
+            mTitle = controlState.mTitle;
+            mAppIntent = controlState.mAppIntent;
+            mIcon = controlState.mIcon;
+            mTint = controlState.mTint;
         }
 
 
         /**
-         * @param control the updated {@link Control} information.
+         * @param controlId the identifier of the {@link Control} for the resulting object.
          * @return {@code this}
          */
         @NonNull
-        public Builder setControl(@NonNull Control control) {
-            mControl = control;
+        public Builder setControlId(@NonNull String controlId) {
+            mControlId = controlId;
             return this;
         }
 
@@ -285,24 +330,50 @@
         }
 
         /**
-         * @param overrideIcon the icon to override the one defined in the corresponding
-         *                            {@code Control}. Pass {@code null} to remove the override.
+         * @param title the title to replace the one defined in the corresponding {@link Control} or
+         *              set by the last {@link ControlState}. Pass {@code null} to keep the last
+         *              value.
          * @return {@code this}
          */
         @NonNull
-        public Builder setOverrideIcon(@Nullable Icon overrideIcon) {
-            mOverrideIcon = overrideIcon;
+        public Builder setTitle(@Nullable CharSequence title) {
+            mTitle = title;
             return this;
         }
 
         /**
-         * @param overrideTint the colors to override the ones defined in the corresponding
-         *                            {@code Control}. Pass {@code null} to remove the override.
+         * @param appIntent the Pending Intent to replace the one defined in the corresponding
+         *                  {@link Control} or set by the last {@link ControlState}. Pass
+         *                  {@code null} to keep the last value.
          * @return {@code this}
          */
         @NonNull
-        public Builder setOverrideTint(@Nullable ColorStateList overrideTint) {
-            mOverrideTint = overrideTint;
+        public Builder setAppIntent(@Nullable PendingIntent appIntent) {
+            mAppIntent = appIntent;
+            return this;
+        }
+
+        /**
+         * @param icon the title to replace the one defined in the corresponding {@link Control} or
+         *             set by the last {@link ControlState}. Pass {@code null} to keep the last
+         *             value.
+         * @return {@code this}
+         */
+        @NonNull
+        public Builder setIcon(@Nullable Icon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * @param tint the title to replace the one defined in the corresponding {@link Control} or
+         *             set by the last {@link ControlState}. Pass {@code null} to keep the last
+         *             value.
+         * @return {@code this}
+         */
+        @NonNull
+        public Builder setTint(@Nullable ColorStateList tint) {
+            mTint = tint;
             return this;
         }
 
@@ -310,8 +381,8 @@
          * @return a new {@link ControlState}
          */
         public ControlState build() {
-            return new ControlState(mControl, mStatus, mControlTemplate, mStatusText,
-                    mOverrideIcon, mOverrideTint);
+            return new ControlState(mControlId, mStatus, mControlTemplate, mStatusText,
+                    mTitle, mAppIntent, mIcon, mTint);
         }
     }
 }
diff --git a/core/java/android/service/controls/ControlTemplate.java b/core/java/android/service/controls/ControlTemplate.java
index e559862..8bcabd6 100644
--- a/core/java/android/service/controls/ControlTemplate.java
+++ b/core/java/android/service/controls/ControlTemplate.java
@@ -16,8 +16,10 @@
 
 package android.service.controls;
 
+import android.annotation.CallSuper;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -40,6 +42,8 @@
  */
 public abstract class ControlTemplate implements Parcelable {
 
+    private static final String KEY_TEMPLATE_ID = "key_template_id";
+
     /**
      * Singleton representing a {@link Control} with no input.
      */
@@ -114,17 +118,28 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public final void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(getTemplateType());
-        dest.writeString(mTemplateId);
+        dest.writeBundle(getDataBundle());
+    }
+
+    /**
+     * Obtain a {@link Bundle} describing this object populated with data.
+     * @return a {@link Bundle} containing the data that represents this object.
+     */
+    @CallSuper
+    protected Bundle getDataBundle() {
+        Bundle b = new Bundle();
+        b.putString(KEY_TEMPLATE_ID, mTemplateId);
+        return b;
     }
 
     private ControlTemplate() {
         mTemplateId = "";
     }
 
-    ControlTemplate(Parcel in) {
-        mTemplateId = in.readString();
+    ControlTemplate(@NonNull Bundle b) {
+        mTemplateId = b.getString(KEY_TEMPLATE_ID);
     }
 
     /**
@@ -148,6 +163,7 @@
         }
     };
 
+
     private static ControlTemplate createTemplateFromType(@TemplateType int type, Parcel source) {
         switch(type) {
             case TYPE_TOGGLE:
@@ -159,9 +175,9 @@
             case TYPE_DISCRETE_TOGGLE:
                 return DiscreteToggleTemplate.CREATOR.createFromParcel(source);
             case TYPE_NONE:
-                return NO_TEMPLATE;
             default:
-                return null;
+                source.readBundle();
+                return NO_TEMPLATE;
         }
     }
 }
diff --git a/core/java/android/service/controls/DiscreteToggleTemplate.java b/core/java/android/service/controls/DiscreteToggleTemplate.java
index 5167af4..5718252 100644
--- a/core/java/android/service/controls/DiscreteToggleTemplate.java
+++ b/core/java/android/service/controls/DiscreteToggleTemplate.java
@@ -17,6 +17,7 @@
 package android.service.controls;
 
 import android.annotation.NonNull;
+import android.os.Bundle;
 import android.os.Parcel;
 
 import com.android.internal.util.Preconditions;
@@ -34,6 +35,9 @@
  */
 public class DiscreteToggleTemplate extends ControlTemplate {
 
+    private static final String KEY_NEGATIVE_BUTTON = "key_negative_button";
+    private static final String KEY_POSITIVE_BUTTON = "key_positive_button";
+
     private final @NonNull ControlButton mNegativeButton;
     private final @NonNull ControlButton mPositiveButton;
 
@@ -52,10 +56,10 @@
         mPositiveButton = positiveButton;
     }
 
-    DiscreteToggleTemplate(Parcel in) {
-        super(in);
-        this.mNegativeButton = ControlButton.CREATOR.createFromParcel(in);
-        this.mPositiveButton = ControlButton.CREATOR.createFromParcel(in);
+    DiscreteToggleTemplate(Bundle b) {
+        super(b);
+        mNegativeButton = b.getParcelable(KEY_NEGATIVE_BUTTON);
+        mPositiveButton = b.getParcelable(KEY_POSITIVE_BUTTON);
     }
 
     /**
@@ -89,17 +93,18 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        mNegativeButton.writeToParcel(dest, flags);
-        mPositiveButton.writeToParcel(dest, flags);
+    protected Bundle getDataBundle() {
+        Bundle b = super.getDataBundle();
+        b.putObject(KEY_NEGATIVE_BUTTON, mNegativeButton);
+        b.putObject(KEY_POSITIVE_BUTTON, mPositiveButton);
+        return b;
     }
 
     public static final Creator<DiscreteToggleTemplate> CREATOR =
             new Creator<DiscreteToggleTemplate>() {
                 @Override
                 public DiscreteToggleTemplate createFromParcel(Parcel source) {
-                    return new DiscreteToggleTemplate(source);
+                    return new DiscreteToggleTemplate(source.readBundle());
                 }
 
                 @Override
diff --git a/core/java/android/service/controls/FloatAction.java b/core/java/android/service/controls/FloatAction.java
index fe6db10..229435f 100644
--- a/core/java/android/service/controls/FloatAction.java
+++ b/core/java/android/service/controls/FloatAction.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Bundle;
 import android.os.Parcel;
 
 /**
@@ -26,6 +27,8 @@
  */
 public final class FloatAction extends ControlAction {
 
+    private static final String KEY_NEW_VALUE = "key_new_value";
+
     private final float mNewValue;
 
     /**
@@ -50,9 +53,9 @@
         mNewValue = newValue;
     }
 
-    public FloatAction(Parcel in) {
-        super(in);
-        mNewValue = in.readFloat();
+    public FloatAction(Bundle b) {
+        super(b);
+        mNewValue = b.getFloat(KEY_NEW_VALUE);
     }
 
     /**
@@ -71,15 +74,16 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeFloat(mNewValue);
+    protected Bundle getDataBundle() {
+        Bundle b = super.getDataBundle();
+        b.putFloat(KEY_NEW_VALUE, mNewValue);
+        return b;
     }
 
     public static final @NonNull Creator<FloatAction> CREATOR = new Creator<FloatAction>() {
         @Override
         public FloatAction createFromParcel(Parcel source) {
-            return new FloatAction(source);
+            return new FloatAction(source.readBundle());
         }
 
         @Override
diff --git a/core/java/android/service/controls/RangeTemplate.java b/core/java/android/service/controls/RangeTemplate.java
index 70bf2dd..f0bce30 100644
--- a/core/java/android/service/controls/RangeTemplate.java
+++ b/core/java/android/service/controls/RangeTemplate.java
@@ -18,10 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Bundle;
 import android.os.Parcel;
 
-import com.android.internal.util.Preconditions;
-
 import java.security.InvalidParameterException;
 
 /**
@@ -32,6 +31,12 @@
  */
 public final class RangeTemplate extends ControlTemplate {
 
+    private static final String KEY_MIN_VALUE = "key_min_value";
+    private static final String KEY_MAX_VALUE = "key_max_value";
+    private static final String KEY_CURRENT_VALUE = "key_current_value";
+    private static final String KEY_STEP_VALUE = "key_step_value";
+    private static final String KEY_FORMAT_STRING = "key_format_string";
+
     private final float mMinValue;
     private final float mMaxValue;
     private final float mCurrentValue;
@@ -67,7 +72,6 @@
             float stepValue,
             @Nullable CharSequence formatString) {
         super(templateId);
-        Preconditions.checkNotNull(formatString);
         mMinValue = minValue;
         mMaxValue = maxValue;
         mCurrentValue = currentValue;
@@ -87,13 +91,13 @@
      * @see RangeTemplate#RangeTemplate(String, float, float, float, float, CharSequence)
      * @hide
      */
-    RangeTemplate(Parcel in) {
-        super(in);
-        mMinValue = in.readFloat();
-        mMaxValue = in.readFloat();
-        mCurrentValue = in.readFloat();
-        mStepValue = in.readFloat();
-        mFormatString = in.readCharSequence();
+    RangeTemplate(Bundle b) {
+        super(b);
+        mMinValue = b.getFloat(KEY_MIN_VALUE);
+        mMaxValue = b.getFloat(KEY_MAX_VALUE);
+        mCurrentValue = b.getFloat(KEY_CURRENT_VALUE);
+        mStepValue = b.getFloat(KEY_STEP_VALUE);
+        mFormatString = b.getCharSequence(KEY_FORMAT_STRING, "%.1f");
         validate();
     }
 
@@ -144,13 +148,14 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeFloat(mMinValue);
-        dest.writeFloat(mMaxValue);
-        dest.writeFloat(mCurrentValue);
-        dest.writeFloat(mStepValue);
-        dest.writeCharSequence(mFormatString);
+    protected Bundle getDataBundle() {
+        Bundle b = super.getDataBundle();
+        b.putFloat(KEY_MIN_VALUE, mMinValue);
+        b.putFloat(KEY_MAX_VALUE, mMaxValue);
+        b.putFloat(KEY_CURRENT_VALUE, mCurrentValue);
+        b.putFloat(KEY_STEP_VALUE, mStepValue);
+        b.putCharSequence(KEY_FORMAT_STRING, mFormatString);
+        return b;
     }
 
     /**
@@ -179,7 +184,7 @@
     public static final Creator<RangeTemplate> CREATOR = new Creator<RangeTemplate>() {
         @Override
         public RangeTemplate createFromParcel(Parcel source) {
-            return new RangeTemplate(source);
+            return new RangeTemplate(source.readBundle());
         }
 
         @Override
diff --git a/core/java/android/service/controls/ThumbnailTemplate.java b/core/java/android/service/controls/ThumbnailTemplate.java
index 796d2de..6e729c0 100644
--- a/core/java/android/service/controls/ThumbnailTemplate.java
+++ b/core/java/android/service/controls/ThumbnailTemplate.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.graphics.drawable.Icon;
+import android.os.Bundle;
 import android.os.Parcel;
 
 import com.android.internal.util.Preconditions;
@@ -28,6 +29,9 @@
  */
 public final class ThumbnailTemplate extends ControlTemplate {
 
+    private static final String KEY_ICON = "key_icon";
+    private static final String KEY_CONTENT_DESCRIPTION = "key_content_description";
+
     private final @NonNull Icon mThumbnail;
     private final @NonNull CharSequence mContentDescription;
 
@@ -45,10 +49,10 @@
         mContentDescription = contentDescription;
     }
 
-    ThumbnailTemplate(Parcel in) {
-        super(in);
-        mThumbnail = Icon.CREATOR.createFromParcel(in);
-        mContentDescription = in.readCharSequence();
+    ThumbnailTemplate(Bundle b) {
+        super(b);
+        mThumbnail = b.getParcelable(KEY_ICON);
+        mContentDescription = b.getCharSequence(KEY_CONTENT_DESCRIPTION, "");
     }
 
     /**
@@ -74,17 +78,19 @@
     public int getTemplateType() {
         return TYPE_THUMBNAIL;
     }
+
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        mThumbnail.writeToParcel(dest, flags);
-        dest.writeCharSequence(mContentDescription);
+    protected Bundle getDataBundle() {
+        Bundle b = super.getDataBundle();
+        b.putObject(KEY_ICON, mThumbnail);
+        b.putObject(KEY_CONTENT_DESCRIPTION, mContentDescription);
+        return b;
     }
 
     public static final Creator<ThumbnailTemplate> CREATOR = new Creator<ThumbnailTemplate>() {
         @Override
         public ThumbnailTemplate createFromParcel(Parcel source) {
-            return new ThumbnailTemplate(source);
+            return new ThumbnailTemplate(source.readBundle());
         }
 
         @Override
diff --git a/core/java/android/service/controls/ToggleTemplate.java b/core/java/android/service/controls/ToggleTemplate.java
index 3766bd1..4c4fd5e 100644
--- a/core/java/android/service/controls/ToggleTemplate.java
+++ b/core/java/android/service/controls/ToggleTemplate.java
@@ -17,6 +17,7 @@
 package android.service.controls;
 
 import android.annotation.NonNull;
+import android.os.Bundle;
 import android.os.Parcel;
 
 import com.android.internal.util.Preconditions;
@@ -24,7 +25,7 @@
 /**
  * A template for a {@link Control} with a single button that can be toggled between two states.
  *
- * The states for the toggle correspond to the states in {@link ControlButton#isActive()}.
+ * The states for the toggle correspond to the states in {@link ControlButton#isChecked()}.
  * An action on this template will originate a {@link BooleanAction} to change that state.
  *
  * @see BooleanAction
@@ -32,6 +33,7 @@
  */
 public final class ToggleTemplate extends ControlTemplate {
 
+    private static final String KEY_BUTTON = "key_button";
     private final @NonNull ControlButton mButton;
 
     /**
@@ -44,9 +46,9 @@
         mButton = button;
     }
 
-    ToggleTemplate(Parcel in) {
-        super(in);
-        mButton = ControlButton.CREATOR.createFromParcel(in);
+    ToggleTemplate(Bundle b) {
+        super(b);
+        mButton = b.getParcelable(KEY_BUTTON);
     }
 
     /**
@@ -66,15 +68,16 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        mButton.writeToParcel(dest, flags);
+    protected Bundle getDataBundle() {
+        Bundle b =  super.getDataBundle();
+        b.putObject(KEY_BUTTON, mButton);
+        return b;
     }
 
     public static final Creator<ToggleTemplate> CREATOR = new Creator<ToggleTemplate>() {
         @Override
         public ToggleTemplate createFromParcel(Parcel source) {
-            return new ToggleTemplate(source);
+            return new ToggleTemplate(source.readBundle());
         }
 
         @Override
diff --git a/core/java/android/service/sms/FinancialSmsService.java b/core/java/android/service/sms/FinancialSmsService.java
deleted file mode 100644
index 5fb7249..0000000
--- a/core/java/android/service/sms/FinancialSmsService.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.service.sms;
-
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.app.Service;
-import android.content.Intent;
-import android.database.CursorWindow;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-
-/**
- * A service to support sms messages read for financial apps.
- *
- * {@hide}
- */
-@SystemApi
-public abstract class FinancialSmsService extends Service {
-
-    private static final String TAG = "FinancialSmsService";
-
-    /**
-     * The {@link Intent} action that must be declared as handled by a service
-     * in its manifest for the system to recognize it as a quota providing
-     * service.
-     */
-    public static final String ACTION_FINANCIAL_SERVICE_INTENT =
-            "android.service.sms.action.FINANCIAL_SERVICE_INTENT";
-
-    /** {@hide} **/
-    public static final String EXTRA_SMS_MSGS = "sms_messages";
-
-    private FinancialSmsServiceWrapper mWrapper;
-
-    private void getSmsMessages(RemoteCallback callback, Bundle params) {
-        final Bundle data = new Bundle();
-        CursorWindow smsMessages = onGetSmsMessages(params);
-        if (smsMessages != null) {
-            data.putParcelable(EXTRA_SMS_MSGS, smsMessages);
-        }
-        callback.sendResult(data);
-    }
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
-
-    /** @hide */
-    public FinancialSmsService() {
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mWrapper = new FinancialSmsServiceWrapper();
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mWrapper;
-    }
-
-    /**
-     * Get sms messages for financial apps.
-     *
-     * @param params parameters passed in by the calling app.
-     * @return the {@code CursorWindow} with all sms messages for the app to read.
-     *
-     * {@hide}
-     */
-    @Nullable
-    @SystemApi
-    public abstract CursorWindow onGetSmsMessages(@NonNull Bundle params);
-
-    private final class FinancialSmsServiceWrapper extends IFinancialSmsService.Stub {
-        @Override
-        public void getSmsMessages(RemoteCallback callback, Bundle params) throws RemoteException {
-            mHandler.sendMessage(obtainMessage(
-                    FinancialSmsService::getSmsMessages,
-                    FinancialSmsService.this,
-                    callback, params));
-        }
-    }
-
-}
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index b00a938..0c27923 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -59,8 +59,12 @@
     public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
     public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
     /**
-     * This constant is actually the length of 364 days, not of a year!
+     * @deprecated Not all years have the same number of days, and this constant is actually the
+     * length of 364 days. Please use other date/time constructs such as
+     * {@link java.util.concurrent.TimeUnit}, {@link java.util.Calendar} or
+     * {@link java.time.Duration} instead.
      */
+    @Deprecated
     public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52;
 
     // The following FORMAT_* symbols are used for specifying the format of
diff --git a/core/java/android/util/CloseGuard.java b/core/java/android/util/CloseGuard.java
new file mode 100644
index 0000000..c39a6c9
--- /dev/null
+++ b/core/java/android/util/CloseGuard.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.NonNull;
+
+/**
+ * CloseGuard is a mechanism for flagging implicit finalizer cleanup of
+ * resources that should have been cleaned up by explicit close
+ * methods (aka "explicit termination methods" in Effective Java).
+ * <p>
+ * A simple example: <pre>   {@code
+ *   class Foo {
+ *
+ *       private final CloseGuard guard = CloseGuard.get();
+ *
+ *       ...
+ *
+ *       public Foo() {
+ *           ...;
+ *           guard.open("cleanup");
+ *       }
+ *
+ *       public void cleanup() {
+ *          guard.close();
+ *          ...;
+ *       }
+ *
+ *       protected void finalize() throws Throwable {
+ *           try {
+ *               // Note that guard could be null if the constructor threw.
+ *               if (guard != null) {
+ *                   guard.warnIfOpen();
+ *               }
+ *               cleanup();
+ *           } finally {
+ *               super.finalize();
+ *           }
+ *       }
+ *   }
+ * }</pre>
+ *
+ * In usage where the resource to be explicitly cleaned up is
+ * allocated after object construction, CloseGuard protection can
+ * be deferred. For example: <pre>   {@code
+ *   class Bar {
+ *
+ *       private final CloseGuard guard = CloseGuard.get();
+ *
+ *       ...
+ *
+ *       public Bar() {
+ *           ...;
+ *       }
+ *
+ *       public void connect() {
+ *          ...;
+ *          guard.open("cleanup");
+ *       }
+ *
+ *       public void cleanup() {
+ *          guard.close();
+ *          ...;
+ *          Reference.reachabilityFence(this);
+ *          // For full correctness in the absence of a close() call, other methods may also need
+ *          // reachabilityFence() calls.
+ *       }
+ *
+ *       protected void finalize() throws Throwable {
+ *           try {
+ *               // Note that guard could be null if the constructor threw.
+ *               if (guard != null) {
+ *                   guard.warnIfOpen();
+ *               }
+ *               cleanup();
+ *           } finally {
+ *               super.finalize();
+ *           }
+ *       }
+ *   }
+ * }</pre>
+ *
+ * When used in a constructor, calls to {@code open} should occur at
+ * the end of the constructor since an exception that would cause
+ * abrupt termination of the constructor will mean that the user will
+ * not have a reference to the object to cleanup explicitly. When used
+ * in a method, the call to {@code open} should occur just after
+ * resource acquisition.
+ */
+public final class CloseGuard {
+    private final dalvik.system.CloseGuard mImpl;
+
+    /**
+     * Constructs a new CloseGuard instance.
+     * {@link #open(String)} can be used to set up the instance to warn on failure to close.
+     */
+    public CloseGuard() {
+        mImpl = dalvik.system.CloseGuard.get();
+    }
+
+    /**
+     * Initializes the instance with a warning that the caller should have explicitly called the
+     * {@code closeMethodName} method instead of relying on finalization.
+     *
+     * @param closeMethodName non-null name of explicit termination method. Printed by warnIfOpen.
+     * @throws NullPointerException if closeMethodName is null.
+     */
+    public void open(@NonNull String closeMethodName) {
+        mImpl.open(closeMethodName);
+    }
+
+    /** Marks this CloseGuard instance as closed to avoid warnings on finalization. */
+    public void close() {
+        mImpl.close();
+    }
+
+    /**
+     * Logs a warning if the caller did not properly cleanup by calling an explicit close method
+     * before finalization.
+     */
+    public void warnIfOpen() {
+        mImpl.warnIfOpen();
+    }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 236e5ae..1b2db36 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -59,6 +59,7 @@
         DEFAULT_FLAGS.put(SETTINGS_WIFITRACKER2, "false");
         DEFAULT_FLAGS.put("settings_work_profile", "true");
         DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false");
+        DEFAULT_FLAGS.put("settings_conditionals", "false");
     }
 
     /**
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
index 9ac4cf2..952d7cb 100644
--- a/core/java/android/util/StatsLog.java
+++ b/core/java/android/util/StatsLog.java
@@ -24,7 +24,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
-import android.os.IStatsManager;
+import android.os.IStatsd;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
@@ -36,7 +36,7 @@
     private static final String TAG = "StatsLog";
     private static final boolean DEBUG = false;
 
-    private static IStatsManager sService;
+    private static IStatsd sService;
 
     private static Object sLogLock = new Object();
 
@@ -52,7 +52,7 @@
     public static boolean logStart(int label) {
         synchronized (sLogLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (service == null) {
                     if (DEBUG) {
                         Slog.d(TAG, "Failed to find statsd when logging start");
@@ -81,7 +81,7 @@
     public static boolean logStop(int label) {
         synchronized (sLogLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (service == null) {
                     if (DEBUG) {
                         Slog.d(TAG, "Failed to find statsd when logging stop");
@@ -109,7 +109,7 @@
     public static boolean logEvent(int label) {
         synchronized (sLogLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (service == null) {
                     if (DEBUG) {
                         Slog.d(TAG, "Failed to find statsd when logging event");
@@ -151,7 +151,7 @@
             @NonNull long[] experimentIds) {
         synchronized (sLogLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (service == null) {
                     if (DEBUG) {
                         Slog.d(TAG, "Failed to find statsd when logging event");
@@ -191,7 +191,7 @@
             long packageVersionCode, int rollbackReason, String failingPackageName) {
         synchronized (sLogLock) {
             try {
-                IStatsManager service = getIStatsManagerLocked();
+                IStatsd service = getIStatsdLocked();
                 if (service == null) {
                     if (DEBUG) {
                         Slog.d(TAG, "Failed to find statsd when logging event");
@@ -215,11 +215,11 @@
     }
 
 
-    private static IStatsManager getIStatsManagerLocked() throws RemoteException {
+    private static IStatsd getIStatsdLocked() throws RemoteException {
         if (sService != null) {
             return sService;
         }
-        sService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+        sService = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
         return sService;
     }
 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b829c9f..9496827 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -44,6 +44,7 @@
 import android.view.IRotationWatcher;
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWallpaperVisibilityListener;
+import android.view.IWindow;
 import android.view.IWindowSession;
 import android.view.IWindowSessionCallback;
 import android.view.KeyEvent;
@@ -120,6 +121,17 @@
     void setDisplayWindowRotationController(IDisplayWindowRotationController controller);
 
     /**
+     * Adds a root container that a client shell can populate with its own windows (usually via
+     * WindowlessWindowManager).
+     *
+     * @param client an IWindow used for window-level communication (ime, finish draw, etc.).
+     * @param windowType used by WM to determine the z-order. This is the same as the window type
+     *                   used in {@link WindowManager.LayoutParams}.
+     * @return a SurfaceControl to add things to.
+     */
+    SurfaceControl addShellRoot(int displayId, IWindow client, int windowType);
+
+    /**
      * Like overridePendingAppTransitionMultiThumb, but uses a future to supply the specs. This is
      * used for recents, where generating the thumbnails of the specs takes a non-trivial amount of
      * time, so we want to move that off the critical path for starting the new activity.
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index dc8bf9b..9ab2c2b 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -69,6 +69,8 @@
     private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
     private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
 
+    private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+
     private boolean mIsAllWindowsCached;
 
     // The SparseArray of all {@link AccessibilityWindowInfo}s on all displays.
@@ -164,16 +166,19 @@
             switch (eventType) {
                 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
                     if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
-                        refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+                        refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
                     }
                     mAccessibilityFocus = event.getSourceNodeId();
-                    refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+                    mAccessibilityFocusedWindow = event.getWindowId();
+                    refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
                 } break;
 
                 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
-                    if (mAccessibilityFocus == event.getSourceNodeId()) {
-                        refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+                    if (mAccessibilityFocus == event.getSourceNodeId()
+                            && mAccessibilityFocusedWindow == event.getWindowId()) {
+                        refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
                         mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+                        mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
                     }
                 } break;
 
@@ -210,6 +215,13 @@
                 } break;
 
                 case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+                    if (event.getWindowChanges()
+                            == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
+                        // Don't need to clear all cache. Unless the changes are related to
+                        // content, we won't clear all cache here.
+                        refreshCachedWindowLocked(event.getWindowId());
+                        break;
+                    }
                 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
                     clear();
                 } break;
@@ -243,6 +255,34 @@
         clearSubTreeLocked(windowId, sourceId);
     }
 
+    private void refreshCachedWindowLocked(int windowId) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "Refreshing cached window.");
+        }
+
+        if (windowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+            return;
+        }
+
+        final int displayCounts = mWindowCacheByDisplay.size();
+        for (int i = 0; i < displayCounts; i++) {
+            final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                    mWindowCacheByDisplay.valueAt(i);
+            if (windowsOfDisplay == null) {
+                continue;
+            }
+            final AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
+            if (window == null) {
+                continue;
+            }
+            if (!mAccessibilityNodeRefresher.refreshWindow(window)) {
+                // If we fail to refresh the window, clear all windows.
+                clearWindowCacheLocked();
+            }
+            return;
+        }
+    }
+
     /**
      * Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting
      * window and the accessibility id of the node.
@@ -413,8 +453,10 @@
                     refreshCachedNodeLocked(windowId, mAccessibilityFocus);
                 }
                 mAccessibilityFocus = sourceId;
+                mAccessibilityFocusedWindow = windowId;
             } else if (mAccessibilityFocus == sourceId) {
                 mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+                mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
             }
             if (clone.isFocused()) {
                 mInputFocus = sourceId;
@@ -439,6 +481,8 @@
 
             mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
             mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+
+            mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
         }
     }
 
@@ -653,8 +697,14 @@
 
     // Layer of indirection included to break dependency chain for testing
     public static class AccessibilityNodeRefresher {
+        /** Refresh the given AccessibilityNodeInfo object. */
         public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) {
             return info.refresh(null, bypassCache);
         }
+
+        /** Refresh the given AccessibilityWindowInfo object. */
+        public boolean refreshWindow(AccessibilityWindowInfo info) {
+            return info.refresh();
+        }
     }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index bb10ef1..3866517 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -223,19 +223,36 @@
      * @return The {@link AccessibilityWindowInfo}.
      */
     public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) {
+        return getWindow(connectionId, accessibilityWindowId, /* bypassCache */ false);
+    }
+
+    /**
+     * Gets the info for a window.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @param accessibilityWindowId A unique window id. Use
+     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+     *     to query the currently active window.
+     * @param bypassCache Whether to bypass the cache.
+     * @return The {@link AccessibilityWindowInfo}.
+     */
+    public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId,
+            boolean bypassCache) {
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
-                AccessibilityWindowInfo window = sAccessibilityCache.getWindow(
-                        accessibilityWindowId);
-                if (window != null) {
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "Window cache hit");
+                AccessibilityWindowInfo window;
+                if (!bypassCache) {
+                    window = sAccessibilityCache.getWindow(accessibilityWindowId);
+                    if (window != null) {
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "Window cache hit");
+                        }
+                        return window;
                     }
-                    return window;
-                }
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "Window cache miss");
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "Window cache miss");
+                    }
                 }
                 final long identityToken = Binder.clearCallingIdentity();
                 try {
@@ -244,7 +261,9 @@
                     Binder.restoreCallingIdentity(identityToken);
                 }
                 if (window != null) {
-                    sAccessibilityCache.addWindow(window);
+                    if (!bypassCache) {
+                        sAccessibilityCache.addWindow(window);
+                    }
                     return window;
                 }
             } else {
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 2cc6e9a..ca5c417 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -87,6 +87,8 @@
     /** @hide */
     public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE;
     /** @hide */
+    public static final int UNDEFINED_CONNECTION_ID = -1;
+    /** @hide */
     public static final int UNDEFINED_WINDOW_ID = -1;
     /** @hide */
     public static final int ANY_WINDOW_ID = -2;
@@ -117,7 +119,7 @@
     private CharSequence mTitle;
     private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
 
-    private int mConnectionId = UNDEFINED_WINDOW_ID;
+    private int mConnectionId = UNDEFINED_CONNECTION_ID;
 
     /**
      * Creates a new {@link AccessibilityWindowInfo}.
@@ -539,6 +541,30 @@
         }
     }
 
+    /**
+     * Refreshes this window with the latest state of the window it represents.
+     * <p>
+     * <strong>Note:</strong> If this method returns false this info is obsolete
+     * since it represents a window that is no longer exist.
+     * </p>
+     *
+     * @hide
+     */
+    public boolean refresh() {
+        if (mConnectionId == UNDEFINED_CONNECTION_ID || mId == UNDEFINED_WINDOW_ID) {
+            return false;
+        }
+        final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        final AccessibilityWindowInfo refreshedInfo = client.getWindow(mConnectionId,
+                mId, /* bypassCache */true);
+        if (refreshedInfo == null) {
+            return false;
+        }
+        init(refreshedInfo);
+        refreshedInfo.recycle();
+        return true;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -586,6 +612,7 @@
         mTitle = other.mTitle;
         mAnchorId = other.mAnchorId;
 
+        if (mChildIds != null) mChildIds.clear();
         if (other.mChildIds != null && other.mChildIds.size() > 0) {
             if (mChildIds == null) {
                 mChildIds = other.mChildIds.clone();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7cec440..c571737 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -3473,10 +3473,18 @@
         return applyAsync(context, parent, executor, listener, null);
     }
 
+    private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) {
+        CancellationSignal cancelSignal = new CancellationSignal();
+        cancelSignal.setOnCancelListener(task);
+
+        task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
+        return cancelSignal;
+    }
+
     /** @hide */
     public CancellationSignal applyAsync(Context context, ViewGroup parent,
             Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
-        return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor);
+        return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor);
     }
 
     private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
@@ -3487,7 +3495,6 @@
 
     private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
             implements CancellationSignal.OnCancelListener {
-        final CancellationSignal mCancelSignal = new CancellationSignal();
         final RemoteViews mRV;
         final ViewGroup mParent;
         final Context mContext;
@@ -3538,7 +3545,6 @@
 
         @Override
         protected void onPostExecute(ViewTree viewTree) {
-            mCancelSignal.setOnCancelListener(null);
             if (mError == null) {
                 if (mListener != null) {
                     mListener.onViewInflated(viewTree.mRoot);
@@ -3575,13 +3581,6 @@
         @Override
         public void onCancel() {
             cancel(true);
-            mCancelSignal.setOnCancelListener(null);
-        }
-
-        private CancellationSignal startTaskOnExecutor(Executor executor) {
-            mCancelSignal.setOnCancelListener(this);
-            executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
-            return mCancelSignal;
         }
     }
 
@@ -3647,8 +3646,8 @@
             }
         }
 
-        return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
-                context, listener, handler, v).startTaskOnExecutor(executor);
+        return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
+                context, listener, handler, v), executor);
     }
 
     private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 2fd5bfd..c64b705 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -15,6 +15,7 @@
  */
 package com.android.internal.app;
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.UserHandle;
 import android.view.View;
@@ -60,7 +61,7 @@
             @Override
             public void onPageSelected(int position) {
                 mCurrentPage = position;
-                getCurrentListAdapter().rebuildList();
+                getActiveListAdapter().rebuildList();
             }
         });
         viewPager.setAdapter(this);
@@ -89,7 +90,7 @@
     }
 
     UserHandle getCurrentUserHandle() {
-        return getCurrentListAdapter().mResolverListController.getUserHandle();
+        return getActiveListAdapter().mResolverListController.getUserHandle();
     }
 
     @Override
@@ -136,7 +137,17 @@
     abstract Object getAdapterForIndex(int pageIndex);
 
     @VisibleForTesting
-    public abstract ResolverListAdapter getCurrentListAdapter();
+    public abstract ResolverListAdapter getActiveListAdapter();
+
+    /**
+     * If this is a device with a work profile, returns the {@link ResolverListAdapter} instance
+     * of the profile that is not the active one. Otherwise returns {@code null}. For example,
+     * if the share sheet is launched in the work profile, this method returns the personal
+     * profile {@link ResolverListAdapter}.
+     * @see #getActiveListAdapter()
+     */
+    @VisibleForTesting
+    public abstract @Nullable ResolverListAdapter getInactiveListAdapter();
 
     abstract Object getCurrentRootAdapter();
 
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index bb7e4d5..eb74612 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -263,6 +263,7 @@
         mHandler.removeMessages(RANKER_SERVICE_RESULT);
         mHandler.removeMessages(RANKER_RESULT_TIMEOUT);
         afterCompute();
+        mAfterCompute = null;
     }
 
     private boolean isDefaultBrowser(ResolveInfo ri) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 1af3926..8856f99 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -362,9 +362,7 @@
                 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
                         + " within " + mImageLoadTimeoutMillis + "ms.");
                 collapseParentView();
-                if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
-                    mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().hideContentPreview();
-                }
+                hideContentPreview();
                 mHideParentOnFail = false;
             }
         }
@@ -431,14 +429,14 @@
                 logDirectShareTargetReceived(
                         MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
                 sendVoiceChoicesIfNeeded();
-                mChooserMultiProfilePagerAdapter.getCurrentListAdapter()
+                mChooserMultiProfilePagerAdapter.getActiveListAdapter()
                         .completeServiceTargetLoading();
             }
         }
 
         @Override
         public void handleMessage(Message msg) {
-            if (mChooserMultiProfilePagerAdapter.getCurrentListAdapter() == null || isDestroyed()) {
+            if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null || isDestroyed()) {
                 return;
             }
 
@@ -455,7 +453,7 @@
                     if (sri.resultTargets != null) {
                         // TODO(arangelov): Instead of using getCurrentListAdapter(), pass the
                         // profileId as part of the message.
-                        mChooserMultiProfilePagerAdapter.getCurrentListAdapter().addServiceResults(
+                        mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
                                 sri.originalTarget, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET);
                     }
                     unbindService(sri.connection);
@@ -479,14 +477,14 @@
                         Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
                     }
 
-                    mChooserMultiProfilePagerAdapter.getCurrentListAdapter().refreshListView();
+                    mChooserMultiProfilePagerAdapter.getActiveListAdapter().refreshListView();
                     break;
 
                 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
                     if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
                     final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
                     if (resultInfo.resultTargets != null) {
-                        mChooserMultiProfilePagerAdapter.getCurrentListAdapter().addServiceResults(
+                        mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
                                 resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1);
                     }
                     break;
@@ -646,7 +644,7 @@
                 if (isFinishing() || isDestroyed()) {
                     return;
                 }
-                if (mChooserMultiProfilePagerAdapter.getCurrentListAdapter().getCount() == 0) {
+                if (mChooserMultiProfilePagerAdapter.getActiveListAdapter().getCount() == 0) {
                     return;
                 }
                 if (resultList.isEmpty()) {
@@ -654,12 +652,12 @@
                     //TODO(arangelov) queryDirectShareTargets indirectly uses mIntents.
                     // Investigate implications for work tab.
                     queryDirectShareTargets(
-                            mChooserMultiProfilePagerAdapter.getCurrentListAdapter(), true);
+                            mChooserMultiProfilePagerAdapter.getActiveListAdapter(), true);
                     return;
                 }
                 final List<DisplayResolveInfo> driList =
                         getDisplayResolveInfos(
-                                mChooserMultiProfilePagerAdapter.getCurrentListAdapter());
+                                mChooserMultiProfilePagerAdapter.getActiveListAdapter());
                 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
                         new ArrayList<>();
                 for (AppTarget appTarget : resultList) {
@@ -809,7 +807,7 @@
 
     @Override
     protected boolean postRebuildList(boolean rebuildCompleted) {
-        mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().maybeLogActionShareWithPreview();
+        updateContentPreview();
         return postRebuildListInternal(rebuildCompleted);
     }
 
@@ -862,7 +860,7 @@
             public void onSomePackagesChanged() {
                 // TODO(arangelov): Dispatch this to all adapters when we have the helper methods
                 // in a follow-up CL
-                mChooserMultiProfilePagerAdapter.getCurrentListAdapter().handlePackagesChanged();
+                mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
                 updateProfileViewButton();
             }
         };
@@ -952,6 +950,12 @@
         }
     }
 
+    private ViewGroup createContentPreviewView(ViewGroup parent) {
+        Intent targetIntent = getTargetIntent();
+        int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
+        return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
+    }
+
     private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
             Intent targetIntent, LayoutInflater layoutInflater, ViewGroup parent) {
         ViewGroup layout = null;
@@ -1319,7 +1323,7 @@
     public void onPrepareAdapterView(ResolverListAdapter adapter) {
         mChooserMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE);
         if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
-            mChooserMultiProfilePagerAdapter.getCurrentListAdapter().addServiceResults(
+            mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
                     /* origTarget */ null,
                     Lists.newArrayList(mCallerChooserTargets),
                     TARGET_TYPE_DEFAULT);
@@ -1404,7 +1408,7 @@
     @Override
     public void startSelected(int which, boolean always, boolean filtered) {
         ChooserListAdapter currentListAdapter =
-                mChooserMultiProfilePagerAdapter.getCurrentListAdapter();
+                mChooserMultiProfilePagerAdapter.getActiveListAdapter();
         TargetInfo targetInfo = currentListAdapter
                 .targetInfoForPosition(which, filtered);
         if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
@@ -1487,7 +1491,7 @@
         String targetPackageName =
                 targetInfo.getChooserTarget().getComponentName().getPackageName();
         ChooserListAdapter currentListAdapter =
-                mChooserMultiProfilePagerAdapter.getCurrentListAdapter();
+                mChooserMultiProfilePagerAdapter.getActiveListAdapter();
         int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(),
                 MAX_LOG_RANK_POSITION);
 
@@ -1841,7 +1845,7 @@
             Intent targetIntent = getTargetIntent();
             if (ri != null && ri.activityInfo != null && targetIntent != null) {
                 ChooserListAdapter currentListAdapter =
-                        mChooserMultiProfilePagerAdapter.getCurrentListAdapter();
+                        mChooserMultiProfilePagerAdapter.getActiveListAdapter();
                 if (currentListAdapter != null) {
                     currentListAdapter.updateModel(info.getResolvedComponentName());
                     currentListAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
@@ -2146,8 +2150,7 @@
                 final int bottomInset = mSystemWindowInsets != null
                                             ? mSystemWindowInsets.bottom : 0;
                 int offset = bottomInset;
-                int rowsToShow = gridAdapter.getContentPreviewRowCount()
-                        + gridAdapter.getProfileRowCount()
+                int rowsToShow = gridAdapter.getProfileRowCount()
                         + gridAdapter.getServiceTargetRowCount()
                         + gridAdapter.getCallerAndRankedTargetRowCount();
 
@@ -2159,13 +2162,17 @@
 
                 // still zero? then use a default height and leave, which
                 // can happen when there are no targets to show
-                if (rowsToShow == 0) {
+                if (rowsToShow == 0 && !shouldShowContentPreview()) {
                     offset += getResources().getDimensionPixelSize(
                             R.dimen.chooser_max_collapsed_height);
                     mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
                     return;
                 }
 
+                if (shouldShowContentPreview()) {
+                    offset += findViewById(R.id.content_preview_container).getHeight();
+                }
+
                 int directShareHeight = 0;
                 rowsToShow = Math.min(4, rowsToShow);
                 for (int i = 0, childCount = recyclerView.getChildCount();
@@ -2216,13 +2223,13 @@
     @Override // ResolverListCommunicator
     public void onHandlePackagesChanged() {
         mServicesRequested.clear();
-        mChooserMultiProfilePagerAdapter.getCurrentListAdapter().notifyDataSetChanged();
+        mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
         super.onHandlePackagesChanged();
     }
 
     @Override // SelectableTargetInfoCommunicator
     public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
-        return mChooserMultiProfilePagerAdapter.getCurrentListAdapter().makePresentationGetter(info);
+        return mChooserMultiProfilePagerAdapter.getActiveListAdapter().makePresentationGetter(info);
     }
 
     @Override // SelectableTargetInfoCommunicator
@@ -2244,22 +2251,21 @@
     }
 
     @Override
-    public void onListRebuilt() {
-        final ChooserListAdapter currentListAdapter =
-                mChooserMultiProfilePagerAdapter.getCurrentListAdapter();
-        if (currentListAdapter.mDisplayList == null
-                || currentListAdapter.mDisplayList.isEmpty()) {
-            currentListAdapter.notifyDataSetChanged();
+    public void onListRebuilt(ResolverListAdapter listAdapter) {
+        ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
+        if (chooserListAdapter.mDisplayList == null
+                || chooserListAdapter.mDisplayList.isEmpty()) {
+            chooserListAdapter.notifyDataSetChanged();
         } else {
             new AsyncTask<Void, Void, Void>() {
                 @Override
                 protected Void doInBackground(Void... voids) {
-                    currentListAdapter.updateAlphabeticalList();
+                    chooserListAdapter.updateAlphabeticalList();
                     return null;
                 }
                 @Override
                 protected void onPostExecute(Void aVoid) {
-                    currentListAdapter.notifyDataSetChanged();
+                    chooserListAdapter.notifyDataSetChanged();
                 }
             }.execute();
         }
@@ -2275,14 +2281,14 @@
                 Log.d(TAG, "querying direct share targets from ShortcutManager");
             }
 
-            queryDirectShareTargets(currentListAdapter, false);
+            queryDirectShareTargets(chooserListAdapter, false);
         }
         if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
             if (DEBUG) {
                 Log.d(TAG, "List built querying services");
             }
 
-            queryTargetServices(currentListAdapter);
+            queryTargetServices(chooserListAdapter);
         }
     }
 
@@ -2304,10 +2310,43 @@
         return false;
     }
 
+    private boolean shouldShowContentPreview() {
+        return mMultiProfilePagerAdapter.getActiveListAdapter().getCount() > 0
+                && isSendAction(getTargetIntent());
+    }
+
+    private void updateContentPreview() {
+        if (shouldShowContentPreview()) {
+            showContentPreview();
+        } else {
+            hideContentPreview();
+        }
+    }
+
+    private void showContentPreview() {
+        ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
+        contentPreviewContainer.setVisibility(View.VISIBLE);
+        ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
+        contentPreviewContainer.addView(contentPreviewView);
+        logActionShareWithPreview();
+    }
+
+    private void hideContentPreview() {
+        ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
+        contentPreviewContainer.removeAllViews();
+        contentPreviewContainer.setVisibility(View.GONE);
+    }
+
+    private void logActionShareWithPreview() {
+        Intent targetIntent = getTargetIntent();
+        int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
+        getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
+                .setSubtype(previewType));
+    }
+
     /**
      * Used to bind types of individual item including
      * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
-     * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
      * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
      * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
      */
@@ -2323,7 +2362,7 @@
                         false/* always */, true/* filterd */));
                 itemView.setOnLongClickListener(v -> {
                     showTargetDetails(
-                            mChooserMultiProfilePagerAdapter.getCurrentListAdapter()
+                            mChooserMultiProfilePagerAdapter.getActiveListAdapter()
                                     .resolveInfoForPosition(mListPosition, /* filtered */ true));
                     return true;
                 });
@@ -2368,15 +2407,13 @@
         private int mChooserTargetWidth = 0;
         private boolean mShowAzLabelIfPoss;
 
-        private boolean mHideContentPreview = false;
         private boolean mLayoutRequested = false;
 
         private static final int VIEW_TYPE_DIRECT_SHARE = 0;
         private static final int VIEW_TYPE_NORMAL = 1;
-        private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
-        private static final int VIEW_TYPE_PROFILE = 3;
-        private static final int VIEW_TYPE_AZ_LABEL = 4;
-        private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
+        private static final int VIEW_TYPE_PROFILE = 2;
+        private static final int VIEW_TYPE_AZ_LABEL = 3;
+        private static final int VIEW_TYPE_CALLER_AND_RANK = 4;
 
         private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
         private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
@@ -2433,12 +2470,6 @@
             return maxTargets;
         }
 
-        public void hideContentPreview() {
-            mHideContentPreview = true;
-            mLayoutRequested = true;
-            notifyDataSetChanged();
-        }
-
         public boolean consumeLayoutRequest() {
             boolean oldValue = mLayoutRequested;
             mLayoutRequested = false;
@@ -2447,8 +2478,7 @@
 
         public int getRowCount() {
             return (int) (
-                    getContentPreviewRowCount()
-                            + getProfileRowCount()
+                    getProfileRowCount()
                             + getServiceTargetRowCount()
                             + getCallerAndRankedTargetRowCount()
                             + getAzLabelRowCount()
@@ -2458,19 +2488,6 @@
             );
         }
 
-        public int getContentPreviewRowCount() {
-            if (!isSendAction(getTargetIntent())) {
-                return 0;
-            }
-
-            if (mHideContentPreview || mChooserListAdapter == null
-                    || mChooserListAdapter.getCount() == 0) {
-                return 0;
-            }
-
-            return 1;
-        }
-
         public int getProfileRowCount() {
             return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
         }
@@ -2499,8 +2516,7 @@
         @Override
         public int getItemCount() {
             return (int) (
-                    getContentPreviewRowCount()
-                            + getProfileRowCount()
+                    getProfileRowCount()
                             + getServiceTargetRowCount()
                             + getCallerAndRankedTargetRowCount()
                             + getAzLabelRowCount()
@@ -2511,8 +2527,6 @@
         @Override
         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
             switch (viewType) {
-                case VIEW_TYPE_CONTENT_PREVIEW:
-                    return new ItemViewHolder(createContentPreviewView(parent), false);
                 case VIEW_TYPE_PROFILE:
                     return new ItemViewHolder(createProfileView(parent), false);
                 case VIEW_TYPE_AZ_LABEL:
@@ -2547,10 +2561,7 @@
         public int getItemViewType(int position) {
             int count;
 
-            int countSum = (count = getContentPreviewRowCount());
-            if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
-
-            countSum += (count = getProfileRowCount());
+            int countSum = (count = getProfileRowCount());
             if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
 
             countSum += (count = getServiceTargetRowCount());
@@ -2569,12 +2580,6 @@
             return mChooserListAdapter.getPositionTargetType(getListPosition(position));
         }
 
-        private ViewGroup createContentPreviewView(ViewGroup parent) {
-            Intent targetIntent = getTargetIntent();
-            int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
-            return displayContentPreview(previewType, targetIntent, mLayoutInflater, parent);
-        }
-
         private View createProfileView(ViewGroup parent) {
             View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
             profileRow.setBackground(
@@ -2770,7 +2775,7 @@
         }
 
         int getListPosition(int position) {
-            position -= getContentPreviewRowCount() + getProfileRowCount();
+            position -= getProfileRowCount();
 
             final int serviceCount = mChooserListAdapter.getServiceTargetCount();
             final int serviceRows = (int) Math.ceil((float) serviceCount
@@ -2814,16 +2819,6 @@
             return mChooserListAdapter;
         }
 
-        void maybeLogActionShareWithPreview() {
-            if (getContentPreviewRowCount() == 0) {
-                return;
-            }
-            Intent targetIntent = getTargetIntent();
-            int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
-            getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
-                    .setSubtype(previewType));
-        }
-
         boolean shouldCellSpan(int position) {
             return getItemViewType(position) == VIEW_TYPE_NORMAL;
         }
@@ -3010,7 +3005,7 @@
 
                 // only expand if we have more than maxTargetsPerRow, and delay that decision
                 // until they start to scroll
-                if (mChooserMultiProfilePagerAdapter.getCurrentListAdapter()
+                if (mChooserMultiProfilePagerAdapter.getActiveListAdapter()
                         .getSelectableServiceTargetCount() <= maxTargetsPerRow) {
                     mHideDirectShareExpansion = true;
                     return;
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index aa8ab28..7d856e1 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -102,11 +102,20 @@
 
     @Override
     @VisibleForTesting
-    public ChooserListAdapter getCurrentListAdapter() {
+    public ChooserListAdapter getActiveListAdapter() {
         return getAdapterForIndex(getCurrentPage()).getListAdapter();
     }
 
     @Override
+    @VisibleForTesting
+    public ChooserListAdapter getInactiveListAdapter() {
+        if (getCount() == 1) {
+            return null;
+        }
+        return getAdapterForIndex(1 - getCurrentPage()).getListAdapter();
+    }
+
+    @Override
     ChooserActivity.ChooserGridAdapter getCurrentRootAdapter() {
         return getAdapterForIndex(getCurrentPage());
     }
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index ea24d5f..d94294f 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.ModelParams;
 import android.os.Bundle;
 import android.os.ParcelUuid;
 
@@ -56,4 +57,16 @@
     int getModelState(in ParcelUuid soundModelId);
 
     @nullable SoundTrigger.ModuleProperties getModuleProperties();
+
+    int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam,
+        int value);
+
+    /**
+     * @throws UnsupportedOperationException if hal or model do not support this API.
+     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+     */
+    int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam);
+
+    @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId,
+        in ModelParams modelParam);
 }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 9cf5e9f..cb7f2e4 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -36,7 +36,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.PermissionChecker;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -240,7 +239,7 @@
         return new PackageMonitor() {
             @Override
             public void onSomePackagesChanged() {
-                mMultiProfilePagerAdapter.getCurrentListAdapter().handlePackagesChanged();
+                mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
                 updateProfileViewButton();
             }
 
@@ -372,7 +371,7 @@
         }
 
         final Set<String> categories = intent.getCategories();
-        MetricsLogger.action(this, mMultiProfilePagerAdapter.getCurrentListAdapter().hasFilteredItem()
+        MetricsLogger.action(this, mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
                 ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
                 : MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
                 intent.getAction() + ":" + intent.getType() + ":"
@@ -461,7 +460,7 @@
 
     protected void onProfileClick(View v) {
         final DisplayResolveInfo dri =
-                mMultiProfilePagerAdapter.getCurrentListAdapter().getOtherProfile();
+                mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
         if (dri == null) {
             return;
         }
@@ -508,7 +507,7 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        mMultiProfilePagerAdapter.getCurrentListAdapter().handlePackagesChanged();
+        mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
 
         if (mSystemWindowInsets != null) {
             mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
@@ -523,10 +522,10 @@
             return;
         }
 
-        int count = mMultiProfilePagerAdapter.getCurrentListAdapter().getCount();
+        int count = mMultiProfilePagerAdapter.getActiveListAdapter().getCount();
         final Option[] options = new Option[count];
         for (int i = 0, N = options.length; i < N; i++) {
-            TargetInfo target = mMultiProfilePagerAdapter.getCurrentListAdapter().getItem(i);
+            TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter().getItem(i);
             if (target == null) {
                 // If this occurs, a new set of targets is being loaded. Let that complete,
                 // and have the next call to send voice choices proceed instead.
@@ -576,7 +575,7 @@
         }
 
         final DisplayResolveInfo dri =
-                mMultiProfilePagerAdapter.getCurrentListAdapter().getOtherProfile();
+                mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
         if (dri != null && !ENABLE_TABBED_VIEW) {
             mProfileView.setVisibility(View.VISIBLE);
             View text = mProfileView.findViewById(R.id.profile_button);
@@ -629,7 +628,7 @@
         // While there may already be a filtered item, we can only use it in the title if the list
         // is already sorted and all information relevant to it is already in the list.
         final boolean named =
-                mMultiProfilePagerAdapter.getCurrentListAdapter().getFilteredPosition() >= 0;
+                mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0;
         if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
             return getString(defaultTitleRes);
         } else if (isHttpSchemeAndViewAction(intent)) {
@@ -638,14 +637,14 @@
             String dialogTitle = null;
             if (named && !mUseLayoutForBrowsables) {
                 dialogTitle = getString(ActionTitle.BROWSABLE_APP_TITLE_RES,
-                        mMultiProfilePagerAdapter.getCurrentListAdapter().getFilteredItem()
+                        mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredItem()
                                 .getDisplayLabel());
             } else if (named && mUseLayoutForBrowsables) {
                 dialogTitle = getString(ActionTitle.BROWSABLE_HOST_APP_TITLE_RES,
                         intent.getData().getHost(),
-                        mMultiProfilePagerAdapter.getCurrentListAdapter().getFilteredItem()
+                        mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredItem()
                                 .getDisplayLabel());
-            } else if (mMultiProfilePagerAdapter.getCurrentListAdapter().areAllTargetsBrowsers()) {
+            } else if (mMultiProfilePagerAdapter.getActiveListAdapter().areAllTargetsBrowsers()) {
                 dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
             } else {
                 dialogTitle = getString(ActionTitle.BROWSABLE_HOST_TITLE_RES,
@@ -655,7 +654,7 @@
         } else {
             return named
                     ? getString(title.namedTitleRes, mMultiProfilePagerAdapter
-                            .getCurrentListAdapter().getFilteredItem().getDisplayLabel())
+                            .getActiveListAdapter().getFilteredItem().getDisplayLabel())
                     : getString(title.titleRes);
         }
     }
@@ -673,7 +672,7 @@
             mPackageMonitor.register(this, getMainLooper(), false);
             mRegistered = true;
         }
-        mMultiProfilePagerAdapter.getCurrentListAdapter().handlePackagesChanged();
+        mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
         updateProfileViewButton();
     }
 
@@ -706,8 +705,8 @@
         if (!isChangingConfigurations() && mPickOptionRequest != null) {
             mPickOptionRequest.cancel();
         }
-        if (mMultiProfilePagerAdapter.getCurrentListAdapter() != null) {
-            mMultiProfilePagerAdapter.getCurrentListAdapter().onDestroy();
+        if (mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
+            mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
         }
     }
 
@@ -757,7 +756,7 @@
         boolean enabled = false;
         ResolveInfo ri = null;
         if (hasValidSelection) {
-            ri = mMultiProfilePagerAdapter.getCurrentListAdapter()
+            ri = mMultiProfilePagerAdapter.getActiveListAdapter()
                     .resolveInfoForPosition(checkedPos, filtered);
             if (ri == null) {
                 Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled");
@@ -780,12 +779,9 @@
         ActivityInfo activityInfo = ri.activityInfo;
 
         boolean hasRecordPermission =
-                PermissionChecker.checkPermissionForPreflight(
-                        getApplicationContext(),
-                        android.Manifest.permission.RECORD_AUDIO, -1,
-                        activityInfo.applicationInfo.uid,
+                mPm.checkPermission(android.Manifest.permission.RECORD_AUDIO,
                         activityInfo.packageName)
-                        == android.content.pm.PackageManager.PERMISSION_GRANTED;
+                    == android.content.pm.PackageManager.PERMISSION_GRANTED;
 
         if (!hasRecordPermission) {
             // OK, we know the record permission, is this a capture device
@@ -800,7 +796,7 @@
     public void onButtonClick(View v) {
         final int id = v.getId();
         ListView listView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
-        ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getCurrentListAdapter();
+        ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
         int which = currentListAdapter.hasFilteredItem()
                 ? currentListAdapter.getFilteredPosition()
                 : listView.getCheckedItemPosition();
@@ -836,7 +832,7 @@
         if (isFinishing()) {
             return;
         }
-        ResolveInfo ri = mMultiProfilePagerAdapter.getCurrentListAdapter()
+        ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
                 .resolveInfoForPosition(which, hasIndexBeenFiltered);
         if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
             Toast.makeText(this, String.format(getResources().getString(
@@ -846,7 +842,7 @@
             return;
         }
 
-        TargetInfo target = mMultiProfilePagerAdapter.getCurrentListAdapter()
+        TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
                 .targetInfoForPosition(which, hasIndexBeenFiltered);
         if (target == null) {
             return;
@@ -863,7 +859,7 @@
                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP);
             }
             MetricsLogger.action(this,
-                    mMultiProfilePagerAdapter.getCurrentListAdapter().hasFilteredItem()
+                    mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
                             ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
                             : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
             finish();
@@ -879,18 +875,17 @@
     }
 
     @Override // ResolverListCommunicator
-    public void onPostListReady() {
+    public void onPostListReady(ResolverListAdapter listAdapter) {
         setHeader();
         resetButtonBar();
-        onListRebuilt();
+        onListRebuilt(listAdapter);
     }
 
-    protected void onListRebuilt() {
-        int count = mMultiProfilePagerAdapter.getCurrentListAdapter().getUnfilteredCount();
-        if (count == 1 && mMultiProfilePagerAdapter.getCurrentListAdapter().getOtherProfile() == null) {
+    protected void onListRebuilt(ResolverListAdapter listAdapter) {
+        int count = listAdapter.getUnfilteredCount();
+        if (count == 1 && listAdapter.getOtherProfile() == null) {
             // Only one target, so we're a candidate to auto-launch!
-            final TargetInfo target =
-                    mMultiProfilePagerAdapter.getCurrentListAdapter().targetInfoForPosition(0, false);
+            final TargetInfo target = listAdapter.targetInfoForPosition(0, false);
             if (shouldAutoLaunchSingleChoice(target)) {
                 safelyStartActivity(target);
                 finish();
@@ -903,8 +898,8 @@
         final Intent intent = target != null ? target.getResolvedIntent() : null;
 
         if (intent != null && (mSupportsAlwaysUseOption
-                || mMultiProfilePagerAdapter.getCurrentListAdapter().hasFilteredItem())
-                && mMultiProfilePagerAdapter.getCurrentListAdapter().getUnfilteredResolveList() != null) {
+                || mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem())
+                && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) {
             // Build a reasonable intent filter, based on what matched.
             IntentFilter filter = new IntentFilter();
             Intent filterIntent;
@@ -989,14 +984,14 @@
             }
 
             if (filter != null) {
-                final int N = mMultiProfilePagerAdapter.getCurrentListAdapter()
+                final int N = mMultiProfilePagerAdapter.getActiveListAdapter()
                         .getUnfilteredResolveList().size();
                 ComponentName[] set;
                 // If we don't add back in the component for forwarding the intent to a managed
                 // profile, the preferred activity may not be updated correctly (as the set of
                 // components we tell it we knew about will have changed).
                 final boolean needToAddBackProfileForwardingComponent =
-                        mMultiProfilePagerAdapter.getCurrentListAdapter().getOtherProfile() != null;
+                        mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null;
                 if (!needToAddBackProfileForwardingComponent) {
                     set = new ComponentName[N];
                 } else {
@@ -1005,7 +1000,7 @@
 
                 int bestMatch = 0;
                 for (int i=0; i<N; i++) {
-                    ResolveInfo r = mMultiProfilePagerAdapter.getCurrentListAdapter()
+                    ResolveInfo r = mMultiProfilePagerAdapter.getActiveListAdapter()
                             .getUnfilteredResolveList().get(i).getResolveInfoAt(0);
                     set[i] = new ComponentName(r.activityInfo.packageName,
                             r.activityInfo.name);
@@ -1013,9 +1008,9 @@
                 }
 
                 if (needToAddBackProfileForwardingComponent) {
-                    set[N] = mMultiProfilePagerAdapter.getCurrentListAdapter()
+                    set[N] = mMultiProfilePagerAdapter.getActiveListAdapter()
                             .getOtherProfile().getResolvedComponentName();
-                    final int otherProfileMatch = mMultiProfilePagerAdapter.getCurrentListAdapter()
+                    final int otherProfileMatch = mMultiProfilePagerAdapter.getActiveListAdapter()
                             .getOtherProfile().getResolveInfo().match;
                     if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
                 }
@@ -1055,7 +1050,7 @@
                     }
                 } else {
                     try {
-                        mMultiProfilePagerAdapter.getCurrentListAdapter()
+                        mMultiProfilePagerAdapter.getActiveListAdapter()
                                 .mResolverListController.setLastChosen(intent, filter, bestMatch);
                     } catch (RemoteException re) {
                         Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
@@ -1200,10 +1195,14 @@
      * @return <code>true</code> if the activity is finishing and creation should halt.
      */
     private boolean configureContentView() {
-        if (mMultiProfilePagerAdapter.getCurrentListAdapter() == null) {
-            throw new IllegalStateException("mAdapter cannot be null.");
+        if (mMultiProfilePagerAdapter.getActiveListAdapter() == null) {
+            throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
+                    + "cannot be null.");
         }
-        boolean rebuildCompleted = mMultiProfilePagerAdapter.getCurrentListAdapter().rebuildList();
+        boolean rebuildCompleted = mMultiProfilePagerAdapter.getActiveListAdapter().rebuildList();
+        if (mMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
+            mMultiProfilePagerAdapter.getInactiveListAdapter().rebuildList();
+        }
         if (useLayoutWithDefault()) {
             mLayoutId = R.layout.resolver_list_with_default;
         } else {
@@ -1231,15 +1230,15 @@
      */
     final boolean postRebuildListInternal(boolean rebuildCompleted) {
 
-        int count = mMultiProfilePagerAdapter.getCurrentListAdapter().getUnfilteredCount();
+        int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
 
         // We only rebuild asynchronously when we have multiple elements to sort. In the case where
         // we're already done, we can check if we should auto-launch immediately.
         if (rebuildCompleted) {
             if (count == 1
-                    && mMultiProfilePagerAdapter.getCurrentListAdapter().getOtherProfile() == null) {
+                    && mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() == null) {
                 // Only one target, so we're a candidate to auto-launch!
-                final TargetInfo target = mMultiProfilePagerAdapter.getCurrentListAdapter()
+                final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
                         .targetInfoForPosition(0, false);
                 if (shouldAutoLaunchSingleChoice(target)) {
                     safelyStartActivity(target);
@@ -1257,12 +1256,12 @@
 
     private void setupViewVisibilities(int count) {
         if (count == 0
-                && mMultiProfilePagerAdapter.getCurrentListAdapter().getPlaceholderCount() == 0) {
+                && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0) {
             final TextView emptyView = findViewById(R.id.empty);
             emptyView.setVisibility(View.VISIBLE);
             findViewById(R.id.profile_pager).setVisibility(View.GONE);
         } else {
-            onPrepareAdapterView(mMultiProfilePagerAdapter.getCurrentListAdapter());
+            onPrepareAdapterView(mMultiProfilePagerAdapter.getActiveListAdapter());
         }
     }
 
@@ -1295,8 +1294,8 @@
      * Configure the area above the app selection list (title, content preview, etc).
      */
     public void setHeader() {
-        if (mMultiProfilePagerAdapter.getCurrentListAdapter().getCount() == 0
-                && mMultiProfilePagerAdapter.getCurrentListAdapter().getPlaceholderCount() == 0) {
+        if (mMultiProfilePagerAdapter.getActiveListAdapter().getCount() == 0
+                && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0) {
             final TextView titleView = findViewById(R.id.title);
             if (titleView != null) {
                 titleView.setVisibility(View.GONE);
@@ -1317,7 +1316,7 @@
 
         final ImageView iconView = findViewById(R.id.icon);
         if (iconView != null) {
-            mMultiProfilePagerAdapter.getCurrentListAdapter().loadFilteredItemIconTaskAsync(iconView);
+            mMultiProfilePagerAdapter.getActiveListAdapter().loadFilteredItemIconTaskAsync(iconView);
         }
     }
 
@@ -1345,7 +1344,7 @@
     }
 
     private void resetAlwaysOrOnceButtonBar() {
-        int filteredPosition = mMultiProfilePagerAdapter.getCurrentListAdapter()
+        int filteredPosition = mMultiProfilePagerAdapter.getActiveListAdapter()
                 .getFilteredPosition();
         if (useLayoutWithDefault() && filteredPosition != ListView.INVALID_POSITION) {
             setAlwaysButtonEnabled(true, filteredPosition, false);
@@ -1365,7 +1364,7 @@
     @Override // ResolverListCommunicator
     public boolean useLayoutWithDefault() {
         return mSupportsAlwaysUseOption
-                && mMultiProfilePagerAdapter.getCurrentListAdapter().hasFilteredItem();
+                && mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem();
     }
 
     /**
@@ -1389,7 +1388,9 @@
 
     @Override // ResolverListCommunicator
     public void onHandlePackagesChanged() {
-        if (mMultiProfilePagerAdapter.getCurrentListAdapter().getCount() == 0) {
+        ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
+        activeListAdapter.rebuildList();
+        if (activeListAdapter.getCount() == 0) {
             // We no longer have any items...  just finish the activity.
             finish();
         }
@@ -1464,7 +1465,7 @@
                 return;
             }
             // If we're still loading, we can't yet enable the buttons.
-            if (mMultiProfilePagerAdapter.getCurrentListAdapter()
+            if (mMultiProfilePagerAdapter.getActiveListAdapter()
                     .resolveInfoForPosition(position, true) == null) {
                 return;
             }
@@ -1497,7 +1498,7 @@
                 // Header views don't count.
                 return false;
             }
-            ResolveInfo ri = mMultiProfilePagerAdapter.getCurrentListAdapter()
+            ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
                     .resolveInfoForPosition(position, true);
             showTargetDetails(ri);
             return true;
@@ -1538,7 +1539,7 @@
 
             final ResolverActivity ra = (ResolverActivity) getActivity();
             if (ra != null) {
-                final TargetInfo ti = ra.mMultiProfilePagerAdapter.getCurrentListAdapter()
+                final TargetInfo ti = ra.mMultiProfilePagerAdapter.getActiveListAdapter()
                         .getItem(selections[0].getIndex());
                 if (ra.onTargetSelected(ti, false)) {
                     ra.mPickOptionRequest = null;
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 48064da..ef7a347 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -113,7 +113,6 @@
     }
 
     public void handlePackagesChanged() {
-        rebuildList();
         mResolverListCommunicator.onHandlePackagesChanged();
     }
 
@@ -351,12 +350,12 @@
      * determine the layout known. We therefore can't update the UI inline and post to the
      * handler thread to update after the current task is finished.
      */
-    private void postListReadyRunnable() {
+    void postListReadyRunnable() {
         if (mPostListReadyRunnable == null) {
             mPostListReadyRunnable = new Runnable() {
                 @Override
                 public void run() {
-                    mResolverListCommunicator.onPostListReady();
+                    mResolverListCommunicator.onPostListReady(ResolverListAdapter.this);
                     mPostListReadyRunnable = null;
                 }
             };
@@ -599,7 +598,7 @@
 
         Intent getReplacementIntent(ActivityInfo activityInfo, Intent defIntent);
 
-        void onPostListReady();
+        void onPostListReady(ResolverListAdapter listAdapter);
 
         void sendVoiceChoicesIfNeeded();
 
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 9e814ab..d72c52b 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -87,13 +87,22 @@
 
     @Override
     @VisibleForTesting
-    public ResolverListAdapter getCurrentListAdapter() {
+    public ResolverListAdapter getActiveListAdapter() {
         return getAdapterForIndex(getCurrentPage());
     }
 
     @Override
+    @VisibleForTesting
+    public ResolverListAdapter getInactiveListAdapter() {
+        if (getCount() == 1) {
+            return null;
+        }
+        return getAdapterForIndex(1 - getCurrentPage());
+    }
+
+    @Override
     ResolverListAdapter getCurrentRootAdapter() {
-        return getCurrentListAdapter();
+        return getActiveListAdapter();
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index bc44fcf..d0a83c4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.os;
 
+import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
+import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
@@ -5267,7 +5270,7 @@
         // Unknown is included in DATA_CONNECTION_OTHER.
         int bin = DATA_CONNECTION_OUT_OF_SERVICE;
         if (hasData) {
-            if (dataType > 0 && dataType <= TelephonyManager.MAX_NETWORK_TYPE) {
+            if (dataType > 0 && dataType <= TelephonyManager.getAllNetworkTypes().length) {
                 bin = dataType;
             } else {
                 switch (serviceType) {
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index a211871..fa823c4 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -367,8 +367,8 @@
         if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
     }
 
-    protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
-            ClassLoader classLoader) {
+    protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
+            String[] argv, ClassLoader classLoader) {
         // If the application calls System.exit(), terminate the process
         // immediately without running any shutdown hooks.  It is not possible to
         // shutdown an Android application gracefully.  Among other things, the
@@ -377,6 +377,7 @@
         nativeSetExitWithoutCleanup(true);
 
         VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
+        VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges);
 
         final Arguments args = new Arguments(argv);
 
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index f0e7796..790d7f7 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -23,16 +23,18 @@
 import android.system.OsConstants;
 import android.system.StructCapUserData;
 import android.system.StructCapUserHeader;
-import android.util.TimingsTraceLog;
 import android.util.Slog;
+import android.util.TimingsTraceLog;
+
 import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+
 import java.io.DataOutputStream;
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
 
-import libcore.io.IoUtils;
-
 /**
  * Startup class for the wrapper process.
  * @hide
@@ -166,10 +168,10 @@
             System.arraycopy(argv, 2, removedArgs, 0, argv.length - 2);
             argv = removedArgs;
         }
-
         // Perform the same initialization that would happen after the Zygote forks.
         Zygote.nativePreApplicationInit();
-        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+        return RuntimeInit.applicationInit(targetSdkVersion, /*disabledCompatChanges*/ null,
+                argv, classLoader);
     }
 
     /**
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index fbacdd7..2b988c1 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -672,6 +672,7 @@
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
             return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
+                                         args.mDisabledCompatChanges,
                                          args.mRemainingArgs,
                                          null /* classLoader */);
         } finally {
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index a23e659..54b2a20 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -215,6 +215,12 @@
     boolean mIsTopApp;
 
     /**
+     * A set of disabled app compatibility changes for the running app. From
+     * --disabled-compat-changes.
+     */
+    long[] mDisabledCompatChanges = null;
+
+    /**
      * Constructs instance and parses args
      *
      * @param args zygote command-line args
@@ -421,6 +427,16 @@
                 expectRuntimeArgs = false;
             } else if (arg.startsWith(Zygote.START_AS_TOP_APP_ARG)) {
                 mIsTopApp = true;
+            } else if (arg.startsWith("--disabled-compat-changes=")) {
+                if (mDisabledCompatChanges != null) {
+                    throw new IllegalArgumentException("Duplicate arg specified");
+                }
+                final String[] params = getAssignmentList(arg);
+                final int length = params.length;
+                mDisabledCompatChanges = new long[length];
+                for (int i = 0; i < length; i++) {
+                    mDisabledCompatChanges[i] = Long.parseLong(params[i]);
+                }
             } else {
                 break;
             }
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index a14b093..3111b6f 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -501,6 +501,7 @@
         } else {
             if (!isZygote) {
                 return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+                        parsedArgs.mDisabledCompatChanges,
                         parsedArgs.mRemainingArgs, null /* classLoader */);
             } else {
                 return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion,
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9ee79ea..49b4cf8 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -553,6 +553,7 @@
              * Pass the remaining arguments to SystemServer.
              */
             return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+                    parsedArgs.mDisabledCompatChanges,
                     parsedArgs.mRemainingArgs, cl);
         }
 
@@ -988,14 +989,16 @@
      *
      * Current recognized args:
      * <ul>
-     *   <li> <code> [--] &lt;start class name&gt;  &lt;args&gt;
+     * <li> <code> [--] &lt;start class name&gt;  &lt;args&gt;
      * </ul>
      *
      * @param targetSdkVersion target SDK version
-     * @param argv arg strings
+     * @param disabledCompatChanges set of disabled compat changes for the process (all others
+     *                              are enabled)
+     * @param argv             arg strings
      */
-    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,
-            ClassLoader classLoader) {
+    public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
+            String[] argv, ClassLoader classLoader) {
         if (RuntimeInit.DEBUG) {
             Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
         }
@@ -1005,7 +1008,8 @@
 
         RuntimeInit.commonInit();
         ZygoteInit.nativeZygoteInit();
-        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+        return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
+                classLoader);
     }
 
     /**
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 49a73ee..7562bad 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -222,6 +222,8 @@
     private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
     private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
 
+    private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>();
+
     /**
      * Map of system pre-defined, uniquely named actors; keys are namespace,
      * value maps actor name to package name.
@@ -382,6 +384,10 @@
         return mBugreportWhitelistedPackages;
     }
 
+    public Set<String> getRollbackWhitelistedPackages() {
+        return mRollbackWhitelistedPackages;
+    }
+
     /**
      * Gets map of packagesNames to userTypes, dictating on which user types each package should be
      * initially installed, and then removes this map from SystemConfig.
@@ -1078,6 +1084,16 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "rollback-whitelisted-app": {
+                        String pkgname = parser.getAttributeValue(null, "package");
+                        if (pkgname == null) {
+                            Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else {
+                            mRollbackWhitelistedPackages.add(pkgname);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     default: {
                         Slog.w(TAG, "Tag " + name + " is unknown in "
                                 + permFile + " at " + parser.getPositionDescription());
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 3f05c3b..fa64fd1 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -7,7 +7,6 @@
 #include "SkAndroidCodec.h"
 #include "SkBRDAllocator.h"
 #include "SkFrontBufferedStream.h"
-#include "SkMakeUnique.h"
 #include "SkMath.h"
 #include "SkPixelRef.h"
 #include "SkStream.h"
@@ -586,7 +585,7 @@
     Asset* asset = reinterpret_cast<Asset*>(native_asset);
     // since we know we'll be done with the asset when we return, we can
     // just use a simple wrapper
-    return doDecode(env, skstd::make_unique<AssetStreamAdaptor>(asset), padding, options,
+    return doDecode(env, std::make_unique<AssetStreamAdaptor>(asset), padding, options,
                     inBitmapHandle, colorSpaceHandle);
 }
 
@@ -594,7 +593,7 @@
         jint offset, jint length, jobject options, jlong inBitmapHandle, jlong colorSpaceHandle) {
 
     AutoJavaByteArray ar(env, byteArray);
-    return doDecode(env, skstd::make_unique<SkMemoryStream>(ar.ptr() + offset, length, false),
+    return doDecode(env, std::make_unique<SkMemoryStream>(ar.ptr() + offset, length, false),
                     nullptr, options, inBitmapHandle, colorSpaceHandle);
 }
 
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 0002f8b..4376b0b 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -44,6 +44,13 @@
     jmethodID    toString;
 } gUUIDMethods;
 
+static const char* const kUnsupportedOperationExceptionClassPathName =
+     "java/lang/UnsupportedOperationException";
+static jclass gUnsupportedOperationExceptionClass;
+static const char* const kIllegalArgumentExceptionClassPathName =
+     "java/lang/IllegalArgumentException";
+static jclass gIllegalArgumentExceptionClass;
+
 static const char* const kSoundTriggerClassPathName = "android/hardware/soundtrigger/SoundTrigger";
 static jclass gSoundTriggerClass;
 
@@ -91,6 +98,11 @@
     jfieldID    keyphrases;
 } gKeyphraseSoundModelFields;
 
+static const char* const kModelParamRangeClassPathName =
+                                "android/hardware/soundtrigger/SoundTrigger$ModelParamRange";
+static jclass gModelParamRangeClass;
+static jmethodID gModelParamRangeCstor;
+
 static const char* const kRecognitionConfigClassPathName =
                                      "android/hardware/soundtrigger/SoundTrigger$RecognitionConfig";
 static jclass gRecognitionConfigClass;
@@ -164,6 +176,16 @@
     SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE = 4,
 };
 
+static jint throwUnsupportedOperationException(JNIEnv *env)
+{
+    return env->ThrowNew(gUnsupportedOperationExceptionClass, nullptr);
+}
+
+static jint throwIllegalArgumentException(JNIEnv *env)
+{
+    return env->ThrowNew(gIllegalArgumentExceptionClass, nullptr);
+}
+
 // ----------------------------------------------------------------------------
 // ref-counted object for callbacks
 class JNISoundTriggerCallback: public SoundTriggerCallback
@@ -822,6 +844,69 @@
     return status;
 }
 
+static jint
+android_hardware_SoundTrigger_setParameter(JNIEnv *env, jobject thiz,
+                                            jint jHandle, jint jModelParam, jint jValue)
+{
+    ALOGV("setParameter");
+    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
+    if (module == NULL) {
+        return SOUNDTRIGGER_STATUS_NO_INIT;
+    }
+    return module->setParameter(jHandle, (sound_trigger_model_parameter_t) jModelParam, jValue);
+}
+
+static jint
+android_hardware_SoundTrigger_getParameter(JNIEnv *env, jobject thiz,
+                                            jint jHandle, jint jModelParam)
+{
+    ALOGV("getParameter");
+    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
+    if (module == NULL) {
+        throwUnsupportedOperationException(env);
+        return -1;
+    }
+
+    jint nValue;
+    jint status = module->getParameter(jHandle,
+            (sound_trigger_model_parameter_t) jModelParam, &nValue);
+
+    switch (status) {
+        case 0:
+            return nValue;
+        case -EINVAL:
+            throwIllegalArgumentException(env);
+            break;
+        default:
+            throwUnsupportedOperationException(env);
+            break;
+    }
+
+    return -1;
+}
+
+static jobject
+android_hardware_SoundTrigger_queryParameter(JNIEnv *env, jobject thiz,
+                                            jint jHandle, jint jModelParam)
+{
+    ALOGV("queryParameter");
+    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
+    if (module == nullptr) {
+        return nullptr;
+    }
+
+    sound_trigger_model_parameter_range_t nRange;
+    jint nValue = module->queryParameter(jHandle,
+            (sound_trigger_model_parameter_t) jModelParam, &nRange);
+
+    if (nValue != 0) {
+        ALOGE("failed to query parameter error code: %d", nValue);
+        return nullptr;
+    }
+
+    return env->NewObject(gModelParamRangeClass, gModelParamRangeCstor, nRange.start, nRange.end);
+}
+
 static const JNINativeMethod gMethods[] = {
     {"listModules",
         "(Ljava/lang/String;Ljava/util/ArrayList;)I",
@@ -854,6 +939,15 @@
     {"getModelState",
         "(I)I",
         (void *)android_hardware_SoundTrigger_getModelState},
+    {"setParameter",
+         "(III)I",
+         (void *)android_hardware_SoundTrigger_setParameter},
+    {"getParameter",
+         "(II)I",
+         (void *)android_hardware_SoundTrigger_getParameter},
+    {"queryParameter",
+         "(II)Landroid/hardware/soundtrigger/SoundTrigger$ModelParamRange;",
+         (void *)android_hardware_SoundTrigger_queryParameter}
 };
 
 int register_android_hardware_SoundTrigger(JNIEnv *env)
@@ -866,6 +960,12 @@
     gUUIDClass = MakeGlobalRefOrDie(env, uuidClass);
     gUUIDMethods.toString = GetMethodIDOrDie(env, uuidClass, "toString", "()Ljava/lang/String;");
 
+    jclass exUClass = FindClassOrDie(env, kUnsupportedOperationExceptionClassPathName);
+    gUnsupportedOperationExceptionClass = MakeGlobalRefOrDie(env, exUClass);
+
+    jclass exIClass = FindClassOrDie(env, kIllegalArgumentExceptionClassPathName);
+    gIllegalArgumentExceptionClass = MakeGlobalRefOrDie(env, exIClass);
+
     jclass lClass = FindClassOrDie(env, kSoundTriggerClassPathName);
     gSoundTriggerClass = MakeGlobalRefOrDie(env, lClass);
 
@@ -906,6 +1006,10 @@
                                          "keyphrases",
                                          "[Landroid/hardware/soundtrigger/SoundTrigger$Keyphrase;");
 
+    jclass modelParamRangeClass = FindClassOrDie(env, kModelParamRangeClassPathName);
+    gModelParamRangeClass = MakeGlobalRefOrDie(env, modelParamRangeClass);
+    gModelParamRangeCstor = GetMethodIDOrDie(env, modelParamRangeClass, "<init>", "(II)V");
+
     jclass recognitionEventClass = FindClassOrDie(env, kRecognitionEventClassPathName);
     gRecognitionEventClass = MakeGlobalRefOrDie(env, recognitionEventClass);
     gRecognitionEventCstor = GetMethodIDOrDie(env, recognitionEventClass, "<init>",
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 31c19ca..a98d7db 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -566,6 +566,7 @@
     optional LowPowerMode low_power_mode = 70;
 
     optional SettingProto lte_service_forced = 71 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    repeated SettingProto max_error_bytes = 151;
     optional SettingProto mdc_initial_max_retry = 72 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     message Mhl {
@@ -1058,5 +1059,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 151;
+    // Next tag = 152;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8f1114c..c925744 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3264,13 +3264,6 @@
     <permission android:name="android.permission.BIND_TV_INPUT"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Must be required by an {@link android.service.sms.FinancialSmsService}
-         to ensure that only the system can bind to it.
-         @hide This is not a third-party API (intended for OEMs and system apps).
-    -->
-    <permission android:name="android.permission.BIND_FINANCIAL_SMS_SERVICE"
-                android:protectionLevel="signature" />
-
     <!-- @SystemApi
          Must be required by a {@link com.android.media.tv.remoteprovider.TvRemoteProvider}
          to ensure that only the system can bind to it.
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index 9f296f8f..0c45e45 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -55,6 +55,12 @@
                   android:layout_centerHorizontal="true"/>
     </RelativeLayout>
 
+    <FrameLayout
+        android:id="@+id/content_preview_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone" />
+
     <com.android.internal.widget.ViewPager
         android:id="@+id/profile_pager"
         android:layout_width="match_parent"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ced5deb..89c913b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2652,11 +2652,6 @@
     <!-- Package name for default network scorer app; overridden by product overlays. -->
     <string name="config_defaultNetworkScorerPackageName"></string>
 
-    <!-- Feature flag to enable memory efficient task snapshots that are used in recents optimized
-         for low memory devices and replace the app transition starting window with the splash
-         screen. -->
-    <bool name="config_lowRamTaskSnapshotsAndRecents">false</bool>
-
     <!-- The amount to scale fullscreen snapshots for Overview and snapshot starting windows. -->
     <item name="config_fullTaskSnapshotScale" format="float" type="dimen">1.0</item>
 
@@ -4188,15 +4183,15 @@
 
     <!-- The default value used for RawContacts.ACCOUNT_NAME when contacts are inserted without this
          column set. These contacts are stored locally on the device and will not be removed even
-         if an android.account.Account with this name and type exists. A null string will be used
-         if the value is left empty. When this is non-empty then config_rawContactsLocalAccountType
+         if no android.account.Account with this name exists. A null string will be used if the
+         value is left empty. When this is non-empty then config_rawContactsLocalAccountType
          should also be non-empty.  -->
     <string name="config_rawContactsLocalAccountName" translatable="false"></string>
 
     <!-- The default value used for RawContacts.ACCOUNT_TYPE when contacts are inserted without this
          column set. These contacts are stored locally on the device and will not be removed even
-         if an android.account.Account with this name and type exists. A null string will be used
-         if the value is left empty.  When this is non-empty then config_rawContactsLocalAccountName
+         if no android.account.Account with this type exists. A null string will be used if the
+         value is left empty.  When this is non-empty then config_rawContactsLocalAccountName
          should also be non-empty.-->
     <string name="config_rawContactsLocalAccountType" translatable="false"></string>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0f5da39..de1b5ba 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1011,53 +1011,32 @@
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readContacts">read your contacts</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_readContacts" product="tablet">Allows the app to read
-      data about your contacts stored on your tablet, including the frequency
-      with which you\'ve called, emailed, or communicated in other ways with
-      specific individuals. Apps will also have access to the accounts on your
-      tablet that have created contacts. This may include accounts created by
-      apps you have installed. This permission allows apps to save your contact
-      data, and malicious apps may share contact data without your
-      knowledge.</string>
+    <string name="permdesc_readContacts" product="tablet">Allows the app to read data about your contacts stored on your tablet.
+      Apps will also have access to the accounts on your tablet that have created contacts.
+      This may include accounts created by apps you have installed.
+      This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge.</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_readContacts" product="tv">Allows the app to read
-      data about your contacts stored on your Android TV device, including the frequency
-      with which you\'ve called, emailed, or communicated in other ways with
-      specific individuals. Apps will also have access to the accounts on your
-      Android TV device that have created contacts. This may include accounts
-      created by apps you have installed. This permission allows apps to save
-      your contact data, and malicious apps may share contact data without your
-      knowledge.</string>
+    <string name="permdesc_readContacts" product="tv">Allows the app to read data about your contacts stored on your Android TV device.
+      Apps will also have access to the accounts on your Android TV device that have created contacts.
+      This may include accounts created by apps you have installed.
+      This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge.</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_readContacts" product="default">Allows the app to
-      read data about your contacts stored on your phone, including the
-      frequency with which you\'ve called, emailed, or communicated in other ways
-      with specific individuals. Apps will also have access to the accounts
-      on your phone that have created contacts. This may include accounts
-      created by apps you have installed. This permission allows apps to
-      save your contact data, and malicious apps may share contact data
-      without your knowledge.</string>
+    <string name="permdesc_readContacts" product="default">Allows the app to read data about your contacts stored on your phone.
+      Apps will also have access to the accounts on your phone that have created contacts.
+      This may include accounts created by apps you have installed.
+      This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_writeContacts">modify your contacts</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_writeContacts" product="tablet">Allows the app to
-      modify the data about your contacts stored on your tablet, including the
-      frequency with which you\'ve called, emailed, or communicated in other ways
-      with specific contacts. This permission allows apps to delete contact
-      data.</string>
+    <string name="permdesc_writeContacts" product="tablet">Allows the app to modify the data about your contacts stored on your tablet.
+      This permission allows apps to delete contact data.</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_writeContacts" product="tv">Allows the app to
-      modify the data about your contacts stored on your Android TV device, including the
-      frequency with which you\'ve called, emailed, or communicated in other ways
-      with specific contacts. This permission allows apps to delete contact
-      data.</string>
+    <string name="permdesc_writeContacts" product="tv">Allows the app to modify the data about your contacts stored on your Android TV device.
+      This permission allows apps to delete contact data.</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_writeContacts" product="default">Allows the app to
-    modify the data about your contacts stored on your phone, including the
-    frequency with which you\'ve called, emailed, or communicated in other ways
-    with specific contacts. This permission allows apps to delete contact
-    data.</string>
+    <string name="permdesc_writeContacts" product="default">Allows the app to modify the data about your contacts stored on your phone.
+      This permission allows apps to delete contact data.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readCallLog">read call log</string>
@@ -1102,6 +1081,7 @@
     <string name="permdesc_writeCalendar" product="default">This app can add, remove, or change calendar events on your phone. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the applicatfion to do this. -->
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_accessLocationExtraCommands">access extra location provider commands</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_accessLocationExtraCommands">Allows the app to access
@@ -1111,21 +1091,17 @@
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_accessFineLocation">access precise location only in the foreground</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_accessFineLocation">This app can get your exact location only when it is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them. This may increase battery consumption.</string>
+    <string name="permdesc_accessFineLocation">This app can get your exact location only when it is in the foreground. Location services must be turned on and available on your device for the app to be able to use them. This may increase battery consumption.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_accessCoarseLocation">access approximate location (network-based) only in the foreground</string>
+    <string name="permlab_accessCoarseLocation">access approximate location only in the foreground</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_accessCoarseLocation" product="tablet">This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when when the app is in the foreground. These location services must be turned on and available on your tablet for the app to be able to use them.</string>
-    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_accessCoarseLocation" product="tv">This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when when the app is in the foreground. These location services must be turned on and available on your Android TV device for the app to be able to use them.</string>
-    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_accessCoarseLocation" product="default">This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them.</string>
+    <string name="permdesc_accessCoarseLocation">This app can get your approximate location only when it is in the foreground. Location services must be turned on and available on your device for the app to be able to use them.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_accessBackgroundLocation">access location in the background</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_accessBackgroundLocation">If this is granted additionally to the approximate or precise location access the app can access the location while running in the background.</string>
+    <string name="permdesc_accessBackgroundLocation">This app can access location while running in the background, in addition to foreground location access.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_modifyAudioSettings">change your audio settings</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 29b9281..6956c39 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -248,6 +248,7 @@
   <java-symbol type="id" name="overlay" />
   <java-symbol type="id" name="app_ops" />
   <java-symbol type="id" name="profile_pager" />
+  <java-symbol type="id" name="content_preview_container" />
 
   <java-symbol type="attr" name="actionModeShareDrawable" />
   <java-symbol type="attr" name="alertDialogCenterButtons" />
@@ -357,7 +358,6 @@
   <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
   <java-symbol type="dimen" name="config_fullTaskSnapshotScale" />
   <java-symbol type="bool" name="config_use16BitTaskSnapshotPixelFormat" />
-  <java-symbol type="bool" name="config_lowRamTaskSnapshotsAndRecents" />
   <java-symbol type="bool" name="config_hasRecents" />
   <java-symbol type="string" name="config_recentsComponentName" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
@@ -441,6 +441,7 @@
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
   <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
+  <java-symbol type="xml" name="config_user_types" />
   <java-symbol type="integer" name="config_safe_media_volume_index" />
   <java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
   <java-symbol type="integer" name="config_mobile_mtu" />
diff --git a/core/res/res/xml/config_user_types.xml b/core/res/res/xml/config_user_types.xml
new file mode 100644
index 0000000..5fd8a95
--- /dev/null
+++ b/core/res/res/xml/config_user_types.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!--
+This xml file allows customization of Android multiuser user types.
+It is parsed by frameworks/base/services/core/java/com/android/server/pm/UserTypeFactory.java.
+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++   IMPORTANT NOTE   ++++++++++++++++++++++++++++++++++++++++
+Although device customization is possible here, it is largely untested.
+In particular, although this file allows new profile types to be created, and allows modifying the
+number of managed profiles allowed on the device, the consequences of doing so is untested.
+OEMs are advised to test very carefully any significant customization.
+Further support is planned for later releases.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Pre-defined (AOSP) user types can be customized and new types can be defined. The syntax is the
+same in both cases.
+
+Currently, only profiles (not full or system users) can be freely customized/defined.
+Full users (i.e. non-system, non-profile) users cannot be defined, and the only property of them
+that can be customized are the default-restrictions.
+System users cannot be customized here; their default-restrictions must be set using
+com.android.internal.R.array.config_defaultFirstUserRestrictions.
+
+The following example modifies two AOSP user types (the FULL user android.os.usertype.full.SECONDARY
+and the PROFILE user android.os.usertype.profile.MANAGED) and creates a new PROFILE user type
+(com.example.profilename):
+
+<user-types>
+    <full-type name="android.os.usertype.full.SECONDARY" >
+        <default-restrictions no_sms="true" />
+    </full-type>
+
+    <profile-type
+        name='android.os.usertype.profile.MANAGED'
+        max-allowed-per-parent='2'
+        icon-badge='@android:drawable/ic_corp_icon_badge_case'
+        badge-plain='@android:drawable/ic_corp_badge_case'
+        badge-no-background='@android:drawable/ic_corp_badge_no_background' >
+        <badge-labels>
+            <item res='@android:string/managed_profile_label_badge' />
+            <item res='@android:string/managed_profile_label_badge_2' />
+        </badge-labels>
+        <badge-colors>
+            <item res='@android:color/profile_badge_1' />
+            <item res='@android:color/profile_badge_2' />
+        </badge-colors>
+        <default-restrictions no_sms="true" no_outgoing_calls="true" />
+    </profile-type>
+
+    <profile-type
+        name="com.example.profilename"
+        max-allowed-per-parent="2" />
+</user-types>
+
+Mandatory attributes:
+    name
+
+Supported optional properties (to be used as shown in the example above) are as follows.
+For profile and full users:
+    default-restrictions (with values defined in UserRestrictionUtils.USER_RESTRICTIONS)
+For profile users only:
+    max-allowed-per-parent
+    icon-badge
+    badge-plain
+    badge-no-background
+    badge-labels
+    badge-colors
+
+See UserTypeFactory.java and UserTypeDetails.java for the meaning (and default values) of these
+fields.
+
+Any property that is specified overwrites the AOSP default. For example, if there is no
+default-restrictions element, then the AOSP defaults for that user type will be used; however, if
+there is a default-restrictions element, then the AOSP default restrictions will be completely
+ignored and will instead obey this configuration.
+
+If this file is updated, the properties of any pre-existing user types will be updated too.
+Note, however, that default-restrictions refers to the restrictions applied at the time of user
+creation; therefore, the active restrictions of any pre-existing users will not be updated.
+
+-->
+<user-types>
+</user-types>
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
index b906d84..ed613c3 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -176,14 +176,12 @@
                         mDevice.setPin(mPin);
                         break;
                     case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
-                        mDevice.setPasskey(mPasskey);
                         break;
                     case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
                     case BluetoothDevice.PAIRING_VARIANT_CONSENT:
                         mDevice.setPairingConfirmation(true);
                         break;
                     case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
-                        mDevice.setRemoteOutOfBandData();
                         break;
                 }
             } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c009f58..caae908 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -24,6 +24,7 @@
     ],
     static_libs: [
         "frameworks-base-testutils",
+        "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport
         "core-tests-support",
         "android-common",
         "frameworks-core-util-lib",
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 8c1c3b5..ae835e4 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -33,9 +33,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.HashMap;
-import java.util.Map;
-
 /** Tests that ensure appropriate settings are backed up. */
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -488,22 +485,20 @@
     }
 
     @Test
-    public void setProperties() {
-        Map<String, String> keyValues = new HashMap<>();
-        keyValues.put(KEY, VALUE);
-        keyValues.put(KEY2, VALUE2);
+    public void setProperties() throws DeviceConfig.BadConfigException {
+        Properties properties = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
+                .setString(KEY2, VALUE2).build();
 
-        DeviceConfig.setProperties(new Properties(NAMESPACE, keyValues));
-        Properties properties = DeviceConfig.getProperties(NAMESPACE);
+        DeviceConfig.setProperties(properties);
+        properties = DeviceConfig.getProperties(NAMESPACE);
         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
 
-        Map<String, String> newKeyValues = new HashMap<>();
-        newKeyValues.put(KEY, VALUE2);
-        newKeyValues.put(KEY3, VALUE3);
+        properties = new Properties.Builder(NAMESPACE).setString(KEY, VALUE2)
+                .setString(KEY3, VALUE3).build();
 
-        DeviceConfig.setProperties(new Properties(NAMESPACE, newKeyValues));
+        DeviceConfig.setProperties(properties);
         properties = DeviceConfig.getProperties(NAMESPACE);
         assertThat(properties.getKeyset()).containsExactly(KEY, KEY3);
         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE2);
@@ -514,18 +509,15 @@
     }
 
     @Test
-    public void setProperties_multipleNamespaces() {
-        Map<String, String> keyValues = new HashMap<>();
-        keyValues.put(KEY, VALUE);
-        keyValues.put(KEY2, VALUE2);
-
-        Map<String, String> keyValues2 = new HashMap<>();
-        keyValues2.put(KEY2, VALUE);
-        keyValues2.put(KEY3, VALUE2);
-
+    public void setProperties_multipleNamespaces() throws DeviceConfig.BadConfigException {
         final String namespace2 = "namespace2";
-        DeviceConfig.setProperties(new Properties(NAMESPACE, keyValues));
-        DeviceConfig.setProperties(new Properties(namespace2, keyValues2));
+        Properties properties1 = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
+                .setString(KEY2, VALUE2).build();
+        Properties properties2 = new Properties.Builder(namespace2).setString(KEY2, VALUE)
+                .setString(KEY3, VALUE2).build();
+
+        DeviceConfig.setProperties(properties1);
+        DeviceConfig.setProperties(properties2);
 
         Properties properties = DeviceConfig.getProperties(NAMESPACE);
         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
@@ -549,6 +541,26 @@
         deleteViaContentProvider(namespace2, KEY3);
     }
 
+    @Test
+    public void propertiesBuilder() {
+        boolean booleanValue = true;
+        int intValue = 123;
+        float floatValue = 4.56f;
+        long longValue = -789L;
+        String key4 = "key4";
+        String key5 = "key5";
+
+        Properties properties = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
+                .setBoolean(KEY2, booleanValue).setInt(KEY3, intValue).setLong(key4, longValue)
+                .setFloat(key5, floatValue).build();
+        assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
+        assertThat(properties.getString(KEY, "defaultValue")).isEqualTo(VALUE);
+        assertThat(properties.getBoolean(KEY2, false)).isEqualTo(booleanValue);
+        assertThat(properties.getInt(KEY3, 0)).isEqualTo(intValue);
+        assertThat(properties.getLong("key4", 0L)).isEqualTo(longValue);
+        assertThat(properties.getFloat("key5", 0f)).isEqualTo(floatValue);
+    }
+
     // TODO(mpape): resolve b/142727848 and re-enable listener tests
 //    @Test
 //    public void onPropertiesChangedListener_setPropertyCallback() throws InterruptedException {
diff --git a/core/tests/coretests/src/android/util/CloseGuardTest.java b/core/tests/coretests/src/android/util/CloseGuardTest.java
new file mode 100644
index 0000000..d86c7b7
--- /dev/null
+++ b/core/tests/coretests/src/android/util/CloseGuardTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import libcore.dalvik.system.CloseGuardSupport;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+/** Unit tests for {@link android.util.CloseGuard} */
+public class CloseGuardTest {
+
+    @Rule
+    public final TestRule rule = CloseGuardSupport.getRule();
+
+    @Test
+    public void testEnabled_NotOpen() throws Throwable {
+        ResourceOwner owner = new ResourceOwner();
+        assertUnreleasedResources(owner, 0);
+    }
+
+    @Test
+    public void testEnabled_OpenNotClosed() throws Throwable {
+        ResourceOwner owner = new ResourceOwner();
+        owner.open();
+        assertUnreleasedResources(owner, 1);
+    }
+
+    @Test
+    public void testEnabled_OpenThenClosed() throws Throwable {
+        ResourceOwner owner = new ResourceOwner();
+        owner.open();
+        owner.close();
+        assertUnreleasedResources(owner, 0);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testOpen_withNullMethodName_throwsNPE() throws Throwable {
+        CloseGuard closeGuard = new CloseGuard();
+        closeGuard.open(null);
+    }
+
+    private void assertUnreleasedResources(ResourceOwner owner, int expectedCount)
+            throws Throwable {
+        try {
+            CloseGuardSupport.getFinalizerChecker().accept(owner, expectedCount);
+        } finally {
+            // Close the resource so that CloseGuard does not generate a warning for real when it
+            // is actually finalized.
+            owner.close();
+        }
+    }
+
+    /**
+     * A test user of {@link CloseGuard}.
+     */
+    private static class ResourceOwner {
+
+        private final CloseGuard mCloseGuard;
+
+        ResourceOwner() {
+            mCloseGuard = new CloseGuard();
+        }
+
+        public void open() {
+            mCloseGuard.open("close");
+        }
+
+        public void close() {
+            mCloseGuard.close();
+        }
+
+        /**
+         * Make finalize public so that it can be tested directly without relying on garbage
+         * collection to trigger it.
+         */
+        @Override
+        public void finalize() throws Throwable {
+            mCloseGuard.warnIfOpen();
+            super.finalize();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 6bce651..0d5db6d 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -436,6 +436,22 @@
     }
 
     @Test
+    public void windowsChangedWithWindowsChangeA11yFocusedEvent_dontClearCache() {
+        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
+        mAccessibilityCache.add(nodeInfo);
+        AccessibilityEvent event = new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
+        event.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED);
+        mAccessibilityCache.onAccessibilityEvent(event);
+        AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1,
+                nodeInfo.getSourceNodeId());
+        try {
+            assertNotNull(cachedNode);
+        } finally {
+            nodeInfo.recycle();
+        }
+    }
+
+    @Test
     public void subTreeChangeEvent_clearsNodeAndChild() {
         AccessibilityEvent event = AccessibilityEvent
                 .obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index a2e0095..2a10443 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -48,7 +48,7 @@
     private UsageStatsManager mUsm;
 
     ChooserListAdapter getAdapter() {
-        return mChooserMultiProfilePagerAdapter.getCurrentListAdapter();
+        return mChooserMultiProfilePagerAdapter.getActiveListAdapter();
     }
 
     boolean getIsSelected() { return mIsSuccessfullySelected; }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 93357af..c5d2cfa 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -46,7 +46,7 @@
     }
 
     ResolverWrapperAdapter getAdapter() {
-        return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getCurrentListAdapter();
+        return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter();
     }
 
     @Override
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 43f65e3..183c04e 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -120,7 +120,7 @@
 
 prebuilt_etc {
     name: "privapp_whitelist_com.android.storagemanager",
-    product_specific: true,
+    system_ext_specific: true,
     sub_dir: "permissions",
     src: "com.android.storagemanager.xml",
     filename_from_src: true,
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index eff353c..3060476 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -7,12 +7,6 @@
       "group": "WM_DEBUG_KEEP_SCREEN_ON",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-2138637148": {
-      "message": "Clearing focused app, displayId=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/ActivityDisplay.java"
-    },
     "-2127842445": {
       "message": "Clearing startingData for token=%s",
       "level": "VERBOSE",
@@ -709,6 +703,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-603199586": {
+      "message": "Clearing focused app, displayId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-583031528": {
       "message": "%s",
       "level": "INFO",
@@ -1315,6 +1315,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
+    "584499099": {
+      "message": "Set focused app to: %s moveFocusNow=%b displayId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "585096182": {
       "message": "SURFACE isColorSpaceAgnostic=%b: %s",
       "level": "INFO",
@@ -1513,12 +1519,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
-    "917739349": {
-      "message": "Set focused app to: %s moveFocusNow=%b displayId=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_FOCUS_LIGHT",
-      "at": "com\/android\/server\/wm\/ActivityDisplay.java"
-    },
     "954470154": {
       "message": "FORCED DISPLAY SCALING DISABLED",
       "level": "INFO",
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 5ad93f4..2648008 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,7 +17,6 @@
 package android.graphics;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.graphics.fonts.FontVariationAxis;
 import android.text.TextUtils;
@@ -58,7 +57,6 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
     public long mNativePtr;
 
     // Points native font family builder. Must be zero after freezing this family.
@@ -67,7 +65,6 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
     public FontFamily() {
         mBuilderPtr = nInitBuilder(null, 0);
         mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
@@ -76,7 +73,6 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
     public FontFamily(@Nullable String[] langs, int variant) {
         final String langsString;
         if (langs == null || langs.length == 0) {
@@ -98,7 +94,6 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
     public boolean freeze() {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("This FontFamily is already frozen");
@@ -115,7 +110,6 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
     public void abortCreation() {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -127,7 +121,6 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
     public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
             int italic) {
         if (mBuilderPtr == 0) {
@@ -151,7 +144,6 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
     public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
             int weight, int italic) {
         if (mBuilderPtr == 0) {
@@ -179,7 +171,6 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
-    @UnsupportedAppUsage(trackingBug = 123768928)
     public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
             boolean isAsset, int ttcIndex, int weight, int isItalic,
             FontVariationAxis[] axes) {
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index 94499ce..8a7b623 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -204,10 +204,7 @@
                     break;
                 }
                 case USE_INDIVIDUAL_ATTESTATION: {
-                    //TODO: Add the Keymaster tag for requesting the use of individual
-                    //attestation certificate, which should be
-                    //KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION
-                    attestArgs.addBoolean(720);
+                    attestArgs.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION);
                     break;
                 }
                 default:
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 9c84634..00ceb2d 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -41,7 +41,7 @@
 
 void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas,
                                                      const SkiaDisplayList& displayList,
-                                                     int nestLevel) {
+                                                     int nestLevel) const {
     LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
     for (auto& child : displayList.mChildNodes) {
         const RenderProperties& childProperties = child.getNodeProperties();
@@ -132,7 +132,7 @@
     RenderNode& mNode;
 };
 
-void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
+void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const {
     RenderNode* renderNode = mRenderNode.get();
     MarkDraw _marker{*canvas, *renderNode};
 
@@ -230,7 +230,14 @@
             // we need to restrict the portion of the surface drawn to the size of the renderNode.
             SkASSERT(renderNode->getLayerSurface()->width() >= bounds.width());
             SkASSERT(renderNode->getLayerSurface()->height() >= bounds.height());
-            canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot().get(), bounds,
+
+            // If SKP recording is active save an annotation that indicates this drawImageRect
+            // could also be rendered with the commands saved at ID associated with this node.
+            if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
+                canvas->drawAnnotation(bounds, String8::format(
+                    "SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr);
+            }
+            canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot(), bounds,
                                   bounds, &paint);
 
             if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) {
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index 6ba8e59..6c390c3 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -58,7 +58,7 @@
      * projection receiver then all projected children (excluding direct children) will be drawn
      * last. Any projected node not matching those requirements will not be drawn by this function.
      */
-    void forceDraw(SkCanvas* canvas);
+    void forceDraw(SkCanvas* canvas) const;
 
     /**
      * Returns readonly render properties for this render node.
@@ -113,7 +113,7 @@
      * @param nestLevel should be always 0. Used to track how far we are from the receiver.
      */
     void drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
-                                     int nestLevel = 0);
+                                     int nestLevel = 0) const;
 
     /**
      * Applies the rendering properties of a view onto a SkCanvas.
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 0647977..11dc013 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -26,11 +26,11 @@
 #include <SkPictureRecorder.h>
 #include <SkSerialProcs.h>
 #include "LightingInfo.h"
-#include "TreeInfo.h"
 #include "VectorDrawable.h"
 #include "thread/CommonPool.h"
 #include "tools/SkSharingProc.h"
 #include "utils/TraceUtils.h"
+#include "utils/String8.h"
 
 #include <unistd.h>
 
@@ -90,58 +90,61 @@
         // only schedule repaint if node still on layer - possible it may have been
         // removed during a dropped frame, but layers may still remain scheduled so
         // as not to lose info on what portion is damaged
-        if (CC_LIKELY(layerNode->getLayerSurface() != nullptr)) {
-            SkASSERT(layerNode->getLayerSurface());
-            SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList();
-            if (!displayList || displayList->isEmpty()) {
-                ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
-                return;
+        if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+            continue;
+        }
+        SkASSERT(layerNode->getLayerSurface());
+        SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList();
+        if (!displayList || displayList->isEmpty()) {
+            ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
+            return;
+        }
+
+        const Rect& layerDamage = layers.entries()[i].damage;
+
+        SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
+
+        int saveCount = layerCanvas->save();
+        SkASSERT(saveCount == 1);
+
+        layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
+
+        // TODO: put localized light center calculation and storage to a drawable related code.
+        // It does not seem right to store something localized in a global state
+        // fix here and in recordLayers
+        const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
+        Vector3 transformedLightCenter(savedLightCenter);
+        // map current light center into RenderNode's coordinate space
+        layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
+        LightingInfo::setLightCenterRaw(transformedLightCenter);
+
+        const RenderProperties& properties = layerNode->properties();
+        const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+        if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
+            return;
+        }
+
+        ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
+                      bounds.height());
+
+        layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
+        layerCanvas->clear(SK_ColorTRANSPARENT);
+
+        RenderNodeDrawable root(layerNode, layerCanvas, false);
+        root.forceDraw(layerCanvas);
+        layerCanvas->restoreToCount(saveCount);
+
+        LightingInfo::setLightCenterRaw(savedLightCenter);
+
+        // cache the current context so that we can defer flushing it until
+        // either all the layers have been rendered or the context changes
+        GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
+        if (cachedContext.get() != currentContext) {
+            if (cachedContext.get()) {
+                ATRACE_NAME("flush layers (context changed)");
+                cachedContext->flush();
             }
-
-            const Rect& layerDamage = layers.entries()[i].damage;
-
-            SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
-
-            int saveCount = layerCanvas->save();
-            SkASSERT(saveCount == 1);
-
-            layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
-
-            // TODO: put localized light center calculation and storage to a drawable related code.
-            // It does not seem right to store something localized in a global state
-            const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
-            Vector3 transformedLightCenter(savedLightCenter);
-            // map current light center into RenderNode's coordinate space
-            layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
-            LightingInfo::setLightCenterRaw(transformedLightCenter);
-
-            const RenderProperties& properties = layerNode->properties();
-            const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
-            if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
-                return;
-            }
-
-            ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
-                          bounds.height());
-
-            layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
-            layerCanvas->clear(SK_ColorTRANSPARENT);
-
-            RenderNodeDrawable root(layerNode, layerCanvas, false);
-            root.forceDraw(layerCanvas);
-            layerCanvas->restoreToCount(saveCount);
-            LightingInfo::setLightCenterRaw(savedLightCenter);
-
-            // cache the current context so that we can defer flushing it until
-            // either all the layers have been rendered or the context changes
-            GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
-            if (cachedContext.get() != currentContext) {
-                if (cachedContext.get()) {
-                    ATRACE_NAME("flush layers (context changed)");
-                    cachedContext->flush();
-                }
-                cachedContext.reset(SkSafeRef(currentContext));
-            }
+            cachedContext.reset(SkSafeRef(currentContext));
         }
     }
 
@@ -275,12 +278,60 @@
     }
 }
 
-SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
+// recurse through the rendernode's children, add any nodes which are layers to the queue.
+static void collectLayers(RenderNode* node, LayerUpdateQueue* layers) {
+    SkiaDisplayList* dl = (SkiaDisplayList*)node->getDisplayList();
+    if (dl) {
+        const auto& prop = node->properties();
+        if (node->hasLayer()) {
+            layers->enqueueLayerWithDamage(node, Rect(prop.getWidth(), prop.getHeight()));
+        }
+        // The way to recurse through rendernodes is to call this with a lambda.
+        dl->updateChildren([&](RenderNode* child) { collectLayers(child, layers); });
+    }
+}
+
+// record the provided layers to the provided canvas as self-contained skpictures.
+static void recordLayers(const LayerUpdateQueue& layers,
+    SkCanvas* mskpCanvas) {
+    const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
+    // Record the commands to re-draw each dirty layer into an SkPicture
+    for (size_t i = 0; i < layers.entries().size(); i++) {
+        RenderNode* layerNode = layers.entries()[i].renderNode.get();
+        const Rect& layerDamage = layers.entries()[i].damage;
+        const RenderProperties& properties = layerNode->properties();
+
+        // Temporarily map current light center into RenderNode's coordinate space
+        Vector3 transformedLightCenter(savedLightCenter);
+        layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
+        LightingInfo::setLightCenterRaw(transformedLightCenter);
+
+        SkPictureRecorder layerRec;
+        auto* recCanvas = layerRec.beginRecording(properties.getWidth(),
+            properties.getHeight());
+        // This is not recorded but still causes clipping.
+        recCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
+        RenderNodeDrawable root(layerNode, recCanvas, false);
+        root.forceDraw(recCanvas);
+        // Now write this picture into the SKP canvas with an annotation indicating what it is
+        mskpCanvas->drawAnnotation(layerDamage.toSkRect(), String8::format(
+            "OffscreenLayerDraw|%" PRId64, layerNode->uniqueId()).c_str(), nullptr);
+        mskpCanvas->drawPicture(layerRec.finishRecordingAsPicture());
+    }
+    LightingInfo::setLightCenterRaw(savedLightCenter);
+}
+
+SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface, RenderNode* root,
+    const LayerUpdateQueue& dirtyLayers) {
     if (CC_LIKELY(!Properties::skpCaptureEnabled)) {
         return surface->getCanvas(); // Bail out early when capture is not turned on.
     }
     // Note that shouldStartNewFileCapture tells us if this is the *first* frame of a capture.
+    bool firstFrameOfAnim = false;
     if (shouldStartNewFileCapture() && mCaptureMode == CaptureMode::MultiFrameSKP) {
+        // set a reminder to record every layer near the end of this method, after we have set up
+        // the nway canvas.
+        firstFrameOfAnim = true;
         if (!setupMultiFrameCapture()) {
             return surface->getCanvas();
         }
@@ -309,6 +360,20 @@
     mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
     mNwayCanvas->addCanvas(surface->getCanvas());
     mNwayCanvas->addCanvas(pictureCanvas);
+
+    if (firstFrameOfAnim) {
+        // On the first frame of any mskp capture we want to record any layers that are needed in
+        // frame but may have been rendered offscreen before recording began.
+        // We do not maintain a list of all layers, since it isn't needed outside this rare,
+        // recording use case. Traverse the tree to find them and put them in this LayerUpdateQueue.
+        LayerUpdateQueue luq;
+        collectLayers(root, &luq);
+        recordLayers(luq, mNwayCanvas.get());
+    } else {
+        // on non-first frames, we record any normal layer draws (dirty regions)
+        recordLayers(dirtyLayers, mNwayCanvas.get());
+    }
+
     return mNwayCanvas.get();
 }
 
@@ -359,13 +424,13 @@
         Properties::skpCaptureEnabled = true;
     }
 
+    // Initialize the canvas for the current frame, that might be a recording canvas if SKP
+    // capture is enabled.
+    SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers);
+
     // draw all layers up front
     renderLayersImpl(layers, opaque);
 
-    // initialize the canvas for the current frame, that might be a recording canvas if SKP
-    // capture is enabled.
-    SkCanvas* canvas = tryCapture(surface.get());
-
     renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform);
 
     endCapture(surface.get());
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 7d575ad..af8414d 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -45,6 +45,8 @@
     void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
                       bool opaque, const LightInfo& lightInfo) override;
 
+    // If the given node didn't have a layer surface, or had one of the wrong size, this method
+    // creates a new one and returns true. Otherwise does nothing and returns false.
     bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
                              ErrorHandler* errorHandler) override;
 
@@ -92,7 +94,7 @@
 
     // Called every frame. Normally returns early with screen canvas.
     // But when capture is enabled, returns an nwaycanvas where commands are also recorded.
-    SkCanvas* tryCapture(SkSurface* surface);
+    SkCanvas* tryCapture(SkSurface* surface, RenderNode* root, const LayerUpdateQueue& dirtyLayers);
     // Called at the end of every frame, closes the recording if necessary.
     void endCapture(SkSurface* surface);
     // Determine if a new file-based capture should be started.
diff --git a/media/apex/java/android/media/MediaParser.java b/media/apex/java/android/media/MediaParser.java
index b83f445..ac66d1b 100644
--- a/media/apex/java/android/media/MediaParser.java
+++ b/media/apex/java/android/media/MediaParser.java
@@ -102,7 +102,8 @@
  *     public void onSeekMap(int i, @NonNull MediaFormat mediaFormat) { \/* Do nothing. *\/ }
  *
  *     \@Override
- *     public void onFormat(int i, @NonNull MediaFormat mediaFormat) {
+ *     public void onTrackData(int i, @NonNull TrackData trackData) {
+ *       MediaFormat mediaFormat = trackData.mediaFormat;
  *       if (videoTrackIndex == -1 && mediaFormat
  *           .getString(MediaFormat.KEY_MIME, \/* defaultValue= *\/ "").startsWith("video/")) {
  *         videoTrackIndex = i;
@@ -200,6 +201,24 @@
         }
     }
 
+    /** Holds information associated with a track. */
+    public static final class TrackData {
+
+        /** Holds {@link MediaFormat} information for the track. */
+        @NonNull public final MediaFormat mediaFormat;
+
+        /**
+         * Holds {@link DrmInitData} necessary to acquire keys associated with the track, or null if
+         * the track has no encryption data.
+         */
+        @Nullable public final DrmInitData drmInitData;
+
+        private TrackData(MediaFormat mediaFormat, DrmInitData drmInitData) {
+            this.mediaFormat = mediaFormat;
+            this.drmInitData = drmInitData;
+        }
+    }
+
     /** Defines a seek point in a media stream. */
     public static final class SeekPoint {
 
@@ -308,12 +327,12 @@
         void onTracksFound(int numberOfTracks);
 
         /**
-         * Called when the {@link MediaFormat} of the track is extracted from the stream.
+         * Called when new {@link TrackData} is extracted from the stream.
          *
-         * @param trackIndex The index of the track for which the {@link MediaFormat} was found.
-         * @param format The extracted {@link MediaFormat}.
+         * @param trackIndex The index of the track for which the {@link TrackData} was extracted.
+         * @param trackData The extracted {@link TrackData}.
          */
-        void onFormat(int trackIndex, @NonNull MediaFormat format);
+        void onTrackData(int trackIndex, @NonNull TrackData trackData);
 
         /**
          * Called to write sample data to the output.
@@ -674,7 +693,10 @@
 
         @Override
         public void format(Format format) {
-            mOutputConsumer.onFormat(mTrackIndex, toMediaFormat(format));
+            mOutputConsumer.onTrackData(
+                    mTrackIndex,
+                    new TrackData(
+                            toMediaFormat(format), toFrameworkDrmInitData(format.drmInitData)));
         }
 
         @Override
@@ -846,6 +868,12 @@
         return 0;
     }
 
+    private static DrmInitData toFrameworkDrmInitData(
+            com.google.android.exoplayer2.drm.DrmInitData drmInitData) {
+        // TODO: Implement.
+        return null;
+    }
+
     private static MediaCodec.CryptoInfo toCryptoInfo(TrackOutput.CryptoData encryptionData) {
         // TODO: Implement.
         return null;
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index c03e8e2..8de3e0a 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.media.audiopolicy.AudioProductStrategy;
@@ -178,6 +179,13 @@
      * utterances.
      */
     public final static int USAGE_ASSISTANT = 16;
+    /**
+     * @hide
+     * Usage value to use for assistant voice interaction with remote caller on Cell and VoIP calls.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public static final int USAGE_CALL_ASSISTANT = 17;
 
     /**
      * IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
@@ -254,6 +262,7 @@
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,    SUPPRESSIBLE_MEDIA);
         SUPPRESSIBLE_USAGES.put(USAGE_GAME,                              SUPPRESSIBLE_MEDIA);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT,                         SUPPRESSIBLE_MEDIA);
+        SUPPRESSIBLE_USAGES.put(USAGE_CALL_ASSISTANT,                    SUPPRESSIBLE_NEVER);
         /** default volume assignment is STREAM_MUSIC, handle unknown usage as media */
         SUPPRESSIBLE_USAGES.put(USAGE_UNKNOWN,                           SUPPRESSIBLE_MEDIA);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_SONIFICATION,           SUPPRESSIBLE_SYSTEM);
@@ -682,6 +691,7 @@
                 case USAGE_GAME:
                 case USAGE_VIRTUAL_SOURCE:
                 case USAGE_ASSISTANT:
+                case USAGE_CALL_ASSISTANT:
                     mUsage = usage;
                     break;
                 default:
@@ -1135,6 +1145,8 @@
                 return new String("USAGE_GAME");
             case USAGE_ASSISTANT:
                 return new String("USAGE_ASSISTANT");
+            case USAGE_CALL_ASSISTANT:
+                return new String("USAGE_CALL_ASSISTANT");
             default:
                 return new String("unknown usage " + usage);
         }
@@ -1238,6 +1250,7 @@
             case USAGE_ASSISTANCE_SONIFICATION:
                 return AudioSystem.STREAM_SYSTEM;
             case USAGE_VOICE_COMMUNICATION:
+            case USAGE_CALL_ASSISTANT:
                 return AudioSystem.STREAM_VOICE_CALL;
             case USAGE_VOICE_COMMUNICATION_SIGNALLING:
                 return fromGetVolumeControlStream ?
@@ -1302,6 +1315,7 @@
         USAGE_ASSISTANCE_SONIFICATION,
         USAGE_GAME,
         USAGE_ASSISTANT,
+        USAGE_CALL_ASSISTANT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AttributeUsage {}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 066bf25..df799fd 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -25,6 +25,7 @@
 import android.content.pm.PackageManager;
 import android.media.audiofx.AudioEffect;
 import android.media.audiopolicy.AudioMix;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -1288,7 +1289,8 @@
      * </ul>
      */
     public static int getPlatformType(Context context) {
-        if (context.getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
+        if (((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
+                .isVoiceCapable()) {
             return PLATFORM_VOICE;
         } else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
             return PLATFORM_TELEVISION;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 94a6f13..dead066 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -46,6 +46,8 @@
  * <tr><th>Name</th><th>Value Type</th><th>Description</th></tr>
  * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
  * <tr><td>{@link #KEY_MAX_INPUT_SIZE}</td><td>Integer</td><td>optional, maximum size of a buffer of input data</td></tr>
+ * <tr><td>{@link #KEY_PIXEL_ASPECT_RATIO_WIDTH}</td><td>Integer</td><td>optional, the pixel aspect ratio width</td></tr>
+ * <tr><td>{@link #KEY_PIXEL_ASPECT_RATIO_HEIGHT}</td><td>Integer</td><td>optional, the pixel aspect ratio height</td></tr>
  * <tr><td>{@link #KEY_BIT_RATE}</td><td>Integer</td><td><b>encoder-only</b>, desired bitrate in bits/second</td></tr>
  * </table>
  *
@@ -272,6 +274,18 @@
     public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
 
     /**
+     * A key describing the pixel aspect ratio width.
+     * The associated value is an integer
+     */
+    public static final String KEY_PIXEL_ASPECT_RATIO_WIDTH = "sar-width";
+
+    /**
+     * A key describing the pixel aspect ratio height.
+     * The associated value is an integer
+     */
+    public static final String KEY_PIXEL_ASPECT_RATIO_HEIGHT = "sar-height";
+
+    /**
      * A key describing the average bitrate in bits/sec.
      * The associated value is an integer
      */
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index dc400ad..61b3e76 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -26,9 +26,11 @@
 import android.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
 import android.os.Bundle;
@@ -67,7 +69,7 @@
     /**
      * @hide
      */
-    public SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService ) {
+    public SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService) {
         if (DBG) {
             Slog.i(TAG, "SoundTriggerManager created.");
         }
@@ -89,14 +91,22 @@
     }
 
     /**
-     * Returns the sound trigger model represented by the given UUID. An instance of {@link Model}
-     * is returned.
+     * Get {@link SoundTriggerManager.Model} which is registered with the passed UUID
+     *
+     * @param soundModelId UUID associated with a loaded model
+     * @return {@link SoundTriggerManager.Model} associated with UUID soundModelId
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    @Nullable
     public Model getModel(UUID soundModelId) {
         try {
-            return new Model(mSoundTriggerService.getSoundModel(
-                    new ParcelUuid(soundModelId)));
+            GenericSoundModel model =
+                    mSoundTriggerService.getSoundModel(new ParcelUuid(soundModelId));
+            if (model == null) {
+                return null;
+            }
+
+            return new Model(model);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -399,4 +409,80 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link SoundTriggerManager#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param soundModelId UUID of model to apply the parameter value to.
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int setParameter(@Nullable UUID soundModelId,
+            @ModelParams int modelParam, int value)
+            throws UnsupportedOperationException, IllegalArgumentException {
+        try {
+            return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam,
+                    value);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See {@link ModelParams} for parameter default values.
+     * {@link SoundTriggerManager#queryParameter} should be checked first before
+     * calling this method. Otherwise, an exception can be thrown.
+     *
+     * @param soundModelId UUID of model to get parameter
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     * @throws UnsupportedOperationException if hal or model do not support this API.
+     *         {@link SoundTriggerManager#queryParameter} should be checked first.
+     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+     *         {@link SoundTriggerManager#queryParameter} should be checked first.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public int getParameter(@NonNull UUID soundModelId,
+            @ModelParams int modelParam)
+            throws UnsupportedOperationException, IllegalArgumentException {
+        try {
+            return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link SoundTriggerManager#setParameter} or
+     * {@link SoundTriggerManager#getParameter}.
+     *
+     * @param soundModelId handle of model to get parameter
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    @Nullable
+    public ModelParamRange queryParameter(@Nullable UUID soundModelId,
+            @ModelParams int modelParam) {
+        try {
+            return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId),
+                    modelParam);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
old mode 100644
new mode 100755
index bd05184..f90c504
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -38,4 +38,5 @@
     void notifyHardwareRemoved(in TvInputHardwareInfo hardwareInfo);
     void notifyHdmiDeviceAdded(in HdmiDeviceInfo deviceInfo);
     void notifyHdmiDeviceRemoved(in HdmiDeviceInfo deviceInfo);
+    void notifyHdmiDeviceUpdated(in HdmiDeviceInfo deviceInfo);
 }
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
old mode 100644
new mode 100755
index ff69779..5c11ed9b
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -173,6 +173,12 @@
                 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_INPUT,
                         deviceInfo).sendToTarget();
             }
+
+            @Override
+            public void notifyHdmiDeviceUpdated(HdmiDeviceInfo deviceInfo) {
+                mServiceHandler.obtainMessage(ServiceHandler.DO_UPDATE_HDMI_INPUT,
+                        deviceInfo).sendToTarget();
+            }
         };
     }
 
@@ -257,6 +263,24 @@
         return null;
     }
 
+    /**
+     * Called when {@code deviceInfo} is updated.
+     *
+     * <p>The changes are usually cuased by the corresponding HDMI-CEC logical device.
+     *
+     * <p>The default behavior ignores all changes.
+     *
+     * <p>The TV input service responsible for {@code deviceInfo} can update the {@link TvInputInfo}
+     * object based on the updated {@code deviceInfo} (e.g. update the label based on the preferred
+     * device OSD name).
+     *
+     * @param deviceInfo the updated {@link HdmiDeviceInfo} object.
+     * @hide
+     */
+    @SystemApi
+    public void onHdmiDeviceUpdated(@NonNull HdmiDeviceInfo deviceInfo) {
+    }
+
     private boolean isPassthroughInput(String inputId) {
         if (mTvInputManager == null) {
             mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
@@ -1962,6 +1986,7 @@
         private static final int DO_REMOVE_HARDWARE_INPUT = 5;
         private static final int DO_ADD_HDMI_INPUT = 6;
         private static final int DO_REMOVE_HDMI_INPUT = 7;
+        private static final int DO_UPDATE_HDMI_INPUT = 8;
 
         private void broadcastAddHardwareInput(int deviceId, TvInputInfo inputInfo) {
             int n = mCallbacks.beginBroadcast();
@@ -2131,6 +2156,11 @@
                     }
                     return;
                 }
+                case DO_UPDATE_HDMI_INPUT: {
+                    HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
+                    onHdmiDeviceUpdated(deviceInfo);
+                    return;
+                }
                 default: {
                     Log.w(TAG, "Unhandled message code: " + msg.what);
                     return;
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index c8f0ff1..79e4d8a 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -38,7 +38,7 @@
 // kPlayOnCallingThread = true prior to R.
 // Changing to false means calls to play() are almost instantaneous instead of taking around
 // ~10ms to launch the AudioTrack. It is perhaps 100x faster.
-static constexpr bool kPlayOnCallingThread = false;
+static constexpr bool kPlayOnCallingThread = true;
 
 // Amount of time for a StreamManager thread to wait before closing.
 static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
@@ -170,6 +170,7 @@
                     if (stream->getSoundID() == soundID) {
                         ALOGV("%s: found soundID %d in restart queue", __func__, soundID);
                         newStream = stream;
+                        fromAvailableQueue = false;
                         break;
                     } else if (newStream == nullptr) {
                         ALOGV("%s: found stream in restart queue", __func__);
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index bffc6b9..03bd61a 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -35,6 +35,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
+import com.android.systemui.statusbar.car.CarShadeControllerImpl;
 import com.android.systemui.statusbar.car.CarStatusBar;
 import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -119,7 +120,7 @@
             KeyguardEnvironmentImpl keyguardEnvironment);
 
     @Binds
-    abstract ShadeController provideShadeController(CarStatusBar statusBar);
+    abstract ShadeController provideShadeController(CarShadeControllerImpl shadeController);
 
     @Provides
     @Singleton
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java
new file mode 100644
index 0000000..d1d352a
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.car;
+
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.car.notification.CarNotificationView;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.ShadeControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/** Car specific implementation of {@link com.android.systemui.statusbar.phone.ShadeController}. */
+@Singleton
+public class CarShadeControllerImpl extends ShadeControllerImpl {
+
+    @Inject
+    public CarShadeControllerImpl(CommandQueue commandQueue,
+            StatusBarStateController statusBarStateController,
+            StatusBarWindowController statusBarWindowController,
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            WindowManager windowManager,
+            Lazy<StatusBar> statusBarLazy,
+            Lazy<AssistManager> assistManagerLazy,
+            Lazy<BubbleController> bubbleControllerLazy) {
+        super(commandQueue, statusBarStateController, statusBarWindowController,
+                statusBarKeyguardViewManager, windowManager,
+                statusBarLazy, assistManagerLazy, bubbleControllerLazy);
+    }
+
+    @Override
+    public void animateCollapsePanels(int flags, boolean force, boolean delayed,
+            float speedUpFactor) {
+        super.animateCollapsePanels(flags, force, delayed, speedUpFactor);
+        if (!getCarStatusBar().isPanelExpanded()
+                || getCarNotificationView().getVisibility() == View.INVISIBLE) {
+            return;
+        }
+
+        mStatusBarWindowController.setStatusBarFocusable(false);
+        getCarStatusBar().getStatusBarWindowViewController().cancelExpandHelper();
+        getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
+
+        getCarStatusBar().animateNotificationPanel(getCarStatusBar().getClosingVelocity(), true);
+
+        if (!getCarStatusBar().isTracking()) {
+            mStatusBarWindowController.setPanelVisible(false);
+            getCarNotificationView().setVisibility(View.INVISIBLE);
+        }
+
+        getCarStatusBar().setPanelExpanded(false);
+    }
+
+    private CarStatusBar getCarStatusBar() {
+        return (CarStatusBar) mStatusBarLazy.get();
+    }
+
+    private CarNotificationView getCarNotificationView() {
+        return getCarStatusBar().getCarNotificationView();
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index c8532e0..fbc03c0 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -118,6 +118,7 @@
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarComponent;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -175,6 +176,7 @@
     private final Object mQueueLock = new Object();
     private final CarNavigationBarController mCarNavigationBarController;
     private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy;
+    private final ShadeController mShadeController;
     private final CarServiceProvider mCarServiceProvider;
 
     private DeviceProvisionedController mDeviceProvisionedController;
@@ -308,6 +310,7 @@
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
+            ShadeController shadeController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             DismissCallbackRegistry dismissCallbackRegistry,
@@ -385,6 +388,7 @@
                 dividerOptional,
                 lightsOutNotifController,
                 statusBarNotificationActivityStarterBuilder,
+                shadeController,
                 superStatusBarViewFactory,
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
@@ -392,6 +396,7 @@
         mScrimController = scrimController;
         mLockscreenLockIconController = lockscreenLockIconController;
         mDeviceProvisionedController = deviceProvisionedController;
+        mShadeController = shadeController;
         mCarServiceProvider = carServiceProvider;
         mPowerManagerHelperLazy = powerManagerHelperLazy;
         mFullscreenUserSwitcherLazy = fullscreenUserSwitcherLazy;
@@ -506,7 +511,7 @@
                     @Override
                     protected void close() {
                         if (mPanelExpanded) {
-                            animateCollapsePanels();
+                            mShadeController.animateCollapsePanels();
                         }
                     }
                 });
@@ -516,7 +521,7 @@
                     @Override
                     protected void close() {
                         if (mPanelExpanded) {
-                            animateCollapsePanels();
+                            mShadeController.animateCollapsePanels();
                         }
                     }
                 });
@@ -551,14 +556,19 @@
         mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
             if (launchResult == ActivityManager.START_TASK_TO_FRONT
                     || launchResult == ActivityManager.START_SUCCESS) {
-                animateCollapsePanels();
+                mShadeController.animateCollapsePanels();
             }
         });
         CarNotificationListener carNotificationListener = new CarNotificationListener();
         mCarUxRestrictionManagerWrapper = new CarUxRestrictionManagerWrapper();
 
         mNotificationDataManager = new NotificationDataManager();
-        mNotificationDataManager.setOnUnseenCountUpdateListener(this::onUnseenCountUpdate);
+
+        mNotificationDataManager.setOnUnseenCountUpdateListener(() -> {
+            if (mNotificationDataManager != null) {
+                onUseenCountUpdate(mNotificationDataManager.getUnseenNotificationCount());
+            }
+        });
 
         mEnableHeadsUpNotificationWhenNotificationShadeOpen = mContext.getResources().getBoolean(
                 R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
@@ -680,15 +690,13 @@
     }
 
     /**
-     * This method is called whenever there is an update to the number of unseen notifications.
-     * This method can be extended by OEMs to customize the desired logic.
+     * This method is automatically called whenever there is an update to the number of unseen
+     * notifications. This method can be extended by OEMs to customize the desired logic.
      */
-    protected void onUnseenCountUpdate() {
-        if (mNotificationDataManager != null) {
-            boolean hasUnseen = mNotificationDataManager.getUnseenNotificationCount() > 0;
-            mCarNavigationBarController.toggleAllNotificationsUnseenIndicator(
-                    mDeviceProvisionedController.isCurrentUserSetup(), hasUnseen);
-        }
+    protected void onUseenCountUpdate(int unseenNotificationCount) {
+        boolean hasUnseen = unseenNotificationCount > 0;
+        mCarNavigationBarController.toggleAllNotificationsUnseenIndicator(
+                mDeviceProvisionedController.isCurrentUserSetup(), hasUnseen);
     }
 
     /**
@@ -712,25 +720,16 @@
         setPanelExpanded(true);
     }
 
-    @Override
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed,
-            float speedUpFactor) {
-        super.animateCollapsePanels(flags, force, delayed, speedUpFactor);
-        if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
-            return;
-        }
-        mStatusBarWindowController.setStatusBarFocusable(false);
-        mStatusBarWindowViewController.cancelExpandHelper();
-        mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
+    public CarNotificationView getCarNotificationView() {
+        return mNotificationView;
+    }
 
-        animateNotificationPanel(mClosingVelocity, true);
+    public float getClosingVelocity() {
+        return mClosingVelocity;
+    }
 
-        if (!mIsTracking) {
-            mStatusBarWindowController.setPanelVisible(false);
-            mNotificationView.setVisibility(View.INVISIBLE);
-        }
-
-        setPanelExpanded(false);
+    public boolean isTracking() {
+        return mIsTracking;
     }
 
     private void maybeCompleteAnimation(MotionEvent event) {
@@ -749,7 +748,7 @@
      * close the notification shade completely with a velocity. If the animation is to close the
      * notification shade this method also makes the view invisible after animation ends.
      */
-    private void animateNotificationPanel(float velocity, boolean isClosing) {
+    void animateNotificationPanel(float velocity, boolean isClosing) {
         float to = 0;
         if (!isClosing) {
             to = mNotificationView.getHeight();
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index eff60fa..ff4dc9c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -78,6 +78,7 @@
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBarComponent;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -183,6 +184,7 @@
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
+            ShadeController shadeController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             DismissCallbackRegistry dismissCallbackRegistry,
@@ -259,6 +261,7 @@
                 superStatusBarViewFactory,
                 lightsOutNotifController,
                 statusBarNotificationActivityStarterBuilder,
+                shadeController,
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
                 dismissCallbackRegistry,
diff --git a/packages/Incremental/NativeAdbDataLoader/Android.bp b/packages/Incremental/NativeAdbDataLoader/Android.bp
new file mode 100644
index 0000000..5d7b5b6
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/Android.bp
@@ -0,0 +1,22 @@
+// Copyright 2019, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_app {
+    name: "NativeAdbDataLoaderService",
+    srcs: ["src/**/*.java"],
+    jni_libs: [ "libnativeadbdataloaderservice_jni"],
+    privileged: true,
+    certificate: "platform",
+    platform_apis: true,
+}
diff --git a/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml
new file mode 100644
index 0000000..a06dc54
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          coreApp="true"
+          package="com.android.incremental.nativeadb"
+          android:sharedUserId="android.uid.system">
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:label="@string/app_name"
+                 android:directBootAware="true">
+
+        <service android:enabled="true"
+                 android:name="com.android.incremental.nativeadb.NativeAdbDataLoaderService"
+                 android:label="@string/app_name"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.LOAD_DATA" />
+           </intent-filter>
+        </service>
+    </application>
+
+</manifest>
diff --git a/packages/Incremental/NativeAdbDataLoader/jni/Android.bp b/packages/Incremental/NativeAdbDataLoader/jni/Android.bp
new file mode 100644
index 0000000..0fcfd28
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/jni/Android.bp
@@ -0,0 +1,37 @@
+// Copyright 2019, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_shared {
+    name: "libnativeadbdataloaderservice_jni",
+    cpp_std: "c++2a",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+        "-Wno-unused-parameter",
+    ],
+
+    srcs: ["com_android_incremental_nativeadb_DataLoaderService.cpp"],
+
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libincfs",
+        "libdataloader",
+        "liblog",
+        "libnativehelper",
+        "libutils",
+    ],
+}
diff --git a/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp b/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp
new file mode 100644
index 0000000..de92fcd5
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_ADB
+#define LOG_TAG "NativeAdbDataLoaderService"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
+#include <android-base/unique_fd.h>
+#include <cutils/trace.h>
+#include <fcntl.h>
+#include <sys/eventfd.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <utils/Log.h>
+
+#include <charconv>
+#include <string>
+#include <thread>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "dataloader.h"
+
+#ifndef _WIN32
+#include <endian.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#else
+#define be32toh(x) _byteswap_ulong(x)
+#define be16toh(x) _byteswap_ushort(x)
+#endif
+
+namespace {
+
+using android::base::unique_fd;
+
+using namespace std::literals;
+
+using BlockSize = int16_t;
+using FileId = int16_t;
+using BlockIdx = int32_t;
+using NumBlocks = int32_t;
+using CompressionType = int16_t;
+using RequestType = int16_t;
+
+static constexpr int COMMAND_SIZE = 2 + 2 + 4;     // bytes
+static constexpr int HEADER_SIZE = 2 + 2 + 4 + 2;  // bytes
+static constexpr std::string_view OKAY = "OKAY"sv;
+
+static constexpr auto PollTimeoutMs = 5000;
+
+static constexpr auto ReadLogBufferSize = 128 * 1024 * 1024;
+static constexpr auto ReadLogMaxEntrySize = 128;
+
+struct BlockHeader {
+    FileId fileId = -1;
+    CompressionType compressionType = -1;
+    BlockIdx blockIdx = -1;
+    BlockSize blockSize = -1;
+} __attribute__((packed));
+
+static_assert(sizeof(BlockHeader) == HEADER_SIZE);
+
+static constexpr RequestType EXIT = 0;
+static constexpr RequestType BLOCK_MISSING = 1;
+static constexpr RequestType PREFETCH = 2;
+
+struct RequestCommand {
+    RequestType requestType;
+    FileId fileId;
+    BlockIdx blockIdx;
+} __attribute__((packed));
+
+static_assert(COMMAND_SIZE == sizeof(RequestCommand));
+
+static bool sendRequest(int fd,
+                        RequestType requestType,
+                        FileId fileId = -1,
+                        BlockIdx blockIdx = -1) {
+    const RequestCommand command{
+            .requestType = static_cast<int16_t>(be16toh(requestType)),
+            .fileId = static_cast<int16_t>(be16toh(fileId)),
+            .blockIdx = static_cast<int32_t>(be32toh(blockIdx))};
+    return android::base::WriteFully(fd, &command, sizeof(command));
+}
+
+static int waitForDataOrSignal(int fd, int event_fd) {
+    struct pollfd pfds[2] = {{fd, POLLIN, 0}, {event_fd, POLLIN, 0}};
+    // Wait indefinitely until either data is ready or stop signal is received
+    int res = poll(pfds, 2, PollTimeoutMs);
+    if (res <= 0) {
+        return res;
+    }
+    // First check if there is a stop signal
+    if (pfds[1].revents == POLLIN) {
+        return event_fd;
+    }
+    // Otherwise check if incoming data is ready
+    if (pfds[0].revents == POLLIN) {
+        return fd;
+    }
+    return -1;
+}
+
+static bool readChunk(int fd, std::vector<uint8_t>& data) {
+    int32_t size;
+    if (!android::base::ReadFully(fd, &size, sizeof(size))) {
+        return false;
+    }
+    size = int32_t(be32toh(size));
+    if (size <= 0) {
+        return false;
+    }
+    data.resize(size);
+    return android::base::ReadFully(fd, data.data(), data.size());
+}
+
+static BlockHeader readHeader(std::span<uint8_t>& data) {
+    BlockHeader header;
+    if (data.size() < sizeof(header)) {
+        return header;
+    }
+
+    header.fileId = static_cast<FileId>(
+            be16toh(*reinterpret_cast<uint16_t*>(&data[0])));
+    header.compressionType = static_cast<CompressionType>(
+            be16toh(*reinterpret_cast<uint16_t*>(&data[2])));
+    header.blockIdx = static_cast<BlockIdx>(
+            be32toh(*reinterpret_cast<uint32_t*>(&data[4])));
+    header.blockSize = static_cast<BlockSize>(
+            be16toh(*reinterpret_cast<uint16_t*>(&data[8])));
+    data = data.subspan(sizeof(header));
+
+    return header;
+}
+
+static std::string extractPackageName(const std::string& staticArgs) {
+    static constexpr auto kPrefix = "package="sv;
+    static constexpr auto kSuffix = "&"sv;
+
+    const auto startPos = staticArgs.find(kPrefix);
+    if (startPos == staticArgs.npos || startPos + kPrefix.size() >= staticArgs.size()) {
+        return {};
+    }
+    const auto endPos = staticArgs.find(kSuffix, startPos + kPrefix.size());
+    return staticArgs.substr(startPos + kPrefix.size(),
+                            endPos == staticArgs.npos ? staticArgs.npos
+                                                     : (endPos - (startPos + kPrefix.size())));
+}
+
+class AdbDataLoader : public android::dataloader::DataLoader {
+private:
+    // Lifecycle.
+    bool onCreate(const android::dataloader::DataLoaderParams& params,
+                  android::dataloader::FilesystemConnectorPtr ifs,
+                  android::dataloader::StatusListenerPtr statusListener,
+                  android::dataloader::ServiceConnectorPtr,
+                  android::dataloader::ServiceParamsPtr) final {
+        CHECK(ifs) << "ifs can't be null";
+        CHECK(statusListener) << "statusListener can't be null";
+        ALOGE("[AdbDataLoader] onCreate: %s/%s/%d", params.staticArgs().c_str(),
+              params.packageName().c_str(), (int)params.dynamicArgs().size());
+
+        if (params.dynamicArgs().empty()) {
+            ALOGE("[AdbDataLoader] Invalid DataLoaderParams. Need in/out FDs.");
+            return false;
+        }
+        for (auto const& namedFd : params.dynamicArgs()) {
+            if (namedFd.name == "inFd") {
+                mInFd.reset(dup(namedFd.fd));
+            }
+            if (namedFd.name == "outFd") {
+                mOutFd.reset(dup(namedFd.fd));
+            }
+        }
+        if (mInFd < 0 || mOutFd < 0) {
+            ALOGE("[AdbDataLoader] Failed to dup FDs.");
+            return false;
+        }
+
+        mEventFd.reset(eventfd(0, EFD_CLOEXEC));
+        if (mEventFd < 0) {
+            ALOGE("[AdbDataLoader] Failed to create eventfd.");
+            return false;
+        }
+
+        std::string logFile;
+        if (const auto packageName = extractPackageName(params.staticArgs()); !packageName.empty()) {
+            logFile = android::base::GetProperty("adb.readlog." + packageName, "");
+        }
+        if (logFile.empty()) {
+            logFile = android::base::GetProperty("adb.readlog", "");
+        }
+        if (!logFile.empty()) {
+            int flags = O_WRONLY | O_CREAT | O_CLOEXEC;
+            mReadLogFd.reset(
+                    TEMP_FAILURE_RETRY(open(logFile.c_str(), flags, 0666)));
+        }
+
+        mIfs = ifs;
+        mStatusListener = statusListener;
+        ALOGE("[AdbDataLoader] Successfully created data loader.");
+        return true;
+    }
+
+    bool onStart() final {
+        char okay_buf[OKAY.size()];
+        if (!android::base::ReadFully(mInFd, okay_buf, OKAY.size())) {
+            ALOGE("[AdbDataLoader] Failed to receive OKAY. Abort.");
+            return false;
+        }
+        if (std::string_view(okay_buf, OKAY.size()) != OKAY) {
+            ALOGE("[AdbDataLoader] Received '%.*s', expecting '%.*s'",
+                  (int)OKAY.size(), okay_buf, (int)OKAY.size(), OKAY.data());
+            return false;
+        }
+
+        mReceiverThread = std::thread([this]() { receiver(); });
+        ALOGI("[AdbDataLoader] started loading...");
+        return true;
+    }
+
+    void onStop() final {
+        mStopReceiving = true;
+        eventfd_write(mEventFd, 1);
+        if (mReceiverThread.joinable()) {
+            mReceiverThread.join();
+        }
+    }
+
+    void onDestroy() final {
+        ALOGE("[AdbDataLoader] Sending EXIT to server.");
+        sendRequest(mOutFd, EXIT);
+        // Make sure the receiver thread was stopped
+        CHECK(!mReceiverThread.joinable());
+
+        mInFd.reset();
+        mOutFd.reset();
+
+        mNodeToMetaMap.clear();
+        mIdToNodeMap.clear();
+
+        flushReadLog();
+        mReadLogFd.reset();
+    }
+
+    // IFS callbacks.
+    void onPendingReads(const android::dataloader::PendingReads& pendingReads) final {
+        std::lock_guard lock{mMapsMutex};
+        CHECK(mIfs);
+        for (auto&& pendingRead : pendingReads) {
+            const android::dataloader::Inode ino = pendingRead.file_ino;
+            const auto blockIdx =
+                    static_cast<BlockIdx>(pendingRead.block_index);
+            /*
+            ALOGI("[AdbDataLoader] Missing: %d", (int) blockIdx);
+            */
+            auto fileIdOr = getFileId(ino);
+            if (!fileIdOr) {
+                ALOGE("[AdbDataLoader] Failed to handle event for inode=%d. "
+                      "Ignore.",
+                      static_cast<int>(ino));
+                continue;
+            }
+            const FileId fileId = *fileIdOr;
+            if (mRequestedFiles.insert(fileId).second) {
+                if (!sendRequest(mOutFd, PREFETCH, fileId, blockIdx)) {
+                    ALOGE("[AdbDataLoader] Failed to request prefetch for "
+                          "inode=%d. Ignore.",
+                          static_cast<int>(ino));
+                    mRequestedFiles.erase(fileId);
+                    mStatusListener->reportStatus(
+                            INCREMENTAL_DATA_LOADER_NO_CONNECTION);
+                }
+            }
+            sendRequest(mOutFd, BLOCK_MISSING, fileId, blockIdx);
+        }
+    }
+
+    struct TracedRead {
+        uint64_t timestampUs;
+        uint64_t fileIno;
+        uint32_t firstBlockIdx;
+        uint32_t count;
+    };
+    void onPageReads(const android::dataloader::PageReads& pageReads) final {
+        auto trace = atrace_is_tag_enabled(ATRACE_TAG);
+        auto log = mReadLogFd != -1;
+        if (CC_LIKELY(!(trace || log))) {
+            return;
+        }
+
+        TracedRead last = {0, 0, 0, 0};
+        std::lock_guard lock{mMapsMutex};
+        for (auto&& read : pageReads) {
+            if (read.file_ino != last.fileIno ||
+                read.block_index != last.firstBlockIdx + last.count) {
+                traceOrLogRead(last, trace, log);
+                last = {read.timestamp_us, read.file_ino, read.block_index, 1};
+            } else {
+                ++last.count;
+            }
+        }
+        traceOrLogRead(last, trace, log);
+    }
+    void onFileCreated(android::dataloader::Inode inode, const android::dataloader::RawMetadata& metadata) {
+    }
+
+private:
+    void receiver() {
+        std::vector<uint8_t> data;
+        std::vector<incfs_new_data_block> instructions;
+        while (!mStopReceiving) {
+            const int res = waitForDataOrSignal(mInFd, mEventFd);
+            if (res == 0) {
+                flushReadLog();
+                continue;
+            }
+            if (res < 0) {
+                ALOGE("[AdbDataLoader] failed to poll. Abort.");
+                mStatusListener->reportStatus(INCREMENTAL_DATA_LOADER_NO_CONNECTION);
+                break;
+            }
+            if (res == mEventFd) {
+                ALOGE("[AdbDataLoader] received stop signal. Exit.");
+                break;
+            }
+            if (!readChunk(mInFd, data)) {
+                ALOGE("[AdbDataLoader] failed to read a message. Abort.");
+                mStatusListener->reportStatus(INCREMENTAL_DATA_LOADER_NO_CONNECTION);
+                break;
+            }
+            auto remainingData = std::span(data);
+            while (!remainingData.empty()) {
+                auto header = readHeader(remainingData);
+                if (header.fileId == -1 && header.compressionType == 0 &&
+                    header.blockIdx == 0 && header.blockSize == 0) {
+                    ALOGI("[AdbDataLoader] stop signal received. Sending "
+                          "exit command (remaining bytes: %d).",
+                          int(remainingData.size()));
+
+                    sendRequest(mOutFd, EXIT);
+                    mStopReceiving = true;
+                    break;
+                }
+                if (header.fileId < 0 || header.blockSize <= 0 ||
+                    header.compressionType < 0 || header.blockIdx < 0) {
+                    ALOGE("[AdbDataLoader] invalid header received. Abort.");
+                    mStopReceiving = true;
+                    break;
+                }
+                const android::dataloader::Inode ino = mIdToNodeMap[header.fileId];
+                if (!ino) {
+                    ALOGE("Unknown data destination for file ID %d. "
+                          "Ignore.",
+                          header.fileId);
+                    continue;
+                }
+                auto inst = incfs_new_data_block{
+                        .file_ino = static_cast<__aligned_u64>(ino),
+                        .block_index = static_cast<uint32_t>(header.blockIdx),
+                        .data_len = static_cast<uint16_t>(header.blockSize),
+                        .data = reinterpret_cast<uint64_t>(
+                                remainingData.data()),
+                        .compression =
+                                static_cast<uint8_t>(header.compressionType)};
+                instructions.push_back(inst);
+                remainingData = remainingData.subspan(header.blockSize);
+            }
+            writeInstructions(instructions);
+        }
+        writeInstructions(instructions);
+        flushReadLog();
+    }
+
+    void writeInstructions(std::vector<incfs_new_data_block>& instructions) {
+        auto res = this->mIfs->writeBlocks(instructions.data(),
+                                           instructions.size());
+        if (res != instructions.size()) {
+            ALOGE("[AdbDataLoader] failed to write data to Incfs (res=%d when "
+                  "expecting %d)",
+                  res, int(instructions.size()));
+        }
+        instructions.clear();
+    }
+
+    struct MetaPair {
+        android::dataloader::RawMetadata meta;
+        FileId fileId;
+    };
+
+    MetaPair* updateMapsForFile(android::dataloader::Inode ino) {
+        android::dataloader::RawMetadata meta = mIfs->getRawMetadata(ino);
+        FileId fileId;
+        auto res =
+                std::from_chars(meta.data(), meta.data() + meta.size(), fileId);
+        if (res.ec != std::errc{} || fileId < 0) {
+            ALOGE("[AdbDataLoader] Invalid metadata for inode=%d (%s)",
+                  static_cast<int>(ino), meta.data());
+            return nullptr;
+        }
+        mIdToNodeMap[fileId] = ino;
+        auto& metaPair = mNodeToMetaMap[ino];
+        metaPair.meta = std::move(meta);
+        metaPair.fileId = fileId;
+        return &metaPair;
+    }
+
+    android::dataloader::RawMetadata* getMeta(android::dataloader::Inode ino) {
+        auto it = mNodeToMetaMap.find(ino);
+        if (it != mNodeToMetaMap.end()) {
+            return &it->second.meta;
+        }
+
+        auto metaPair = updateMapsForFile(ino);
+        if (!metaPair) {
+            return nullptr;
+        }
+
+        return &metaPair->meta;
+    }
+
+    FileId* getFileId(android::dataloader::Inode ino) {
+        auto it = mNodeToMetaMap.find(ino);
+        if (it != mNodeToMetaMap.end()) {
+            return &it->second.fileId;
+        }
+
+        auto* metaPair = updateMapsForFile(ino);
+        if (!metaPair) {
+            return nullptr;
+        }
+
+        return &metaPair->fileId;
+    }
+
+    void traceOrLogRead(const TracedRead& read, bool trace, bool log) {
+        if (!read.count) {
+            return;
+        }
+        if (trace) {
+            auto* meta = getMeta(read.fileIno);
+            auto str = android::base::StringPrintf(
+                    "page_read: index=%lld count=%lld meta=%.*s",
+                    static_cast<long long>(read.firstBlockIdx),
+                    static_cast<long long>(read.count),
+                    meta ? int(meta->size()) : 0, meta ? meta->data() : "");
+            ATRACE_BEGIN(str.c_str());
+            ATRACE_END();
+        }
+        if (log) {
+            mReadLog.reserve(ReadLogBufferSize);
+
+            auto fileId = getFileId(read.fileIno);
+            android::base::StringAppendF(
+                    &mReadLog, "%lld:%lld:%lld:%lld\n",
+                    static_cast<long long>(read.timestampUs),
+                    static_cast<long long>(fileId ? *fileId : -1),
+                    static_cast<long long>(read.firstBlockIdx),
+                    static_cast<long long>(read.count));
+
+            if (mReadLog.size() >= mReadLog.capacity() - ReadLogMaxEntrySize) {
+                flushReadLog();
+            }
+        }
+    }
+
+    void flushReadLog() {
+        if (mReadLog.empty() || mReadLogFd == -1) {
+            return;
+        }
+
+        android::base::WriteStringToFd(mReadLog, mReadLogFd);
+        mReadLog.clear();
+    }
+
+private:
+    android::dataloader::FilesystemConnectorPtr mIfs = nullptr;
+    android::dataloader::StatusListenerPtr mStatusListener = nullptr;
+    android::base::unique_fd mInFd;
+    android::base::unique_fd mOutFd;
+    android::base::unique_fd mEventFd;
+    android::base::unique_fd mReadLogFd;
+    std::string mReadLog;
+    std::thread mReceiverThread;
+    std::mutex mMapsMutex;
+    std::unordered_map<android::dataloader::Inode, MetaPair> mNodeToMetaMap GUARDED_BY(mMapsMutex);
+    std::unordered_map<FileId, android::dataloader::Inode> mIdToNodeMap GUARDED_BY(mMapsMutex);
+    /** Tracks which files have been requested */
+    std::unordered_set<FileId> mRequestedFiles;
+    std::atomic<bool> mStopReceiving = false;
+};
+
+}  // namespace
+
+int JNI_OnLoad(JavaVM* jvm, void* /* reserved */) {
+  android::dataloader::DataLoader::initialize(
+            [](auto) { return std::make_unique<AdbDataLoader>(); });
+    return JNI_VERSION_1_6;
+}
diff --git a/packages/Incremental/NativeAdbDataLoader/res/values/strings.xml b/packages/Incremental/NativeAdbDataLoader/res/values/strings.xml
new file mode 100644
index 0000000..9921ae6
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Name of the Data Loader Service. [CHAR LIMIT=40] -->
+    <string name="app_name">Native Adb Data Loader Service</string>
+</resources>
diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
new file mode 100644
index 0000000..1f88114
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.incremental.nativeadb;
+
+import android.service.incremental.IncrementalDataLoaderService;
+
+/** This code is used for testing only. */
+public class NativeAdbDataLoaderService extends IncrementalDataLoaderService {
+    public static final String TAG = "NativeAdbDataLoaderService";
+    static {
+        System.loadLibrary("nativeadbdataloaderservice_jni");
+    }
+
+    @Override
+    public DataLoader onCreateDataLoader() {
+        return null;
+    }
+}
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 39c55fd..9b4a6d6 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -140,8 +140,6 @@
         <item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx">aptX™</xliff:g> audio</item>
         <item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx_hd">aptX™ HD</xliff:g> audio</item>
         <item>LDAC</item>
-        <item>Enable Optional Codecs</item>
-        <item>Disable Optional Codecs</item>
     </string-array>
 
     <!-- Values for Bluetooth Audio Codec selection preference. -->
@@ -152,8 +150,6 @@
         <item>2</item>
         <item>3</item>
         <item>4</item>
-        <item>5</item>
-        <item>6</item>
     </string-array>
 
     <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
@@ -164,8 +160,6 @@
         <item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx">aptX™</xliff:g> audio</item>
         <item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx_hd">aptX™ HD</xliff:g> audio</item>
         <item>LDAC</item>
-        <item>Enable Optional Codecs</item>
-        <item>Disable Optional Codecs</item>
     </string-array>
 
     <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index e660e43..d3f9cd4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -116,7 +116,7 @@
         }
         return new Pair<>(
                 getBluetoothDrawable(context,
-                        com.android.internal.R.drawable.ic_settings_bluetooth),
+                        com.android.internal.R.drawable.ic_settings_bluetooth).mutate(),
                 context.getString(R.string.bluetooth_talkback_bluetooth));
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 0666596..747ceb1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -721,12 +721,8 @@
 
         refresh();
 
-        if (bondState == BluetoothDevice.BOND_BONDED) {
-            if (mDevice.isBluetoothDock()) {
-                onBondingDockConnect();
-            } else if (mDevice.isBondingInitiatedLocally()) {
-                connect(false);
-            }
+        if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) {
+            connect(false);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
index 7162121..387bae1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -1,8 +1,7 @@
 # Default reviewers for this and subdirectories.
-asapperstein@google.com
-asargent@google.com
-eisenbach@google.com
-jackqdyulei@google.com
 siyuanh@google.com
+hughchen@google.com
+timhypeng@google.com
+robertluo@google.com
 
-# Emergency approvers in case the above are not available
\ No newline at end of file
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 5cf44e1..a82231a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -23,6 +23,9 @@
 import android.text.TextUtils;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import java.util.ArrayList;
@@ -132,34 +135,44 @@
         return ((Instrumentable) object).getMetricsCategory();
     }
 
-    public void logDashboardStartIntent(Context context, Intent intent,
-            int sourceMetricsCategory) {
+    /**
+     * Logs an event when the preference is clicked.
+     *
+     * @return true if the preference is loggable, otherwise false
+     */
+    public boolean logClickedPreference(@NonNull Preference preference, int sourceMetricsCategory) {
+        if (preference == null) {
+            return false;
+        }
+        return logSettingsTileClick(preference.getKey(), sourceMetricsCategory)
+                || logStartedIntent(preference.getIntent(), sourceMetricsCategory)
+                || logSettingsTileClick(preference.getFragment(), sourceMetricsCategory);
+    }
+
+    /**
+     * Logs an event when the intent is started.
+     *
+     * @return true if the intent is loggable, otherwise false
+     */
+    public boolean logStartedIntent(Intent intent, int sourceMetricsCategory) {
         if (intent == null) {
-            return;
+            return false;
         }
         final ComponentName cn = intent.getComponent();
-        if (cn == null) {
-            final String action = intent.getAction();
-            if (TextUtils.isEmpty(action)) {
-                // Not loggable
-                return;
-            }
-            action(sourceMetricsCategory,
-                    MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
-                    SettingsEnums.PAGE_UNKNOWN,
-                    action,
-                    0);
-            return;
-        } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) {
-            // Going to a Setting internal page, skip click logging in favor of page's own
-            // visibility logging.
-            return;
+        return logSettingsTileClick(cn != null ? cn.flattenToString() : intent.getAction(),
+                sourceMetricsCategory);
+    }
+
+    private boolean logSettingsTileClick(String logKey, int sourceMetricsCategory) {
+        if (TextUtils.isEmpty(logKey)) {
+            // Not loggable
+            return false;
         }
         action(sourceMetricsCategory,
                 MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
                 SettingsEnums.PAGE_UNKNOWN,
-                cn.flattenToString(),
+                logKey,
                 0);
+        return true;
     }
-
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 49e214b..443543b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -1395,7 +1395,7 @@
         mConfig = new WifiConfiguration();
         mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
 
-        if (security == SECURITY_NONE || !getWifiManager().isEasyConnectSupported()) {
+        if (security == SECURITY_NONE) {
             mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
         } else {
             mConfig.allowedKeyManagement.set(KeyMgmt.OWE);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 2a70506..3818057 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -1713,4 +1713,21 @@
                 .setScanResults(new ArrayList<ScanResult>(Arrays.asList(scanResult)))
                 .build();
     }
+
+    @Test
+    public void testGenerateOpenNetworkConfig_oweNotSupported_shouldGetCorrectSecurity() {
+        when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+        AccessPoint oweAccessPoint = new TestAccessPointBuilder(mMockContext)
+                .setSecurity(AccessPoint.SECURITY_OWE).build();
+        AccessPoint noneAccessPoint = new TestAccessPointBuilder(mMockContext)
+                .setSecurity(AccessPoint.SECURITY_NONE).build();
+
+        oweAccessPoint.generateOpenNetworkConfig();
+        noneAccessPoint.generateOpenNetworkConfig();
+
+        assertThat(oweAccessPoint.getConfig().allowedKeyManagement.get(KeyMgmt.NONE)).isFalse();
+        assertThat(oweAccessPoint.getConfig().allowedKeyManagement.get(KeyMgmt.OWE)).isTrue();
+        assertThat(noneAccessPoint.getConfig().allowedKeyManagement.get(KeyMgmt.NONE)).isTrue();
+        assertThat(noneAccessPoint.getConfig().allowedKeyManagement.get(KeyMgmt.OWE)).isFalse();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 8a0ae91..ed0857c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -26,6 +26,8 @@
 import android.content.Context;
 import android.content.Intent;
 
+import androidx.preference.Preference;
+
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import org.junit.Before;
@@ -60,19 +62,42 @@
     }
 
     @Test
-    public void logDashboardStartIntent_intentEmpty_shouldNotLog() {
-        mProvider.logDashboardStartIntent(mContext, null /* intent */,
+    public void logClickedPreference_preferenceEmpty_shouldNotLog() {
+        final boolean loggable = mProvider.logClickedPreference(null /* preference */,
                 MetricsEvent.SETTINGS_GESTURES);
 
+        assertThat(loggable).isFalse();
         verifyNoMoreInteractions(mLogWriter);
     }
 
     @Test
-    public void logDashboardStartIntent_intentHasNoComponent_shouldLog() {
+    public void logClickedPreference_preferenceHasKey_shouldLog() {
+        final String key = "abc";
+        final Preference preference = new Preference(mContext);
+        preference.setKey(key);
+
+        final boolean loggable = mProvider.logClickedPreference(preference,
+                MetricsEvent.SETTINGS_GESTURES);
+
+        assertThat(loggable).isTrue();
+        verify(mLogWriter).action(
+                MetricsEvent.SETTINGS_GESTURES,
+                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+                SettingsEnums.PAGE_UNKNOWN,
+                key,
+                0);
+    }
+
+    @Test
+    public void logClickedPreference_preferenceHasIntent_shouldLog() {
+        final Preference preference = new Preference(mContext);
         final Intent intent = new Intent(Intent.ACTION_ASSIST);
+        preference.setIntent(intent);
 
-        mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
+        final boolean loggable = mProvider.logClickedPreference(preference,
+                MetricsEvent.SETTINGS_GESTURES);
 
+        assertThat(loggable).isTrue();
         verify(mLogWriter).action(
                 MetricsEvent.SETTINGS_GESTURES,
                 MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
@@ -82,11 +107,54 @@
     }
 
     @Test
-    public void logDashboardStartIntent_intentIsExternal_shouldLog() {
+    public void logClickedPreference_preferenceHasFragment_shouldLog() {
+        final Preference preference = new Preference(mContext);
+        final String fragment = "com.android.settings.tts.TextToSpeechSettings";
+        preference.setFragment(fragment);
+
+        final boolean loggable = mProvider.logClickedPreference(preference,
+                MetricsEvent.SETTINGS_GESTURES);
+
+        assertThat(loggable).isTrue();
+        verify(mLogWriter).action(
+                MetricsEvent.SETTINGS_GESTURES,
+                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+                SettingsEnums.PAGE_UNKNOWN,
+                fragment,
+                0);
+    }
+
+    @Test
+    public void logStartedIntent_intentEmpty_shouldNotLog() {
+        final boolean loggable = mProvider.logStartedIntent(null /* intent */,
+                MetricsEvent.SETTINGS_GESTURES);
+
+        assertThat(loggable).isFalse();
+        verifyNoMoreInteractions(mLogWriter);
+    }
+
+    @Test
+    public void logStartedIntent_intentHasNoComponent_shouldLog() {
+        final Intent intent = new Intent(Intent.ACTION_ASSIST);
+
+        final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES);
+
+        assertThat(loggable).isTrue();
+        verify(mLogWriter).action(
+                MetricsEvent.SETTINGS_GESTURES,
+                MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+                SettingsEnums.PAGE_UNKNOWN,
+                Intent.ACTION_ASSIST,
+                0);
+    }
+
+    @Test
+    public void logStartedIntent_intentIsExternal_shouldLog() {
         final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls"));
 
-        mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
+        final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES);
 
+        assertThat(loggable).isTrue();
         verify(mLogWriter).action(
                 MetricsEvent.SETTINGS_GESTURES,
                 MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
diff --git a/packages/SettingsProvider/res/values-af/strings.xml b/packages/SettingsProvider/res/values-af/strings.xml
index 2c45484..8c2f8b3 100644
--- a/packages/SettingsProvider/res/values-af/strings.xml
+++ b/packages/SettingsProvider/res/values-af/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Instellingsberging"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Veranderings aan jou warmkolinstellings"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Jou warmkolband het verander."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Hierdie toestel steun nie jou voorkeur vir net 5 GHz nie. Hierdie toestel sal pleks daarvan die 5 GHz-band gebruik wanneer dit beskikbaar is."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-am/strings.xml b/packages/SettingsProvider/res/values-am/strings.xml
index 0630560..640301a 100644
--- a/packages/SettingsProvider/res/values-am/strings.xml
+++ b/packages/SettingsProvider/res/values-am/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"የቅንብሮች ማከማቻ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"በእርስዎ ሆትስፖት ቅንብሮች ላይ ለውጦች"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"የእርስዎ ሆትስፖት ባንድ ተለውጧል።"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ይህ መሣሪያ የእርስዎን ምርጫ ለ5GHz ብቻ አይደግፍም። በምትኩ፣ ይህ መሣሪያ ሲገኝ 5GHz ባንድ ይጠቀማል።"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ar/strings.xml b/packages/SettingsProvider/res/values-ar/strings.xml
index 19306a6..2675839 100644
--- a/packages/SettingsProvider/res/values-ar/strings.xml
+++ b/packages/SettingsProvider/res/values-ar/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"تخزين الإعدادات"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"التغييرات التي طرأت على إعدادات نقطة الاتصال"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"تمّ تغيير نطاق نقطة الاتصال الخاصة بك."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"لا يتوافق هذا الجهاز مع إعدادك المفضّل الخاص باستخدام النطاق 5 غيغاهرتز فقط. وسيستخدم الجهاز بدلاً من ذلك النطاق 5 غيغاهرتز عندما يكون متاحًا."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-as/strings.xml b/packages/SettingsProvider/res/values-as/strings.xml
index 13b90d6..b70146a 100644
--- a/packages/SettingsProvider/res/values-as/strings.xml
+++ b/packages/SettingsProvider/res/values-as/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ছেটিংছসমূহৰ সঞ্চয়াগাৰ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"আপোনাৰ হটস্পট ছেটিংসমূহত হোৱা সালসলনিসমূহ"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"আপোনাৰ হটস্পটৰ বেণ্ড সলনি কৰা হৈছে।"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"আপোনাৰ কেৱল ৫ গিগাহাৰ্টজৰ প্ৰতি অগ্ৰাধিকাৰ এই ডিভাচইচটোৱে সমৰ্থন নকৰে। ইয়াৰ পৰিৱৰ্তে, ডিভাচইচটোৱে যেতিয়া ৫ গিগাহাৰ্টজ বেণ্ড উপলব্ধ হ’ব তেতিয়া তাক ব্যৱহাৰ কৰিব।"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-az/strings.xml b/packages/SettingsProvider/res/values-az/strings.xml
index 4737a1e..8b4f75b 100644
--- a/packages/SettingsProvider/res/values-az/strings.xml
+++ b/packages/SettingsProvider/res/values-az/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Ayarlar Deposu"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot ayarlarınızda dəyişiklik"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot diapazonu dəyişib."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Bu cihaz yalnız 5GHz üçün tərcihinizi dəstəkləmir. Əvəzində, əlçatan olduqda bu cihaz 5GHz diapazonundan istifadə edəcək."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml b/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml
index 032dbc5..9c3a00e 100644
--- a/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Podešavanja skladišta"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Promene podešavanja za hotspot"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Opseg hotspota je promenjen."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ovaj uređaj ne podržava podešavanje samo za 5 GHz. Uređaj će koristiti opseg od 5 GHz kada bude dostupan."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-be/strings.xml b/packages/SettingsProvider/res/values-be/strings.xml
index 60bdb2d..0e098e7 100644
--- a/packages/SettingsProvider/res/values-be/strings.xml
+++ b/packages/SettingsProvider/res/values-be/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Сховішча налад"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Змяненні ў наладах хот-спота"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Частата хот-спота змянілася."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Прылада не можа працаваць толькі на частаце 5 ГГц. Гэта частата будзе выкарыстоўвацца, калі гэта магчыма."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-bg/strings.xml b/packages/SettingsProvider/res/values-bg/strings.xml
index c3a994b..30526f2 100644
--- a/packages/SettingsProvider/res/values-bg/strings.xml
+++ b/packages/SettingsProvider/res/values-bg/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Настройки за хранилище"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Промени в настройките ви за точка за достъп"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Честотната лента на точката ви за достъп е променена."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Това устройство не поддържа предпочитанието ви за използване само на честотната лента от 5 ГХц. Вместо това то ще я ползва, когато е възможно."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-bn/strings.xml b/packages/SettingsProvider/res/values-bn/strings.xml
index 71ee99b..8fc6bbb 100644
--- a/packages/SettingsProvider/res/values-bn/strings.xml
+++ b/packages/SettingsProvider/res/values-bn/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"সেটিংস স্টোরেজ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"আপনার হটস্পট সেটিংসে করা পরিবর্তনগুলি"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"আপনার হটস্পট ব্যান্ড পরিবর্তন করা হয়েছে।"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"এই ডিভাইসটি শুধুমাত্র 5GHz এর জন্য আপনার পছন্দ সমর্থন করে না। পরিবর্তে, এই ডিভাইসটি 5GHz ব্যান্ড ব্যবহার করবে।"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-bs/strings.xml b/packages/SettingsProvider/res/values-bs/strings.xml
index 09d4c68..ddacb32 100644
--- a/packages/SettingsProvider/res/values-bs/strings.xml
+++ b/packages/SettingsProvider/res/values-bs/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Postavke za pohranu podataka"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Promjene postavki pristupne tačke"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Opseg pristupne tačke je promijenjen."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ovaj uređaj ne podržava vašu postavku za mreže od isključivo 5 GHz. Uređaj će koristiti opseg of 5 GHz kada bude dostupan."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ca/strings.xml b/packages/SettingsProvider/res/values-ca/strings.xml
index ee6b51e..0c2ad73 100644
--- a/packages/SettingsProvider/res/values-ca/strings.xml
+++ b/packages/SettingsProvider/res/values-ca/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Configuració de l\'emmagatzematge"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Canvis en la configuració del punt d\'accés Wi‑Fi"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ha canviat la teva banda del punt d\'accés Wi‑Fi."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Aquest dispositiu no admet utilitzar exclusivament una banda de 5 GHz. El dispositiu utilitzarà una banda de 5 GHz quan estigui disponible."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-cs/strings.xml b/packages/SettingsProvider/res/values-cs/strings.xml
index f134e05..ab474b1 100644
--- a/packages/SettingsProvider/res/values-cs/strings.xml
+++ b/packages/SettingsProvider/res/values-cs/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Paměť pro nastavení"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Změny nastavení hotspotu"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Pásmo hotspotu se změnilo."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Toto zařízení nepodporuje vaše nastavení jen 5GHz pásma. Zařízení použije pásmo 5 GHz, jen když bude dostupné."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-da/strings.xml b/packages/SettingsProvider/res/values-da/strings.xml
index 99988a5..719614c 100644
--- a/packages/SettingsProvider/res/values-da/strings.xml
+++ b/packages/SettingsProvider/res/values-da/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Lagring af indstillinger"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ændringer af dine indstillinger for hotspot"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Dit hotspotbånd er ændret."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Denne enhed understøtter ikke din præference om kun 5 GHz. Denne enhed vil i stedet bruge 5 GHz-båndet, når det er muligt."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-de/strings.xml b/packages/SettingsProvider/res/values-de/strings.xml
index ddcabaa..6e253e0 100644
--- a/packages/SettingsProvider/res/values-de/strings.xml
+++ b/packages/SettingsProvider/res/values-de/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Einstellungsspeicher"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Änderungen an deinen Hotspot-Einstellungen"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Dein Hotspot-Band hat sich geändert."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Dieses Gerät unterstützt die ausschließliche Nutzung von 5 GHz nicht. Es greift aber immer auf das 5-GHz-Band zurück, wenn dieses verfügbar ist."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-el/strings.xml b/packages/SettingsProvider/res/values-el/strings.xml
index 924fab5..c47fea2 100644
--- a/packages/SettingsProvider/res/values-el/strings.xml
+++ b/packages/SettingsProvider/res/values-el/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Αποθηκευτικός χώρος ρυθμίσεων"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Αλλαγές στις ρυθμίσεις σημείου πρόσβασης Wi-Fi"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Το εύρος σημείου πρόσβασης Wi-Fi άλλαξε."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Αυτή η συσκευή δεν υποστηρίζει την προτίμησή σας για αποκλειστική χρήση του εύρους 5 GHz. Αντ\' αυτού, αυτή η συσκευή θα χρησιμοποιεί το εύρος 5 GHz όταν είναι διαθέσιμο."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-en-rAU/strings.xml b/packages/SettingsProvider/res/values-en-rAU/strings.xml
index 05cd54e..fac51d83 100644
--- a/packages/SettingsProvider/res/values-en-rAU/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rAU/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-en-rCA/strings.xml b/packages/SettingsProvider/res/values-en-rCA/strings.xml
index 05cd54e..fac51d83 100644
--- a/packages/SettingsProvider/res/values-en-rCA/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rCA/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-en-rGB/strings.xml b/packages/SettingsProvider/res/values-en-rGB/strings.xml
index 05cd54e..fac51d83 100644
--- a/packages/SettingsProvider/res/values-en-rGB/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rGB/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-en-rIN/strings.xml b/packages/SettingsProvider/res/values-en-rIN/strings.xml
index 05cd54e..fac51d83 100644
--- a/packages/SettingsProvider/res/values-en-rIN/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rIN/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-es-rUS/strings.xml b/packages/SettingsProvider/res/values-es-rUS/strings.xml
index d5e6da9..af90257 100644
--- a/packages/SettingsProvider/res/values-es-rUS/strings.xml
+++ b/packages/SettingsProvider/res/values-es-rUS/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Almacenamiento de configuración"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Cambios en la configuración de tu hotspot"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Cambió la banda de tu hotspot."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Si bien este dispositivo no admite la opción para conectarse exclusivamente a bandas de 5 GHz, las usará cuando estén disponibles."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-es/strings.xml b/packages/SettingsProvider/res/values-es/strings.xml
index d5e6da9..ff4ee38 100644
--- a/packages/SettingsProvider/res/values-es/strings.xml
+++ b/packages/SettingsProvider/res/values-es/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Almacenamiento de configuración"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Cambios en los ajustes del punto de acceso"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La banda de tu punto de acceso ha cambiado."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo no admite la opción de conectarse únicamente a bandas de 5 GHz, pero las usará cuando estén disponibles."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-et/strings.xml b/packages/SettingsProvider/res/values-et/strings.xml
index a3e4db6..a0ec593 100644
--- a/packages/SettingsProvider/res/values-et/strings.xml
+++ b/packages/SettingsProvider/res/values-et/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Seadete talletusruum"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Muudatused teie kuumkoha seadetes"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Teie kuumkoha sagedusriba on muutunud."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"See seade ei toeta teie eelistatud ainult 5 GHz riba. Seade kasutab 5 GHz riba ainult siis, kui see on saadaval."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-eu/strings.xml b/packages/SettingsProvider/res/values-eu/strings.xml
index 8d410de1..220b486 100644
--- a/packages/SettingsProvider/res/values-eu/strings.xml
+++ b/packages/SettingsProvider/res/values-eu/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Ezarpenen biltegia"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Aldaketak egin dira sare publikoaren ezarpenetan"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Aldatu da sare publikoaren banda."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Gailuak ez du onartzen 5 GHz-ko banda soilik erabiltzeko hobespena. Horren ordez, erabilgarri dagoen bakoitzean erabiliko da 5 GHz-ko banda."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-fa/strings.xml b/packages/SettingsProvider/res/values-fa/strings.xml
index ed9d168..6819d2f 100644
--- a/packages/SettingsProvider/res/values-fa/strings.xml
+++ b/packages/SettingsProvider/res/values-fa/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"تنظیم محل ذخیره"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"تغییرات در تنظیمات نقطه اتصال"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"نوار نقطه اتصال شما تغییر کرد."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"این دستگاه از اولویت فقط ۵ گیگاهرتز شما پشتیبانی نمی‌کند. هرزمان نوار ۵ گیگاهرتزی دردسترس باشد، این دستگاه از آن استفاده خواهد کرد."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-fi/strings.xml b/packages/SettingsProvider/res/values-fi/strings.xml
index 9ff2c41..9ad01eb 100644
--- a/packages/SettingsProvider/res/values-fi/strings.xml
+++ b/packages/SettingsProvider/res/values-fi/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Asetuksien tallennus"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot-asetustesi muutokset"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot-taajuutesi on muuttunut."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Tämä laite ei tue asetustasi (vain 5 GHz). Sen sijaan laite käyttää 5 GHz:n taajuutta sen ollessa käytettävissä."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-fr-rCA/strings.xml b/packages/SettingsProvider/res/values-fr-rCA/strings.xml
index fdfdb1d..62951bd 100644
--- a/packages/SettingsProvider/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsProvider/res/values-fr-rCA/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Stockage des paramètres"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modifications apportées à vos paramètres de point d\'accès"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La bande de votre point d\'accès a changé."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Cet appareil ne prend pas en charge votre préférence pour la bande de 5 GHz seulement. Au lieu de cela, cet appareil utilisera la bande de 5 GHz lorsqu\'elle sera disponible."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-fr/strings.xml b/packages/SettingsProvider/res/values-fr/strings.xml
index fdfdb1d..56bc65b 100644
--- a/packages/SettingsProvider/res/values-fr/strings.xml
+++ b/packages/SettingsProvider/res/values-fr/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Stockage des paramètres"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modifications apportées à vos paramètres de point d\'accès"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La bande utilisée par votre point d\'accès a été modifiée."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Cet appareil n\'est pas conçu pour utiliser exclusivement la bande 5 GHz, mais il l\'utilisera chaque fois que disponible."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-gl/strings.xml b/packages/SettingsProvider/res/values-gl/strings.xml
index 7b1599e..771fade 100644
--- a/packages/SettingsProvider/res/values-gl/strings.xml
+++ b/packages/SettingsProvider/res/values-gl/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Almacenamento da configuración"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Cambios na configuración da zona wifi"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Modificouse a banda da zona wifi."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo non admite a opción de conectarse só a bandas de 5 GHz, pero usaraas se están dispoñibles."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-gu/strings.xml b/packages/SettingsProvider/res/values-gu/strings.xml
index 00246f9..a561924 100644
--- a/packages/SettingsProvider/res/values-gu/strings.xml
+++ b/packages/SettingsProvider/res/values-gu/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"સેટિંગ્સ સંગ્રહ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"તમારા હૉટસ્પૉટ સેટિંગને બદલે છે"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"તમારું હૉટસ્પૉટ બૅન્ડ બદલાઈ ગયું છે."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"આ ડિવાઇસ તમારી ફક્ત 5GHz માટેની પસંદગીને સપોર્ટ કરતું નથી. તેના બદલે, જ્યારે 5GHz બૅન્ડ ઉપલબ્ધ હશે ત્યારે આ ડિવાઇસ તેનો ઉપયોગ કરશે."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-hi/strings.xml b/packages/SettingsProvider/res/values-hi/strings.xml
index 35ed78f..199a546 100644
--- a/packages/SettingsProvider/res/values-hi/strings.xml
+++ b/packages/SettingsProvider/res/values-hi/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"सेटिंग मेमोरी"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"आपकी हॉटस्पॉट की सेटिंग में किए गए बदलाव"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"आपका हॉटस्पॉट का बैंड बदल दिया गया है."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"इस डिवाइस पर आप \'सिर्फ़ 5 गीगाहर्ट्ज़\' वाला विकल्प नहीं चुन सकते. इसके बजाय, 5 गीगाहर्ट्ज़ बैंड उपलब्ध होने पर यह डिवाइस उसका इस्तेमाल करेगा."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-hr/strings.xml b/packages/SettingsProvider/res/values-hr/strings.xml
index 070d061..9129a04 100644
--- a/packages/SettingsProvider/res/values-hr/strings.xml
+++ b/packages/SettingsProvider/res/values-hr/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Postavke pohrane"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Promjene postavki vaše žarišne točke"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Promijenila se frekvencija vaše žarišne točke."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ovaj uređaj ne podržava vašu postavku za upotrebu samo 5 GHz. Upotrebljavat će frekvenciju od 5 GHz kada je dostupna."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-hu/strings.xml b/packages/SettingsProvider/res/values-hu/strings.xml
index 97124ae..a1ed494 100644
--- a/packages/SettingsProvider/res/values-hu/strings.xml
+++ b/packages/SettingsProvider/res/values-hu/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Beállítástároló"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"A hotspot beállításainak módosítása"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"A hotspot sávja megváltozott."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ez az eszköz nem támogatja a csak 5 GHz-es sávra vonatkozó beállítást. Az eszköz akkor használ 5 GHz-es sávot, ha a sáv rendelkezésre áll."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-hy/strings.xml b/packages/SettingsProvider/res/values-hy/strings.xml
index d494012..6d716f8 100644
--- a/packages/SettingsProvider/res/values-hy/strings.xml
+++ b/packages/SettingsProvider/res/values-hy/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Կարգավորումների պահուստ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Փոփոխություններ թեժ կետի կարգավորումներում"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ձեր թեժ կետի հաճախականությունը փոխվել է։"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Սարքը չի կարող աշխատել միայն 5 ԳՀց հաճախականությամբ։ Այդ հաճախականությունը կօգտագործվի հնարավորության դեպքում։"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-in/strings.xml b/packages/SettingsProvider/res/values-in/strings.xml
index 4ec8f84..8b1b1f4 100644
--- a/packages/SettingsProvider/res/values-in/strings.xml
+++ b/packages/SettingsProvider/res/values-in/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Setelan Penyimpanan"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Perubahan pada setelan hotspot Anda"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Band hotspot Anda telah berubah."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Perangkat ini tidak mendukung preferensi Anda, yaitu hanya 5GHz. Sebagai gantinya, perangkat ini akan menggunakan band 5GHz jika tersedia."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-is/strings.xml b/packages/SettingsProvider/res/values-is/strings.xml
index d75abae..150c084 100644
--- a/packages/SettingsProvider/res/values-is/strings.xml
+++ b/packages/SettingsProvider/res/values-is/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Stillingageymsla"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Breytingar á stillingum heits reits"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Tíðnisvið heita reitsins hefur breyst."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Þetta tæki styður ekki val þitt fyrir aðeins 5 GHz. Í staðinn mun þetta tæki nota 5 GHz tíðnisvið þegar það er í boði."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-it/strings.xml b/packages/SettingsProvider/res/values-it/strings.xml
index a1e3901..6715c3c 100644
--- a/packages/SettingsProvider/res/values-it/strings.xml
+++ b/packages/SettingsProvider/res/values-it/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Memoria impostazioni"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modifiche alle tue impostazioni dell\'hotspot"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La banda dell\'hotspot è cambiata."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Questo dispositivo non supporta la tua preferenza esclusiva per 5 GHz. Utilizzerà la banda a 5 GHz solo quando è disponibile."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-iw/strings.xml b/packages/SettingsProvider/res/values-iw/strings.xml
index c14f776..dd44329 100644
--- a/packages/SettingsProvider/res/values-iw/strings.xml
+++ b/packages/SettingsProvider/res/values-iw/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"אחסון הגדרות"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"שינויים להגדרות של הנקודה לשיתוף אינטרנט"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"התדר של הנקודה לשיתוף אינטרנט השתנה."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"‏מכשיר זה לא תומך בהעדפות שלך ל-5GHz בלבד. במקום זאת, מכשיר זה ישתמש בתדר 5GHz כשיהיה זמין."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ja/strings.xml b/packages/SettingsProvider/res/values-ja/strings.xml
index b11ec3b..7bbcd46 100644
--- a/packages/SettingsProvider/res/values-ja/strings.xml
+++ b/packages/SettingsProvider/res/values-ja/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ストレージの設定"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"アクセス ポイントの設定の変更"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"アクセス ポイントの帯域幅が変更されました。"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"このデバイスは 5 GHz のみという設定に対応していません。ただし、5 GHz 周波数帯が利用できるときには利用します。"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ka/strings.xml b/packages/SettingsProvider/res/values-ka/strings.xml
index d08e71e..86db4f3 100644
--- a/packages/SettingsProvider/res/values-ka/strings.xml
+++ b/packages/SettingsProvider/res/values-ka/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"პარამეტრების საცავი"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"თქვენი უსადენო ქსელის პარამეტრების ცვლილება"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"თქვენი უსადენო ქსელის დიაპაზონი შეიცვალა."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ამ მოწყობილობას არ შეუძლია მხოლოდ 5 გჰც სიხშირეზე მუშაობა. აღნიშნული სიხშირის გამოყენება მოხდება მაშინ, როცა ეს შესაძლებელია."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-kk/strings.xml b/packages/SettingsProvider/res/values-kk/strings.xml
index c07264c..a093d08 100644
--- a/packages/SettingsProvider/res/values-kk/strings.xml
+++ b/packages/SettingsProvider/res/values-kk/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Параметрлер жады"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Хотспот параметрлеріне өзгерістер енгізілді"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Хотспот жолағы өзгертілді."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Бұл құрылғы тек 5 ГГц жиілікте жұмыс істей алмайды. Бұл жиілік мүмкін болған жағдайда ғана қолданылады."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-km/strings.xml b/packages/SettingsProvider/res/values-km/strings.xml
index c880ead..f0a2712 100644
--- a/packages/SettingsProvider/res/values-km/strings.xml
+++ b/packages/SettingsProvider/res/values-km/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"កំណត់​ការ​ផ្ទុក"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ប្ដូរ​ទៅ​ការ​កំណត់​ហតស្ប៉ត​របស់អ្នក"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"កម្រិតបញ្ជូន​ហតស្ប៉ត​របស់​អ្នកបាន​ផ្លាស់ប្ដូរ។"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ឧបករណ៍នេះ​មិនស្គាល់​ចំណូលចិត្ត​របស់អ្នក​សម្រាប់តែ 5GHz ប៉ុណ្ណោះ។ ផ្ទុយមកវិញ ឧបករណ៍នេះ​នឹងប្រើ​កម្រិតបញ្ជូន 5GHz នៅពេល​ដែលអាច​ប្រើបាន។"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-kn/strings.xml b/packages/SettingsProvider/res/values-kn/strings.xml
index d823323..f2c1d5e 100644
--- a/packages/SettingsProvider/res/values-kn/strings.xml
+++ b/packages/SettingsProvider/res/values-kn/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಸಂಗ್ರಹಣೆ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ನಿಮ್ಮ ಹಾಟ್‌ಸ್ಪಾಟ್‌ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿನ ಬದಲಾವಣೆಗಳು"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ನಿಮ್ಮ ಹಾಟ್‌ಸ್ಪಾಟ್‌ ಬ್ಯಾಂಡ್ ಬದಲಾಗಿದೆ."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ಈ ಸಾಧನವು 5GHz ಗೆ ಮಾತ್ರ ನಿಮ್ಮ ಆದ್ಯತೆಯನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ. ಬದಲಿಗೆ, ಈ ಸಾಧನವು 5GHz ಬ್ಯಾಂಡ್ ಅನ್ನು ಲಭ್ಯವಿರುವಾಗ ಬಳಸುತ್ತದೆ."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ko/strings.xml b/packages/SettingsProvider/res/values-ko/strings.xml
index ab8fb2b..841832d 100644
--- a/packages/SettingsProvider/res/values-ko/strings.xml
+++ b/packages/SettingsProvider/res/values-ko/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"설정 저장소"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"핫스팟 설정 변경"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"핫스팟 대역이 변경되었습니다."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"이 기기에서는 5GHz 전용 환경설정이 지원되지 않습니다. 대신 가능할 때만 기기에서 5GHz 대역이 사용됩니다."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ky/strings.xml b/packages/SettingsProvider/res/values-ky/strings.xml
index 566c481..014c66c 100644
--- a/packages/SettingsProvider/res/values-ky/strings.xml
+++ b/packages/SettingsProvider/res/values-ky/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Жөндөөлөрдү сактоо"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Байланыш түйүнүңүздүн жөндөөлөрү өзгөрүлдү"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Байланыш түйүнүңүздүн жыштыгы өзгөрдү."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Бул түзмөк 5ГГцти гана колдонуу жөндөөсүн колдоого албайт. Анын ордуна, бул түзмөк 5ГГц жыштыгын ал жеткиликтүү болгондо колдонот."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-lo/strings.xml b/packages/SettingsProvider/res/values-lo/strings.xml
index 1aa5fe9..9d60ba1 100644
--- a/packages/SettingsProvider/res/values-lo/strings.xml
+++ b/packages/SettingsProvider/res/values-lo/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ບ່ອນເກັບຂໍ້ມູນການຕັ້ງຄ່າ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ການປ່ຽນແປງການຕັ້ງຄ່າຮັອດສະປອດຂອງທ່ານ"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ຄື້ນຄວາມຖີ່ຮັອດສະປອດຂອງທ່ານປ່ຽນແປງແລ້ວ."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ອຸປະກອນນີ້ບໍ່ຮອງຮັບການຕັ້ງຄ່າຂອງທ່ານສຳລັບ 5GHz ເທົ່ານັ້ນ. ແຕ່ວ່າອຸປະກອນນີ້ຈະໃຊ້ຄື້ນຄວາມຖີ່ 5GHz ເມື່ອສາມາດໃຊ້ໄດ້."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-lt/strings.xml b/packages/SettingsProvider/res/values-lt/strings.xml
index 47cd6b4..775d3b9 100644
--- a/packages/SettingsProvider/res/values-lt/strings.xml
+++ b/packages/SettingsProvider/res/values-lt/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Nustatymų saugykla"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Viešosios interneto prieigos taško nustatymų pakeitimai"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Viešosios prieigos taško dažnio juosta pasikeitė."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Šiame įrenginyje nepalaikoma tik 5 GHz nuostata. Vietoj to šiame įrenginyje bus naudojama 5 GHz dažnio juosta, kai bus pasiekiama."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-lv/strings.xml b/packages/SettingsProvider/res/values-lv/strings.xml
index d221d46..f7f3117 100644
--- a/packages/SettingsProvider/res/values-lv/strings.xml
+++ b/packages/SettingsProvider/res/values-lv/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Iestatījumu krātuve"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Izmaiņas tīklāja iestatījumos"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ir mainīts tīklāja joslas platums."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Šajā ierīcē netiek atbalstīta jūsu preference par tikai 5 GHz joslu. 5 GHz josla ierīcē tiks izmantota, kad tā būs pieejama."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-mk/strings.xml b/packages/SettingsProvider/res/values-mk/strings.xml
index dc74c4a..a245e5f 100644
--- a/packages/SettingsProvider/res/values-mk/strings.xml
+++ b/packages/SettingsProvider/res/values-mk/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Поставки за меморија"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Промени на поставките за точка на пристап"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Опсегот за точка на пристап е променет."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Уредов не ги поддржува вашите поставки за само 5 GHz. Наместо тоа, ќе го користи опсегот од 5 GHz кога е достапен."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ml/strings.xml b/packages/SettingsProvider/res/values-ml/strings.xml
index a11e236..b63f20e 100644
--- a/packages/SettingsProvider/res/values-ml/strings.xml
+++ b/packages/SettingsProvider/res/values-ml/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"സംഭരണ ക്രമീകരണം"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"നിങ്ങളുടെ ഹോട്ട്‌സ്‌പോട്ട് ക്രമീകരണത്തിൽ വരുത്തിയ മാറ്റങ്ങൾ"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"നിങ്ങളുടെ ഹോട്ട്‌സ്‌പോട്ട് ബാൻഡ് മാറിയിരിക്കുന്നു."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"നിങ്ങളുടെ മുൻഗണനയനുസരിച്ചുള്ള, 5GHz മാത്രം എന്നത് ഈ ഉപകരണം പിന്തുണയ്ക്കുന്നില്ല. പകരം, 5GHz ബാൻഡ് ലഭ്യമാകുമ്പോൾ അത് ഉപയോഗിക്കും."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-mn/strings.xml b/packages/SettingsProvider/res/values-mn/strings.xml
index f6437c75..c839177 100644
--- a/packages/SettingsProvider/res/values-mn/strings.xml
+++ b/packages/SettingsProvider/res/values-mn/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Тохиргооны Сан"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Таны сүлжээний цэгийн тохиргооны өөрчлөлт"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Таны сүлжээний цэгийн хязгаарыг өөрчилсөн."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Энэ төхөөрөмж нь зөвхөн 5Гц гэсэн таны сонголтыг дэмждэггүй. Оронд нь энэ төхөөрөмж 5Гц-н хязгаарыг боломжтой үед нь ашиглах болно."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-mr/strings.xml b/packages/SettingsProvider/res/values-mr/strings.xml
index 7888488..0c7041e 100644
--- a/packages/SettingsProvider/res/values-mr/strings.xml
+++ b/packages/SettingsProvider/res/values-mr/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"सेटिंग्ज संचयन"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"तुमच्या हॉटस्पॉट सेटिंग्जमधील बदल"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"तुमचा हॉटस्पॉट बँड बदलला गेला आहे."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"हे डिव्हाइस फक्त ५GHz च्या तुमच्या प्राधान्याला सपोर्ट करत नाही. त्याऐवजी, उपलब्ध असेल तेव्हा हे डिव्हाइस ५GHz बँड वापरेल."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ms/strings.xml b/packages/SettingsProvider/res/values-ms/strings.xml
index bcde71c..a1574df 100644
--- a/packages/SettingsProvider/res/values-ms/strings.xml
+++ b/packages/SettingsProvider/res/values-ms/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Storan Tetapan"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Perubahan kepada tetapan tempat liputan anda"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Jalur tempat liputan anda telah berubah."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Peranti ini tidak menyokong pilihan anda untuk 5GHz sahaja. Sebaliknya, peranti ini akan menggunakan jalur 5GHz apabila tersedia."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-my/strings.xml b/packages/SettingsProvider/res/values-my/strings.xml
index b2e670e..48d4dba 100644
--- a/packages/SettingsProvider/res/values-my/strings.xml
+++ b/packages/SettingsProvider/res/values-my/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"သိုလှောင်မှုဆက်တင်များ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"သင်၏ဟော့စပေါ့ ဆက်တင်များ ပြောင်းလဲမှု"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"သင်၏ ဟော့စပေါ့လိုင်း ပြောင်းသွားပါပြီ။"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ဤစက်ပစ္စည်းသည် သင်၏ 5GHz သီးသန့်ရွေးချယ်မှုအတွက် ပံ့ပိုးမထားပါ။ ၎င်းအစား ဤစက်ပစ္စည်းသည် ရနိုင်သည့်အခါ 5GHz လိုင်းကို သုံးသွားပါမည်။"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-nb/strings.xml b/packages/SettingsProvider/res/values-nb/strings.xml
index 5c60ad7..e0cbd7e 100644
--- a/packages/SettingsProvider/res/values-nb/strings.xml
+++ b/packages/SettingsProvider/res/values-nb/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Lagring av innstillinger"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Endres til innstillingene dine for Wi-Fi-soner"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Båndet ditt for Wi-Fi-sone er endret."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Denne enheten støtter ikke innstillingen din for bare 5 GHz. I stedet bruker enheten 5 GHz-båndet når det er tilgjengelig."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ne/strings.xml b/packages/SettingsProvider/res/values-ne/strings.xml
index 8c4f8c8..2fd9b00 100644
--- a/packages/SettingsProvider/res/values-ne/strings.xml
+++ b/packages/SettingsProvider/res/values-ne/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"सेटिङहरू भण्डारण"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"तपाईंका हटस्पट सेटिङहरूमा गरिएका परिवर्तनहरू"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"तपाईंको हटस्पट ब्यान्ड परिवर्तन भएको छ।"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"यो यन्त्रले तपाईंको 5GHz मात्रको प्राथमिकतालाई समर्थन गर्दैन। बरु, उपलब्ध भएको खण्डमा यो यन्त्रले 5GHz ब्यान्ड प्रयोग गर्ने छ।"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-nl/strings.xml b/packages/SettingsProvider/res/values-nl/strings.xml
index 7ad4588..0b843ae 100644
--- a/packages/SettingsProvider/res/values-nl/strings.xml
+++ b/packages/SettingsProvider/res/values-nl/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Opslagruimte voor instellingen"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Wijzigingen in je hotspot-instellingen"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Je hotspot-band is gewijzigd."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Dit apparaat biedt geen ondersteuning voor je voorkeur voor alleen 5 GHz. In plaats daarvan gebruikt dit apparaat de 5-GHz-band wanneer deze beschikbaar is."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-or/strings.xml b/packages/SettingsProvider/res/values-or/strings.xml
index 1837219..53f6104 100644
--- a/packages/SettingsProvider/res/values-or/strings.xml
+++ b/packages/SettingsProvider/res/values-or/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ସେଟିଙ୍ଗ ଷ୍ଟୋରେଜ୍‌"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ଆପଣଙ୍କର ହଟ୍‌ସ୍ପଟ୍ ସେଟିଂସ୍‌କୁ ବଦଳାଇଥାଏ"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ଆପଣଙ୍କର ହଟ୍‌ସ୍ପଟ୍ ପରିବର୍ତ୍ତନ କରାଯାଇଛି।"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"କେବଳ 5GHz ପାଇଁ, ଏହି ଡିଭାଇସ୍ ଆପଣଙ୍କର ପସନ୍ଦକୁ ସମର୍ଥନ କରେ ନାହିଁ। ଏହା ପରିବର୍ତ୍ତେ, ଉପଲବ୍ଧ ହେଲେ ଏହି ଡିଭାଇସ୍ 5GHz ବ୍ୟାଣ୍ଡ ବ୍ୟବହାର କରିବ।"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-pa/strings.xml b/packages/SettingsProvider/res/values-pa/strings.xml
index 2961435..9a41e36 100644
--- a/packages/SettingsProvider/res/values-pa/strings.xml
+++ b/packages/SettingsProvider/res/values-pa/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ਸੈਟਿੰਗਾਂ ਸਟੋਰੇਜ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ਤੁਹਾਡੀਆਂ ਹੌਟਸਪੌਟ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲਾਅ"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ਤੁਹਾਡਾ ਹੌਟਸਪੌਟ ਬੈਂਡ ਬਦਲ ਗਿਆ ਹੈ।"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ਇਹ ਡੀਵਾਈਸ ਸਿਰਫ਼ 5GHz ਦੀ ਤੁਹਾਡੀ ਤਰਜੀਹ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ ਹੈ। ਇਸਦੀ ਬਜਾਏ, ਇਹ ਡੀਵਾਈਸ ਉਪਲਬਧ ਹੋਣ \'ਤੇ 5GHz ਬੈਂਡ ਵਰਤੇਗਾ।"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-pl/strings.xml b/packages/SettingsProvider/res/values-pl/strings.xml
index 9963aee..7fd2b13 100644
--- a/packages/SettingsProvider/res/values-pl/strings.xml
+++ b/packages/SettingsProvider/res/values-pl/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Pamięć ustawień"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Zmieniono ustawienia hotspotu"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Zmieniono pasmo hotspotu."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"To urządzenie nie może korzystać tylko z częstotliwości 5 GHz. Będzie korzystać z tego pasma, jeśli będzie ono dostępne."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-pt-rBR/strings.xml b/packages/SettingsProvider/res/values-pt-rBR/strings.xml
index e25e82e..18db8f6 100644
--- a/packages/SettingsProvider/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsProvider/res/values-pt-rBR/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Armazenamento de configurações"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mudanças nas suas configurações de ponto de acesso"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Sua banda de ponto de acesso foi modificada."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo não é compatível com sua preferência apenas por 5 GHz. Em vez disso, o dispositivo usará a banda de 5 GHz quando ela estiver disponível."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-pt-rPT/strings.xml b/packages/SettingsProvider/res/values-pt-rPT/strings.xml
index 3b95a31..be88cce 100644
--- a/packages/SettingsProvider/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsProvider/res/values-pt-rPT/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Armazenamento de definições"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Alterações às definições de zona Wi-Fi"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"A banda da sua zona Wi-Fi foi alterada."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo não suporta a sua preferência apenas para 5 GHz. Em alternativa, este dispositivo vai utilizar a banda de 5 GHz quando estiver disponível."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-pt/strings.xml b/packages/SettingsProvider/res/values-pt/strings.xml
index e25e82e..18db8f6 100644
--- a/packages/SettingsProvider/res/values-pt/strings.xml
+++ b/packages/SettingsProvider/res/values-pt/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Armazenamento de configurações"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mudanças nas suas configurações de ponto de acesso"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Sua banda de ponto de acesso foi modificada."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo não é compatível com sua preferência apenas por 5 GHz. Em vez disso, o dispositivo usará a banda de 5 GHz quando ela estiver disponível."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ro/strings.xml b/packages/SettingsProvider/res/values-ro/strings.xml
index 5a5ac6d..3a234e1 100644
--- a/packages/SettingsProvider/res/values-ro/strings.xml
+++ b/packages/SettingsProvider/res/values-ro/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Stocare setări"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modificări aduse setărilor pentru hotspot"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"S-a schimbat banda de frecvență a hotspotului."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Dispozitivul nu acceptă doar preferința pentru 5 GHz. Dispozitivul va folosi banda de 5 GHz când este disponibilă."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ru/strings.xml b/packages/SettingsProvider/res/values-ru/strings.xml
index 15ca313..184afdd 100644
--- a/packages/SettingsProvider/res/values-ru/strings.xml
+++ b/packages/SettingsProvider/res/values-ru/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Хранилище настроек"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Изменения в настройках точки доступа"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Частота точки доступа изменена."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Устройство не может работать только на частоте 5 ГГц. Эта частота будет использоваться, когда это возможно."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-si/strings.xml b/packages/SettingsProvider/res/values-si/strings.xml
index feff43c..69e04f1 100644
--- a/packages/SettingsProvider/res/values-si/strings.xml
+++ b/packages/SettingsProvider/res/values-si/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"සැකසීම් ගබඩාව"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ඔබගේ හොට්ස්පොට් සැකසීම්වලට වෙනස් කිරීම්"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ඔබගේ හොට්ස්පොට් කලාපය වෙනස් වී ඇත."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"මෙම උපාංගය 5GHz සඳහා ඔබේ මනාපවලට සහාය නොදක්වයි. ඒ වෙනුවට, මෙම උපාංගය ලබා ගත හැකි විට 5GHz කලාපය භාවිතා කරනු ඇත."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sk/strings.xml b/packages/SettingsProvider/res/values-sk/strings.xml
index 1207a94..a53178d 100644
--- a/packages/SettingsProvider/res/values-sk/strings.xml
+++ b/packages/SettingsProvider/res/values-sk/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Ukladací priestor nastavení"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Zmeny nastavení hotspotu"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Pásmo vášho hotspotu sa zmenilo."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Toto zariadenie nepodporuje vašu predvoľbu používať iba 5 GHz. Namiesto toho bude pásmo 5 GHz používať vtedy, keď bude k dispozícii."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sl/strings.xml b/packages/SettingsProvider/res/values-sl/strings.xml
index 28a9937..ea697fe 100644
--- a/packages/SettingsProvider/res/values-sl/strings.xml
+++ b/packages/SettingsProvider/res/values-sl/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Shramba nastavitev"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Spremembe nastavitev dostopne točke"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Pas dostopne točke je spremenjen."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ta naprava ne podpira prednostne nastavitve samo za 5-GHz pas. Namesto tega bo ta naprava uporabljala 5-GHz pas, ko bo na voljo."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sq/strings.xml b/packages/SettingsProvider/res/values-sq/strings.xml
index 977fe02..a111576 100644
--- a/packages/SettingsProvider/res/values-sq/strings.xml
+++ b/packages/SettingsProvider/res/values-sq/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Hapësira ruajtëse e \"Cilësimeve\""</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ndryshimet në cilësimet e zonës së qasjes për internet"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Brezi yt i zonës së qasjes për internet ka ndryshuar."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Kjo pajisje nuk e mbështet preferencën për vetëm 5 GHz. Përkundrazi, pajisja do të përdorë brezin 5 GHz nëse ka."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sr/strings.xml b/packages/SettingsProvider/res/values-sr/strings.xml
index 74b9dde..d473102 100644
--- a/packages/SettingsProvider/res/values-sr/strings.xml
+++ b/packages/SettingsProvider/res/values-sr/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Подешавања складишта"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Промене подешавања за хотспот"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Опсег хотспота је промењен."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Овај уређај не подржава подешавање само за 5 GHz. Уређај ће користити опсег од 5 GHz када буде доступан."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sv/strings.xml b/packages/SettingsProvider/res/values-sv/strings.xml
index 1444626..fea3e5e 100644
--- a/packages/SettingsProvider/res/values-sv/strings.xml
+++ b/packages/SettingsProvider/res/values-sv/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Lagring av inställningar"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ändringar i inställningarna för surfzon"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Frekvensbandet för surfzonen har ändrats."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Den här enheten har inte stöd för inställningen för att endast använda 5 GHz. I stället används 5 GHz när det är möjligt."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sw/strings.xml b/packages/SettingsProvider/res/values-sw/strings.xml
index c244d88..4d05817 100644
--- a/packages/SettingsProvider/res/values-sw/strings.xml
+++ b/packages/SettingsProvider/res/values-sw/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Hifadhi ya Mipangilio"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mabadiliko kwenye mipangilio ya mtandaopepe"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Bendi ya mtandaopepe wako imebadilika."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Kifaa hiki hakitumii mapendeleo yako ya GHz 5 pekee. Badala yake, kifaa hiki kitatumia bendi ya GHz 5 itakapopatikana."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ta/strings.xml b/packages/SettingsProvider/res/values-ta/strings.xml
index b26c875..f518a78 100644
--- a/packages/SettingsProvider/res/values-ta/strings.xml
+++ b/packages/SettingsProvider/res/values-ta/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"அமைப்புகளின் சேமிப்பிடம்"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"உங்கள் ஹாட்ஸ்பாட் அமைப்புகளில் செய்யப்பட்டுள்ள மாற்றங்கள்"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"உங்கள் ஹாட்ஸ்பாட்டின் அலைவரிசை வரம்பு மாறிவிட்டது."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"இந்தச் சாதனத்தில், ’5GHz மட்டும்’ எனும் முன்னுரிமைத் தேர்வு ஆதரிக்கப்படவில்லை. எனினும் 5GHz அலைவரிசை வரம்பிற்குள் இருக்கும்போது சாதனம் அதைப் பயன்படுத்திக்கொள்ளும்."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-te/strings.xml b/packages/SettingsProvider/res/values-te/strings.xml
index 2a4971a..6c59223 100644
--- a/packages/SettingsProvider/res/values-te/strings.xml
+++ b/packages/SettingsProvider/res/values-te/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"సెట్టింగ్‌ల నిల్వ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"మీ హాట్‌స్పాట్ సెట్టింగ్‌లకు మార్పులు"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"మీ హాట్‌స్పాట్ బ్యాండ్ మార్చబడింది."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ఈ పరికరం 5GHz కోసం మాత్రమే మీ ప్రాధాన్యతకు మద్దతు ఇవ్వదు. బదులుగా, ఈ పరికరం అందుబాటులో ఉన్నప్పుడు 5GHz బ్యాండ్‌ను ఉపయోగిస్తుంది."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-th/strings.xml b/packages/SettingsProvider/res/values-th/strings.xml
index db47654..4bf148f 100644
--- a/packages/SettingsProvider/res/values-th/strings.xml
+++ b/packages/SettingsProvider/res/values-th/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ที่เก็บข้อมูลการตั้งค่า"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"มีการเปลี่ยนแปลงการตั้งค่าฮอตสปอต"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ย่านความถี่ฮอตสปอตมีการเปลี่ยนแปลง"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"อุปกรณ์นี้ไม่รองรับค่ากำหนดของคุณเฉพาะสำหรับ 5 GHz เท่านั้น และจะใช้ย่านความถี่ 5 GHz แทน เมื่อใช้ได้"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-tl/strings.xml b/packages/SettingsProvider/res/values-tl/strings.xml
index 71c6266..2a36d58 100644
--- a/packages/SettingsProvider/res/values-tl/strings.xml
+++ b/packages/SettingsProvider/res/values-tl/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Storage ng Mga Setting"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mga pagbabago sa mga setting ng iyong hotspot"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Nagbago ang band ng iyong hotspot."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Hindi sinusuportahan ng device na ito ang kagustuhan mong gumamit lang ng 5GHz. Sa halip, gagamitin ng device na ito ang 5GHz na band kapag available."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-tr/strings.xml b/packages/SettingsProvider/res/values-tr/strings.xml
index 4737a1e..add1fdb 100644
--- a/packages/SettingsProvider/res/values-tr/strings.xml
+++ b/packages/SettingsProvider/res/values-tr/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Ayarlar Deposu"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot ayarlarınız değişti"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot bandınız değişti."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Bu cihaz yalnızca 5 GHz bandının kullanılmasına yönelik tercihinizi desteklemiyor. Bunun yerine, bu cihaz 5 GHz bandını mevcut olduğunda kullanacak."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-uk/strings.xml b/packages/SettingsProvider/res/values-uk/strings.xml
index 2c77c6a61..cd678bc 100644
--- a/packages/SettingsProvider/res/values-uk/strings.xml
+++ b/packages/SettingsProvider/res/values-uk/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Сховище налаштувань"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Зміни в налаштуваннях точки доступу"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Діапазон частот точки доступу змінено."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"На цьому пристрої не підтримується налаштування \"Лише 5 ГГц\". Натомість буде використано діапазон частот 5 ГГц (якщо доступно)."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ur/strings.xml b/packages/SettingsProvider/res/values-ur/strings.xml
index 31694e0..2241ce9 100644
--- a/packages/SettingsProvider/res/values-ur/strings.xml
+++ b/packages/SettingsProvider/res/values-ur/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ترتیبات کا اسٹوریج"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"اپنے ہاٹ اسپاٹ کی ترتیبات میں تبدیلیاں"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"آپ کا ہاٹ اسپات بینڈ تبدیل ہو گیا ہے۔"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"‏یہ آلہ صرف 5GHz کے لیے آپ کی ترجیح کو سپورٹ نہیں کرے گا۔ بلکہ 5GHz بینڈ کے دستیاب ہونے پر اس کا استعمال کرے گا۔"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-uz/strings.xml b/packages/SettingsProvider/res/values-uz/strings.xml
index 86a980e..a266bf0 100644
--- a/packages/SettingsProvider/res/values-uz/strings.xml
+++ b/packages/SettingsProvider/res/values-uz/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Sozlamalar xotirasi"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot sozlamalari o‘zgartirildi"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot chastotasi oʻzgartirildi."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Qurilma faqat 5 GGs chastotada ishlay olmaydi. Bu chastotadan imkoniyatga qarab foydalaniladi."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-vi/strings.xml b/packages/SettingsProvider/res/values-vi/strings.xml
index 6476927..74f93b2 100644
--- a/packages/SettingsProvider/res/values-vi/strings.xml
+++ b/packages/SettingsProvider/res/values-vi/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Lưu trữ bộ nhớ"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Những thay đổi trong mục cài đặt điểm phát sóng của bạn"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Bằng tần của điểm phát sóng đã thay đổi."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Thiết bị này không hỗ trợ tùy chọn chỉ sử dụng băng tần 5 GHz. Thay vào đó, thiết bị này sẽ sử dụng băng tần 5 GHz khi có thể."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-zh-rCN/strings.xml b/packages/SettingsProvider/res/values-zh-rCN/strings.xml
index 1395912..95b15e0 100644
--- a/packages/SettingsProvider/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsProvider/res/values-zh-rCN/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"设置存储"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"您的热点设置已变更"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"您的热点频段已变更。"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"此设备不支持您的偏好设置(仅限 5GHz),而且会在 5GHz 频段可用时使用该频段。"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-zh-rHK/strings.xml b/packages/SettingsProvider/res/values-zh-rHK/strings.xml
index 2845264..41ebe27 100644
--- a/packages/SettingsProvider/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsProvider/res/values-zh-rHK/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"設定儲存空間"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"您的熱點設定變更"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"您的熱點頻段已變更。"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"此裝置不支援只限 5 GHz 的偏好設定,但會在 5 GHz 頻段可用時採用。"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-zh-rTW/strings.xml b/packages/SettingsProvider/res/values-zh-rTW/strings.xml
index 2845264..d0a30f5 100644
--- a/packages/SettingsProvider/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsProvider/res/values-zh-rTW/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"設定儲存空間"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"無線基地台設定變更"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"你的無線基地台頻帶已變更。"</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"這部裝置不支援你的偏好設定 (僅限 5GHz),而是會在 5GHz 可用時使用該頻帶。"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-zu/strings.xml b/packages/SettingsProvider/res/values-zu/strings.xml
index 8c1d9e0..0440b3b 100644
--- a/packages/SettingsProvider/res/values-zu/strings.xml
+++ b/packages/SettingsProvider/res/values-zu/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Izilungiselelo zesitoreji"</string>
-    <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
-    <skip />
-    <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
-    <skip />
+    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ushintsho kuzilungiselelo zakho ze-hotspot"</string>
+    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ibhendi yakho ye-hotspot ishintshile."</string>
+    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Le divayisi ayisekeli okuncamelayo kwe-5GHz kuphela. Kunalokho, le divayisi izosebenzisa ibhendi ye-5GHz uma itholakala."</string>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 2027345..375a650 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -951,8 +951,7 @@
                                                 (1 << AudioManager.STREAM_NOTIFICATION) |
                                                 (1 << AudioManager.STREAM_SYSTEM) |
                                                 (1 << AudioManager.STREAM_SYSTEM_ENFORCED);
-                if (!mContext.getResources().getBoolean(
-                        com.android.internal.R.bool.config_voice_capable)) {
+                if (!getTelephonyManager().isVoiceCapable()) {
                     ringerModeAffectedStreams |= (1 << AudioManager.STREAM_MUSIC);
                 }
                 db.execSQL("DELETE FROM system WHERE name='"
@@ -2548,7 +2547,7 @@
             StringBuilder val = new StringBuilder();
             List<Integer> defaultNetworks = TelephonyProperties.default_network();
             int phoneCount = 1;
-            TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+            TelephonyManager telephonyManager = getTelephonyManager();
             if (telephonyManager != null) {
                 phoneCount = telephonyManager.getSupportedModemCount();
             }
@@ -2663,4 +2662,8 @@
     private String getDefaultDeviceName() {
         return mContext.getResources().getString(R.string.def_device_name_simple, Build.MODEL);
     }
+
+    private TelephonyManager getTelephonyManager() {
+        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 443288c..c19a340 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -28,7 +28,7 @@
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
 import android.net.Uri;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
@@ -875,32 +875,23 @@
     }
 
     private byte[] getSoftAPConfiguration() {
-        try {
-            return mWifiManager.getWifiApConfiguration().getBytesForBackup();
-        } catch (IOException ioe) {
-            Log.e(TAG, "Failed to marshal SoftAPConfiguration" + ioe.getMessage());
-            return new byte[0];
-        }
+        return mWifiManager.retrieveSoftApBackupData();
     }
 
     private void restoreSoftApConfiguration(byte[] data) {
-        try {
-            WifiConfiguration config = WifiConfiguration
-                    .getWifiConfigFromBackup(new DataInputStream(new ByteArrayInputStream(data)));
-            if (DEBUG) Log.d(TAG, "Successfully unMarshaled WifiConfiguration ");
-            int originalApBand = config.apBand;
-            mWifiManager.setWifiApConfiguration(config);
+        SoftApConfiguration config = mWifiManager.restoreSoftApBackupData(data);
+        if (config != null) {
+            int originalApBand = config.getBand();
+            if (DEBUG) Log.d(TAG, "Successfully unMarshaled SoftApConfiguration ");
 
             // Depending on device hardware, we may need to notify the user of a setting change for
             // the apBand preference
             boolean dualMode = mWifiManager.isDualModeSupported();
-            int storedApBand = mWifiManager.getWifiApConfiguration().apBand;
+            int storedApBand = mWifiManager.getSoftApConfiguration().getBand();
             if (dualMode && storedApBand != originalApBand) {
                 Log.d(TAG, "restored ap configuration requires a conversion, notify the user");
                 WifiSoftApBandChangedNotifier.notifyUserOfApBandConversion(this);
             }
-        } catch (IOException | BackupUtils.BadVersionException e) {
-            Log.e(TAG, "Failed to unMarshal SoftAPConfiguration " + e.getMessage());
         }
     }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 0a2dd38..80077c8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -691,6 +691,9 @@
         dumpRepeatedSetting(s, p,
                 Settings.Global.ERROR_LOGCAT_PREFIX,
                 GlobalSettingsProto.ERROR_LOGCAT_LINES);
+        dumpRepeatedSetting(s, p,
+                Settings.Global.MAX_ERROR_BYTES_PREFIX,
+                GlobalSettingsProto.MAX_ERROR_BYTES);
 
         final long euiccToken = p.start(GlobalSettingsProto.EUICC);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c23a494..d5a3254 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -324,6 +324,7 @@
                     Settings.Global.LOW_POWER_MODE_SUGGESTION_PARAMS,
                     Settings.Global.LTE_SERVICE_FORCED,
                     Settings.Global.LID_BEHAVIOR,
+                    Settings.Global.MAX_ERROR_BYTES_PREFIX,
                     Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
                     Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
                     Settings.Global.MDC_INITIAL_MAX_RETRY,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2a5bdc7..404e791 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -127,7 +127,7 @@
         "SystemUI-proto",
         "metrics-helper-lib",
         "androidx.test.rules", "hamcrest-library",
-        "mockito-target-inline-minus-junit4",
+        "mockito-target-extended-minus-junit4",
         "testables",
         "truth-prebuilt",
         "dagger2-2.19",
diff --git a/packages/SystemUI/plugin_core/Android.bp b/packages/SystemUI/plugin_core/Android.bp
index 42d6762..581fef7 100644
--- a/packages/SystemUI/plugin_core/Android.bp
+++ b/packages/SystemUI/plugin_core/Android.bp
@@ -16,4 +16,8 @@
     sdk_version: "current",
     name: "PluginCoreLib",
     srcs: ["src/**/*.java"],
+
+    // Enforce that the library is built against java 8 so that there are
+    // no compatibility issues with launcher
+    java_version: "1.8",
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 70f8e15..deaafca 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -28,6 +28,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import android.annotation.NonNull;
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -161,6 +162,23 @@
     }
 
     /**
+     * Removes the outdated snapshot of home task.
+     */
+    public void invalidateHomeTaskSnapshot(final Activity homeActivity) {
+        mBackgroundExecutor.submit(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    ActivityTaskManager.getService().invalidateHomeTaskSnapshot(
+                            homeActivity.getActivityToken());
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to invalidate home snapshot", e);
+                }
+            }
+        });
+    }
+
+    /**
      * @return the activity label, badging if necessary.
      */
     public String getBadgedActivityLabel(ActivityInfo info, int userId) {
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index d66a53c..caf5ee0 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -163,8 +163,7 @@
     public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
             boolean showMissingSim) {
         mContext = context;
-        mIsEmergencyCallCapable = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_voice_capable);
+        mIsEmergencyCallCapable = getTelephonyManager().isVoiceCapable();
 
         mShowAirplaneMode = showAirplaneMode;
         mShowMissingSim = showMissingSim;
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 867014b6..5d084e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -28,6 +28,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
 import android.util.Slog;
 import android.view.MotionEvent;
@@ -86,13 +87,16 @@
 
     public EmergencyButton(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mIsVoiceCapable = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_voice_capable);
+        mIsVoiceCapable = getTelephonyManager().isVoiceCapable();
         mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
     }
 
+    private TelephonyManager getTelephonyManager() {
+        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 2afcb12..d68fe15 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -42,6 +42,7 @@
 import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -288,6 +289,7 @@
     public void onTuningChanged(String key, String newValue) {
         if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
             ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue);
+            setVisibility(icons.contains(mSlotBattery) ? View.GONE : View.VISIBLE);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 6821265..dd38a33 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -124,6 +124,7 @@
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.wm.DisplayWindowController;
+import com.android.systemui.wm.SystemWindows;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -323,6 +324,7 @@
     @Inject Lazy<Recents> mRecents;
     @Inject Lazy<StatusBar> mStatusBar;
     @Inject Lazy<DisplayWindowController> mDisplayWindowController;
+    @Inject Lazy<SystemWindows> mSystemWindows;
 
     @Inject
     public Dependency() {
@@ -513,6 +515,7 @@
         mProviders.put(Recents.class, mRecents::get);
         mProviders.put(StatusBar.class, mStatusBar::get);
         mProviders.put(DisplayWindowController.class, mDisplayWindowController::get);
+        mProviders.put(SystemWindows.class, mSystemWindows::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index b562243..1277736 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -109,8 +109,6 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-import dagger.Lazy;
-
 /**
  * Bubbles are a special type of content that can "float" on top of other apps or System UI.
  * Bubbles can be expanded to show more content.
@@ -147,7 +145,7 @@
     private BubbleExpandListener mExpandListener;
     @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
     private final NotificationGroupManager mNotificationGroupManager;
-    private final Lazy<ShadeController> mShadeController;
+    private final ShadeController mShadeController;
     private final RemoteInputUriController mRemoteInputUriController;
     private Handler mHandler = new Handler() {};
 
@@ -162,6 +160,10 @@
     // Saves notification keys of user created "fake" bubbles so that we can allow notifications
     // like these to bubble by default. Doesn't persist across reboots, not a long-term solution.
     private final HashSet<String> mUserCreatedBubbles;
+    // If we're auto-bubbling bubbles via a whitelist, we need to track which notifs from that app
+    // have been "demoted" back to a notification so that we don't auto-bubbles those again.
+    // Doesn't persist across reboots, not a long-term solution.
+    private final HashSet<String> mUserBlockedBubbles;
 
     // Bubbles get added to the status bar view
     private final StatusBarWindowController mStatusBarWindowController;
@@ -243,7 +245,7 @@
     public BubbleController(Context context,
             StatusBarWindowController statusBarWindowController,
             StatusBarStateController statusBarStateController,
-            Lazy<ShadeController> shadeController,
+            ShadeController shadeController,
             BubbleData data,
             ConfigurationController configurationController,
             NotificationInterruptionStateProvider interruptionStateProvider,
@@ -261,7 +263,7 @@
     public BubbleController(Context context,
             StatusBarWindowController statusBarWindowController,
             StatusBarStateController statusBarStateController,
-            Lazy<ShadeController> shadeController,
+            ShadeController shadeController,
             BubbleData data,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             ConfigurationController configurationController,
@@ -272,6 +274,7 @@
             NotificationEntryManager entryManager,
             RemoteInputUriController remoteInputUriController) {
         mContext = context;
+        mShadeController = shadeController;
         mNotificationInterruptionStateProvider = interruptionStateProvider;
         mNotifUserManager = notifUserManager;
         mZenModeController = zenModeController;
@@ -319,7 +322,6 @@
                     }
                 });
 
-        mShadeController = shadeController;
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarStateListener = new StatusBarStateListener();
         statusBarStateController.addCallback(mStatusBarStateListener);
@@ -348,6 +350,7 @@
                 });
 
         mUserCreatedBubbles = new HashSet<>();
+        mUserBlockedBubbles = new HashSet<>();
 
         mScreenshotHelper = new ScreenshotHelper(context);
     }
@@ -579,10 +582,11 @@
         if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
             Log.d(TAG, "onUserCreatedBubble: " + entry.getKey());
         }
-        mShadeController.get().collapsePanel(true);
+        mShadeController.collapsePanel(true);
         entry.setFlagBubble(true);
         updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
         mUserCreatedBubbles.add(entry.getKey());
+        mUserBlockedBubbles.remove(entry.getKey());
     }
 
     /**
@@ -598,6 +602,12 @@
         entry.setFlagBubble(false);
         removeBubble(entry.getKey(), DISMISS_BLOCKED);
         mUserCreatedBubbles.remove(entry.getKey());
+        if (BubbleExperimentConfig.isPackageWhitelistedToAutoBubble(
+                mContext, entry.getSbn().getPackageName())) {
+            // This package is whitelist but user demoted the bubble, let's save it so we don't
+            // auto-bubble for the whitelist again.
+            mUserBlockedBubbles.add(entry.getKey());
+        }
     }
 
     /**
@@ -727,8 +737,9 @@
         @Override
         public void onPendingEntryAdded(NotificationEntry entry) {
             boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
+            boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
             boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
-                    mContext, entry, previouslyUserCreated);
+                    mContext, entry, previouslyUserCreated, userBlocked);
 
             if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) {
@@ -743,8 +754,9 @@
         @Override
         public void onPreEntryUpdated(NotificationEntry entry) {
             boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
+            boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
             boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
-                    mContext, entry, previouslyUserCreated);
+                    mContext, entry, previouslyUserCreated, userBlocked);
 
             boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && (canLaunchInActivityView(mContext, entry) || wasAdjusted);
@@ -1107,36 +1119,33 @@
     }
 
     private void sendScreenshotToBubble(Bubble bubble) {
-        // delay allows the bubble menu to disappear before the screenshot
-        // done here because we already have a Handler to delay with.
-        // TODO: Hide bubble + menu UI from screenshots entirely instead of just delaying.
-        mHandler.postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                mScreenshotHelper.takeScreenshot(
-                        android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
-                        true /* hasStatus */,
-                        true /* hasNav */,
-                        mHandler,
-                        new Consumer<Uri>() {
-                            @Override
-                            public void accept(Uri uri) {
-                                if (uri != null) {
-                                    NotificationEntry entry = bubble.getEntry();
-                                    Pair<RemoteInput, Notification.Action> pair = entry.getSbn()
-                                            .getNotification().findRemoteInputActionPair(false);
-                                    RemoteInput remoteInput = pair.first;
-                                    Notification.Action action = pair.second;
-                                    Intent dataIntent = prepareRemoteInputFromData("image/png", uri,
-                                            remoteInput, entry);
-                                    sendRemoteInput(dataIntent, entry, action.actionIntent);
-                                    mBubbleData.setSelectedBubble(bubble);
-                                    mBubbleData.setExpanded(true);
-                                }
+        mScreenshotHelper.takeScreenshot(
+                android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+                true /* hasStatus */,
+                true /* hasNav */,
+                mHandler,
+                new Consumer<Uri>() {
+                    @Override
+                    public void accept(Uri uri) {
+                        if (uri != null) {
+                            NotificationEntry entry = bubble.getEntry();
+                            Pair<RemoteInput, Notification.Action> pair = entry.getSbn()
+                                    .getNotification().findRemoteInputActionPair(false);
+                            if (pair != null) {
+                                RemoteInput remoteInput = pair.first;
+                                Notification.Action action = pair.second;
+                                Intent dataIntent = prepareRemoteInputFromData("image/png", uri,
+                                        remoteInput, entry);
+                                sendRemoteInput(dataIntent, entry, action.actionIntent);
+                                mBubbleData.setSelectedBubble(bubble);
+                                mBubbleData.setExpanded(true);
+                            } else {
+                                Log.w(TAG, "No RemoteInput found for notification: "
+                                        + entry.getSbn().getKey());
                             }
-                        });
-            }
-        }, 200);
+                        }
+                    }
+                });
     }
 
     private final BubbleScreenshotListener mBubbleScreenshotListener =
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 4187985..21471ec 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -143,7 +143,7 @@
      * @return whether an adjustment was made.
      */
     static boolean adjustForExperiments(Context context, NotificationEntry entry,
-            boolean previouslyUserCreated) {
+            boolean previouslyUserCreated, boolean userBlocked) {
         Notification.BubbleMetadata metadata = null;
         boolean addedMetadata = false;
         boolean whiteListedToAutoBubble =
@@ -205,7 +205,9 @@
             }
         }
 
-        boolean bubbleForWhitelist = whiteListedToAutoBubble && (addedMetadata || hasMetadata);
+        boolean bubbleForWhitelist = !userBlocked
+                && whiteListedToAutoBubble
+                && (addedMetadata || hasMetadata);
         if ((previouslyUserCreated && addedMetadata) || bubbleForWhitelist) {
             // Update to a previous bubble (or new autobubble), set its flag now.
             if (DEBUG_EXPERIMENTS) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java
index e8eb72e..bf83065 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java
@@ -31,6 +31,9 @@
     private FrameLayout mMenu;
     private boolean mShowing = false;
 
+    /** Delay before taking a screenshot once the button is tapped to allow the menu time to hide.*/
+    public static final long SCREENSHOT_DELAY = 200;
+
     public BubbleMenuView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 29a4bb1..245b232 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -1731,17 +1731,18 @@
      */
     public void showBubbleMenu() {
         PointF currentPos = mStackAnimationController.getStackPosition();
-        float yPos = currentPos.y;
-        float xPos = currentPos.x;
-        if (mStackAnimationController.isStackOnLeftSide()) {
-            xPos += mBubbleSize;
-        } else {
-            //TODO: Use the width of the menu instead of this fixed offset. Offset used for now
-            // because menu width isn't correct the first time the menu is shown.
-            xPos -= mBubbleMenuOffset;
-        }
+        mBubbleMenuView.setVisibility(View.INVISIBLE);
+        post(() -> {
+            float yPos = currentPos.y;
+            float xPos = currentPos.x;
+            if (mStackAnimationController.isStackOnLeftSide()) {
+                xPos += mBubbleSize;
+            } else {
+                xPos -= mBubbleMenuView.getMenuView().getWidth();
+            }
 
-        mBubbleMenuView.show(xPos, yPos);
+            mBubbleMenuView.show(xPos, yPos);
+        });
     }
 
     /**
@@ -1762,6 +1763,12 @@
      * Take a screenshot and send it to the specified bubble.
      */
     public void sendScreenshotToBubble(Bubble bubble) {
-        mBubbleScreenshotListener.onBubbleScreenshot(bubble);
+        hideBubbleMenu();
+        // delay allows the bubble menu to disappear before the screenshot
+        // done here because we already have a Handler to delay with.
+        // TODO: Hide bubble + menu UI from screenshots entirely instead of just delaying.
+        postDelayed(() -> {
+            mBubbleScreenshotListener.onBubbleScreenshot(bubble);
+        }, BubbleMenuView.SCREENSHOT_DELAY);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index b1d205c..995b35f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -64,6 +64,7 @@
     private boolean mMovedEnough;
     private int mTouchSlopSquared;
     private VelocityTracker mVelocityTracker;
+    private Runnable mShowBubbleMenuRunnable;
 
     /** View that was initially touched, when we received the first ACTION_DOWN event. */
     private View mTouchedView;
@@ -133,6 +134,11 @@
                 if (isStack) {
                     mViewPositionOnTouchDown.set(mStack.getStackPosition());
                     mStack.onDragStart();
+                    if (!mStack.isShowingBubbleMenu() && !mStack.isExpanded()) {
+                        mShowBubbleMenuRunnable = mStack::showBubbleMenu;
+                        mStack.postDelayed(mShowBubbleMenuRunnable,
+                                ViewConfiguration.getLongPressTimeout());
+                    }
                 } else if (isFlyout) {
                     mStack.onFlyoutDragStart();
                 } else {
@@ -156,6 +162,7 @@
                 }
 
                 if (mMovedEnough) {
+                    mStack.removeCallbacks(mShowBubbleMenuRunnable);
                     if (isStack) {
                         mStack.onDragged(viewX, viewY);
                     } else if (isFlyout) {
@@ -163,13 +170,6 @@
                     } else {
                         mStack.onBubbleDragged(mTouchedView, viewX, viewY);
                     }
-                } else {
-                    float touchTime = event.getEventTime() - event.getDownTime();
-                    if (touchTime > ViewConfiguration.getLongPressTimeout() && !mStack.isExpanded()
-                            && BubbleExperimentConfig.allowBubbleScreenshotMenu(mContext)) {
-                        mStack.showBubbleMenu();
-                        return true;
-                    }
                 }
 
                 final boolean currentlyInDismissTarget = mStack.isInDismissTarget(event);
@@ -196,6 +196,8 @@
                 if (mStack.isShowingBubbleMenu()) {
                     resetForNextGesture();
                     return true;
+                } else {
+                    mStack.removeCallbacks(mShowBubbleMenuRunnable);
                 }
                 trackMovement(event);
                 mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 4593164..df79310 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -22,8 +22,6 @@
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.settings.BrightnessDialog;
 import com.android.systemui.tuner.TunerActivity;
-import com.android.systemui.usb.UsbDebuggingActivity;
-import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
 
 import dagger.Binds;
 import dagger.Module;
@@ -58,17 +56,4 @@
     @IntoMap
     @ClassKey(BrightnessDialog.class)
     public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
-
-    /** Inject into UsbDebuggingActivity. */
-    @Binds
-    @IntoMap
-    @ClassKey(UsbDebuggingActivity.class)
-    public abstract Activity bindUsbDebuggingActivity(UsbDebuggingActivity activity);
-
-    /** Inject into UsbDebuggingSecondaryUserActivity. */
-    @Binds
-    @IntoMap
-    @ClassKey(UsbDebuggingSecondaryUserActivity.class)
-    public abstract Activity bindUsbDebuggingSecondaryUserActivity(
-            UsbDebuggingSecondaryUserActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 606605a..6ea3f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -20,6 +20,7 @@
 
 import android.app.INotificationManager;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
@@ -150,6 +151,13 @@
     /** */
     @Singleton
     @Provides
+    public IPackageManager provideIPackageManager() {
+        return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+    }
+
+    /** */
+    @Singleton
+    @Provides
     public LayoutInflater providerLayoutInflater(Context context) {
         return LayoutInflater.from(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index f44eae7..ccb6c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -39,7 +39,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.ShadeControllerImpl;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.util.Optional;
@@ -82,7 +82,7 @@
             KeyguardEnvironmentImpl keyguardEnvironment);
 
     @Binds
-    abstract ShadeController provideShadeController(StatusBar statusBar);
+    abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
 
     @Singleton
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java b/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java
index ea1def0..d2fe394 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java
@@ -28,10 +28,12 @@
  * and triaging purposes.
  */
 public class DozeEvent extends RichEvent {
-    public static final int TOTAL_EVENT_TYPES = 19;
-
-    public DozeEvent(int logLevel, int type, String reason) {
-        super(logLevel, type, reason);
+    /**
+     * Initializes a doze event
+     */
+    public DozeEvent init(@EventType int type, String reason) {
+        super.init(DEBUG, type, reason);
+        return this;
     }
 
     /**
@@ -89,21 +91,6 @@
         }
     }
 
-    /**
-     * Builds a DozeEvent.
-     */
-    public static class DozeEventBuilder extends RichEvent.Builder<DozeEventBuilder> {
-        @Override
-        public DozeEventBuilder getBuilder() {
-            return this;
-        }
-
-        @Override
-        public RichEvent build() {
-            return new DozeEvent(mLogLevel, mType, mReason);
-        }
-    }
-
     @IntDef({PICKUP_WAKEUP, PULSE_START, PULSE_FINISH, NOTIFICATION_PULSE, DOZING, FLING,
             EMERGENCY_CALL, KEYGUARD_BOUNCER_CHANGED, SCREEN_ON, SCREEN_OFF, MISSED_TICK,
             TIME_TICK_SCHEDULED, KEYGUARD_VISIBILITY_CHANGE, DOZE_STATE_CHANGED, WAKE_DISPLAY,
@@ -132,6 +119,7 @@
     public static final int PULSE_DROPPED = 16;
     public static final int PULSE_DISABLED_BY_PROX = 17;
     public static final int SENSOR_TRIGGERED = 18;
+    public static final int TOTAL_EVENT_TYPES = 19;
 
     public static final int TOTAL_REASONS = 10;
     @IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 2e4466d..fe50421 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -35,9 +35,11 @@
  *      dependency DumpController DozeLog
  */
 @Singleton
-public class DozeLog extends SysuiLog {
+public class DozeLog extends SysuiLog<DozeEvent> {
     private static final String TAG = "DozeLog";
 
+    private DozeEvent mRecycledEvent;
+
     private boolean mPulsing;
     private long mSince;
     private SummaryStats mPickupPulseNearVibrationStats;
@@ -73,8 +75,8 @@
      * Appends pickup wakeup event to the logs
      */
     public void tracePickupWakeUp(boolean withinVibrationThreshold) {
-        if (log(DozeEvent.PICKUP_WAKEUP,
-                "withinVibrationThreshold=" + withinVibrationThreshold)) {
+        log(DozeEvent.PICKUP_WAKEUP, "withinVibrationThreshold=" + withinVibrationThreshold);
+        if (mEnabled) {
             (withinVibrationThreshold ? mPickupPulseNearVibrationStats
                     : mPickupPulseNotNearVibrationStats).append();
         }
@@ -85,27 +87,24 @@
      * @param reason why the pulse started
      */
     public void tracePulseStart(@DozeEvent.Reason int reason) {
-        if (log(DozeEvent.PULSE_START, DozeEvent.reasonToString(reason))) {
-            mPulsing = true;
-        }
+        log(DozeEvent.PULSE_START, DozeEvent.reasonToString(reason));
+        if (mEnabled) mPulsing = true;
     }
 
     /**
      * Appends pulse finished event to the logs
      */
     public void tracePulseFinish() {
-        if (log(DozeEvent.PULSE_FINISH)) {
-            mPulsing = false;
-        }
+        log(DozeEvent.PULSE_FINISH);
+        if (mEnabled) mPulsing = false;
     }
 
     /**
      * Appends pulse event to the logs
      */
     public void traceNotificationPulse() {
-        if (log(DozeEvent.NOTIFICATION_PULSE)) {
-            mNotificationPulseStats.append();
-        }
+        log(DozeEvent.NOTIFICATION_PULSE);
+        if (mEnabled) mNotificationPulseStats.append();
     }
 
     /**
@@ -113,9 +112,8 @@
      * @param dozing true if dozing, else false
      */
     public void traceDozing(boolean dozing) {
-        if (log(DozeEvent.DOZING, "dozing=" + dozing)) {
-            mPulsing = false;
-        }
+        log(DozeEvent.DOZING, "dozing=" + dozing);
+        if (mEnabled) mPulsing = false;
     }
 
     /**
@@ -133,9 +131,8 @@
      * Appends emergency call event to the logs
      */
     public void traceEmergencyCall() {
-        if (log(DozeEvent.EMERGENCY_CALL)) {
-            mEmergencyCallStats.append();
-        }
+        log(DozeEvent.EMERGENCY_CALL);
+        if (mEnabled) mEmergencyCallStats.append();
     }
 
     /**
@@ -150,7 +147,8 @@
      * Appends screen-on event to the logs
      */
     public void traceScreenOn() {
-        if (log(DozeEvent.SCREEN_ON, "pulsing=" + mPulsing)) {
+        log(DozeEvent.SCREEN_ON, "pulsing=" + mPulsing);
+        if (mEnabled) {
             (mPulsing ? mScreenOnPulsingStats : mScreenOnNotPulsingStats).append();
             mPulsing = false;
         }
@@ -188,10 +186,8 @@
      * @param showing whether the keyguard is now showing
      */
     public void traceKeyguard(boolean showing) {
-        if (log(DozeEvent.KEYGUARD_VISIBILITY_CHANGE, "showing=" + showing)
-                && !showing) {
-            mPulsing = false;
-        }
+        log(DozeEvent.KEYGUARD_VISIBILITY_CHANGE, "showing=" + showing);
+        if (mEnabled && !showing) mPulsing = false;
     }
 
     /**
@@ -217,12 +213,11 @@
      * @param reason why proximity result was triggered
      */
     public void traceProximityResult(boolean near, long millis, @DozeEvent.Reason int reason) {
-        if (log(DozeEvent.PROXIMITY_RESULT,
+        log(DozeEvent.PROXIMITY_RESULT,
                 " reason=" + DozeEvent.reasonToString(reason)
-                + " near=" + near
-                + " millis=" + millis)) {
-            mProxStats[reason][near ? 0 : 1].append();
-        }
+                        + " near=" + near
+                        + " millis=" + millis);
+        if (mEnabled) mProxStats[reason][near ? 0 : 1].append();
     }
 
     /**
@@ -250,15 +245,16 @@
         }
     }
 
-    private boolean log(@DozeEvent.EventType int eventType) {
-        return log(eventType, "");
+    private void log(@DozeEvent.EventType int eventType) {
+        log(eventType, "");
     }
 
-    private boolean log(@DozeEvent.EventType int eventType, String msg) {
-        return super.log(new DozeEvent.DozeEventBuilder()
-                .setType(eventType)
-                .setReason(msg)
-                .build());
+    private void log(@DozeEvent.EventType int eventType, String msg) {
+        if (mRecycledEvent != null) {
+            mRecycledEvent = log(mRecycledEvent.init(eventType, msg));
+        } else {
+            mRecycledEvent = log(new DozeEvent().init(eventType, msg));
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8d08b28..beba203 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -210,8 +210,7 @@
     private AlarmManager mAlarmManager;
     private AudioManager mAudioManager;
     private StatusBarManager mStatusBarManager;
-    private final StatusBarWindowController mStatusBarWindowController =
-            Dependency.get(StatusBarWindowController.class);
+    private final StatusBarWindowController mStatusBarWindowController;
     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
 
     private boolean mSystemReady;
@@ -688,12 +687,14 @@
             FalsingManager falsingManager,
             LockPatternUtils lockPatternUtils,
             BroadcastDispatcher broadcastDispatcher,
+            StatusBarWindowController statusBarWindowController,
             Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManagerLazy,
             DismissCallbackRegistry dismissCallbackRegistry) {
         super(context);
         mFalsingManager = falsingManager;
         mLockPatternUtils = lockPatternUtils;
         mBroadcastDispatcher = broadcastDispatcher;
+        mStatusBarWindowController = statusBarWindowController;
         mStatusBarKeyguardViewManagerLazy = statusBarKeyguardViewManagerLazy;
         mDismissCallbackRegistry = dismissCallbackRegistry;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/log/Event.java b/packages/SystemUI/src/com/android/systemui/log/Event.java
index 92862a2..7bc1abf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/Event.java
+++ b/packages/SystemUI/src/com/android/systemui/log/Event.java
@@ -37,20 +37,28 @@
     public static final int INFO = 4;
     public static final int WARN = 5;
     public static final int ERROR = 6;
+    public static final @Level int DEFAULT_LOG_LEVEL = DEBUG;
 
     private long mTimestamp;
-    private @Level int mLogLevel = DEBUG;
-    protected String mMessage;
+    private @Level int mLogLevel = DEFAULT_LOG_LEVEL;
+    private String mMessage = "";
 
-    public Event(String message) {
-        mTimestamp = System.currentTimeMillis();
-        mMessage = message;
+    /**
+     * initialize an event with a message
+     */
+    public Event init(String message) {
+        init(DEFAULT_LOG_LEVEL, message);
+        return this;
     }
 
-    public Event(@Level int logLevel, String message) {
+    /**
+     * initialize an event with a logLevel and message
+     */
+    public Event init(@Level int logLevel, String message) {
         mTimestamp = System.currentTimeMillis();
         mLogLevel = logLevel;
         mMessage = message;
+        return this;
     }
 
     public String getMessage() {
@@ -64,4 +72,13 @@
     public @Level int getLogLevel() {
         return mLogLevel;
     }
+
+    /**
+     * Recycle this event
+     */
+    void recycle() {
+        mTimestamp = -1;
+        mLogLevel = DEFAULT_LOG_LEVEL;
+        mMessage = "";
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
index acf761ed..470f2b0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
@@ -23,23 +23,21 @@
  * Events are stored in {@link SysuiLog} and can be printed in a dumpsys.
  */
 public abstract class RichEvent extends Event {
-    private final int mType;
-    private final String mReason;
+    private int mType;
 
     /**
-     * Create a rich event that includes an event type that matches with an index in the array
+     * Initializes a rich event that includes an event type that matches with an index in the array
      * getEventLabels().
      */
-    public RichEvent(@Event.Level int logLevel, int type, String reason) {
-        super(logLevel, null);
+    public RichEvent init(@Event.Level int logLevel, int type, String reason) {
         final int numEvents = getEventLabels().length;
         if (type < 0 || type >= numEvents) {
             throw new IllegalArgumentException("Unsupported event type. Events only supported"
                     + " from 0 to " + (numEvents - 1) + ", but given type=" + type);
         }
         mType = type;
-        mReason = reason;
-        mMessage = getEventLabels()[mType] + " " + mReason;
+        super.init(logLevel, getEventLabels()[mType] + " " + reason);
+        return this;
     }
 
     /**
@@ -49,25 +47,43 @@
      */
     public abstract String[] getEventLabels();
 
-    public int getType() {
-        return mType;
+    @Override
+    public void recycle() {
+        super.recycle();
+        mType = -1;
     }
 
-    public String getReason() {
-        return mReason;
+    public int getType() {
+        return mType;
     }
 
     /**
      * Builder to build a RichEvent.
      * @param <B> Log specific builder that is extending this builder
+     * @param <E> Type of event we'll be building
      */
-    public abstract static class Builder<B extends Builder<B>> {
+    public abstract static class Builder<B extends Builder<B, E>, E extends RichEvent> {
         public static final int UNINITIALIZED = -1;
 
+        public final SysuiLog mLog;
         private B mBuilder = getBuilder();
-        protected int mType = UNINITIALIZED;
+        protected int mType;
         protected String mReason;
-        protected @Level int mLogLevel = VERBOSE;
+        protected @Level int mLogLevel;
+
+        public Builder(SysuiLog sysuiLog) {
+            mLog = sysuiLog;
+            reset();
+        }
+
+        /**
+         * Reset this builder's parameters so it can be reused to build another RichEvent.
+         */
+        public void reset() {
+            mType = UNINITIALIZED;
+            mReason = null;
+            mLogLevel = VERBOSE;
+        }
 
         /**
          * Get the log-specific builder.
@@ -75,9 +91,9 @@
         public abstract B getBuilder();
 
         /**
-         * Build the log-specific event.
+         * Build the log-specific event given an event to populate.
          */
-        public abstract RichEvent build();
+        public abstract E build(E e);
 
         /**
          * Optional - set the log level. Defaults to DEBUG.
diff --git a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
index 0f71d22..4e15668 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
@@ -20,6 +20,7 @@
 import android.os.SystemProperties;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.DumpController;
 import com.android.systemui.Dumpable;
@@ -39,23 +40,26 @@
  * To manually view the logs via adb:
  *      adb shell dumpsys activity service com.android.systemui/.SystemUIService \
  *      dependency DumpController <SysuiLogId>
+ *
+ * Logs can be disabled by setting the following SystemProperty and then restarting the device:
+ *      adb shell setprop persist.sysui.log.enabled.<id> true/false && adb reboot
+ *
+ * @param <E> Type of event we'll be logging
  */
-public class SysuiLog implements Dumpable {
+public class SysuiLog<E extends Event> implements Dumpable {
     public static final SimpleDateFormat DATE_FORMAT =
             new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
 
-    private final Object mDataLock = new Object();
+    protected final Object mDataLock = new Object();
     private final String mId;
     private final int mMaxLogs;
     protected boolean mEnabled;
     protected boolean mLogToLogcatEnabled;
 
-    @VisibleForTesting protected ArrayDeque<Event> mTimeline;
+    @VisibleForTesting protected ArrayDeque<E> mTimeline;
 
     /**
      * Creates a SysuiLog
-     * To enable or disable logs, set the system property and then restart the device:
-     *      adb shell setprop sysui.log.enabled.<id> true/false && adb reboot
      * @param dumpController where to register this logger's dumpsys
      * @param id user-readable tag for this logger
      * @param maxDebugLogs maximum number of logs to retain when {@link sDebuggable} is true
@@ -79,23 +83,20 @@
         dumpController.registerDumpable(mId, this);
     }
 
-    public SysuiLog(DumpController dumpController, String id) {
-        this(dumpController, id, DEFAULT_MAX_DEBUG_LOGS, DEFAULT_MAX_LOGS);
-    }
-
     /**
      * Logs an event to the timeline which can be printed by the dumpsys.
      * May also log to logcat if enabled.
-     * @return true if event was logged, else false
+     * @return the last event that was discarded from the Timeline (can be recycled)
      */
-    public boolean log(Event event) {
+    public E log(E event) {
         if (!mEnabled) {
-            return false;
+            return null;
         }
 
+        E recycledEvent = null;
         synchronized (mDataLock) {
             if (mTimeline.size() >= mMaxLogs) {
-                mTimeline.removeFirst();
+                recycledEvent = mTimeline.removeFirst();
             }
 
             mTimeline.add(event);
@@ -121,13 +122,18 @@
                     break;
             }
         }
-        return true;
+
+        if (recycledEvent != null) {
+            recycledEvent.recycle();
+        }
+
+        return recycledEvent;
     }
 
     /**
      * @return user-readable string of the given event with timestamp
      */
-    public String eventToTimestampedString(Event event) {
+    private String eventToTimestampedString(Event event) {
         StringBuilder sb = new StringBuilder();
         sb.append(SysuiLog.DATE_FORMAT.format(event.getTimestamp()));
         sb.append(" ");
@@ -142,9 +148,7 @@
         return event.getMessage();
     }
 
-    /**
-     * only call on this method if you have the mDataLock
-     */
+    @GuardedBy("mDataLock")
     private void dumpTimelineLocked(PrintWriter pw) {
         pw.println("\tTimeline:");
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index feb10a2..db52e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -177,7 +177,7 @@
                 record.tile.removeCallback(record.callback);
             }
             mTileLayout = mMediaTileLayout;
-            setTiles(mHost.getTiles());
+            if (mHost != null) setTiles(mHost.getTiles());
             mTileLayout.setListening(mListening);
             return true;
         } else if (!mMediaPlayer.hasMediaSession()
@@ -190,7 +190,7 @@
                 record.tile.removeCallback(record.callback);
             }
             mTileLayout = mRegularTileLayout;
-            setTiles(mHost.getTiles());
+            if (mHost != null) setTiles(mHost.getTiles());
             mTileLayout.setListening(mListening);
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 9dcfb6a..9a64b30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -89,11 +90,22 @@
         }
         final RankingMap currentRanking = getCurrentRanking();
         mMainHandler.post(() -> {
+            // There's currently a race condition between the calls to getActiveNotifications() and
+            // getCurrentRanking(). It's possible for the ranking that we store here to not contain
+            // entries for every notification in getActiveNotifications(). To prevent downstream
+            // crashes, we temporarily fill in these missing rankings with stubs.
+            // See b/146011844 for long-term fix
+            final List<Ranking> newRankings = new ArrayList<>();
+            for (StatusBarNotification sbn : notifications) {
+                newRankings.add(getRankingOrTemporaryStandIn(currentRanking, sbn.getKey()));
+            }
+            final RankingMap completeMap = new RankingMap(newRankings.toArray(new Ranking[0]));
+
             for (StatusBarNotification sbn : notifications) {
                 if (mDownstreamListener != null) {
-                    mDownstreamListener.onNotificationPosted(sbn, currentRanking);
+                    mDownstreamListener.onNotificationPosted(sbn, completeMap);
                 }
-                mEntryManager.addNotification(sbn, currentRanking);
+                mEntryManager.addNotification(sbn, completeMap);
             }
         });
         NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
@@ -192,6 +204,35 @@
         }
     }
 
+    private static Ranking getRankingOrTemporaryStandIn(RankingMap rankingMap, String key) {
+        Ranking ranking = new Ranking();
+        if (!rankingMap.getRanking(key, ranking)) {
+            ranking.populate(
+                    key,
+                    0,
+                    false,
+                    0,
+                    0,
+                    0,
+                    null,
+                    null,
+                    null,
+                    new ArrayList<>(),
+                    new ArrayList<>(),
+                    false,
+                    0,
+                    false,
+                    0,
+                    false,
+                    new ArrayList<>(),
+                    new ArrayList<>(),
+                    false,
+                    false
+            );
+        }
+        return ranking;
+    }
+
     public interface NotificationSettingsListener {
 
         default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
deleted file mode 100644
index 1ac8198..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-
-/**
- * Interface for accepting notification updates from {@link NotificationListener}.
- */
-public interface NotificationUpdateHandler {
-    /**
-     * Add a new notification and update the current notification ranking map.
-     *
-     * @param notification Notification to add
-     * @param ranking RankingMap to update with
-     */
-    void addNotification(StatusBarNotification notification,
-            NotificationListenerService.RankingMap ranking);
-
-    /**
-     * Remove a notification and update the current notification ranking map.
-     *
-     * @param key Key identifying the notification to remove
-     * @param ranking RankingMap to update with
-     * @param reason why the notification is being removed, e.g.
-     * {@link NotificationListenerService#REASON_CANCEL}.
-     */
-    void removeNotification(String key, NotificationListenerService.RankingMap ranking, int reason);
-
-    /**
-     * Update a given notification and the current notification ranking map.
-     *
-     * @param notification Updated notification
-     * @param ranking RankingMap to update with
-     */
-    void updateNotification(StatusBarNotification notification,
-            NotificationListenerService.RankingMap ranking);
-
-    /**
-     * Update with a new notification ranking map.
-     *
-     * @param ranking RankingMap to update with
-     */
-    void updateNotificationRanking(NotificationListenerService.RankingMap ranking);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 4204f68..1648196 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -37,7 +37,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.Utils;
 
@@ -49,8 +48,6 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-import dagger.Lazy;
-
 /**
  * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
  * on their group structure. For example, if a notification becomes bundled with another,
@@ -75,9 +72,6 @@
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final NotificationEntryManager mEntryManager;
 
-    // Lazy
-    private final Lazy<ShadeController> mShadeController;
-
     /**
      * {@code true} if notifications not part of a group should by default be rendered in their
      * expanded state. If {@code false}, then only the first notification will be expanded if
@@ -105,7 +99,6 @@
             VisualStabilityManager visualStabilityManager,
             StatusBarStateController statusBarStateController,
             NotificationEntryManager notificationEntryManager,
-            Lazy<ShadeController> shadeController,
             KeyguardBypassController bypassController,
             BubbleController bubbleController,
             DynamicPrivacyController privacyController) {
@@ -117,7 +110,6 @@
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
         mEntryManager = notificationEntryManager;
-        mShadeController = shadeController;
         Resources res = context.getResources();
         mAlwaysExpandNonGroupedNotification =
                 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 7a58097..dbefc7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -37,7 +37,6 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.NotificationUpdateHandler;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
@@ -94,7 +93,6 @@
 public class NotificationEntryManager implements
         Dumpable,
         NotificationContentInflater.InflationCallback,
-        NotificationUpdateHandler,
         VisualStabilityManager.Callback {
     private static final String TAG = "NotificationEntryMgr";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -267,14 +265,13 @@
             NotificationEntry entry = mPendingNotifications.get(key);
             entry.abortTask();
             mPendingNotifications.remove(key);
-            mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry.getSbn(), null,
-                    "PendingNotification aborted. " + reason);
+            mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry, "PendingNotification aborted"
+                    + " reason=" + reason);
         }
         NotificationEntry addedEntry = getActiveNotificationUnfiltered(key);
         if (addedEntry != null) {
             addedEntry.abortTask();
-            mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getSbn(),
-                    null, reason);
+            mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getKey() + " " + reason);
         }
     }
 
@@ -347,7 +344,6 @@
     }
 
 
-    @Override
     public void removeNotification(String key, RankingMap ranking,
             int reason) {
         removeNotificationInternal(key, ranking, obtainVisibility(key), false /* forceRemove */,
@@ -501,13 +497,12 @@
 
         abortExistingInflation(key, "addNotification");
         mPendingNotifications.put(key, entry);
-        mNotifLog.log(NotifEvent.NOTIF_ADDED, entry.getSbn());
+        mNotifLog.log(NotifEvent.NOTIF_ADDED, entry);
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onPendingEntryAdded(entry);
         }
     }
 
-    @Override
     public void addNotification(StatusBarNotification notification, RankingMap ranking) {
         try {
             addNotificationInternal(notification, ranking);
@@ -536,7 +531,7 @@
         entry.setSbn(notification);
         mGroupManager.onEntryUpdated(entry, oldSbn);
 
-        mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry.getSbn(), entry.getRanking());
+        mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry);
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onPreEntryUpdated(entry);
         }
@@ -557,7 +552,6 @@
         }
     }
 
-    @Override
     public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
         try {
             updateNotificationInternal(notification, ranking);
@@ -577,7 +571,6 @@
         }
     }
 
-    @Override
     public void updateNotificationRanking(RankingMap rankingMap) {
         List<NotificationEntry> entries = new ArrayList<>();
         entries.addAll(getVisibleNotifications());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
index 21a4b4f..a1cfb54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
@@ -29,7 +29,6 @@
 import android.annotation.MainThread;
 import android.annotation.Nullable;
 import android.util.ArrayMap;
-import android.util.Log;
 
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -40,6 +39,8 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.logging.NotifEvent;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.time.SystemClock;
 
@@ -59,8 +60,8 @@
 @MainThread
 @Singleton
 public class NotifListBuilderImpl implements NotifListBuilder {
-
     private final SystemClock mSystemClock;
+    private final NotifLog mNotifLog;
 
     private final List<ListEntry> mNotifList = new ArrayList<>();
 
@@ -86,9 +87,10 @@
     private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
 
     @Inject
-    public NotifListBuilderImpl(SystemClock systemClock) {
+    public NotifListBuilderImpl(SystemClock systemClock, NotifLog notifLog) {
         Assert.isMainThread();
         mSystemClock = systemClock;
+        mNotifLog = notifLog;
     }
 
     /**
@@ -193,7 +195,8 @@
                     Assert.isMainThread();
                     mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
 
-                    Log.i(TAG, "Build request received from NotifCollection");
+                    mNotifLog.log(NotifEvent.ON_BUILD_LIST, "Request received from "
+                            + "NotifCollection");
                     mAllEntries = entries;
                     buildList();
                 }
@@ -202,8 +205,7 @@
     private void onFilterInvalidated(NotifFilter filter) {
         Assert.isMainThread();
 
-        // TODO: Convert these log statements (here and elsewhere) into timeline logging
-        Log.i(TAG, String.format(
+        mNotifLog.log(NotifEvent.FILTER_INVALIDATED, String.format(
                 "Filter \"%s\" invalidated; pipeline state is %d",
                 filter.getName(),
                 mPipelineState.getState()));
@@ -214,7 +216,7 @@
     private void onPromoterInvalidated(NotifPromoter filter) {
         Assert.isMainThread();
 
-        Log.i(TAG, String.format(
+        mNotifLog.log(NotifEvent.PROMOTER_INVALIDATED, String.format(
                 "NotifPromoter \"%s\" invalidated; pipeline state is %d",
                 filter.getName(),
                 mPipelineState.getState()));
@@ -225,7 +227,7 @@
     private void onSectionsProviderInvalidated(SectionsProvider provider) {
         Assert.isMainThread();
 
-        Log.i(TAG, String.format(
+        mNotifLog.log(NotifEvent.SECTIONS_PROVIDER_INVALIDATED, String.format(
                 "Sections provider \"%s\" invalidated; pipeline state is %d",
                 provider.getName(),
                 mPipelineState.getState()));
@@ -236,7 +238,7 @@
     private void onNotifComparatorInvalidated(NotifComparator comparator) {
         Assert.isMainThread();
 
-        Log.i(TAG, String.format(
+        mNotifLog.log(NotifEvent.COMPARATOR_INVALIDATED, String.format(
                 "Comparator \"%s\" invalidated; pipeline state is %d",
                 comparator.getName(),
                 mPipelineState.getState()));
@@ -254,7 +256,7 @@
      * if we detect that behavior, we should crash instantly.
      */
     private void buildList() {
-        Log.i(TAG, "Starting notif list build #" + mIterationCount + "...");
+        mNotifLog.log(NotifEvent.START_BUILD_LIST, "Run #" + mIterationCount + "...");
 
         mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
         mPipelineState.setState(STATE_BUILD_STARTED);
@@ -288,15 +290,16 @@
         freeEmptyGroups();
 
         // Step 5: Dispatch the new list, first to any listeners and then to the view layer
-        Log.i(TAG, "List finalized, is:\n" + dumpList(mNotifList));
-        Log.i(TAG, "Dispatching final list to listeners...");
+        mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
+                + dumpList(mNotifList));
         dispatchOnBeforeRenderList(mReadOnlyNotifList);
         if (mOnRenderListListener != null) {
             mOnRenderListListener.onRenderList(mReadOnlyNotifList);
         }
 
         // Step 6: We're done!
-        Log.i(TAG, "Notif list build #" + mIterationCount + " completed");
+        mNotifLog.log(NotifEvent.LIST_BUILD_COMPLETE,
+                "Notif list build #" + mIterationCount + " completed");
         mPipelineState.setState(STATE_IDLE);
         mIterationCount++;
     }
@@ -354,7 +357,7 @@
                     if (existingSummary == null) {
                         group.setSummary(entry);
                     } else {
-                        Log.w(TAG, String.format(
+                        mNotifLog.log(NotifEvent.WARN, String.format(
                                 "Duplicate summary for group '%s': '%s' vs. '%s'",
                                 group.getKey(),
                                 existingSummary.getKey(),
@@ -377,7 +380,8 @@
 
                 final String topLevelKey = entry.getKey();
                 if (mGroups.containsKey(topLevelKey)) {
-                    Log.wtf(TAG, "Duplicate non-group top-level key: " + topLevelKey);
+                    mNotifLog.log(NotifEvent.WARN,
+                            "Duplicate non-group top-level key: " + topLevelKey);
                 } else {
                     entry.setParent(ROOT_ENTRY);
                     out.add(entry);
@@ -539,7 +543,7 @@
     private void logParentingChanges() {
         for (NotificationEntry entry : mAllEntries) {
             if (entry.getParent() != entry.getPreviousParent()) {
-                Log.i(TAG, String.format(
+                mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format(
                         "%s: parent changed from %s to %s",
                         entry.getKey(),
                         entry.getPreviousParent() == null
@@ -550,7 +554,7 @@
         }
         for (GroupEntry group : mGroups.values()) {
             if (group.getParent() != group.getPreviousParent()) {
-                Log.i(TAG, String.format(
+                mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format(
                         "%s: parent changed from %s to %s",
                         group.getKey(),
                         group.getPreviousParent() == null
@@ -607,17 +611,17 @@
 
         if (filter != entry.mExcludingFilter) {
             if (entry.mExcludingFilter == null) {
-                Log.i(TAG, String.format(
+                mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
                         "%s: filtered out by '%s'",
                         entry.getKey(),
                         filter.getName()));
             } else if (filter == null) {
-                Log.i(TAG, String.format(
+                mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
                         "%s: no longer filtered out (previous filter was '%s')",
                         entry.getKey(),
                         entry.mExcludingFilter.getName()));
             } else {
-                Log.i(TAG, String.format(
+                mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
                         "%s: filter changed: '%s' -> '%s'",
                         entry.getKey(),
                         entry.mExcludingFilter,
@@ -648,23 +652,22 @@
 
         if (promoter != entry.mNotifPromoter) {
             if (entry.mNotifPromoter == null) {
-                Log.i(TAG, String.format(
+                mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
                         "%s: Entry promoted to top level by '%s'",
                         entry.getKey(),
                         promoter.getName()));
             } else if (promoter == null) {
-                Log.i(TAG, String.format(
+                mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
                         "%s: Entry is no longer promoted to top level (previous promoter was '%s')",
                         entry.getKey(),
                         entry.mNotifPromoter.getName()));
             } else {
-                Log.i(TAG, String.format(
+                mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
                         "%s: Top-level promoter changed: '%s' -> '%s'",
                         entry.getKey(),
                         entry.mNotifPromoter,
                         promoter));
             }
-
             entry.mNotifPromoter = promoter;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 511aafc..b68cb0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import android.Manifest;
-import android.app.AppGlobals;
 import android.app.Notification;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -43,10 +42,13 @@
     private static final String TAG = "DeviceProvisionedCoordinator";
 
     private final DeviceProvisionedController mDeviceProvisionedController;
+    private final IPackageManager mIPackageManager;
 
     @Inject
-    public DeviceProvisionedCoordinator(DeviceProvisionedController deviceProvisionedController) {
+    public DeviceProvisionedCoordinator(DeviceProvisionedController deviceProvisionedController,
+            IPackageManager packageManager) {
         mDeviceProvisionedController = deviceProvisionedController;
+        mIPackageManager = packageManager;
     }
 
     @Override
@@ -56,7 +58,7 @@
         notifListBuilder.addFilter(mNotifFilter);
     }
 
-    protected final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
         @Override
         public boolean shouldFilterOut(NotificationEntry entry, long now) {
             return !mDeviceProvisionedController.isDeviceProvisioned()
@@ -70,17 +72,16 @@
      * marking them as relevant for setup are allowed to show when device is unprovisioned
      */
     private boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
-        final boolean hasPermission = checkUidPermission(AppGlobals.getPackageManager(),
+        final boolean hasPermission = checkUidPermission(
                 Manifest.permission.NOTIFICATION_DURING_SETUP,
                 sbn.getUid()) == PackageManager.PERMISSION_GRANTED;
         return hasPermission
                 && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
     }
 
-    private static int checkUidPermission(IPackageManager packageManager, String permission,
-            int uid) {
+    private int checkUidPermission(String permission, int uid) {
         try {
-            return packageManager.checkUidPermission(permission, uid);
+            return mIPackageManager.checkUidPermission(permission, uid);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
index 4803cf4..378599b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
@@ -24,7 +24,6 @@
 
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.qualifiers.BgHandler;
 import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
@@ -52,12 +51,11 @@
  */
 @Singleton
 public class ForegroundCoordinator implements Coordinator {
-    private static final String TAG = "ForegroundNotificationCoordinator";
+    private static final String TAG = "ForegroundCoordinator";
 
     private final ForegroundServiceController mForegroundServiceController;
     private final AppOpsController mAppOpsController;
     private final Handler mMainHandler;
-    private final Handler mBgHandler;
 
     private NotifCollection mNotifCollection;
 
@@ -65,12 +63,10 @@
     public ForegroundCoordinator(
             ForegroundServiceController foregroundServiceController,
             AppOpsController appOpsController,
-            @MainHandler Handler mainHandler,
-            @BgHandler Handler bgHandler) {
+            @MainHandler Handler mainHandler) {
         mForegroundServiceController = foregroundServiceController;
         mAppOpsController = appOpsController;
         mMainHandler = mainHandler;
-        mBgHandler = bgHandler;
     }
 
     @Override
@@ -93,7 +89,7 @@
     /**
      * Filters out notifications that represent foreground services that are no longer running.
      */
-    protected final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
         @Override
         public boolean shouldFilterOut(NotificationEntry entry, long now) {
             StatusBarNotification sbn = entry.getSbn();
@@ -120,7 +116,8 @@
      * Extends the lifetime of foreground notification services such that they show for at least
      * five seconds
      */
-    private final NotifLifetimeExtender mForegroundLifetimeExtender = new NotifLifetimeExtender() {
+    private final NotifLifetimeExtender mForegroundLifetimeExtender =
+            new NotifLifetimeExtender() {
         private static final int MIN_FGS_TIME_MS = 5000;
         private OnEndLifetimeExtensionCallback mEndCallback;
         private Map<String, Runnable> mEndRunnables = new HashMap<>();
@@ -154,8 +151,8 @@
                         }
                     };
                     mEndRunnables.put(entry.getKey(), runnable);
-                    mBgHandler.postDelayed(runnable, MIN_FGS_TIME_MS
-                            - (currTime - entry.getSbn().getPostTime()));
+                    mMainHandler.postDelayed(runnable,
+                            MIN_FGS_TIME_MS - (currTime - entry.getSbn().getPostTime()));
                 }
             }
 
@@ -166,7 +163,7 @@
         public void cancelLifetimeExtension(NotificationEntry entry) {
             if (mEndRunnables.containsKey(entry.getKey())) {
                 Runnable endRunnable = mEndRunnables.remove(entry.getKey());
-                mBgHandler.removeCallbacks(endRunnable);
+                mMainHandler.removeCallbacks(endRunnable);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 6daf3fc..4413dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -51,7 +51,7 @@
  */
 @Singleton
 public class KeyguardCoordinator implements Coordinator {
-    private static final String TAG = "KeyguardNotificationCoordinator";
+    private static final String TAG = "KeyguardCoordinator";
 
     private final Context mContext;
     private final Handler mMainHandler;
@@ -86,7 +86,7 @@
         notifListBuilder.addFilter(mNotifFilter);
     }
 
-    protected final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
         @Override
         public boolean shouldFilterOut(NotificationEntry entry, long now) {
             final StatusBarNotification sbn = entry.getSbn();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index c390f96..24e7a79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -26,7 +26,10 @@
 import javax.inject.Singleton;
 
 /**
- * Filters out NotificationEntries based on its Ranking.
+ * Filters out NotificationEntries based on its Ranking and dozing state.
+ * We check the NotificationEntry's Ranking for:
+ *  - whether the notification's app is suspended or hiding its notifications
+ *  - whether DND settings are hiding notifications from ambient display or the notification list
  */
 @Singleton
 public class RankingCoordinator implements Coordinator {
@@ -51,7 +54,7 @@
      * NotifListBuilder invalidates the notification list each time the ranking is updated,
      * so we don't need to explicitly invalidate this filter on ranking update.
      */
-    protected final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
         @Override
         public boolean shouldFilterOut(NotificationEntry entry, long now) {
             // App suspended from Ranking
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index 8ebbca2..3b06220 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -17,10 +17,12 @@
 package com.android.systemui.statusbar.notification.logging;
 
 import android.annotation.IntDef;
-import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 
 import com.android.systemui.log.RichEvent;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -31,103 +33,71 @@
  * here to mitigate memory usage.
  */
 public class NotifEvent extends RichEvent {
-    public static final int TOTAL_EVENT_TYPES = 11;
-
     /**
-     * Creates a NotifEvent with an event type that matches with an index in the array
-     * getSupportedEvents() and {@link EventType}.
-     *
-     * The status bar notification and ranking objects are stored as shallow copies of the current
-     * state of the event when this event occurred.
+     * Initializes a rich event that includes an event type that matches with an index in the array
+     * getEventLabels().
      */
-    public NotifEvent(int logLevel, int type, String reason, StatusBarNotification sbn,
-            Ranking ranking) {
-        super(logLevel, type, reason);
-        mMessage += getExtraInfo(sbn, ranking);
-    }
-
-    private String getExtraInfo(StatusBarNotification sbn, Ranking ranking) {
-        StringBuilder extraInfo = new StringBuilder();
-
+    public NotifEvent init(@EventType int type, StatusBarNotification sbn,
+            NotificationListenerService.Ranking ranking, String reason) {
+        StringBuilder extraInfo = new StringBuilder(reason);
         if (sbn != null) {
-            extraInfo.append(" Sbn=");
-            extraInfo.append(sbn);
+            extraInfo.append(" " + sbn.getKey());
         }
 
         if (ranking != null) {
             extraInfo.append(" Ranking=");
-            extraInfo.append(ranking);
+            extraInfo.append(ranking.getRank());
         }
-
-        return extraInfo.toString();
+        super.init(INFO, type, extraInfo.toString());
+        return this;
     }
 
     /**
-     * Event labels for NotifEvents
-     * Index corresponds to the {@link EventType}
+     * Event labels for ListBuilderEvents
+     * Index corresponds to an # in {@link EventType}
      */
     @Override
     public String[] getEventLabels() {
-        final String[] events = new String[]{
-                "NotifAdded",
-                "NotifRemoved",
-                "NotifUpdated",
-                "Filter",
-                "Sort",
-                "FilterAndSort",
-                "NotifVisibilityChanged",
-                "LifetimeExtended",
-                "RemoveIntercepted",
-                "InflationAborted",
-                "Inflated"
-        };
-
-        if (events.length != TOTAL_EVENT_TYPES) {
-            throw new IllegalStateException("NotifEvents events.length should match "
-                    + TOTAL_EVENT_TYPES
-                    + " events.length=" + events.length
-                    + " TOTAL_EVENT_LENGTH=" + TOTAL_EVENT_TYPES);
-        }
-        return events;
+        assert (TOTAL_EVENT_LABELS == (TOTAL_NEM_EVENT_TYPES + TOTAL_LIST_BUILDER_EVENT_TYPES));
+        return EVENT_LABELS;
     }
 
     /**
-     * Builds a NotifEvent.
+     * @return if this event occurred in {@link NotifListBuilder}
      */
-    public static class NotifEventBuilder extends RichEvent.Builder<NotifEventBuilder> {
-        private StatusBarNotification mSbn;
-        private Ranking mRanking;
-
-        @Override
-        public NotifEventBuilder getBuilder() {
-            return this;
-        }
-
-        /**
-         * Stores the status bar notification object. A shallow copy is stored in the NotifEvent's
-         * constructor.
-         */
-        public NotifEventBuilder setSbn(StatusBarNotification sbn) {
-            mSbn = sbn;
-            return this;
-        }
-
-        /**
-         * Stores the ranking object. A shallow copy is stored in the NotifEvent's
-         * constructor.
-         */
-        public NotifEventBuilder setRanking(Ranking ranking) {
-            mRanking = ranking;
-            return this;
-        }
-
-        @Override
-        public RichEvent build() {
-            return new NotifEvent(mLogLevel, mType, mReason, mSbn, mRanking);
-        }
+    static boolean isListBuilderEvent(@EventType int type) {
+        return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES);
     }
 
-    @IntDef({NOTIF_ADDED,
+    /**
+     * @return if this event occurred in {@link NotificationEntryManager}
+     */
+    static boolean isNemEvent(@EventType int type) {
+        return isBetweenInclusive(type, TOTAL_LIST_BUILDER_EVENT_TYPES,
+                TOTAL_LIST_BUILDER_EVENT_TYPES + TOTAL_NEM_EVENT_TYPES);
+    }
+
+    private static boolean isBetweenInclusive(int x, int a, int b) {
+        return x >= a && x <= b;
+    }
+
+    @IntDef({
+            // NotifListBuilder events:
+            WARN,
+            ON_BUILD_LIST,
+            START_BUILD_LIST,
+            DISPATCH_FINAL_LIST,
+            LIST_BUILD_COMPLETE,
+            FILTER_INVALIDATED,
+            PROMOTER_INVALIDATED,
+            SECTIONS_PROVIDER_INVALIDATED,
+            COMPARATOR_INVALIDATED,
+            PARENT_CHANGED,
+            FILTER_CHANGED,
+            PROMOTER_CHANGED,
+
+            // NotificationEntryManager events:
+            NOTIF_ADDED,
             NOTIF_REMOVED,
             NOTIF_UPDATED,
             FILTER,
@@ -139,22 +109,72 @@
             INFLATION_ABORTED,
             INFLATED
     })
-
-    /**
-     * Types of NotifEvents
-     */
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
-    public static final int NOTIF_ADDED = 0;
-    public static final int NOTIF_REMOVED = 1;
-    public static final int NOTIF_UPDATED = 2;
-    public static final int FILTER = 3;
-    public static final int SORT = 4;
-    public static final int FILTER_AND_SORT = 5;
-    public static final int NOTIF_VISIBILITY_CHANGED = 6;
-    public static final int LIFETIME_EXTENDED = 7;
+
+    private static final String[] EVENT_LABELS =
+            new String[]{
+                    // NotifListBuilder labels:
+                    "Warning",
+                    "OnBuildList",
+                    "StartBuildList",
+                    "DispatchFinalList",
+                    "ListBuildComplete",
+                    "FilterInvalidated",
+                    "PromoterInvalidated",
+                    "SectionsProviderInvalidated",
+                    "ComparatorInvalidated",
+                    "ParentChanged",
+                    "FilterChanged",
+                    "PromoterChanged",
+
+                    // NEM event labels:
+                    "NotifAdded",
+                    "NotifRemoved",
+                    "NotifUpdated",
+                    "Filter",
+                    "Sort",
+                    "FilterAndSort",
+                    "NotifVisibilityChanged",
+                    "LifetimeExtended",
+                    "RemoveIntercepted",
+                    "InflationAborted",
+                    "Inflated"
+            };
+
+    private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length;
+
+    /**
+     * Events related to {@link NotifListBuilder}
+     */
+    public static final int WARN = 0;
+    public static final int ON_BUILD_LIST = 1;
+    public static final int START_BUILD_LIST = 2;
+    public static final int DISPATCH_FINAL_LIST = 3;
+    public static final int LIST_BUILD_COMPLETE = 4;
+    public static final int FILTER_INVALIDATED = 5;
+    public static final int PROMOTER_INVALIDATED = 6;
+    public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
+    public static final int COMPARATOR_INVALIDATED = 8;
+    public static final int PARENT_CHANGED = 9;
+    public static final int FILTER_CHANGED = 10;
+    public static final int PROMOTER_CHANGED = 11;
+    private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 12;
+
+    /**
+     * Events related to {@link NotificationEntryManager}
+     */
+    public static final int NOTIF_ADDED = TOTAL_LIST_BUILDER_EVENT_TYPES + 0;
+    public static final int NOTIF_REMOVED = TOTAL_LIST_BUILDER_EVENT_TYPES + 1;
+    public static final int NOTIF_UPDATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 2;
+    public static final int FILTER = TOTAL_LIST_BUILDER_EVENT_TYPES + 3;
+    public static final int SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 4;
+    public static final int FILTER_AND_SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 5;
+    public static final int NOTIF_VISIBILITY_CHANGED = TOTAL_LIST_BUILDER_EVENT_TYPES + 6;
+    public static final int LIFETIME_EXTENDED = TOTAL_LIST_BUILDER_EVENT_TYPES + 7;
     // unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor}
-    public static final int REMOVE_INTERCEPTED = 8;
-    public static final int INFLATION_ABORTED = 9;
-    public static final int INFLATED = 10;
+    public static final int REMOVE_INTERCEPTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 8;
+    public static final int INFLATION_ABORTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 9;
+    public static final int INFLATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 10;
+    private static final int TOTAL_NEM_EVENT_TYPES = 11;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
index 1292831..299d628 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.logging;
 
+import android.os.SystemProperties;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.StatusBarNotification;
 
@@ -33,93 +34,82 @@
  *      dependency DumpController NotifLog
  */
 @Singleton
-public class NotifLog extends SysuiLog {
+public class NotifLog extends SysuiLog<NotifEvent> {
     private static final String TAG = "NotifLog";
+    private static final boolean SHOW_NEM_LOGS =
+            SystemProperties.getBoolean("persist.sysui.log.notif.nem", true);
+    private static final boolean SHOW_LIST_BUILDER_LOGS =
+            SystemProperties.getBoolean("persist.sysui.log.notif.listbuilder", true);
+
     private static final int MAX_DOZE_DEBUG_LOGS = 400;
     private static final int MAX_DOZE_LOGS = 50;
 
+    private NotifEvent mRecycledEvent;
+
     @Inject
     public NotifLog(DumpController dumpController) {
         super(dumpController, TAG, MAX_DOZE_DEBUG_LOGS, MAX_DOZE_LOGS);
     }
 
     /**
-     * Logs a {@link NotifEvent} with a notification, ranking and message
+     * Logs a {@link NotifEvent} with a notification, ranking and message.
+     * Uses the last recycled event if available.
      * @return true if successfully logged, else false
      */
-    public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn,
-            Ranking ranking, String msg) {
-        return log(new NotifEvent.NotifEventBuilder()
-                .setType(eventType)
-                .setSbn(sbn)
-                .setRanking(ranking)
-                .setReason(msg)
-                .build());
+    public void log(@NotifEvent.EventType int eventType,
+            StatusBarNotification sbn, Ranking ranking, String msg) {
+        if (!mEnabled
+                || (NotifEvent.isListBuilderEvent(eventType) && !SHOW_LIST_BUILDER_LOGS)
+                || (NotifEvent.isNemEvent(eventType) && !SHOW_NEM_LOGS)) {
+            return;
+        }
+
+        if (mRecycledEvent != null) {
+            mRecycledEvent = log(mRecycledEvent.init(eventType, sbn, ranking, msg));
+        } else {
+            mRecycledEvent = log(new NotifEvent().init(eventType, sbn, ranking, msg));
+        }
     }
 
     /**
-     * Logs a {@link NotifEvent}
-     * @return true if successfully logged, else false
+     * Logs a {@link NotifEvent} with no extra information aside from the event type
      */
-    public boolean log(@NotifEvent.EventType int eventType) {
-        return log(eventType, null, null, null);
+    public void log(@NotifEvent.EventType int eventType) {
+        log(eventType, null, null, "");
     }
 
     /**
      * Logs a {@link NotifEvent} with a message
-     * @return true if successfully logged, else false
      */
-    public boolean log(@NotifEvent.EventType int eventType, String msg) {
-        return log(eventType, null, null, msg);
+    public void log(@NotifEvent.EventType int eventType, String msg) {
+        log(eventType, null, null, msg);
     }
 
     /**
-     * Logs a {@link NotifEvent} with a notification
-     * @return true if successfully logged, else false
+     * Logs a {@link NotifEvent} with a entry
      */
-    public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn) {
-        return log(eventType, sbn, null, "");
+    public void log(@NotifEvent.EventType int eventType, NotificationEntry entry) {
+        log(eventType, entry.getSbn(), entry.getRanking(), "");
     }
 
     /**
-     * Logs a {@link NotifEvent} with a notification
-     * @return true if successfully logged, else false
+     * Logs a {@link NotifEvent} with a NotificationEntry and message
      */
-    public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn, String msg) {
-        return log(eventType, sbn, null, msg);
+    public void log(@NotifEvent.EventType int eventType, NotificationEntry entry, String msg) {
+        log(eventType, entry.getSbn(), entry.getRanking(), msg);
     }
 
     /**
-     * Logs a {@link NotifEvent} with a ranking
-     * @return true if successfully logged, else false
+     * Logs a {@link NotifEvent} with a notification and message
      */
-    public boolean log(@NotifEvent.EventType int eventType, Ranking ranking) {
-        return log(eventType, null, ranking, "");
+    public void log(@NotifEvent.EventType int eventType, StatusBarNotification sbn, String msg) {
+        log(eventType, sbn, null, msg);
     }
 
     /**
-     * Logs a {@link NotifEvent} with a notification and ranking
-     * @return true if successfully logged, else false
+     * Logs a {@link NotifEvent} with a ranking and message
      */
-    public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn,
-            Ranking ranking) {
-        return log(eventType, sbn, ranking, "");
-    }
-
-    /**
-     * Logs a {@link NotifEvent} with a notification entry
-     * @return true if successfully logged, else false
-     */
-    public boolean log(@NotifEvent.EventType int eventType, NotificationEntry entry) {
-        return log(eventType, entry.getSbn(), entry.getRanking(), "");
-    }
-
-    /**
-     * Logs a {@link NotifEvent} with a notification entry
-     * @return true if successfully logged, else false
-     */
-    public boolean log(@NotifEvent.EventType int eventType, NotificationEntry entry,
-            String msg) {
-        return log(eventType, entry.getSbn(), entry.getRanking(), msg);
+    public void log(@NotifEvent.EventType int eventType, Ranking ranking, String msg) {
+        log(eventType, null, ranking, msg);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 43af3aa..71342c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5519,7 +5519,7 @@
 
         if (viewsToRemove.isEmpty()) {
             if (closeShade) {
-                mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
             }
             return;
         }
@@ -5581,7 +5581,7 @@
                     setDismissAllInProgress(false);
                     onAnimationComplete.run();
                 });
-                mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+                mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
             } else {
                 setDismissAllInProgress(false);
                 onAnimationComplete.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 865d7e7..250f730 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -41,6 +41,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -136,6 +137,7 @@
     private final Handler mHandler;
     private final KeyguardBypassController mKeyguardBypassController;
     private PowerManager.WakeLock mWakeLock;
+    private final ShadeController mShadeController;
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final DozeParameters mDozeParameters;
     private final KeyguardStateController mKeyguardStateController;
@@ -159,20 +161,23 @@
     @Inject
     public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
-            StatusBar statusBar, KeyguardStateController keyguardStateController, Handler handler,
+            StatusBar statusBar, ShadeController shadeController,
+            StatusBarWindowController statusBarWindowController,
+            KeyguardStateController keyguardStateController, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             @MainResources Resources resources,
             KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters,
             MetricsLogger metricsLogger, DumpController dumpController) {
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
+        mShadeController = shadeController;
         mUpdateMonitor = keyguardUpdateMonitor;
         mDozeParameters = dozeParameters;
         mUpdateMonitor.registerCallback(this);
         mMediaManager = Dependency.get(NotificationMediaManager.class);
         Dependency.get(WakefulnessLifecycle.class).addObserver(mWakefulnessObserver);
         Dependency.get(ScreenLifecycle.class).addObserver(mScreenObserver);
-        mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
+        mStatusBarWindowController = statusBarWindowController;
         mDozeScrimController = dozeScrimController;
         mKeyguardViewMediator = keyguardViewMediator;
         mScrimController = scrimController;
@@ -358,8 +363,8 @@
         if (mMode == MODE_SHOW_BOUNCER) {
             mStatusBarKeyguardViewManager.showBouncer(false);
         }
-        mStatusBarKeyguardViewManager.animateCollapsePanels(
-                BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
+        mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
+                false /* delayed */, BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
         mPendingShowBouncer = false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 0703d8c..ebe2117 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -164,6 +164,7 @@
     private int mDisabledFlags1;
     private int mDisabledFlags2;
     private final Lazy<StatusBar> mStatusBarLazy;
+    private final ShadeController mShadeController;
     private Recents mRecents;
     private StatusBar mStatusBar;
     private final Divider mDivider;
@@ -216,7 +217,7 @@
             mNavigationBarView.getRotationButtonController().setRotateSuggestionButtonState(false);
 
             // Hide the notifications panel when quick step starts
-            mStatusBarLazy.get().collapsePanel(true /* animate */);
+            mShadeController.collapsePanel(true /* animate */);
         }
 
         @Override
@@ -272,6 +273,7 @@
             BroadcastDispatcher broadcastDispatcher,
             CommandQueue commandQueue, Divider divider,
             Optional<Recents> recentsOptional, Lazy<StatusBar> statusBarLazy,
+            ShadeController shadeController,
             @MainHandler Handler mainHandler) {
         mAccessibilityManagerWrapper = accessibilityManagerWrapper;
         mDeviceProvisionedController = deviceProvisionedController;
@@ -280,6 +282,7 @@
         mAssistManager = assistManager;
         mSysUiFlagsContainer = sysUiFlagsContainer;
         mStatusBarLazy = statusBarLazy;
+        mShadeController = shadeController;
         mAssistantAvailable = mAssistManager.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
         mOverviewProxyService = overviewProxyService;
         mNavigationModeController = navigationModeController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
index deea3f1..2fa6795 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
@@ -32,12 +32,24 @@
      */
     void instantExpandNotificationsPanel();
 
+    /** See {@link #animateCollapsePanels(int, boolean)}. */
+    void animateCollapsePanels();
+
+    /** See {@link #animateCollapsePanels(int, boolean)}. */
+    void animateCollapsePanels(int flags);
+
     /**
      * Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
      * dismissing {@link StatusBar} when on {@link StatusBarState#SHADE}.
      */
     void animateCollapsePanels(int flags, boolean force);
 
+    /** See {@link #animateCollapsePanels(int, boolean)}. */
+    void animateCollapsePanels(int flags, boolean force, boolean delayed);
+
+    /** See {@link #animateCollapsePanels(int, boolean)}. */
+    void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
+
     /**
      * If the notifications panel is not fully expanded, collapse it animated.
      *
@@ -61,11 +73,9 @@
     void addPostCollapseAction(Runnable action);
 
     /**
-     * Notify the shade controller that the current user changed
-     *
-     * @param newUserId userId of the new user
+     * Run all of the runnables added by {@link #addPostCollapseAction}.
      */
-    void setLockscreenUser(int newUserId);
+    void runPostCollapseRunnables();
 
     /**
      * If secure with redaction: Show bouncer, go to unlocked shade.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
new file mode 100644
index 0000000..57e7014
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.StatusBarState;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/** An implementation of {@link com.android.systemui.statusbar.phone.ShadeController}. */
+@Singleton
+public class ShadeControllerImpl implements ShadeController {
+
+    private static final String TAG = "ShadeControllerImpl";
+    private static final boolean SPEW = false;
+
+    private final CommandQueue mCommandQueue;
+    private final StatusBarStateController mStatusBarStateController;
+    protected final StatusBarWindowController mStatusBarWindowController;
+    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final int mDisplayId;
+    protected final Lazy<StatusBar> mStatusBarLazy;
+    private final Lazy<AssistManager> mAssistManagerLazy;
+    private final Lazy<BubbleController> mBubbleControllerLazy;
+
+    private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+
+    @Inject
+    public ShadeControllerImpl(
+            CommandQueue commandQueue,
+            StatusBarStateController statusBarStateController,
+            StatusBarWindowController statusBarWindowController,
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            WindowManager windowManager,
+            Lazy<StatusBar> statusBarLazy,
+            Lazy<AssistManager> assistManagerLazy,
+            Lazy<BubbleController> bubbleControllerLazy
+    ) {
+        mCommandQueue = commandQueue;
+        mStatusBarStateController = statusBarStateController;
+        mStatusBarWindowController = statusBarWindowController;
+        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
+        // TODO: Remove circular reference to StatusBar when possible.
+        mStatusBarLazy = statusBarLazy;
+        mAssistManagerLazy = assistManagerLazy;
+        mBubbleControllerLazy = bubbleControllerLazy;
+    }
+
+    @Override
+    public void instantExpandNotificationsPanel() {
+        // Make our window larger and the panel expanded.
+        getStatusBar().makeExpandedVisible(true /* force */);
+        getNotificationPanelView().expand(false /* animate */);
+        mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
+    }
+
+    @Override
+    public void animateCollapsePanels() {
+        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+    }
+
+    @Override
+    public void animateCollapsePanels(int flags) {
+        animateCollapsePanels(flags, false /* force */, false /* delayed */,
+                1.0f /* speedUpFactor */);
+    }
+
+    @Override
+    public void animateCollapsePanels(int flags, boolean force) {
+        animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+    }
+
+    @Override
+    public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
+        animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+    }
+
+    @Override
+    public void animateCollapsePanels(int flags, boolean force, boolean delayed,
+            float speedUpFactor) {
+        if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
+            runPostCollapseRunnables();
+            return;
+        }
+        if (SPEW) {
+            Log.d(TAG, "animateCollapse():"
+                    + " mExpandedVisible=" + getStatusBar().isExpandedVisible()
+                    + " flags=" + flags);
+        }
+
+        if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) {
+            getStatusBar().postHideRecentApps();
+        }
+
+        // TODO(b/62444020): remove when this bug is fixed
+        Log.v(TAG, "mStatusBarWindow: " + getStatusBarWindowView() + " canPanelBeCollapsed(): "
+                + getNotificationPanelView().canPanelBeCollapsed());
+        if (getStatusBarWindowView() != null && getNotificationPanelView().canPanelBeCollapsed()) {
+            // release focus immediately to kick off focus change transition
+            mStatusBarWindowController.setStatusBarFocusable(false);
+
+            getStatusBar().getStatusBarWindowViewController().cancelExpandHelper();
+            getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
+        } else {
+            mBubbleControllerLazy.get().collapseStack();
+        }
+    }
+
+
+    @Override
+    public boolean closeShadeIfOpen() {
+        if (!getNotificationPanelView().isFullyCollapsed()) {
+            mCommandQueue.animateCollapsePanels(
+                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+            getStatusBar().visibilityChanged(false);
+            mAssistManagerLazy.get().hideAssist();
+        }
+        return false;
+    }
+
+    @Override
+    public void postOnShadeExpanded(Runnable executable) {
+        getNotificationPanelView().getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        if (getStatusBar().getStatusBarWindow().getHeight()
+                                != getStatusBar().getStatusBarHeight()) {
+                            getNotificationPanelView().getViewTreeObserver()
+                                    .removeOnGlobalLayoutListener(this);
+                            getNotificationPanelView().post(executable);
+                        }
+                    }
+                });
+    }
+
+    @Override
+    public void addPostCollapseAction(Runnable action) {
+        mPostCollapseRunnables.add(action);
+    }
+
+    @Override
+    public void runPostCollapseRunnables() {
+        ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
+        mPostCollapseRunnables.clear();
+        int size = clonedList.size();
+        for (int i = 0; i < size; i++) {
+            clonedList.get(i).run();
+        }
+        mStatusBarKeyguardViewManager.readyForKeyguardDone();
+    }
+
+    @Override
+    public void goToLockedShade(View startingChild) {
+        // TODO: Move this code out of StatusBar into ShadeController.
+        getStatusBar().goToLockedShade(startingChild);
+    }
+
+    @Override
+    public boolean collapsePanel() {
+        if (!getNotificationPanelView().isFullyCollapsed()) {
+            // close the shade if it was open
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                    true /* force */, true /* delayed */);
+            getStatusBar().visibilityChanged(false);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void collapsePanel(boolean animate) {
+        if (animate) {
+            boolean willCollapse = collapsePanel();
+            if (!willCollapse) {
+                runPostCollapseRunnables();
+            }
+        } else if (!getPresenter().isPresenterFullyCollapsed()) {
+            getStatusBar().instantCollapseNotificationPanel();
+            getStatusBar().visibilityChanged(false);
+        } else {
+            runPostCollapseRunnables();
+        }
+    }
+
+    private StatusBar getStatusBar() {
+        return mStatusBarLazy.get();
+    }
+
+    private NotificationPresenter getPresenter() {
+        return getStatusBar().getPresenter();
+    }
+
+    protected StatusBarWindowView getStatusBarWindowView() {
+        return getStatusBar().getStatusBarWindow();
+    }
+
+    protected PhoneStatusBarView getStatusBarView() {
+        return (PhoneStatusBarView) getStatusBar().getStatusBarView();
+    }
+
+    private NotificationPanelView getNotificationPanelView() {
+        return getStatusBar().getPanel();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index e31ad9f..a9d7601 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -107,7 +107,6 @@
 import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -232,7 +231,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.ArrayList;
 import java.util.Map;
 import java.util.Optional;
 
@@ -245,8 +243,7 @@
         ActivityStarter, KeyguardStateController.Callback,
         OnHeadsUpChangedListener, CommandQueue.Callbacks,
         ColorExtractor.OnColorsChangedListener, ConfigurationListener,
-        StatusBarStateController.StateListener, ShadeController,
-        ActivityLaunchAnimator.Callback {
+        StatusBarStateController.StateListener, ActivityLaunchAnimator.Callback {
     public static final boolean MULTIUSER_DEBUG = false;
 
     public static final boolean ENABLE_CHILD_NOTIFICATIONS
@@ -387,6 +384,7 @@
     private final Optional<Divider> mDividerOptional;
     private final StatusBarNotificationActivityStarter.Builder
             mStatusBarNotificationActivityStarterBuilder;
+    private final ShadeController mShadeController;
     private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private final LightsOutNotifController mLightsOutNotifController;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
@@ -409,7 +407,6 @@
     private boolean mExpandedVisible;
 
     private final int[] mAbsPos = new int[2];
-    private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
@@ -683,6 +680,7 @@
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
+            ShadeController shadeController,
             SuperStatusBarViewFactory superStatusBarViewFactory,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
@@ -754,6 +752,7 @@
         mRemoteInputUriController = remoteInputUriController;
         mDividerOptional = dividerOptional;
         mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder;
+        mShadeController = shadeController;
         mSuperStatusBarViewFactory = superStatusBarViewFactory;
         mLightsOutNotifController =  lightsOutNotifController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -1240,7 +1239,7 @@
                 mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
                 mNotificationAlertingManager, rowBinder, mKeyguardStateController,
                 mKeyguardIndicationController,
-                this /* statusBar */, mCommandQueue);
+                this /* statusBar */, mShadeController, mCommandQueue);
 
         mNotificationListController =
                 new NotificationListController(
@@ -1318,7 +1317,7 @@
             mRemoteInputManager.checkRemoteInputOutside(event);
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
                 if (mExpandedVisible) {
-                    animateCollapsePanels();
+                    mShadeController.animateCollapsePanels();
                 }
             }
             return mStatusBarWindow.onTouchEvent(event);
@@ -1419,6 +1418,10 @@
         return mStatusBarWindow;
     }
 
+    public StatusBarWindowViewController getStatusBarWindowViewController() {
+        return mStatusBarWindowViewController;
+    }
+
     protected ViewGroup getBouncerContainer() {
         return mStatusBarWindow;
     }
@@ -1574,7 +1577,7 @@
 
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
-                animateCollapsePanels();
+                mShadeController.animateCollapsePanels();
             }
         }
 
@@ -1598,7 +1601,7 @@
         if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
             updateQsExpansionEnabled();
             if ((state1 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
-                animateCollapsePanels();
+                mShadeController.animateCollapsePanels();
             }
         }
     }
@@ -1840,7 +1843,7 @@
                 && !mActivityLaunchAnimator.isLaunchForActivity()) {
             onClosingFinished();
         } else {
-            collapsePanel(true /* animate */);
+            mShadeController.collapsePanel(true /* animate */);
         }
     }
 
@@ -1888,7 +1891,7 @@
                     animateExpandSettingsPanel((String) m.obj);
                     break;
                 case MSG_CLOSE_PANELS:
-                    animateCollapsePanels();
+                    mShadeController.animateCollapsePanels();
                     break;
                 case MSG_LAUNCH_TRANSITION_TIMEOUT:
                     onLaunchTransitionTimeout();
@@ -1985,20 +1988,13 @@
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
     }
 
-    public void animateCollapsePanels() {
-        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-    }
-
-    private final Runnable mAnimateCollapsePanels = this::animateCollapsePanels;
-
     public void postAnimateCollapsePanels() {
-        mHandler.post(mAnimateCollapsePanels);
+        mHandler.post(mShadeController::animateCollapsePanels);
     }
 
     public void postAnimateForceCollapsePanels() {
-        mHandler.post(() -> {
-            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
-        });
+        mHandler.post(() -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
+                true /* force */));
     }
 
     public void postAnimateOpenPanels() {
@@ -2008,67 +2004,35 @@
     @Override
     public void togglePanel() {
         if (mPanelExpanded) {
-            animateCollapsePanels();
+            mShadeController.animateCollapsePanels();
         } else {
             animateExpandNotificationsPanel();
         }
     }
 
-    public void animateCollapsePanels(int flags) {
-        animateCollapsePanels(flags, false /* force */, false /* delayed */,
+    @Override
+    public void animateCollapsePanels(int flags, boolean force) {
+        mShadeController.animateCollapsePanels(flags, force, false /* delayed */,
                 1.0f /* speedUpFactor */);
     }
 
-    @Override
-    public void animateCollapsePanels(int flags, boolean force) {
-        animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
-    }
-
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
-        animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
-    }
-
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed,
-            float speedUpFactor) {
-        if (!force && mState != StatusBarState.SHADE) {
-            runPostCollapseRunnables();
-            return;
-        }
-        if (SPEW) {
-            Log.d(TAG, "animateCollapse():"
-                    + " mExpandedVisible=" + mExpandedVisible
-                    + " flags=" + flags);
-        }
-
-        if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) {
-            if (!mHandler.hasMessages(MSG_HIDE_RECENT_APPS)) {
-                mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
-                mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
-            }
-        }
-
-        // TODO(b/62444020): remove when this bug is fixed
-        Log.v(TAG, "mStatusBarWindow: " + mStatusBarWindow + " canPanelBeCollapsed(): "
-                + mNotificationPanel.canPanelBeCollapsed());
-        if (mStatusBarWindow != null && mNotificationPanel.canPanelBeCollapsed()) {
-            // release focus immediately to kick off focus change transition
-            mStatusBarWindowController.setStatusBarFocusable(false);
-
-            mStatusBarWindowViewController.cancelExpandHelper();
-            mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
-        } else {
-            mBubbleController.collapseStack();
+    /**
+     * Called by {@link ShadeController} when it calls
+     * {@link ShadeController#animateCollapsePanels(int, boolean, boolean, float)}.
+     */
+    void postHideRecentApps() {
+        if (!mHandler.hasMessages(MSG_HIDE_RECENT_APPS)) {
+            mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
+            mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
         }
     }
 
-    private void runPostCollapseRunnables() {
-        ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
-        mPostCollapseRunnables.clear();
-        int size = clonedList.size();
-        for (int i = 0; i < size; i++) {
-            clonedList.get(i).run();
-        }
-        mStatusBarKeyguardViewManager.readyForKeyguardDone();
+    public boolean isExpandedVisible() {
+        return mExpandedVisible;
+    }
+
+    public boolean isPanelExpanded() {
+        return mPanelExpanded;
     }
 
     /**
@@ -2147,7 +2111,7 @@
         mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
                 true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
 
-        runPostCollapseRunnables();
+        mShadeController.runPostCollapseRunnables();
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
             showBouncerIfKeyguard();
@@ -2672,12 +2636,12 @@
             }
             if (dismissShade) {
                 if (mExpandedVisible && !mBouncerShowing) {
-                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
-                            true /* delayed*/);
+                    mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                            true /* force */, true /* delayed*/);
                 } else {
 
                     // Do it after DismissAction has been processed to conserve the needed ordering.
-                    mHandler.post(this::runPostCollapseRunnables);
+                    mHandler.post(mShadeController::runPostCollapseRunnables);
                 }
             } else if (isInLaunchTransition() && mNotificationPanel.isLaunchTransitionFinished()) {
 
@@ -2709,7 +2673,7 @@
                     if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
                         flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
                     }
-                    animateCollapsePanels(flags);
+                    mShadeController.animateCollapsePanels(flags);
                 }
             }
             else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
@@ -2803,7 +2767,11 @@
         mScreenPinningRequest.onConfigurationChanged();
     }
 
-    @Override
+    /**
+     * Notify the shade controller that the current user changed
+     *
+     * @param newUserId userId of the new user
+     */
     public void setLockscreenUser(int newUserId) {
         if (mLockscreenWallpaper != null) {
             mLockscreenWallpaper.setCurrentUser(newUserId);
@@ -3145,7 +3113,7 @@
     private void updatePanelExpansionForKeyguard() {
         if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
                 != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
-            instantExpandNotificationsPanel();
+            mShadeController.instantExpandNotificationsPanel();
         } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
             instantCollapseNotificationPanel();
         }
@@ -3160,10 +3128,6 @@
         mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
-    public void addPostCollapseAction(Runnable r) {
-        mPostCollapseRunnables.add(r);
-    }
-
     public boolean isInLaunchTransition() {
         return mNotificationPanel.isLaunchTransitionRunning()
                 || mNotificationPanel.isLaunchTransitionFinished();
@@ -3396,7 +3360,7 @@
 
     public boolean onMenuPressed() {
         if (shouldUnlockOnMenuPressed()) {
-            animateCollapsePanels(
+            mShadeController.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
             return true;
         }
@@ -3426,7 +3390,7 @@
         }
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
             if (mNotificationPanel.canPanelBeCollapsed()) {
-                animateCollapsePanels();
+                mShadeController.animateCollapsePanels();
             } else {
                 mBubbleController.performBackPressIfNeeded();
             }
@@ -3440,7 +3404,7 @@
 
     public boolean onSpacePressed() {
         if (mDeviceInteractive && mState != StatusBarState.SHADE) {
-            animateCollapsePanels(
+            mShadeController.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
             return true;
         }
@@ -3454,43 +3418,9 @@
         }
     }
 
-    @Override
-    public void instantExpandNotificationsPanel() {
-        // Make our window larger and the panel expanded.
-        makeExpandedVisible(true);
-        mNotificationPanel.expand(false /* animate */);
-        mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
-    }
-
-    @Override
-    public boolean closeShadeIfOpen() {
-        if (!mNotificationPanel.isFullyCollapsed()) {
-            mCommandQueue.animateCollapsePanels(
-                    CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-            visibilityChanged(false);
-            mAssistManagerLazy.get().hideAssist();
-        }
-        return false;
-    }
-
-    @Override
-    public void postOnShadeExpanded(Runnable executable) {
-        mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        if (getStatusBarWindow().getHeight() != getStatusBarHeight()) {
-                            mNotificationPanel.getViewTreeObserver()
-                                    .removeOnGlobalLayoutListener(this);
-                            mNotificationPanel.post(executable);
-                        }
-                    }
-                });
-    }
-
-    private void instantCollapseNotificationPanel() {
+    void instantCollapseNotificationPanel() {
         mNotificationPanel.instantCollapse();
-        runPostCollapseRunnables();
+        mShadeController.runPostCollapseRunnables();
     }
 
     @Override
@@ -3576,11 +3506,11 @@
     }
 
     public void onTrackingStarted() {
-        runPostCollapseRunnables();
+        mShadeController.runPostCollapseRunnables();
     }
 
     public void onClosingFinished() {
-        runPostCollapseRunnables();
+        mShadeController.runPostCollapseRunnables();
         if (!mPresenter.isPresenterFullyCollapsed()) {
             // if we set it not to be focusable when collapsing, we have to undo it when we aborted
             // the closing
@@ -3641,7 +3571,7 @@
      *
      * @param expandView The view to expand after going to the shade.
      */
-    public void goToLockedShade(View expandView) {
+    void goToLockedShade(View expandView) {
         if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
             return;
         }
@@ -3700,7 +3630,7 @@
             mStatusBarWindowViewController.cancelCurrentTouch();
         }
         if (mPanelExpanded && mState == StatusBarState.SHADE) {
-            animateCollapsePanels();
+            mShadeController.animateCollapsePanels();
         }
     }
 
@@ -4067,7 +3997,7 @@
                 Settings.Secure.putInt(mContext.getContentResolver(),
                         Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
                 if (BANNER_ACTION_SETUP.equals(action)) {
-                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+                    mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
                             true /* force */);
                     mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
                             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -4078,35 +4008,6 @@
         }
     };
 
-    @Override
-    public void collapsePanel(boolean animate) {
-        if (animate) {
-            boolean willCollapse = collapsePanel();
-            if (!willCollapse) {
-                runPostCollapseRunnables();
-            }
-        } else if (!mPresenter.isPresenterFullyCollapsed()) {
-            instantCollapseNotificationPanel();
-            visibilityChanged(false);
-        } else {
-            runPostCollapseRunnables();
-        }
-    }
-
-    @Override
-    public boolean collapsePanel() {
-        if (!mNotificationPanel.isFullyCollapsed()) {
-            // close the shade if it was open
-            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
-                    true /* delayed */);
-            visibilityChanged(false);
-
-            return true;
-        } else {
-            return false;
-        }
-    }
-
     private final NotificationListener mNotificationListener;
 
     public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
@@ -4217,7 +4118,7 @@
                 action.run();
             }).start();
 
-            return collapsePanel();
+            return mShadeController.collapsePanel();
         }, afterKeyguardGone);
     }
 
@@ -4277,7 +4178,7 @@
         return options.toBundle();
     }
 
-    protected void visibilityChanged(boolean visible) {
+    void visibilityChanged(boolean visible) {
         if (mVisible != visible) {
             mVisible = visible;
             if (!visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index dac4e58..f51174b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -48,7 +48,6 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.RemoteInputController;
@@ -921,12 +920,6 @@
         mStatusBar.keyguardGoingAway();
     }
 
-    public void animateCollapsePanels(float speedUpFactor) {
-        mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
-                false /* delayed */, speedUpFactor);
-    }
-
-
     /**
      * Called when cancel button in bouncer is pressed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
index 312c85f..e31c53a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -164,6 +164,7 @@
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
+            ShadeController shadeController,
             SuperStatusBarViewFactory superStatusBarViewFactory,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
@@ -237,6 +238,7 @@
                 dividerOptional,
                 lightsOutNotifController,
                 statusBarNotificationActivityStarterBuilder,
+                shadeController,
                 superStatusBarViewFactory,
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 1988b42..3123f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -524,7 +524,7 @@
         private final ActivityIntentHelper mActivityIntentHelper;
         private final BubbleController mBubbleController;
         private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
-        private ShadeController mShadeController;
+        private final ShadeController mShadeController;
         private NotificationPresenter mNotificationPresenter;
         private ActivityLaunchAnimator mActivityLaunchAnimator;
         private StatusBar mStatusBar;
@@ -553,6 +553,7 @@
                 @BgHandler Handler backgroundHandler,
                 ActivityIntentHelper activityIntentHelper,
                 BubbleController bubbleController,
+                ShadeController shadeController,
                 SuperStatusBarViewFactory superStatusBarViewFactory) {
             mContext = context;
             mCommandQueue = commandQueue;
@@ -577,13 +578,13 @@
             mBackgroundHandler = backgroundHandler;
             mActivityIntentHelper = activityIntentHelper;
             mBubbleController = bubbleController;
+            mShadeController = shadeController;
             mSuperStatusBarViewFactory = superStatusBarViewFactory;
         }
 
-        /** Sets the status bar to use as {@link StatusBar} and {@link ShadeController}. */
+        /** Sets the status bar to use as {@link StatusBar}. */
         public Builder setStatusBar(StatusBar statusBar) {
             mStatusBar = statusBar;
-            mShadeController = statusBar;
             return this;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 2649166..8fc624d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -88,7 +88,6 @@
 
     private static final String TAG = "StatusBarNotificationPresenter";
 
-    private final ShadeController mShadeController = Dependency.get(ShadeController.class);
     private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationViewHierarchyManager mViewHierarchyManager =
@@ -116,6 +115,7 @@
     private final Context mContext;
     private final KeyguardIndicationController mKeyguardIndicationController;
     private final StatusBar mStatusBar;
+    private final ShadeController mShadeController;
     private final CommandQueue mCommandQueue;
 
     private final AccessibilityManager mAccessibilityManager;
@@ -145,6 +145,7 @@
             KeyguardStateController keyguardStateController,
             KeyguardIndicationController keyguardIndicationController,
             StatusBar statusBar,
+            ShadeController shadeController,
             CommandQueue commandQueue) {
         mContext = context;
         mKeyguardStateController = keyguardStateController;
@@ -154,6 +155,7 @@
         mKeyguardIndicationController = keyguardIndicationController;
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mStatusBar = statusBar;
+        mShadeController = shadeController;
         mCommandQueue = commandQueue;
         mAboveShelfObserver = new AboveShelfObserver(stackScroller);
         mActivityLaunchAnimator = activityLaunchAnimator;
@@ -387,7 +389,7 @@
         }
         updateNotificationViews();
         mMediaManager.clearCurrentMediaNotification();
-        mShadeController.setLockscreenUser(newUserId);
+        mStatusBar.setLockscreenUser(newUserId);
         updateMediaMetaData(true, false);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 2012b57..6193a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -50,8 +50,6 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-import dagger.Lazy;
-
 /**
  */
 @Singleton
@@ -62,9 +60,9 @@
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final ActivityStarter mActivityStarter;
-    private final Lazy<ShadeController> mShadeControllerLazy;
     private final Context mContext;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final ShadeController mShadeController;
     private final ActivityIntentHelper mActivityIntentHelper;
     private final NotificationGroupManager mGroupManager;
     private View mPendingWorkRemoteInputView;
@@ -83,16 +81,16 @@
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            ActivityStarter activityStarter, Lazy<ShadeController> shadeControllerLazy,
+            ActivityStarter activityStarter, ShadeController shadeController,
             CommandQueue commandQueue) {
         mContext = context;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mShadeController = shadeController;
         mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL,
                 new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null);
         mLockscreenUserManager = notificationLockscreenUserManager;
         mKeyguardStateController = keyguardStateController;
         mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
-        mShadeControllerLazy = shadeControllerLazy;
         mActivityStarter = activityStarter;
         mStatusBarStateController.addCallback(this);
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
@@ -167,8 +165,8 @@
                     });
                 }
             };
-            mShadeControllerLazy.get().postOnShadeExpanded(clickPendingViewRunnable);
-            mShadeControllerLazy.get().instantExpandNotificationsPanel();
+            mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
+            mShadeController.instantExpandNotificationsPanel();
         }
     }
 
@@ -256,7 +254,7 @@
                 boolean handled = defaultHandler.handleClick();
 
                 // close the shade if it was open and maybe wait for activity start.
-                return handled && mShadeControllerLazy.get().closeShadeIfOpen();
+                return handled && mShadeController.closeShadeIfOpen();
             }, null, afterKeyguardGone);
             return true;
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
index f8929e0..eb86bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
@@ -57,12 +57,9 @@
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Controller for {@link StatusBarWindowView}.
  */
-//@Singleton
 public class StatusBarWindowViewController {
     private final InjectionInflationController mInjectionInflationController;
     private final NotificationWakeUpCoordinator mCoordinator;
@@ -80,7 +77,7 @@
     private final DozeParameters mDozeParameters;
     private final CommandQueue mCommandQueue;
     private final StatusBarWindowView mView;
-    private final Lazy<ShadeController> mShadeControllerLazy;
+    private final ShadeController mShadeController;
 
     private GestureDetector mGestureDetector;
     private View mBrightnessMirror;
@@ -114,7 +111,7 @@
             DozeLog dozeLog,
             DozeParameters dozeParameters,
             CommandQueue commandQueue,
-            Lazy<ShadeController> shadeControllerLazy,
+            ShadeController shadeController,
             DockManager dockManager,
             StatusBarWindowView statusBarWindowView) {
         mInjectionInflationController = injectionInflationController;
@@ -133,7 +130,7 @@
         mDozeParameters = dozeParameters;
         mCommandQueue = commandQueue;
         mView = statusBarWindowView;
-        mShadeControllerLazy = shadeControllerLazy;
+        mShadeController = shadeController;
         mDockManager = dockManager;
 
         // This view is not part of the newly inflated expanded status bar.
@@ -153,7 +150,7 @@
                 mBypassController,
                 mFalsingManager,
                 mPluginManager,
-                mShadeControllerLazy.get(),
+                mShadeController,
                 mNotificationLockscreenUserManager,
                 mNotificationEntryManager,
                 mKeyguardStateController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index b0cd90c..a6108a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -91,7 +91,8 @@
     @VisibleForTesting
     boolean mIsShowingIconGracefully = false;
     // Some specific carriers have 5GE network which is special LTE CA network.
-    private static final int NETWORK_TYPE_LTE_CA_5GE = TelephonyManager.MAX_NETWORK_TYPE + 1;
+    private static final int NETWORK_TYPE_LTE_CA_5GE =
+            TelephonyManager.getAllNetworkTypes().length + 1;
 
     // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
     // need listener lists anymore.
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index 2f13f39..367d4d2 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -16,19 +16,13 @@
 
 package com.android.systemui.usb;
 
-import android.app.Activity;
 import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.debug.IAdbManager;
-import android.hardware.usb.UsbManager;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 import android.util.EventLog;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -42,25 +36,14 @@
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import javax.inject.Inject;
 
 public class UsbDebuggingActivity extends AlertActivity
                                   implements DialogInterface.OnClickListener {
     private static final String TAG = "UsbDebuggingActivity";
 
     private CheckBox mAlwaysAllow;
-    private UsbDisconnectedReceiver mDisconnectedReceiver;
-    private final BroadcastDispatcher mBroadcastDispatcher;
     private String mKey;
 
-    @Inject
-    public UsbDebuggingActivity(BroadcastDispatcher broadcastDispatcher) {
-        super();
-        mBroadcastDispatcher = broadcastDispatcher;
-    }
-
     @Override
     public void onCreate(Bundle icicle) {
         Window window = getWindow();
@@ -70,10 +53,6 @@
 
         super.onCreate(icicle);
 
-        if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
-            mDisconnectedReceiver = new UsbDisconnectedReceiver(this);
-        }
-
         Intent intent = getIntent();
         String fingerprints = intent.getStringExtra("fingerprints");
         mKey = intent.getStringExtra("key");
@@ -126,40 +105,6 @@
         super.onWindowAttributesChanged(params);
     }
 
-    private class UsbDisconnectedReceiver extends BroadcastReceiver {
-        private final Activity mActivity;
-        public UsbDisconnectedReceiver(Activity activity) {
-            mActivity = activity;
-        }
-
-        @Override
-        public void onReceive(Context content, Intent intent) {
-            String action = intent.getAction();
-            if (!UsbManager.ACTION_USB_STATE.equals(action)) {
-                return;
-            }
-            boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
-            if (!connected) {
-                mActivity.finish();
-            }
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE);
-        mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter);
-    }
-
-    @Override
-    protected void onStop() {
-        if (mDisconnectedReceiver != null) {
-            mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver);
-        }
-        super.onStop();
-    }
-
     @Override
     public void onClick(DialogInterface dialog, int which) {
         boolean allow = (which == AlertDialog.BUTTON_POSITIVE);
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
index 032b72e..4214242 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
@@ -16,41 +16,19 @@
 
 package com.android.systemui.usb;
 
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbManager;
 import android.os.Bundle;
-import android.os.SystemProperties;
 
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import javax.inject.Inject;
 
 public class UsbDebuggingSecondaryUserActivity extends AlertActivity
         implements DialogInterface.OnClickListener {
-    private UsbDisconnectedReceiver mDisconnectedReceiver;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-
-    @Inject
-    public UsbDebuggingSecondaryUserActivity(BroadcastDispatcher broadcastDispatcher) {
-        mBroadcastDispatcher = broadcastDispatcher;
-    }
-
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
-            mDisconnectedReceiver = new UsbDisconnectedReceiver(this);
-        }
-
         final AlertController.AlertParams ap = mAlertParams;
         ap.mTitle = getString(R.string.usb_debugging_secondary_user_title);
         ap.mMessage = getString(R.string.usb_debugging_secondary_user_message);
@@ -60,40 +38,6 @@
         setupAlert();
     }
 
-    private class UsbDisconnectedReceiver extends BroadcastReceiver {
-        private final Activity mActivity;
-        public UsbDisconnectedReceiver(Activity activity) {
-            mActivity = activity;
-        }
-
-        @Override
-        public void onReceive(Context content, Intent intent) {
-            String action = intent.getAction();
-            if (UsbManager.ACTION_USB_STATE.equals(action)) {
-                boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
-                if (!connected) {
-                    mActivity.finish();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE);
-        mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter);
-    }
-
-    @Override
-    protected void onStop() {
-        if (mDisconnectedReceiver != null) {
-            mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver);
-        }
-        super.onStop();
-    }
-
     @Override
     public void onClick(DialogInterface dialog, int which) {
         finish();
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
new file mode 100644
index 0000000..264a683
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
+import static android.os.Process.SYSTEM_UID;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.Size;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.Surface;
+
+import com.android.internal.R;
+
+import java.util.List;
+
+/**
+ * Contains information about the layout-properties of a display. This refers to internal layout
+ * like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to
+ * DisplayPolicy.
+ */
+public class DisplayLayout {
+    // Navigation bar position values
+    private static final int NAV_BAR_LEFT = 1 << 0;
+    private static final int NAV_BAR_RIGHT = 1 << 1;
+    private static final int NAV_BAR_BOTTOM = 1 << 2;
+
+    private int mUiMode;
+    private int mWidth;
+    private int mHeight;
+    private DisplayCutout mCutout;
+    private int mRotation;
+    private int mDensityDpi;
+    private final Rect mNonDecorInsets = new Rect();
+    private final Rect mStableInsets = new Rect();
+    private boolean mHasNavigationBar = false;
+    private boolean mHasStatusBar = false;
+
+    /**
+     * Create empty layout.
+     */
+    public DisplayLayout() {
+    }
+
+    /**
+     * Construct a custom display layout using a DisplayInfo.
+     * @param info
+     * @param res
+     */
+    public DisplayLayout(DisplayInfo info, Resources res, boolean hasNavigationBar,
+            boolean hasStatusBar) {
+        init(info, res, hasNavigationBar, hasStatusBar);
+    }
+
+    /**
+     * Construct a display layout based on a live display.
+     * @param context Used for resources.
+     */
+    public DisplayLayout(@NonNull Context context, @NonNull Display rawDisplay) {
+        final int displayId = rawDisplay.getDisplayId();
+        DisplayInfo info = new DisplayInfo();
+        rawDisplay.getDisplayInfo(info);
+        init(info, context.getResources(), hasNavigationBar(info, context, displayId),
+                hasStatusBar(displayId));
+    }
+
+    public DisplayLayout(DisplayLayout dl) {
+        set(dl);
+    }
+
+    /** sets this DisplayLayout to a copy of another on. */
+    public void set(DisplayLayout dl) {
+        mUiMode = dl.mUiMode;
+        mWidth = dl.mWidth;
+        mHeight = dl.mHeight;
+        mCutout = dl.mCutout;
+        mRotation = dl.mRotation;
+        mDensityDpi = dl.mDensityDpi;
+        mHasNavigationBar = dl.mHasNavigationBar;
+        mHasStatusBar = dl.mHasStatusBar;
+        mNonDecorInsets.set(dl.mNonDecorInsets);
+        mStableInsets.set(dl.mStableInsets);
+    }
+
+    private void init(DisplayInfo info, Resources res, boolean hasNavigationBar,
+            boolean hasStatusBar) {
+        mUiMode = res.getConfiguration().uiMode;
+        mWidth = info.logicalWidth;
+        mHeight = info.logicalHeight;
+        mRotation = info.rotation;
+        mCutout = info.displayCutout;
+        mDensityDpi = info.logicalDensityDpi;
+        mHasNavigationBar = hasNavigationBar;
+        mHasStatusBar = hasStatusBar;
+        recalcInsets(res);
+    }
+
+    private void recalcInsets(Resources res) {
+        computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mUiMode, mNonDecorInsets,
+                mHasNavigationBar);
+        mStableInsets.set(mNonDecorInsets);
+        if (mHasStatusBar) {
+            convertNonDecorInsetsToStableInsets(res, mStableInsets, mWidth, mHeight, mHasStatusBar);
+        }
+    }
+
+    /**
+     * Apply a rotation to this layout and its parameters.
+     * @param res
+     * @param targetRotation
+     */
+    public void rotateTo(Resources res, @Surface.Rotation int targetRotation) {
+        final int rotationDelta = (targetRotation - mRotation + 4) % 4;
+        final boolean changeOrient = (rotationDelta % 2) != 0;
+
+        final int origWidth = mWidth;
+        final int origHeight = mHeight;
+
+        mRotation = targetRotation;
+        if (changeOrient) {
+            mWidth = origHeight;
+            mHeight = origWidth;
+        }
+
+        if (mCutout != null && !mCutout.isEmpty()) {
+            mCutout = calculateDisplayCutoutForRotation(mCutout, rotationDelta, origWidth,
+                    origHeight);
+        }
+
+        recalcInsets(res);
+    }
+
+    /** Get this layout's non-decor insets. */
+    public Rect nonDecorInsets() {
+        return mNonDecorInsets;
+    }
+
+    /** Get this layout's stable insets. */
+    public Rect stableInsets() {
+        return mStableInsets;
+    }
+
+    /** Get this layout's width. */
+    public int width() {
+        return mWidth;
+    }
+
+    /** Get this layout's height. */
+    public int height() {
+        return mHeight;
+    }
+
+    /** Get this layout's display rotation. */
+    public int rotation() {
+        return mRotation;
+    }
+
+    /** Get this layout's display density. */
+    public int densityDpi() {
+        return mDensityDpi;
+    }
+
+    /** Get whether this layout is landscape. */
+    public boolean isLandscape() {
+        return mWidth > mHeight;
+    }
+
+    /** Gets the orientation of this layout */
+    public int getOrientation() {
+        return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+    }
+
+    /** Gets the calculated stable-bounds for this layout */
+    public void getStableBounds(Rect outBounds) {
+        outBounds.set(0, 0, mWidth, mHeight);
+        outBounds.inset(mStableInsets);
+    }
+
+    /**
+     * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta`
+     * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and
+     * remains at 0,0 after rotation.
+     *
+     * Only 'bounds' is mutated.
+     */
+    public static void rotateBounds(Rect inOutBounds, Rect parentBounds, int delta) {
+        int rdelta = ((delta % 4) + 4) % 4;
+        int origLeft = inOutBounds.left;
+        switch (rdelta) {
+            case 0:
+                return;
+            case 1:
+                inOutBounds.left = inOutBounds.top;
+                inOutBounds.top = parentBounds.right - inOutBounds.right;
+                inOutBounds.right = inOutBounds.bottom;
+                inOutBounds.bottom = parentBounds.right - origLeft;
+                return;
+            case 2:
+                inOutBounds.left = parentBounds.right - inOutBounds.right;
+                inOutBounds.right = parentBounds.right - origLeft;
+                return;
+            case 3:
+                inOutBounds.left = parentBounds.bottom - inOutBounds.bottom;
+                inOutBounds.bottom = inOutBounds.right;
+                inOutBounds.right = parentBounds.bottom - inOutBounds.top;
+                inOutBounds.top = origLeft;
+                return;
+        }
+    }
+
+    /**
+     * Calculates the stable insets if we already have the non-decor insets.
+     */
+    private static void convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets,
+            int displayWidth, int displayHeight, boolean hasStatusBar) {
+        if (!hasStatusBar) {
+            return;
+        }
+        int statusBarHeight = getStatusBarHeight(displayWidth > displayHeight, res);
+        inOutInsets.top = Math.max(inOutInsets.top, statusBarHeight);
+    }
+
+    /**
+     * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
+     * bar or button bar.
+     *
+     * @param displayRotation the current display rotation
+     * @param displayWidth the current display width
+     * @param displayHeight the current display height
+     * @param displayCutout the current display cutout
+     * @param outInsets the insets to return
+     */
+    static void computeNonDecorInsets(Resources res, int displayRotation, int displayWidth,
+            int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
+            boolean hasNavigationBar) {
+        outInsets.setEmpty();
+
+        // Only navigation bar
+        if (hasNavigationBar) {
+            int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
+            int navBarSize =
+                    getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
+            if (position == NAV_BAR_BOTTOM) {
+                outInsets.bottom = navBarSize;
+            } else if (position == NAV_BAR_RIGHT) {
+                outInsets.right = navBarSize;
+            } else if (position == NAV_BAR_LEFT) {
+                outInsets.left = navBarSize;
+            }
+        }
+
+        if (displayCutout != null) {
+            outInsets.left += displayCutout.getSafeInsetLeft();
+            outInsets.top += displayCutout.getSafeInsetTop();
+            outInsets.right += displayCutout.getSafeInsetRight();
+            outInsets.bottom += displayCutout.getSafeInsetBottom();
+        }
+    }
+
+    /**
+     * Calculates the stable insets without running a layout.
+     *
+     * @param displayRotation the current display rotation
+     * @param displayWidth the current display width
+     * @param displayHeight the current display height
+     * @param displayCutout the current display cutout
+     * @param outInsets the insets to return
+     */
+    static void computeStableInsets(Resources res, int displayRotation, int displayWidth,
+            int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
+            boolean hasNavigationBar, boolean hasStatusBar) {
+        outInsets.setEmpty();
+
+        // Navigation bar and status bar.
+        computeNonDecorInsets(res, displayRotation, displayWidth, displayHeight, displayCutout,
+                uiMode, outInsets, hasNavigationBar);
+        convertNonDecorInsetsToStableInsets(res, outInsets, displayWidth, displayHeight,
+                hasStatusBar);
+    }
+
+    /** Retrieve the statusbar height from resources. */
+    static int getStatusBarHeight(boolean landscape, Resources res) {
+        return landscape ? res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_height_landscape)
+                    : res.getDimensionPixelSize(
+                            com.android.internal.R.dimen.status_bar_height_portrait);
+    }
+
+    /** Calculate the DisplayCutout for a particular display size/rotation. */
+    public static DisplayCutout calculateDisplayCutoutForRotation(
+            DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
+        if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
+            return null;
+        }
+        if (rotation == ROTATION_0) {
+            return computeSafeInsets(
+                    cutout, displayWidth, displayHeight);
+        }
+        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+        Rect[] cutoutRects = computeSafeInsets(cutout, displayWidth, displayHeight)
+                        .getBoundingRectsAll();
+        final Rect[] newBounds = new Rect[cutoutRects.length];
+        final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight);
+        for (int i = 0; i < cutoutRects.length; ++i) {
+            newBounds[i] = new Rect(cutoutRects[i]);
+            rotateBounds(newBounds[i], displayBounds, rotation);
+        }
+        return computeSafeInsets(DisplayCutout.fromBounds(newBounds),
+                rotated ? displayHeight : displayWidth,
+                rotated ? displayWidth : displayHeight);
+    }
+
+    /** Calculate safe insets. */
+    public static DisplayCutout computeSafeInsets(DisplayCutout inner,
+            int displayWidth, int displayHeight) {
+        if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) {
+            return null;
+        }
+
+        final Size displaySize = new Size(displayWidth, displayHeight);
+        final Rect safeInsets = computeSafeInsets(displaySize, inner);
+        return inner.replaceSafeInsets(safeInsets);
+    }
+
+    private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
+        if (displaySize.getWidth() < displaySize.getHeight()) {
+            final List<Rect> boundingRects = cutout.replaceSafeInsets(
+                    new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2))
+                    .getBoundingRects();
+            int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP);
+            int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM);
+            return new Rect(0, topInset, 0, bottomInset);
+        } else if (displaySize.getWidth() > displaySize.getHeight()) {
+            final List<Rect> boundingRects = cutout.replaceSafeInsets(
+                    new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0))
+                    .getBoundingRects();
+            int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT);
+            int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT);
+            return new Rect(leftInset, 0, right, 0);
+        } else {
+            throw new UnsupportedOperationException("not implemented: display=" + displaySize
+                    + " cutout=" + cutout);
+        }
+    }
+
+    private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) {
+        int inset = 0;
+        final int size = boundingRects.size();
+        for (int i = 0; i < size; i++) {
+            Rect boundingRect = boundingRects.get(i);
+            switch (gravity) {
+                case Gravity.TOP:
+                    if (boundingRect.top == 0) {
+                        inset = Math.max(inset, boundingRect.bottom);
+                    }
+                    break;
+                case Gravity.BOTTOM:
+                    if (boundingRect.bottom == display.getHeight()) {
+                        inset = Math.max(inset, display.getHeight() - boundingRect.top);
+                    }
+                    break;
+                case Gravity.LEFT:
+                    if (boundingRect.left == 0) {
+                        inset = Math.max(inset, boundingRect.right);
+                    }
+                    break;
+                case Gravity.RIGHT:
+                    if (boundingRect.right == display.getWidth()) {
+                        inset = Math.max(inset, display.getWidth() - boundingRect.left);
+                    }
+                    break;
+                default:
+                    throw new IllegalArgumentException("unknown gravity: " + gravity);
+            }
+        }
+        return inset;
+    }
+
+    static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            // Allow a system property to override this. Used by the emulator.
+            final String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
+            if ("1".equals(navBarOverride)) {
+                return false;
+            } else if ("0".equals(navBarOverride)) {
+                return true;
+            }
+            return context.getResources().getBoolean(R.bool.config_showNavigationBar);
+        } else {
+            boolean isUntrustedVirtualDisplay = info.type == Display.TYPE_VIRTUAL
+                    && info.ownerUid != SYSTEM_UID;
+            final ContentResolver resolver = context.getContentResolver();
+            boolean forceDesktopOnExternal = Settings.Global.getInt(resolver,
+                    DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
+
+            return ((info.flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
+                    || (forceDesktopOnExternal && !isUntrustedVirtualDisplay));
+            // TODO(b/142569966): make sure VR2D and DisplayWindowSettings are moved here somehow.
+        }
+    }
+
+    static boolean hasStatusBar(int displayId) {
+        return displayId == Display.DEFAULT_DISPLAY;
+    }
+
+    /** Retrieve navigation bar position from resources based on rotation and size. */
+    public static int navigationBarPosition(Resources res, int displayWidth, int displayHeight,
+            int rotation) {
+        boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean(
+                com.android.internal.R.bool.config_navBarCanMove);
+        if (navBarCanMove && displayWidth > displayHeight) {
+            if (rotation == Surface.ROTATION_90) {
+                return NAV_BAR_RIGHT;
+            } else {
+                return NAV_BAR_LEFT;
+            }
+        }
+        return NAV_BAR_BOTTOM;
+    }
+
+    /** Retrieve navigation bar size from resources based on side/orientation/ui-mode */
+    public static int getNavigationBarSize(Resources res, int navBarSide, boolean landscape,
+            int uiMode) {
+        final boolean carMode = (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR;
+        if (carMode) {
+            if (navBarSide == NAV_BAR_BOTTOM) {
+                return res.getDimensionPixelSize(landscape
+                        ? R.dimen.navigation_bar_height_landscape_car_mode
+                        : R.dimen.navigation_bar_height_car_mode);
+            } else {
+                return res.getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode);
+            }
+        } else {
+            if (navBarSide == NAV_BAR_BOTTOM) {
+                return res.getDimensionPixelSize(landscape
+                        ? R.dimen.navigation_bar_height_landscape
+                        : R.dimen.navigation_bar_height);
+            } else {
+                return res.getDimensionPixelSize(R.dimen.navigation_bar_width);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
index 19fff79..ae82115 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
@@ -16,16 +16,20 @@
 
 package com.android.systemui.wm;
 
+import android.annotation.Nullable;
+import android.content.Context;
 import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.Display;
 import android.view.IDisplayWindowListener;
 import android.view.IDisplayWindowRotationCallback;
 import android.view.IDisplayWindowRotationController;
+import android.view.IWindowManager;
 import android.view.WindowContainerTransaction;
-import android.view.WindowManagerGlobal;
 
 import com.android.systemui.dagger.qualifiers.MainHandler;
 
@@ -45,6 +49,8 @@
     private static final String TAG = "DisplayWindowController";
 
     private final Handler mHandler;
+    private final Context mContext;
+    private final IWindowManager mWmService;
 
     private final ArrayList<OnDisplayWindowRotationController> mRotationControllers =
             new ArrayList<>();
@@ -76,6 +82,14 @@
                 }
             };
 
+    /**
+     * Get's a display by id from DisplayManager.
+     */
+    public Display getDisplay(int displayId) {
+        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        return displayManager.getDisplay(displayId);
+    }
+
     private final IDisplayWindowListener mDisplayContainerListener =
             new IDisplayWindowListener.Stub() {
                 @Override
@@ -87,6 +101,10 @@
                             }
                             DisplayRecord record = new DisplayRecord();
                             record.mDisplayId = displayId;
+                            Display display = getDisplay(displayId);
+                            record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
+                                    : mContext.createDisplayContext(display);
+                            record.mDisplayLayout = new DisplayLayout(record.mContext, display);
                             mDisplays.put(displayId, record);
                             for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
                                 mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
@@ -105,6 +123,13 @@
                                         + " display.");
                                 return;
                             }
+                            Display display = getDisplay(displayId);
+                            Context perDisplayContext = mContext;
+                            if (displayId != Display.DEFAULT_DISPLAY) {
+                                perDisplayContext = mContext.createDisplayContext(display);
+                            }
+                            dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
+                            dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
                             for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
                                 mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
                                         displayId, newConfig);
@@ -127,19 +152,36 @@
             };
 
     @Inject
-    public DisplayWindowController(@MainHandler Handler mainHandler) {
+    public DisplayWindowController(Context context, @MainHandler Handler mainHandler,
+            IWindowManager wmService) {
         mHandler = mainHandler;
+        mContext = context;
+        mWmService = wmService;
         try {
-            WindowManagerGlobal.getWindowManagerService().registerDisplayWindowListener(
-                    mDisplayContainerListener);
-            WindowManagerGlobal.getWindowManagerService().setDisplayWindowRotationController(
-                    mDisplayRotationController);
+            mWmService.registerDisplayWindowListener(mDisplayContainerListener);
+            mWmService.setDisplayWindowRotationController(mDisplayRotationController);
         } catch (RemoteException e) {
             throw new RuntimeException("Unable to register hierarchy listener");
         }
     }
 
     /**
+     * Gets the DisplayLayout associated with a display.
+     */
+    public @Nullable DisplayLayout getDisplayLayout(int displayId) {
+        final DisplayRecord r = mDisplays.get(displayId);
+        return r != null ? r.mDisplayLayout : null;
+    }
+
+    /**
+     * Gets a display-specific context for a display.
+     */
+    public @Nullable Context getDisplayContext(int displayId) {
+        final DisplayRecord r = mDisplays.get(displayId);
+        return r != null ? r.mContext : null;
+    }
+
+    /**
      * Add a display window-container listener. It will get notified whenever a display's
      * configuration changes or when displays are added/removed from the WM hierarchy.
      */
@@ -184,6 +226,8 @@
 
     private static class DisplayRecord {
         int mDisplayId;
+        Context mContext;
+        DisplayLayout mDisplayLayout;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
new file mode 100644
index 0000000..5ec61c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.DragEvent;
+import android.view.IWindow;
+import android.view.IWindowManager;
+import android.view.IWindowSession;
+import android.view.IWindowSessionCallback;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowlessViewRoot;
+import android.view.WindowlessWindowManager;
+
+import com.android.internal.os.IResultReceiver;
+
+import java.util.HashMap;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Represents the "windowing" layer of the System-UI. This layer allows system-ui components to
+ * place and manipulate windows without talking to WindowManager.
+ */
+@Singleton
+public class SystemWindows {
+    private static final String TAG = "SystemWindows";
+
+    private final SparseArray<PerDisplay> mPerDisplay = new SparseArray<>();
+    final HashMap<View, WindowlessViewRoot> mViewRoots = new HashMap<>();
+    Context mContext;
+    IWindowSession mSession;
+    DisplayWindowController mDisplayController;
+    IWindowManager mWmService;
+
+    private final DisplayWindowController.DisplayWindowListener mDisplayListener =
+            new DisplayWindowController.DisplayWindowListener() {
+                @Override
+                public void onDisplayAdded(int displayId) { }
+
+                @Override
+                public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+                    PerDisplay pd = mPerDisplay.get(displayId);
+                    if (pd == null) {
+                        return;
+                    }
+                    pd.updateConfiguration(newConfig);
+                }
+
+                @Override
+                public void onDisplayRemoved(int displayId) { }
+            };
+
+    @Inject
+    public SystemWindows(Context context, DisplayWindowController displayController,
+            IWindowManager wmService) {
+        mContext = context;
+        mWmService = wmService;
+        mDisplayController = displayController;
+        mDisplayController.addDisplayWindowListener(mDisplayListener);
+        try {
+            mSession = wmService.openSession(
+                    new IWindowSessionCallback.Stub() {
+                        @Override
+                        public void onAnimatorScaleChanged(float scale) {}
+                    });
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to create layer", e);
+        }
+    }
+
+    /**
+     * Adds a view to system-ui window management.
+     */
+    public void addView(View view, WindowManager.LayoutParams attrs, int displayId,
+            int windowType) {
+        PerDisplay pd = mPerDisplay.get(displayId);
+        if (pd == null) {
+            pd = new PerDisplay(displayId);
+            mPerDisplay.put(displayId, pd);
+        }
+        pd.addView(view, attrs, windowType);
+    }
+
+    /**
+     * Removes a view from system-ui window management.
+     * @param view
+     */
+    public void removeView(View view) {
+        WindowlessViewRoot root = mViewRoots.remove(view);
+        root.die();
+    }
+
+    /**
+     * Updates the layout params of a view.
+     */
+    public void updateViewLayout(@NonNull View view, ViewGroup.LayoutParams params) {
+        WindowlessViewRoot root = mViewRoots.get(view);
+        if (root == null || !(params instanceof WindowManager.LayoutParams)) {
+            return;
+        }
+        view.setLayoutParams(params);
+        root.relayout((WindowManager.LayoutParams) params);
+    }
+
+    /**
+     * Adds a root for system-ui window management with no views. Only useful for IME.
+     */
+    public void addRoot(int displayId, int windowType) {
+        PerDisplay pd = mPerDisplay.get(displayId);
+        if (pd == null) {
+            pd = new PerDisplay(displayId);
+            mPerDisplay.put(displayId, pd);
+        }
+        pd.addRoot(windowType);
+    }
+
+    /**
+     * Get the IWindow token for a specific root.
+     *
+     * @param windowType A window type from {@link android.view.WindowManager}.
+     */
+    IWindow getWindow(int displayId, int windowType) {
+        PerDisplay pd = mPerDisplay.get(displayId);
+        if (pd == null) {
+            return null;
+        }
+        return pd.getWindow(windowType);
+    }
+
+    private class PerDisplay {
+        final int mDisplayId;
+        private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>();
+
+        PerDisplay(int displayId) {
+            mDisplayId = displayId;
+        }
+
+        public void addView(View view, WindowManager.LayoutParams attrs, int windowType) {
+            SysUiWindowManager wwm = addRoot(windowType);
+            if (wwm == null) {
+                Slog.e(TAG, "Unable to create systemui root");
+                return;
+            }
+            final Display display = mDisplayController.getDisplay(mDisplayId);
+            WindowlessViewRoot viewRoot = new WindowlessViewRoot(mContext, display, wwm);
+            attrs.flags |= FLAG_HARDWARE_ACCELERATED;
+            viewRoot.addView(view, attrs);
+            mViewRoots.put(view, viewRoot);
+        }
+
+        SysUiWindowManager addRoot(int windowType) {
+            SysUiWindowManager wwm = mWwms.get(windowType);
+            if (wwm != null) {
+                return wwm;
+            }
+            SurfaceControl rootSurface = null;
+            ContainerWindow win = new ContainerWindow();
+            try {
+                rootSurface = mWmService.addShellRoot(mDisplayId, win, windowType);
+            } catch (RemoteException e) {
+            }
+            if (rootSurface == null) {
+                Slog.e(TAG, "Unable to get root surfacecontrol for systemui");
+                return null;
+            }
+            Context displayContext = mDisplayController.getDisplayContext(mDisplayId);
+            wwm = new SysUiWindowManager(mDisplayId, displayContext, rootSurface, win);
+            mWwms.put(windowType, wwm);
+            return wwm;
+        }
+
+        IWindow getWindow(int windowType) {
+            SysUiWindowManager wwm = mWwms.get(windowType);
+            if (wwm == null) {
+                return null;
+            }
+            return wwm.mContainerWindow;
+        }
+
+        void updateConfiguration(Configuration configuration) {
+            for (int i = 0; i < mWwms.size(); ++i) {
+                mWwms.valueAt(i).updateConfiguration(configuration);
+            }
+        }
+    }
+
+    /**
+     * A subclass of WindowlessWindowManager that provides insets to its viewroots.
+     */
+    public class SysUiWindowManager extends WindowlessWindowManager {
+        final int mDisplayId;
+        ContainerWindow mContainerWindow;
+        public SysUiWindowManager(int displayId, Context ctx, SurfaceControl rootSurface,
+                ContainerWindow container) {
+            super(ctx.getResources().getConfiguration(), rootSurface, null /* hostInputToken */);
+            mContainerWindow = container;
+            mDisplayId = displayId;
+        }
+
+        @Override
+        public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
+                int requestedWidth, int requestedHeight, int viewVisibility, int flags,
+                long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
+                Rect outVisibleInsets, Rect outStableInsets,
+                DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
+                SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
+            int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight,
+                    viewVisibility, flags, frameNumber, outFrame, outOverscanInsets,
+                    outContentInsets, outVisibleInsets, outStableInsets,
+                    cutout, mergedConfiguration, outSurfaceControl, outInsetsState);
+            if (res != 0) {
+                return res;
+            }
+            DisplayLayout dl = mDisplayController.getDisplayLayout(mDisplayId);
+            outStableInsets.set(dl.stableInsets());
+            return 0;
+        }
+
+        void updateConfiguration(Configuration configuration) {
+            setConfiguration(configuration);
+        }
+    }
+
+    class ContainerWindow extends IWindow.Stub {
+        ContainerWindow() {}
+
+        @Override
+        public void resized(Rect frame, Rect contentInsets, Rect visibleInsets, Rect stableInsets,
+                boolean reportDraw, MergedConfiguration newMergedConfiguration, Rect backDropFrame,
+                boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
+                DisplayCutout.ParcelableWrapper displayCutout) {}
+
+        @Override
+        public void locationInParentDisplayChanged(Point offset) {}
+
+        @Override
+        public void insetsChanged(InsetsState insetsState) {}
+
+        @Override
+        public void insetsControlChanged(InsetsState insetsState,
+                InsetsSourceControl[] activeControls) {}
+
+        @Override
+        public void showInsets(int types, boolean fromIme) {}
+
+        @Override
+        public void hideInsets(int types, boolean fromIme) {}
+
+        @Override
+        public void moved(int newX, int newY) {}
+
+        @Override
+        public void dispatchAppVisibility(boolean visible) {}
+
+        @Override
+        public void dispatchGetNewSurface() {}
+
+        @Override
+        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {}
+
+        @Override
+        public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {}
+
+        @Override
+        public void closeSystemDialogs(String reason) {}
+
+        @Override
+        public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
+                boolean sync) {}
+
+        @Override
+        public void dispatchWallpaperCommand(String action, int x, int y,
+                int z, Bundle extras, boolean sync) {}
+
+        /* Drag/drop */
+        @Override
+        public void dispatchDragEvent(DragEvent event) {}
+
+        @Override
+        public void updatePointerIcon(float x, float y) {}
+
+        @Override
+        public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
+                int localValue, int localChanges) {}
+
+        @Override
+        public void dispatchWindowShown() {}
+
+        @Override
+        public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {}
+
+        @Override
+        public void dispatchPointerCaptureChanged(boolean hasCapture) {}
+    }
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 81e2c22..e5f56d4 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -32,7 +32,8 @@
 
 LOCAL_JNI_SHARED_LIBRARIES := \
     libdexmakerjvmtiagent \
-    libmultiplejvmtiagentsinterferenceagent
+    libmultiplejvmtiagentsinterferenceagent \
+    libstaticjvmtiagent
 
 LOCAL_JAVA_LIBRARIES := \
     android.test.runner \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index e0d31d0..2ccecec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -94,8 +94,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import dagger.Lazy;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -153,7 +151,7 @@
     @Mock
     private Resources mResources;
     @Mock
-    private Lazy<ShadeController> mShadeController;
+    private ShadeController mShadeController;
     @Mock
     private RemoteInputUriController mRemoteInputUriController;
 
@@ -719,7 +717,7 @@
         TestableBubbleController(Context context,
                 StatusBarWindowController statusBarWindowController,
                 StatusBarStateController statusBarStateController,
-                Lazy<ShadeController> shadeController,
+                ShadeController shadeController,
                 BubbleData data,
                 ConfigurationController configurationController,
                 NotificationInterruptionStateProvider interruptionStateProvider,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index cbfcfdd..a8a2b33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -68,15 +68,14 @@
 
         mDependency.injectTestDependency(FalsingManager.class, mFalsingManager);
         mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mUpdateMonitor);
-        mDependency.injectTestDependency(StatusBarWindowController.class,
-                mStatusBarWindowController);
 
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
 
         TestableLooper.get(this).runWithLooper(() -> {
             mViewMediator = new KeyguardViewMediator(
                     mContext, mFalsingManager, mLockPatternUtils, mBroadcastDispatcher,
-                    () -> mStatusBarKeyguardViewManager, mDismissCallbackRegistry);
+                    mStatusBarWindowController, () -> mStatusBarKeyguardViewManager,
+                    mDismissCallbackRegistry);
         });
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
index 2f90641..4a90bb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
@@ -57,7 +57,7 @@
 
     class TestableRichEvent extends RichEvent {
         TestableRichEvent(int logLevel, int type, String reason) {
-            super(logLevel, type, reason);
+            init(logLevel, type, reason);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
index 1e8ebea..e7b317e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
@@ -35,11 +35,12 @@
 @RunWith(AndroidTestingRunner.class)
 public class SysuiLogTest extends SysuiTestCase {
     private static final String TEST_ID = "TestLogger";
+    private static final String TEST_MSG = "msg";
     private static final int MAX_LOGS = 5;
 
     @Mock
     private DumpController mDumpController;
-    private SysuiLog mSysuiLog;
+    private SysuiLog<Event> mSysuiLog;
 
     @Before
     public void setup() {
@@ -48,35 +49,63 @@
 
     @Test
     public void testLogDisabled_noLogsWritten() {
-        mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, false, false);
-        assertEquals(mSysuiLog.mTimeline, null);
+        mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, false);
+        assertEquals(null, mSysuiLog.mTimeline);
 
-        mSysuiLog.log(new Event("msg"));
-        assertEquals(mSysuiLog.mTimeline, null);
+        mSysuiLog.log(createEvent(TEST_MSG));
+        assertEquals(null, mSysuiLog.mTimeline);
     }
 
     @Test
     public void testLogEnabled_logWritten() {
-        mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, true, false);
-        assertEquals(mSysuiLog.mTimeline.size(), 0);
+        mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
+        assertEquals(0, mSysuiLog.mTimeline.size());
 
-        mSysuiLog.log(new Event("msg"));
-        assertEquals(mSysuiLog.mTimeline.size(), 1);
+        mSysuiLog.log(createEvent(TEST_MSG));
+        assertEquals(1, mSysuiLog.mTimeline.size());
     }
 
     @Test
     public void testMaxLogs() {
-        mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, true, false);
+        mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
         assertEquals(mSysuiLog.mTimeline.size(), 0);
 
-        final String msg = "msg";
         for (int i = 0; i < MAX_LOGS + 1; i++) {
-            mSysuiLog.log(new Event(msg + i));
+            mSysuiLog.log(createEvent(TEST_MSG + i));
         }
 
-        assertEquals(mSysuiLog.mTimeline.size(), MAX_LOGS);
+        assertEquals(MAX_LOGS, mSysuiLog.mTimeline.size());
 
-        // check the first message (msg0) is deleted:
-        assertEquals(mSysuiLog.mTimeline.getFirst().getMessage(), msg + "1");
+        // check the first message (msg0) was replaced with msg1:
+        assertEquals(TEST_MSG + "1", mSysuiLog.mTimeline.getFirst().getMessage());
+    }
+
+    @Test
+    public void testRecycleLogs() {
+        // GIVEN a SysuiLog with one log
+        mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
+        Event e = createEvent(TEST_MSG); // msg
+        mSysuiLog.log(e); // Logs: [msg]
+
+        Event recycledEvent = null;
+        // WHEN we add MAX_LOGS after the first log
+        for (int i = 0; i < MAX_LOGS; i++) {
+            recycledEvent = mSysuiLog.log(createEvent(TEST_MSG + i));
+        }
+        // Logs: [msg1, msg2, msg3, msg4]
+
+        // THEN we see the recycledEvent is e
+        assertEquals(e, recycledEvent);
+    }
+
+    private Event createEvent(String msg) {
+        return new Event().init(msg);
+    }
+
+    public class TestSysuiLog extends SysuiLog<Event> {
+        protected TestSysuiLog(DumpController dumpController, String id, int maxLogs,
+                boolean enabled) {
+            super(dumpController, id, maxLogs, enabled, false);
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 46a8dad..07d2e31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -53,7 +53,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.util.Assert;
 
 import com.google.android.collect.Lists;
@@ -79,7 +78,6 @@
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationGroupManager mGroupManager;
     @Mock private VisualStabilityManager mVisualStabilityManager;
-    @Mock private ShadeController mShadeController;
 
     private TestableLooper mTestableLooper;
     private Handler mHandler;
@@ -99,14 +97,12 @@
                 mLockscreenUserManager);
         mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
-        mDependency.injectTestDependency(ShadeController.class, mShadeController);
 
         mHelper = new NotificationTestHelper(mContext, mDependency);
 
         mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
                 mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
                 mock(StatusBarStateControllerImpl.class), mEntryManager,
-                () -> mShadeController,
                 mock(KeyguardBypassController.class),
                 mock(BubbleController.class),
                 mock(DynamicPrivacyController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
index ffaea15..bbabb11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -76,6 +77,7 @@
     private NotifListBuilderImpl mListBuilder;
     private FakeSystemClock mSystemClock = new FakeSystemClock();
 
+    @Mock private NotifLog mNotifLog;
     @Mock private NotifCollection mNotifCollection;
     @Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener;
     @Spy private OnBeforeSortListener mOnBeforeSortListener;
@@ -97,7 +99,7 @@
         MockitoAnnotations.initMocks(this);
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
 
-        mListBuilder = new NotifListBuilderImpl(mSystemClock);
+        mListBuilder = new NotifListBuilderImpl(mSystemClock, mNotifLog);
         mListBuilder.setOnRenderListListener(mOnRenderListListener);
 
         mListBuilder.attach(mNotifCollection);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
new file mode 100644
index 0000000..a9413c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.ActivityManagerInternal;
+import android.app.Notification;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DeviceProvisionedCoordinatorTest extends SysuiTestCase {
+    private static final int NOTIF_UID = 0;
+
+    private static final String SHOW_WHEN_UNPROVISIONED_FLAG =
+            Notification.EXTRA_ALLOW_DURING_SETUP;
+    private static final String SETUP_NOTIF_PERMISSION =
+            Manifest.permission.NOTIFICATION_DURING_SETUP;
+
+    private MockitoSession mMockitoSession;
+
+    @Mock private ActivityManagerInternal mActivityMangerInternal;
+    @Mock private IPackageManager mIPackageManager;
+    @Mock private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock private NotifListBuilderImpl mNotifListBuilder;
+    private Notification mNotification;
+    private NotificationEntry mEntry;
+    private DeviceProvisionedCoordinator mDeviceProvisionedCoordinator;
+    private NotifFilter mDeviceProvisionedFilter;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mDeviceProvisionedCoordinator = new DeviceProvisionedCoordinator(
+                mDeviceProvisionedController, mIPackageManager);
+
+        mNotification = new Notification();
+        mEntry = new NotificationEntryBuilder()
+                .setNotification(mNotification)
+                .setUid(NOTIF_UID)
+                .build();
+
+        ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
+        mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder);
+        verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+        mDeviceProvisionedFilter = filterCaptor.getValue();
+    }
+
+    @Test
+    public void deviceProvisioned() {
+        // GIVEN device is provisioned
+        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
+
+        // THEN don't filter out the notification
+        assertFalse(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void deviceUnprovisioned() {
+        // GIVEN device is unprovisioned
+        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
+
+        // THEN filter out the notification
+        assertTrue(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void deviceUnprovisionedCanBypass() throws RemoteException {
+        // GIVEN device is unprovisioned
+        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
+
+        // GIVEN notification has a flag to allow the notification during setup
+        Bundle extras = new Bundle();
+        extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
+        mNotification.extras = extras;
+
+        // GIVEN notification has the permission to display during setup
+        when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        // THEN don't filter out the notification
+        assertFalse(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void deviceUnprovisionedTryBypassWithoutPermission() throws RemoteException {
+        // GIVEN device is unprovisioned
+        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
+
+        // GIVEN notification has a flag to allow the notification during setup
+        Bundle extras = new Bundle();
+        extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
+        mNotification.extras = extras;
+
+        // GIVEN notification does NOT have permission to display during setup
+        when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        // THEN filter out the notification
+        assertTrue(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    private RankingBuilder getRankingForUnfilteredNotif() {
+        return new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setSuppressedVisualEffects(0)
+                .setSuspended(false);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
new file mode 100644
index 0000000..ffaa335
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.appops.AppOpsController;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ForegroundCoordinatorTest extends SysuiTestCase {
+    private static final String TEST_PKG = "test_pkg";
+    private static final int NOTIF_USER_ID = 0;
+
+    @Mock private Handler mMainHandler;
+    @Mock private ForegroundServiceController mForegroundServiceController;
+    @Mock private AppOpsController mAppOpsController;
+    @Mock private NotifListBuilderImpl mNotifListBuilder;
+    @Mock private NotifCollection mNotifCollection;
+
+    private NotificationEntry mEntry;
+    private Notification mNotification;
+    private ForegroundCoordinator mForegroundCoordinator;
+    private NotifFilter mForegroundFilter;
+    private NotifLifetimeExtender mForegroundNotifLifetimeExtender;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mForegroundCoordinator = new ForegroundCoordinator(
+                mForegroundServiceController, mAppOpsController, mMainHandler);
+
+        mNotification = new Notification();
+        mEntry = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID))
+                .setNotification(mNotification)
+                .build();
+
+        ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
+        ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor =
+                ArgumentCaptor.forClass(NotifLifetimeExtender.class);
+
+        mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder);
+        verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+        verify(mNotifCollection, times(1)).addNotificationLifetimeExtender(
+                lifetimeExtenderCaptor.capture());
+
+        mForegroundFilter = filterCaptor.getValue();
+        mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue();
+    }
+
+    @Test
+    public void filterTest_disclosureUnnecessary() {
+        StatusBarNotification sbn = mEntry.getSbn();
+
+        // GIVEN the notification is a disclosure notification
+        when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(true);
+
+        // GIVEN the disclosure isn't needed for this user
+        when(mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId()))
+                .thenReturn(false);
+
+        // THEN filter out the notification
+        assertTrue(mForegroundFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void filterTest_systemAlertNotificationUnnecessary() {
+        StatusBarNotification sbn = mEntry.getSbn();
+
+        // GIVEN the notification is a system alert notification + not a disclosure notification
+        when(mForegroundServiceController.isSystemAlertNotification(sbn)).thenReturn(true);
+        when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(false);
+
+        // GIVEN the alert notification isn't needed for this user
+        final Bundle extras = new Bundle();
+        extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS,
+                new String[]{TEST_PKG});
+        mNotification.extras = extras;
+        when(mForegroundServiceController.isSystemAlertWarningNeeded(sbn.getUserId(), TEST_PKG))
+                .thenReturn(false);
+
+        // THEN filter out the notification
+        assertTrue(mForegroundFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void filterTest_doNotFilter() {
+        StatusBarNotification sbn = mEntry.getSbn();
+
+        // GIVEN the notification isn't a system alert notification nor a disclosure notification
+        when(mForegroundServiceController.isSystemAlertNotification(sbn)).thenReturn(false);
+        when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(false);
+
+        // THEN don't filter out the notification
+        assertFalse(mForegroundFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void extendLifetimeText_notForeground() {
+        // GIVEN the notification doesn't represent a foreground service
+        mNotification.flags = 0;
+
+        // THEN don't extend the lifetime
+        assertFalse(mForegroundNotifLifetimeExtender
+                .shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
+    }
+
+    @Test
+    public void extendLifetimeText_foregroundNotifRecentlyPosted() {
+        // GIVEN the notification represents a foreground service that was just posted
+        mNotification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        mEntry = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID))
+                .setSbn(new StatusBarNotification(TEST_PKG, TEST_PKG, NOTIF_USER_ID, "",
+                        NOTIF_USER_ID, NOTIF_USER_ID, mNotification,
+                        new UserHandle(NOTIF_USER_ID), "", System.currentTimeMillis()))
+                .setNotification(mNotification)
+                .build();
+
+        // THEN extend the lifetime
+        assertTrue(mForegroundNotifLifetimeExtender
+                .shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
+    }
+
+    @Test
+    public void extendLifetimeText_foregroundNotifOld() {
+        // GIVEN the notification represents a foreground service that was posted 10 seconds ago
+        mNotification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        mEntry = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID))
+                .setSbn(new StatusBarNotification(TEST_PKG, TEST_PKG, NOTIF_USER_ID, "",
+                        NOTIF_USER_ID, NOTIF_USER_ID, mNotification,
+                        new UserHandle(NOTIF_USER_ID), "",
+                        System.currentTimeMillis() - 10000))
+                .setNotification(mNotification)
+                .build();
+
+        // THEN don't extend the lifetime because the extended time exceeds
+        // ForegroundCoordinator.MIN_FGS_TIME_MS
+        assertFalse(mForegroundNotifLifetimeExtender
+                .shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index 87b3783d..527370e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -24,6 +24,8 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.os.Handler;
@@ -40,12 +42,15 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -61,14 +66,16 @@
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private NotifListBuilderImpl mNotifListBuilder;
 
     private NotificationEntry mEntry;
-    private KeyguardCoordinator mKeyguardNotificationCoordinator;
+    private KeyguardCoordinator mKeyguardCoordinator;
+    private NotifFilter mKeyguardFilter;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mKeyguardNotificationCoordinator = new KeyguardCoordinator(
+        mKeyguardCoordinator = new KeyguardCoordinator(
                 mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
                 mBroadcastDispatcher, mStatusBarStateController,
                 mKeyguardUpdateMonitor);
@@ -76,6 +83,11 @@
         mEntry = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID))
                 .build();
+
+        ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
+        mKeyguardCoordinator.attach(null, mNotifListBuilder);
+        verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+        mKeyguardFilter = filterCaptor.getValue();
     }
 
     @Test
@@ -84,7 +96,7 @@
         setupUnfilteredState();
 
         // THEN don't filter out the entry
-        assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+        assertFalse(mKeyguardFilter.shouldFilterOut(mEntry, 0));
     }
 
     @Test
@@ -94,7 +106,7 @@
         when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(false);
 
         // THEN filter out the entry
-        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+        assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
     }
 
     @Test
@@ -104,7 +116,7 @@
         when(mKeyguardStateController.isShowing()).thenReturn(false);
 
         // THEN don't filter out the entry
-        assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+        assertFalse(mKeyguardFilter.shouldFilterOut(mEntry, 0));
     }
 
     @Test
@@ -116,7 +128,7 @@
         when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false);
 
         // THEN filter out the entry
-        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+        assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
     }
 
     @Test
@@ -128,7 +140,7 @@
         when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(true);
 
         // THEN filter out the entry
-        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+        assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
     }
 
     @Test
@@ -143,7 +155,7 @@
                 .thenReturn(false);
 
         // THEN filter out the entry
-        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+        assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
     }
 
     @Test
@@ -159,7 +171,7 @@
                 .setVisibilityOverride(VISIBILITY_SECRET).build());
 
         // THEN filter out the entry
-        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+        assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
     }
 
     @Test
@@ -174,7 +186,7 @@
                 .build());
 
         // THEN filter out the entry
-        assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+        assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
     }
 
     @Test
@@ -199,7 +211,7 @@
         mEntry.setParent(group);
 
         // THEN don't filter out the entry
-        assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+        assertFalse(mKeyguardFilter.shouldFilterOut(mEntry, 0));
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
new file mode 100644
index 0000000..182e866
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class RankingCoordinatorTest extends SysuiTestCase {
+
+    @Mock private StatusBarStateController mStatusBarStateController;
+    @Mock private NotifListBuilderImpl mNotifListBuilder;
+    private NotificationEntry mEntry;
+    private RankingCoordinator mRankingCoordinator;
+    private NotifFilter mRankingFilter;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mRankingCoordinator = new RankingCoordinator(mStatusBarStateController);
+        mEntry = new NotificationEntryBuilder().build();
+
+        ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
+        mRankingCoordinator.attach(null, mNotifListBuilder);
+        verify(mNotifListBuilder, times(1)).addFilter(filterCaptor.capture());
+        mRankingFilter = filterCaptor.getValue();
+    }
+
+    @Test
+    public void testUnfilteredState() {
+        // GIVEN no suppressed visual effects + app not suspended
+        mEntry.setRanking(getRankingForUnfilteredNotif().build());
+
+        // THEN don't filter out the notification
+        assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void filterSuspended() {
+        // GIVEN the notification's app is suspended
+        mEntry.setRanking(getRankingForUnfilteredNotif()
+                .setSuspended(true)
+                .build());
+
+        // THEN filter out the notification
+        assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void filterDozingSuppressAmbient() {
+        // GIVEN should suppress ambient
+        mEntry.setRanking(getRankingForUnfilteredNotif()
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_AMBIENT)
+                .build());
+
+        // WHEN it's dozing (on ambient display)
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+
+        // THEN filter out the notification
+        assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0));
+
+        // WHEN it's not dozing (showing the notification list)
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+
+        // THEN don't filter out the notification
+        assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    @Test
+    public void filterDozingSuppressNotificationList() {
+        // GIVEN should suppress from the notification list
+        mEntry.setRanking(getRankingForUnfilteredNotif()
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+                .build());
+
+        // WHEN it's dozing (on ambient display)
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+
+        // THEN don't filter out the notification
+        assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0));
+
+        // WHEN it's not dozing (showing the notification list)
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+
+        // THEN filter out the notification
+        assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0));
+    }
+
+    private RankingBuilder getRankingForUnfilteredNotif() {
+        return new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setSuppressedVisualEffects(0)
+                .setSuspended(false);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 4451fa4..5907a0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -76,6 +77,8 @@
     @Mock
     private StatusBar mStatusBar;
     @Mock
+    private ShadeController mShadeController;
+    @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private Handler mHandler;
@@ -98,13 +101,12 @@
         when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true);
         mContext.addMockSystemService(PowerManager.class, mPowerManager);
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
-        mDependency.injectTestDependency(StatusBarWindowController.class,
-                mStatusBarWindowController);
         res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
         mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
-                mKeyguardViewMediator, mScrimController, mStatusBar, mKeyguardStateController,
-                mHandler, mUpdateMonitor, res.getResources(), mKeyguardBypassController,
-                mDozeParameters, mMetricsLogger, mDumpController);
+                mKeyguardViewMediator, mScrimController, mStatusBar, mShadeController,
+                mStatusBarWindowController, mKeyguardStateController, mHandler, mUpdateMonitor,
+                res.getResources(), mKeyguardBypassController, mDozeParameters, mMetricsLogger,
+                mDumpController);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
     }
 
@@ -113,7 +115,8 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FINGERPRINT);
         verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
-        verify(mStatusBarKeyguardViewManager).animateCollapsePanels(anyFloat());
+        verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
+                anyFloat());
     }
 
     @Test
@@ -136,7 +139,8 @@
                 BiometricSourceType.FINGERPRINT);
 
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
-        verify(mStatusBarKeyguardViewManager).animateCollapsePanels(anyFloat());
+        verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
+                anyFloat());
     }
 
     @Test
@@ -155,7 +159,8 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FACE);
 
-        verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+        verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
+                anyBoolean(), anyFloat());
         verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
     }
 
@@ -168,7 +173,8 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FACE);
 
-        verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+        verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
+                anyBoolean(), anyFloat());
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
     }
 
@@ -201,7 +207,8 @@
                 BiometricSourceType.FACE);
 
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
-        verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+        verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
+                anyBoolean(), anyFloat());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_NONE);
     }
@@ -253,7 +260,8 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FACE);
 
-        verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+        verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
+                anyBoolean(), anyFloat());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 0df2ebc8..39afbe0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -254,6 +254,7 @@
                 mDivider,
                 Optional.of(mRecents),
                 () -> mock(StatusBar.class),
+                mock(ShadeController.class),
                 mHandler);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index d7c00cf..532192b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -111,6 +111,8 @@
     private Handler mHandler;
     @Mock
     private BubbleController mBubbleController;
+    @Mock
+    private ShadeControllerImpl mShadeController;
 
     @Mock
     private ActivityIntentHelper mActivityIntentHelper;
@@ -177,7 +179,7 @@
                 mKeyguardStateController,
                 mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
                 mock(LockPatternUtils.class), mHandler, mHandler, mActivityIntentHelper,
-                mBubbleController, mSuperStatusBarViewFactory))
+                mBubbleController, mShadeController, mSuperStatusBarViewFactory))
                 .setStatusBar(mStatusBar)
                 .setNotificationPresenter(mock(NotificationPresenter.class))
                 .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
@@ -194,7 +196,7 @@
 
         // set up addPostCollapseAction to synchronously invoke the Runnable arg
         doAnswer(answerVoid(Runnable::run))
-                .when(mStatusBar).addPostCollapseAction(any(Runnable.class));
+                .when(mShadeController).addPostCollapseAction(any(Runnable.class));
 
         // set up Handler to synchronously invoke the Runnable arg
         doAnswer(answerVoid(Runnable::run))
@@ -219,7 +221,7 @@
         mNotificationActivityStarter.onNotificationClicked(sbn, mNotificationRow);
 
         // Then
-        verify(mStatusBar, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapsePanel();
 
         verify(mContentIntent).sendAndReturnResult(
                 any(Context.class),
@@ -254,7 +256,7 @@
         verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
 
         // This is called regardless, and simply short circuits when there is nothing to do.
-        verify(mStatusBar, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapsePanel();
 
         verify(mAssistManager).hideAssist();
 
@@ -284,7 +286,7 @@
         // Then
         verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
 
-        verify(mStatusBar, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapsePanel();
 
         verify(mAssistManager).hideAssist();
 
@@ -314,7 +316,7 @@
         // Then
         verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
 
-        verify(mStatusBar, atLeastOnce()).collapsePanel();
+        verify(mShadeController, atLeastOnce()).collapsePanel();
 
         verify(mAssistManager).hideAssist();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index fb6e168..575f145 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -115,7 +115,7 @@
                 mock(NotificationAlertingManager.class),
                 mock(NotificationRowBinderImpl.class), mock(KeyguardStateController.class),
                 mock(KeyguardIndicationController.class),
-                mStatusBar, mCommandQueue);
+                mStatusBar, mock(ShadeControllerImpl.class), mCommandQueue);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 6dfd082..cd2c349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -73,7 +73,7 @@
         mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
                 mock(NotificationGroupManager.class), mNotificationLockscreenUserManager,
                 mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
-                mActivityStarter, () -> mShadeController, new CommandQueue(mContext)));
+                mActivityStarter, mShadeController, new CommandQueue(mContext)));
         mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index be68097..d3fce56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -63,6 +63,7 @@
 import android.util.SparseArray;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
 import android.widget.LinearLayout;
 
 import androidx.test.filters.SmallTest;
@@ -243,6 +244,7 @@
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
     @Mock private StatusBarNotificationActivityStarter.Builder
             mStatusBarNotificationActivityStarterBuilder;
+    private ShadeController mShadeController;
 
     @Before
     public void setup() throws Exception {
@@ -310,6 +312,11 @@
         when(mStatusBarComponent.getStatusBarWindowViewController()).thenReturn(
                 mStatusBarWindowViewController);
 
+        mShadeController = new ShadeControllerImpl(mCommandQueue,
+                mStatusBarStateController, mStatusBarWindowController,
+                mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
+                () -> mStatusBar, () -> mAssistManager, () -> mBubbleController);
+
         mStatusBar = new StatusBar(
                 mContext,
                 mFeatureFlags,
@@ -382,6 +389,7 @@
                 Optional.of(mDivider),
                 mLightsOutNotifController,
                 mStatusBarNotificationActivityStarterBuilder,
+                mShadeController,
                 mSuperStatusBarViewFactory,
                 mStatusBarKeyguardViewManager,
                 mViewMediatorCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
index 00ea187..9f899ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
@@ -102,7 +102,7 @@
                 mDozeLog,
                 mDozeParameters,
                 new CommandQueue(mContext),
-                () -> mShadeController,
+                mShadeController,
                 mDockManager,
                 mView);
         mController.setupExpandedStatusBar();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 426aba0..260ff2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -69,8 +69,11 @@
         }
 
         for (Pair<Executor, OnPropertiesChangedListener> listener : mListeners) {
-            listener.first.execute(() -> listener.second.onPropertiesChanged(
-                    new Properties(namespace, mProperties.get(namespace))));
+            Properties.Builder propBuilder = new Properties.Builder(namespace);
+            for (String key : mProperties.get(namespace).keySet()) {
+                propBuilder.setString(key, mProperties.get(namespace).get(key));
+            }
+            listener.first.execute(() -> listener.second.onPropertiesChanged(propBuilder.build()));
         }
         return true;
     }
@@ -88,10 +91,12 @@
 
     private Properties propsForNamespaceAndName(String namespace, String name) {
         if (mProperties.containsKey(namespace) && mProperties.get(namespace).containsKey(name)) {
-            return new Properties(namespace, mProperties.get(namespace));
+            return new Properties.Builder(namespace)
+                    .setString(name, mProperties.get(namespace).get(name)).build();
         }
         if (mDefaultProperties.containsKey(namespace)) {
-            return new Properties(namespace, mDefaultProperties.get(namespace));
+            return new Properties.Builder(namespace)
+                    .setString(name, mDefaultProperties.get(namespace).get(name)).build();
         }
 
         return null;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wm/DisplayLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/wm/DisplayLayoutTest.java
new file mode 100644
index 0000000..9596a73
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wm/DisplayLayoutTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+
+@SmallTest
+public class DisplayLayoutTest extends SysuiTestCase {
+
+    @Test
+    public void testInsets() {
+        Resources res = createResources(40, 50, false, 30, 40);
+        // Test empty display, no bars or anything
+        DisplayInfo info = createDisplayInfo(1000, 1500, 0, ROTATION_0);
+        DisplayLayout dl = new DisplayLayout(info, res, false, false);
+        assertEquals(new Rect(0, 0, 0, 0), dl.stableInsets());
+        assertEquals(new Rect(0, 0, 0, 0), dl.nonDecorInsets());
+
+        // Test with bars
+        dl = new DisplayLayout(info, res, true, true);
+        assertEquals(new Rect(0, 40, 0, 50), dl.stableInsets());
+        assertEquals(new Rect(0, 0, 0, 50), dl.nonDecorInsets());
+
+        // Test just cutout
+        info = createDisplayInfo(1000, 1500, 60, ROTATION_0);
+        dl = new DisplayLayout(info, res, false, false);
+        assertEquals(new Rect(0, 60, 0, 0), dl.stableInsets());
+        assertEquals(new Rect(0, 60, 0, 0), dl.nonDecorInsets());
+
+        // Test with bars and cutout
+        dl = new DisplayLayout(info, res, true, true);
+        assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets());
+        assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets());
+    }
+
+    @Test
+    public void testRotate() {
+        // Basic rotate utility
+        Rect testParent = new Rect(0, 0, 1000, 600);
+        Rect testInner = new Rect(40, 20, 120, 80);
+        Rect testResult = new Rect(testInner);
+        DisplayLayout.rotateBounds(testResult, testParent, 1);
+        assertEquals(new Rect(20, 880, 80, 960), testResult);
+        testResult.set(testInner);
+        DisplayLayout.rotateBounds(testResult, testParent, 2);
+        assertEquals(new Rect(880, 20, 960, 80), testResult);
+        testResult.set(testInner);
+        DisplayLayout.rotateBounds(testResult, testParent, 3);
+        assertEquals(new Rect(520, 40, 580, 120), testResult);
+
+        Resources res = createResources(40, 50, false, 30, 40);
+        DisplayInfo info = createDisplayInfo(1000, 1500, 60, ROTATION_0);
+        DisplayLayout dl = new DisplayLayout(info, res, true, true);
+        assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets());
+        assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets());
+
+        // Rotate to 90
+        dl.rotateTo(res, ROTATION_90);
+        assertEquals(new Rect(60, 30, 0, 40), dl.stableInsets());
+        assertEquals(new Rect(60, 0, 0, 40), dl.nonDecorInsets());
+
+        // Rotate with moving navbar
+        res = createResources(40, 50, true, 30, 40);
+        dl = new DisplayLayout(info, res, true, true);
+        dl.rotateTo(res, ROTATION_270);
+        assertEquals(new Rect(40, 30, 60, 0), dl.stableInsets());
+        assertEquals(new Rect(40, 0, 60, 0), dl.nonDecorInsets());
+    }
+
+    private Resources createResources(
+            int navLand, int navPort, boolean navMoves, int statusLand, int statusPort) {
+        Configuration cfg = new Configuration();
+        cfg.uiMode = UI_MODE_TYPE_NORMAL;
+        Resources res = mock(Resources.class);
+        doReturn(navLand).when(res).getDimensionPixelSize(
+                R.dimen.navigation_bar_height_landscape_car_mode);
+        doReturn(navPort).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height_car_mode);
+        doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode);
+        doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height_landscape);
+        doReturn(navPort).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height);
+        doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_width);
+        doReturn(navMoves).when(res).getBoolean(R.bool.config_navBarCanMove);
+        doReturn(statusLand).when(res).getDimensionPixelSize(R.dimen.status_bar_height_landscape);
+        doReturn(statusPort).when(res).getDimensionPixelSize(R.dimen.status_bar_height_portrait);
+        doReturn(cfg).when(res).getConfiguration();
+        return res;
+    }
+
+    private DisplayInfo createDisplayInfo(int width, int height, int cutoutHeight, int rotation) {
+        DisplayInfo info = new DisplayInfo();
+        info.logicalWidth = width;
+        info.logicalHeight = height;
+        info.rotation = rotation;
+        if (cutoutHeight > 0) {
+            info.displayCutout = new DisplayCutout(
+                    Insets.of(0, cutoutHeight, 0, 0) /* safeInsets */, null /* boundLeft */,
+                    new Rect(width / 2 - cutoutHeight, 0, width / 2 + cutoutHeight,
+                            cutoutHeight) /* boundTop */, null /* boundRight */,
+                    null /* boundBottom */);
+        } else {
+            info.displayCutout = DisplayCutout.NO_CUTOUT;
+        }
+        info.logicalDensityDpi = 300;
+        return info;
+    }
+}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index eb0d443..7fb286b 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -17,6 +17,7 @@
 
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -175,6 +176,10 @@
      */
     @Deprecated
     public int tether(@NonNull String iface) {
+        if (mConnector == null) {
+            Slog.wtf(TAG, "Tethering not ready yet");
+            return TETHER_ERROR_SERVICE_UNAVAIL;
+        }
         try {
             mConnector.tether(iface);
         } catch (RemoteException e) {
@@ -191,6 +196,10 @@
      */
     @Deprecated
     public int untether(@NonNull String iface) {
+        if (mConnector == null) {
+            Slog.wtf(TAG, "Tethering not ready yet");
+            return TETHER_ERROR_SERVICE_UNAVAIL;
+        }
         try {
             mConnector.untether(iface);
         } catch (RemoteException e) {
@@ -210,6 +219,10 @@
      */
     @Deprecated
     public int setUsbTethering(boolean enable) {
+        if (mConnector == null) {
+            Slog.wtf(TAG, "Tethering not ready yet");
+            return TETHER_ERROR_SERVICE_UNAVAIL;
+        }
         try {
             mConnector.setUsbTethering(enable);
         } catch (RemoteException e) {
@@ -227,6 +240,10 @@
     // TODO: improve the usage of ResultReceiver, b/145096122
     public void startTethering(int type, @NonNull ResultReceiver receiver,
             boolean showProvisioningUi) {
+        if (mConnector == null) {
+            Slog.wtf(TAG, "Tethering not ready yet");
+            return;
+        }
         try {
             mConnector.startTethering(type, receiver, showProvisioningUi);
         } catch (RemoteException e) {
@@ -241,6 +258,10 @@
      * {@hide}
      */
     public void stopTethering(int type) {
+        if (mConnector == null) {
+            Slog.wtf(TAG, "Tethering not ready yet");
+            return;
+        }
         try {
             mConnector.stopTethering(type);
         } catch (RemoteException e) {
@@ -258,6 +279,10 @@
     // TODO: improve the usage of ResultReceiver, b/145096122
     public void requestLatestTetheringEntitlementResult(int type, @NonNull ResultReceiver receiver,
             boolean showEntitlementUi) {
+        if (mConnector == null) {
+            Slog.wtf(TAG, "Tethering not ready yet");
+            return;
+        }
         try {
             mConnector.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
         } catch (RemoteException e) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 7e8edf2..26bb7c3 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -465,7 +465,7 @@
 
         if (mCreateFillUiRunnable != null) {
             if (sDebug) Slog.d(TAG, "start the pending fill UI request..");
-            mCreateFillUiRunnable.run();
+            mHandler.post(mCreateFillUiRunnable);
             mCreateFillUiRunnable = null;
         }
     }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index e8c5299..3651a41 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -274,9 +274,14 @@
         }
     }
 
-    // This method should not perform any I/O (e.g. do not call isBackupActivatedForUser),
-    // it's used in multiple places where I/O waits would cause system lock-ups.
-    private boolean isUserReadyForBackup(int userId) {
+    /**
+     * This method should not perform any I/O (e.g. do not call isBackupActivatedForUser),
+     * it's used in multiple places where I/O waits would cause system lock-ups.
+     * @param userId User id for which this operation should be performed.
+     * @return true if the user is ready for backup and false otherwise.
+     */
+    @Override
+    public boolean isUserReadyForBackup(int userId) {
         return mUserServices.get(UserHandle.USER_SYSTEM) != null
                 && mUserServices.get(userId) != null;
     }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index f8f685d..b719435 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5573,7 +5573,7 @@
      * @param linkProperties the initial link properties of this network. They can be updated
      *         later : see {@link #updateLinkProperties}.
      * @param networkCapabilities the initial capabilites of this network. They can be updated
-     *         later : see {@link #updateNetworkCapabilities}.
+     *         later : see {@link #updateCapabilities}.
      * @param currentScore the initial score of the network. See
      *         {@link NetworkAgentInfo#getCurrentScore}.
      * @param networkMisc metadata about the network. This is never updated.
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index cbf2a62..bbcfdc6 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -56,6 +56,7 @@
 import com.android.server.location.GnssMeasurementsProvider;
 import com.android.server.location.GnssNavigationMessageProvider;
 import com.android.server.location.GnssStatusListenerHelper;
+import com.android.server.location.LocationUsageLogger;
 import com.android.server.location.RemoteListenerHelper;
 
 import java.io.FileDescriptor;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 6300ab8..cad917b 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -104,6 +104,7 @@
 import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
 import com.android.server.location.LocationRequestStatistics.PackageStatistics;
 import com.android.server.location.LocationSettingsStore;
+import com.android.server.location.LocationUsageLogger;
 import com.android.server.location.MockProvider;
 import com.android.server.location.PassiveProvider;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -158,21 +159,15 @@
         }
     }
 
-    private static final String TAG = "LocationManagerService";
+    public static final String TAG = "LocationManagerService";
     public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String WAKELOCK_KEY = "*location*";
 
-    // Location resolution level: no location data whatsoever
     private static final int RESOLUTION_LEVEL_NONE = 0;
-    // Location resolution level: coarse location data only
     private static final int RESOLUTION_LEVEL_COARSE = 1;
-    // Location resolution level: fine location data
     private static final int RESOLUTION_LEVEL_FINE = 2;
 
-    private static final String ACCESS_LOCATION_EXTRA_COMMANDS =
-            android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS;
-
     private static final String NETWORK_LOCATION_SERVICE_ACTION =
             "com.android.location.service.v3.NetworkLocationProvider";
     private static final String FUSED_LOCATION_SERVICE_ACTION =
@@ -198,6 +193,8 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private final Handler mHandler;
+    private final LocationSettingsStore mSettingsStore;
+    private final LocationUsageLogger mLocationUsageLogger;
 
     private AppOpsManager mAppOps;
     private PackageManager mPackageManager;
@@ -205,8 +202,6 @@
     private ActivityManager mActivityManager;
     private UserManager mUserManager;
 
-    private LocationSettingsStore mSettingsStore;
-
     private GeofenceManager mGeofenceManager;
     private LocationFudger mLocationFudger;
     private GeocoderProxy mGeocodeProvider;
@@ -219,12 +214,12 @@
 
     // list of currently active providers
     @GuardedBy("mLock")
-    private final ArrayList<LocationProvider> mProviders = new ArrayList<>();
+    private final ArrayList<LocationProviderManager> mProviders = new ArrayList<>();
 
     // list of non-mock providers, so that when mock providers replace real providers, they can be
     // later re-replaced
     @GuardedBy("mLock")
-    private final ArrayList<LocationProvider> mRealProviders = new ArrayList<>();
+    private final ArrayList<LocationProviderManager> mRealProviders = new ArrayList<>();
 
     @GuardedBy("mLock")
     private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
@@ -251,12 +246,10 @@
     @PowerManager.LocationPowerSaveMode
     private int mBatterySaverMode;
 
-    @GuardedBy("mLock")
-    private final LocationUsageLogger mLocationUsageLogger;
-
     private LocationManagerService(Context context) {
         mContext = context;
         mHandler = FgThread.getHandler();
+        mSettingsStore = new LocationSettingsStore(mContext, mHandler);
         mLocationUsageLogger = new LocationUsageLogger();
 
         // Let the package manager query which are the default location
@@ -274,6 +267,8 @@
     }
 
     private void onSystemReady() {
+        mSettingsStore.onSystemReady();
+
         synchronized (mLock) {
             mPackageManager = mContext.getPackageManager();
             mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -281,7 +276,6 @@
             mActivityManager = mContext.getSystemService(ActivityManager.class);
             mUserManager = mContext.getSystemService(UserManager.class);
 
-            mSettingsStore = new LocationSettingsStore(mContext, mHandler);
             mLocationFudger = new LocationFudger(mContext, mHandler);
             mGeofenceManager = new GeofenceManager(mContext, mSettingsStore);
 
@@ -421,14 +415,14 @@
         for (Receiver receiver : mReceivers.values()) {
             receiver.updateMonitoring(true);
         }
-        for (LocationProvider p : mProviders) {
+        for (LocationProviderManager p : mProviders) {
             applyRequirementsLocked(p);
         }
     }
 
     @GuardedBy("mLock")
     private void onPermissionsChangedLocked() {
-        for (LocationProvider p : mProviders) {
+        for (LocationProviderManager p : mProviders) {
             applyRequirementsLocked(p);
         }
     }
@@ -448,7 +442,7 @@
 
         mBatterySaverMode = newLocationMode;
 
-        for (LocationProvider p : mProviders) {
+        for (LocationProviderManager p : mProviders) {
             applyRequirementsLocked(p);
         }
     }
@@ -456,7 +450,7 @@
     @GuardedBy("mLock")
     private void onScreenStateChangedLocked() {
         if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
-            for (LocationProvider p : mProviders) {
+            for (LocationProviderManager p : mProviders) {
                 applyRequirementsLocked(p);
             }
         }
@@ -472,7 +466,7 @@
         intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId));
         mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
 
-        for (LocationProvider p : mProviders) {
+        for (LocationProviderManager p : mProviders) {
             p.onUseableChangedLocked(userId);
         }
     }
@@ -527,21 +521,21 @@
 
     @GuardedBy("mLock")
     private void onBackgroundThrottleIntervalChangedLocked() {
-        for (LocationProvider provider : mProviders) {
+        for (LocationProviderManager provider : mProviders) {
             applyRequirementsLocked(provider);
         }
     }
 
     @GuardedBy("mLock")
     private void onBackgroundThrottleWhitelistChangedLocked() {
-        for (LocationProvider p : mProviders) {
+        for (LocationProviderManager p : mProviders) {
             applyRequirementsLocked(p);
         }
     }
 
     @GuardedBy("lock")
     private void onIgnoreSettingsWhitelistChangedLocked() {
-        for (LocationProvider p : mProviders) {
+        for (LocationProviderManager p : mProviders) {
             applyRequirementsLocked(p);
         }
     }
@@ -630,14 +624,15 @@
     @GuardedBy("mLock")
     private void initializeProvidersLocked() {
         // create a passive location provider, which is always enabled
-        LocationProvider passiveProviderManager = new LocationProvider(PASSIVE_PROVIDER);
+        LocationProviderManager passiveProviderManager = new LocationProviderManager(
+                PASSIVE_PROVIDER);
         addProviderLocked(passiveProviderManager);
         mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager);
         passiveProviderManager.attachLocked(mPassiveProvider);
 
         if (GnssManagerService.isGnssSupported()) {
             // Create a gps location provider manager
-            LocationProvider gnssProviderManager = new LocationProvider(GPS_PROVIDER);
+            LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER);
             mRealProviders.add(gnssProviderManager);
             addProviderLocked(gnssProviderManager);
 
@@ -668,7 +663,8 @@
         ensureFallbackFusedProviderPresentLocked(pkgs);
 
         // bind to network provider
-        LocationProvider networkProviderManager = new LocationProvider(NETWORK_PROVIDER);
+        LocationProviderManager networkProviderManager = new LocationProviderManager(
+                NETWORK_PROVIDER);
         LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
                 mContext,
                 networkProviderManager,
@@ -685,7 +681,7 @@
         }
 
         // bind to fused provider
-        LocationProvider fusedProviderManager = new LocationProvider(FUSED_PROVIDER);
+        LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER);
         LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
                 mContext,
                 fusedProviderManager,
@@ -758,7 +754,7 @@
                     Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
                     Integer.parseInt(fragments[8]) /* powerRequirement */,
                     Integer.parseInt(fragments[9]) /* accuracy */);
-            LocationProvider testProviderManager = new LocationProvider(name);
+            LocationProviderManager testProviderManager = new LocationProviderManager(name);
             addProviderLocked(testProviderManager);
             testProviderManager.attachLocked(
                     new MockProvider(mContext, testProviderManager, properties));
@@ -780,7 +776,7 @@
         onUserProfilesChangedLocked();
 
         // let providers know the current user has changed
-        for (LocationProvider p : mProviders) {
+        for (LocationProviderManager p : mProviders) {
             p.onUseableChangedLocked(oldUserId);
             p.onUseableChangedLocked(mCurrentUserId);
         }
@@ -789,7 +785,7 @@
     /**
      * Location provider manager, manages a LocationProvider.
      */
-    class LocationProvider implements AbstractLocationProvider.LocationProviderManager {
+    class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager {
 
         private final String mName;
 
@@ -807,7 +803,7 @@
         @Nullable
         private ProviderProperties mProperties;
 
-        private LocationProvider(String name) {
+        private LocationProviderManager(String name) {
             mName = name;
 
             mProvider = null;
@@ -944,7 +940,7 @@
                 return;
             }
             synchronized (mLock) {
-                LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
+                LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
                 if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
                     Slog.w(TAG, "reportLocationBatch() called without user permission");
                     return;
@@ -1035,7 +1031,7 @@
         }
     }
 
-    private class MockLocationProvider extends LocationProvider {
+    private class MockLocationProvider extends LocationProviderManager {
 
         private ProviderRequest mCurrentRequest;
 
@@ -1185,7 +1181,8 @@
                 // See if receiver has any enabled update records.  Also note if any update records
                 // are high power (has a high power provider with an interval under a threshold).
                 for (UpdateRecord updateRecord : mUpdateRecords.values()) {
-                    LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
+                    LocationProviderManager provider = getLocationProviderLocked(
+                            updateRecord.mProvider);
                     if (provider == null) {
                         continue;
                     }
@@ -1461,7 +1458,7 @@
     }
 
     @GuardedBy("mLock")
-    private void addProviderLocked(LocationProvider provider) {
+    private void addProviderLocked(LocationProviderManager provider) {
         Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
 
         mProviders.add(provider);
@@ -1472,7 +1469,7 @@
     }
 
     @GuardedBy("mLock")
-    private void removeProviderLocked(LocationProvider provider) {
+    private void removeProviderLocked(LocationProviderManager provider) {
         if (mProviders.remove(provider)) {
             // it would be more correct to call this for all users, but we know this can only
             // affect the current user since providers are disabled for non-current users
@@ -1482,8 +1479,8 @@
 
     @GuardedBy("mLock")
     @Nullable
-    private LocationProvider getLocationProviderLocked(String providerName) {
-        for (LocationProvider provider : mProviders) {
+    private LocationProviderManager getLocationProviderLocked(String providerName) {
+        for (LocationProviderManager provider : mProviders) {
             if (providerName.equals(provider.getName())) {
                 return provider;
             }
@@ -1534,7 +1531,7 @@
             // network and fused providers are ok with COARSE or FINE
             return RESOLUTION_LEVEL_COARSE;
         } else {
-            for (LocationProvider lp : mProviders) {
+            for (LocationProviderManager lp : mProviders) {
                 if (!lp.getName().equals(provider)) {
                     continue;
                 }
@@ -1634,7 +1631,7 @@
     public List<String> getAllProviders() {
         synchronized (mLock) {
             ArrayList<String> providers = new ArrayList<>(mProviders.size());
-            for (LocationProvider provider : mProviders) {
+            for (LocationProviderManager provider : mProviders) {
                 String name = provider.getName();
                 if (FUSED_PROVIDER.equals(name)) {
                     continue;
@@ -1655,7 +1652,7 @@
         int allowedResolutionLevel = getCallerAllowedResolutionLevel();
         synchronized (mLock) {
             ArrayList<String> providers = new ArrayList<>(mProviders.size());
-            for (LocationProvider provider : mProviders) {
+            for (LocationProviderManager provider : mProviders) {
                 String name = provider.getName();
                 if (FUSED_PROVIDER.equals(name)) {
                     continue;
@@ -1705,7 +1702,7 @@
     }
 
     @GuardedBy("mLock")
-    private void updateProviderUseableLocked(LocationProvider provider) {
+    private void updateProviderUseableLocked(LocationProviderManager provider) {
         boolean useable = provider.isUseableLocked();
 
         ArrayList<Receiver> deadReceivers = null;
@@ -1744,14 +1741,14 @@
 
     @GuardedBy("mLock")
     private void applyRequirementsLocked(String providerName) {
-        LocationProvider provider = getLocationProviderLocked(providerName);
+        LocationProviderManager provider = getLocationProviderLocked(providerName);
         if (provider != null) {
             applyRequirementsLocked(provider);
         }
     }
 
     @GuardedBy("mLock")
-    private void applyRequirementsLocked(LocationProvider provider) {
+    private void applyRequirementsLocked(LocationProviderManager provider) {
         ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
         WorkSource worksource = new WorkSource();
         ProviderRequest providerRequest = new ProviderRequest();
@@ -2201,7 +2198,7 @@
             throw new IllegalArgumentException("provider name must not be null");
         }
 
-        LocationProvider provider = getLocationProviderLocked(name);
+        LocationProviderManager provider = getLocationProviderLocked(name);
         if (provider == null) {
             throw new IllegalArgumentException("provider doesn't exist: " + name);
         }
@@ -2323,7 +2320,7 @@
                 // or use the fused provider
                 String name = request.getProvider();
                 if (name == null) name = LocationManager.FUSED_PROVIDER;
-                LocationProvider provider = getLocationProviderLocked(name);
+                LocationProviderManager provider = getLocationProviderLocked(name);
                 if (provider == null) return null;
 
                 // only the current user or location providers may get location this way
@@ -2453,7 +2450,7 @@
                 "Access Fine Location permission not granted to inject Location");
 
         synchronized (mLock) {
-            LocationProvider provider = getLocationProviderLocked(location.getProvider());
+            LocationProviderManager provider = getLocationProviderLocked(location.getProvider());
             if (provider == null || !provider.isUseableLocked()) {
                 return false;
             }
@@ -2624,6 +2621,10 @@
             // throw NullPointerException to remain compatible with previous implementation
             throw new NullPointerException();
         }
+
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, null);
+
         synchronized (mLock) {
             checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
                     providerName);
@@ -2633,13 +2634,7 @@
                     LocationStatsEnums.API_SEND_EXTRA_COMMAND,
                     providerName);
 
-            // and check for ACCESS_LOCATION_EXTRA_COMMANDS
-            if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
-                    != PERMISSION_GRANTED)) {
-                throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
-            }
-
-            LocationProvider provider = getLocationProviderLocked(providerName);
+            LocationProviderManager provider = getLocationProviderLocked(providerName);
             if (provider != null) {
                 provider.sendExtraCommand(command, extras);
             }
@@ -2662,7 +2657,7 @@
     @Override
     public ProviderProperties getProviderProperties(String providerName) {
         synchronized (mLock) {
-            LocationProvider provider = getLocationProviderLocked(providerName);
+            LocationProviderManager provider = getLocationProviderLocked(providerName);
             if (provider == null) {
                 return null;
             }
@@ -2675,7 +2670,7 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
                 Manifest.permission.READ_DEVICE_CONFIG + " permission required");
         synchronized (mLock) {
-            for (LocationProvider provider : mProviders) {
+            for (LocationProviderManager provider : mProviders) {
                 if (provider.getPackagesLocked().contains(packageName)) {
                     return true;
                 }
@@ -2689,7 +2684,7 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
                 Manifest.permission.READ_DEVICE_CONFIG + " permission required");
         synchronized (mLock) {
-            LocationProvider provider = getLocationProviderLocked(providerName);
+            LocationProviderManager provider = getLocationProviderLocked(providerName);
             return provider == null ? Collections.emptyList() : provider.getPackagesLocked();
         }
     }
@@ -2758,7 +2753,7 @@
         if (FUSED_PROVIDER.equals(providerName)) return false;
 
         synchronized (mLock) {
-            LocationProvider provider = getLocationProviderLocked(providerName);
+            LocationProviderManager provider = getLocationProviderLocked(providerName);
             return provider != null && provider.isUseableLocked(userId);
         }
     }
@@ -2797,7 +2792,7 @@
     }
 
     @GuardedBy("mLock")
-    private void handleLocationChangedLocked(Location location, LocationProvider provider) {
+    private void handleLocationChangedLocked(Location location, LocationProviderManager provider) {
         if (!mProviders.contains(provider)) {
             return;
         }
@@ -3029,7 +3024,7 @@
         synchronized (mLock) {
             long identity = Binder.clearCallingIdentity();
             try {
-                LocationProvider oldProvider = getLocationProviderLocked(name);
+                LocationProviderManager oldProvider = getLocationProviderLocked(name);
                 if (oldProvider != null) {
                     removeProviderLocked(oldProvider);
                 }
@@ -3053,7 +3048,7 @@
         synchronized (mLock) {
             long identity = Binder.clearCallingIdentity();
             try {
-                LocationProvider testProvider = getLocationProviderLocked(name);
+                LocationProviderManager testProvider = getLocationProviderLocked(name);
                 if (testProvider == null || !testProvider.isMock()) {
                     return;
                 }
@@ -3061,8 +3056,8 @@
                 removeProviderLocked(testProvider);
 
                 // reinstate real provider if available
-                LocationProvider realProvider = null;
-                for (LocationProvider provider : mRealProviders) {
+                LocationProviderManager realProvider = null;
+                for (LocationProviderManager provider : mRealProviders) {
                     if (name.equals(provider.getName())) {
                         realProvider = provider;
                         break;
@@ -3086,7 +3081,7 @@
         }
 
         synchronized (mLock) {
-            LocationProvider testProvider = getLocationProviderLocked(providerName);
+            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
             if (testProvider == null || !testProvider.isMock()) {
                 throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
             }
@@ -3110,7 +3105,7 @@
         }
 
         synchronized (mLock) {
-            LocationProvider testProvider = getLocationProviderLocked(providerName);
+            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
             if (testProvider == null || !testProvider.isMock()) {
                 throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
             }
@@ -3128,7 +3123,7 @@
         }
 
         synchronized (mLock) {
-            LocationProvider testProvider = getLocationProviderLocked(providerName);
+            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
             if (testProvider == null || !testProvider.isMock()) {
                 throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
             }
@@ -3237,7 +3232,7 @@
 
             ipw.println("Location Providers:");
             ipw.increaseIndent();
-            for (LocationProvider provider : mProviders) {
+            for (LocationProviderManager provider : mProviders) {
                 provider.dumpLocked(fd, ipw, args);
             }
             ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index c0f10a3..92d1da5 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+
 import android.Manifest;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -37,6 +39,7 @@
 import android.os.UserHandle;
 import android.service.carrier.CarrierMessagingService;
 import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Slog;
@@ -523,11 +526,11 @@
 
                 // Grant permission for the carrier app.
                 Intent intent = new Intent(action);
-                TelephonyManager telephonyManager =
-                        (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-                List<String> carrierPackages =
-                        telephonyManager.getCarrierPackageNamesForIntentAndPhone(
-                                intent, SubscriptionManager.getPhoneId(subId));
+                TelephonyManager telephonyManager = (TelephonyManager)
+                        mContext.getSystemService(Context.TELEPHONY_SERVICE);
+                List<String> carrierPackages = telephonyManager
+                        .getCarrierPackageNamesForIntentAndPhone(
+                                intent, getPhoneIdFromSubId(subId));
                 if (carrierPackages != null && carrierPackages.size() == 1) {
                     LocalServices.getService(UriGrantsManagerInternal.class)
                             .grantUriPermissionFromIntent(callingUid, carrierPackages.get(0),
@@ -539,4 +542,13 @@
             return contentUri;
         }
     }
+
+    private int getPhoneIdFromSubId(int subId) {
+        SubscriptionManager subManager = (SubscriptionManager)
+                mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        if (subManager == null) return INVALID_SIM_SLOT_INDEX;
+        SubscriptionInfo info = subManager.getActiveSubscriptionInfo(subId);
+        if (info == null) return INVALID_SIM_SLOT_INDEX;
+        return info.getSimSlotIndex();
+    }
 }
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 521b393..deff440 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -29,6 +29,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -82,6 +83,12 @@
     static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED =
             "watchdog_explicit_health_check_enabled";
 
+    // TODO: make the following values configurable via DeviceConfig
+    private static final long NATIVE_CRASH_POLLING_INTERVAL_MILLIS =
+            TimeUnit.SECONDS.toMillis(30);
+    private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
+
+
     public static final int FAILURE_REASON_UNKNOWN = 0;
     public static final int FAILURE_REASON_NATIVE_CRASH = 1;
     public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
@@ -110,6 +117,8 @@
     // Whether explicit health checks are enabled or not
     private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
 
+    private long mNumberOfNativeCrashPollsRemaining;
+
     private static final int DB_VERSION = 1;
     private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
     private static final String TAG_PACKAGE = "package";
@@ -188,6 +197,7 @@
         mHealthCheckController = controller;
         mConnectivityModuleConnector = connectivityModuleConnector;
         mSystemClock = clock;
+        mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
         loadFromFile();
     }
 
@@ -336,38 +346,70 @@
                 if (mAllObservers.isEmpty()) {
                     return;
                 }
+                boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
+                        || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
+                if (requiresImmediateAction) {
+                    handleFailureImmediately(packages, failureReason);
+                } else {
+                    for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
+                        VersionedPackage versionedPackage = packages.get(pIndex);
+                        // Observer that will receive failure for versionedPackage
+                        PackageHealthObserver currentObserverToNotify = null;
+                        int currentObserverImpact = Integer.MAX_VALUE;
 
-                for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
-                    VersionedPackage versionedPackage = packages.get(pIndex);
-                    // Observer that will receive failure for versionedPackage
-                    PackageHealthObserver currentObserverToNotify = null;
-                    int currentObserverImpact = Integer.MAX_VALUE;
-
-                    // Find observer with least user impact
-                    for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
-                        ObserverInternal observer = mAllObservers.valueAt(oIndex);
-                        PackageHealthObserver registeredObserver = observer.registeredObserver;
-                        if (registeredObserver != null
-                                && observer.onPackageFailureLocked(
-                                        versionedPackage.getPackageName())) {
-                            int impact = registeredObserver.onHealthCheckFailed(versionedPackage);
-                            if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
-                                    && impact < currentObserverImpact) {
-                                currentObserverToNotify = registeredObserver;
-                                currentObserverImpact = impact;
+                        // Find observer with least user impact
+                        for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
+                            ObserverInternal observer = mAllObservers.valueAt(oIndex);
+                            PackageHealthObserver registeredObserver = observer.registeredObserver;
+                            if (registeredObserver != null
+                                    && observer.onPackageFailureLocked(
+                                    versionedPackage.getPackageName())) {
+                                int impact = registeredObserver.onHealthCheckFailed(
+                                        versionedPackage, failureReason);
+                                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                                        && impact < currentObserverImpact) {
+                                    currentObserverToNotify = registeredObserver;
+                                    currentObserverImpact = impact;
+                                }
                             }
                         }
-                    }
 
-                    // Execute action with least user impact
-                    if (currentObserverToNotify != null) {
-                        currentObserverToNotify.execute(versionedPackage, failureReason);
+                        // Execute action with least user impact
+                        if (currentObserverToNotify != null) {
+                            currentObserverToNotify.execute(versionedPackage, failureReason);
+                        }
                     }
                 }
             }
         });
     }
 
+    /**
+     * For native crashes or explicit health check failures, call directly into each observer to
+     * mitigate the error without going through failure threshold logic.
+     */
+    private void handleFailureImmediately(List<VersionedPackage> packages,
+            @FailureReasons int failureReason) {
+        VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
+        PackageHealthObserver currentObserverToNotify = null;
+        int currentObserverImpact = Integer.MAX_VALUE;
+        for (ObserverInternal observer: mAllObservers.values()) {
+            PackageHealthObserver registeredObserver = observer.registeredObserver;
+            if (registeredObserver != null) {
+                int impact = registeredObserver.onHealthCheckFailed(
+                        failingPackage, failureReason);
+                if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                        && impact < currentObserverImpact) {
+                    currentObserverToNotify = registeredObserver;
+                    currentObserverImpact = impact;
+                }
+            }
+        }
+        if (currentObserverToNotify != null) {
+            currentObserverToNotify.execute(failingPackage,  failureReason);
+        }
+    }
+
     // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
     // avoid holding lock?
     // This currently adds about 7ms extra to shutdown thread
@@ -400,6 +442,37 @@
         }
     }
 
+    /**
+     * This method should be only called on mShortTaskHandler, since it modifies
+     * {@link #mNumberOfNativeCrashPollsRemaining}.
+     */
+    private void checkAndMitigateNativeCrashes() {
+        mNumberOfNativeCrashPollsRemaining--;
+        // Check if native watchdog reported a crash
+        if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
+            // We rollback everything available when crash is unattributable
+            onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
+            // we stop polling after an attempt to execute rollback, regardless of whether the
+            // attempt succeeds or not
+        } else {
+            if (mNumberOfNativeCrashPollsRemaining > 0) {
+                mShortTaskHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
+                        NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
+            }
+        }
+    }
+
+    /**
+     * Since this method can eventually trigger a rollback, it should be called
+     * only once boot has completed {@code onBootCompleted} and not earlier, because the install
+     * session must be entirely completed before we try to rollback.
+     */
+    public void scheduleCheckAndMitigateNativeCrashes() {
+        Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
+                + "and mitigate native crashes");
+        mShortTaskHandler.post(()->checkAndMitigateNativeCrashes());
+    }
+
     /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
     @Retention(SOURCE)
     @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
@@ -422,17 +495,28 @@
         /**
          * Called when health check fails for the {@code versionedPackage}.
          *
+         * @param versionedPackage the package that is failing. This may be null if a native
+         *                          service is crashing.
+         * @param failureReason   the type of failure that is occurring.
+         *
+         *
          * @return any one of {@link PackageHealthObserverImpact} to express the impact
          * to the user on {@link #execute}
          */
-        @PackageHealthObserverImpact int onHealthCheckFailed(VersionedPackage versionedPackage);
+        @PackageHealthObserverImpact int onHealthCheckFailed(
+                @Nullable VersionedPackage versionedPackage,
+                @FailureReasons int failureReason);
 
         /**
          * Executes mitigation for {@link #onHealthCheckFailed}.
          *
+         * @param versionedPackage the package that is failing. This may be null if a native
+         *                          service is crashing.
+         * @param failureReason   the type of failure that is occurring.
          * @return {@code true} if action was executed successfully, {@code false} otherwise
          */
-        boolean execute(VersionedPackage versionedPackage, @FailureReasons int failureReason);
+        boolean execute(@Nullable VersionedPackage versionedPackage,
+                @FailureReasons int failureReason);
 
         // TODO(b/120598832): Ensure uniqueness?
         /**
@@ -448,6 +532,17 @@
         default boolean isPersistent() {
             return false;
         }
+
+        /**
+         * Returns {@code true} if this observer wishes to observe the given package, {@code false}
+         * otherwise
+         *
+         * <p> A persistent observer may choose to start observing certain failing packages, even if
+         * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+         */
+        default boolean mayObservePackage(String packageName) {
+            return false;
+        }
     }
 
     long getTriggerFailureCount() {
@@ -784,13 +879,8 @@
                         Slog.wtf(TAG, "NetworkStack failed but could not find its package");
                         return;
                     }
-                    // This is a severe failure and recovery should be attempted immediately.
-                    // TODO: have a better way to handle such failures.
                     final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
-                    final long failureCount = getTriggerFailureCount();
-                    for (int i = 0; i < failureCount; i++) {
-                        onPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
-                    }
+                    onPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
                 });
     }
 
@@ -937,6 +1027,11 @@
          */
         @GuardedBy("mLock")
         public boolean onPackageFailureLocked(String packageName) {
+            if (packages.get(packageName) == null && registeredObserver.isPersistent()
+                    && registeredObserver.mayObservePackage(packageName)) {
+                packages.put(packageName, sPackageWatchdog.newMonitoredPackage(
+                        packageName, DEFAULT_OBSERVING_DURATION_MS, false));
+            }
             MonitoredPackage p = packages.get(packageName);
             if (p != null) {
                 return p.onFailureLocked();
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0a6473a..29d3a1d7 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1584,10 +1584,8 @@
         // Snapshot feature flag used for this boot
         SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT, Boolean.toString(
                 SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, true)));
-        SystemProperties.set(StorageManager.PROP_FUSE_SNAPSHOT, Boolean.toString(
-                SystemProperties.getBoolean(StorageManager.PROP_FUSE, false)));
 
-        mIsFuseEnabled = SystemProperties.getBoolean(StorageManager.PROP_FUSE_SNAPSHOT, false);
+        mIsFuseEnabled = SystemProperties.getBoolean(StorageManager.PROP_FUSE, false);
         mContext = context;
         mResolver = mContext.getContentResolver();
         mCallbacks = new Callbacks(FgThread.get().getLooper());
@@ -1647,12 +1645,22 @@
      *  and updates PROP_FUSE (reboots if changed).
      */
     private void updateFusePropFromSettings() {
-        Boolean settingsFuseFlag = SystemProperties.getBoolean((FeatureFlagUtils.PERSIST_PREFIX
-                + FeatureFlagUtils.SETTINGS_FUSE_FLAG), false);
-        Slog.d(TAG, "The value of Settings Fuse Flag is " + settingsFuseFlag);
-        if (SystemProperties.getBoolean(StorageManager.PROP_FUSE, false) != settingsFuseFlag) {
+        String settingsFuseFlag = SystemProperties.get(StorageManager.PROP_SETTINGS_FUSE);
+        Slog.d(TAG, "The value of Settings Fuse Flag is "
+                + (settingsFuseFlag == null || settingsFuseFlag.isEmpty()
+                ? "null" : settingsFuseFlag));
+        // Set default value of PROP_SETTINGS_FUSE and PROP_FUSE if it
+        // is unset (neither true nor false, this happens only on the first boot
+        // after wiping data partition).
+        if (settingsFuseFlag == null || settingsFuseFlag.isEmpty()) {
+            SystemProperties.set(StorageManager.PROP_SETTINGS_FUSE, "false");
+            SystemProperties.set(StorageManager.PROP_FUSE, "false");
+            return;
+        }
+
+        if (!SystemProperties.get(StorageManager.PROP_FUSE).equals(settingsFuseFlag)) {
             Slog.d(TAG, "Set persist.sys.fuse to " + settingsFuseFlag);
-            SystemProperties.set(StorageManager.PROP_FUSE, Boolean.toString(settingsFuseFlag));
+            SystemProperties.set(StorageManager.PROP_FUSE, settingsFuseFlag);
             // Perform hard reboot to kick policy into place
             mContext.getSystemService(PowerManager.class).reboot("Reboot device for FUSE system"
                     + "property change to take effect");
@@ -3097,14 +3105,6 @@
 
     @Override
     public void mkdirs(String callingPkg, String appPath) {
-        if (mIsFuseEnabled) {
-            // TODO(b/144332951): Calling into Vold is risky because the FUSE daemon can go down
-            // anytime and Vold will hang forever. We should either remove this call
-            // or at least call into the FUSE daemon to mkdir instead
-            Slog.w(TAG, "Not making dir for package " + callingPkg + " with path " + appPath);
-            return;
-        }
-
         final int callingUid = Binder.getCallingUid();
         final int userId = UserHandle.getUserId(callingUid);
         final UserEnvironment userEnv = new UserEnvironment(userId);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index b560761..7f62747 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
 import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
 import static android.telephony.TelephonyRegistryManager.SIM_ACTIVATION_TYPE_DATA;
 import static android.telephony.TelephonyRegistryManager.SIM_ACTIVATION_TYPE_VOICE;
@@ -59,6 +60,7 @@
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -97,7 +99,7 @@
  * and 15973975 by saving the phoneId of the registrant and then using the
  * phoneId when deciding to to make a callback. This is necessary because
  * a subId changes from to a dummy value when a SIM is removed and thus won't
- * compare properly. Because SubscriptionManager.getPhoneId(int subId) handles
+ * compare properly. Because getPhoneIdFromSubId(int subId) handles
  * the dummy value conversion we properly do the callbacks.
  *
  * Eventually we may want to remove the notion of dummy value but for now this
@@ -131,7 +133,7 @@
 
         int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-        int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+        int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
 
         boolean matchPhoneStateListenerEvent(int events) {
             return (callback != null) && ((events & this.events) != 0);
@@ -231,7 +233,7 @@
 
     private int mDefaultSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-    private int mDefaultPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+    private int mDefaultPhoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
 
     private int[] mRingingCallState;
 
@@ -358,7 +360,7 @@
                         SubscriptionManager.getDefaultSubscriptionId());
                 int newDefaultPhoneId = intent.getIntExtra(
                         SubscriptionManager.EXTRA_SLOT_INDEX,
-                        SubscriptionManager.getPhoneId(newDefaultSubId));
+                        getPhoneIdFromSubId(newDefaultSubId));
                 if (DBG) {
                     log("onReceive:current mDefaultSubId=" + mDefaultSubId
                             + " current mDefaultPhoneId=" + mDefaultPhoneId
@@ -768,7 +770,7 @@
                 return;
             }
 
-            int phoneId = SubscriptionManager.getPhoneId(subId);
+            int phoneId = getPhoneIdFromSubId(subId);
             synchronized (mRecords) {
                 // register
                 IBinder b = callback.asBinder();
@@ -1099,7 +1101,7 @@
         // Called only by Telecomm to communicate call state across different phone accounts. So
         // there is no need to add a valid subId or slotId.
         broadcastCallStateChanged(state, phoneNumber,
-                SubscriptionManager.INVALID_PHONE_INDEX,
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
     }
 
@@ -1313,7 +1315,8 @@
         // only CarrierService with carrier privilege rule should have the permission
         int[] subIds = Arrays.stream(SubscriptionManager.from(mContext)
                     .getActiveSubscriptionIdList(false))
-                    .filter(i -> TelephonyPermissions.checkCarrierPrivilegeForSubId(i)).toArray();
+                    .filter(i -> TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext,
+                            i)).toArray();
         if (ArrayUtils.isEmpty(subIds)) {
             loge("notifyCarrierNetworkChange without carrier privilege");
             // the active subId does not have carrier privilege.
@@ -1323,7 +1326,7 @@
         synchronized (mRecords) {
             mCarrierNetworkChangeState = active;
             for (int subId : subIds) {
-                int phoneId = SubscriptionManager.getPhoneId(subId);
+                int phoneId = getPhoneIdFromSubId(subId);
 
                 if (VDBG) {
                     log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
@@ -1356,7 +1359,7 @@
             log("notifyCellInfoForSubscriber: subId=" + subId
                 + " cellInfo=" + cellInfo);
         }
-        int phoneId = SubscriptionManager.getPhoneId(subId);
+        int phoneId = getPhoneIdFromSubId(subId);
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mCellInfo.set(phoneId, cellInfo);
@@ -1447,7 +1450,7 @@
             log("notifyCallForwardingChangedForSubscriber: subId=" + subId
                 + " cfi=" + cfi);
         }
-        int phoneId = SubscriptionManager.getPhoneId(subId);
+        int phoneId = getPhoneIdFromSubId(subId);
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mCallForwarding[phoneId] = cfi;
@@ -1475,7 +1478,7 @@
         if (!checkNotifyPermission("notifyDataActivity()" )) {
             return;
         }
-        int phoneId = SubscriptionManager.getPhoneId(subId);
+        int phoneId = getPhoneIdFromSubId(subId);
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mDataActivity[phoneId] = state;
@@ -1626,7 +1629,7 @@
             log("notifyCellLocationForSubscriber: subId=" + subId
                 + " cellLocation=" + cellLocation);
         }
-        int phoneId = SubscriptionManager.getPhoneId(subId);
+        int phoneId = getPhoneIdFromSubId(subId);
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mCellLocation[phoneId] = cellLocation;
@@ -1654,7 +1657,7 @@
         if (!checkNotifyPermission("notifyOtaspChanged()" )) {
             return;
         }
-        int phoneId = SubscriptionManager.getPhoneId(subId);
+        int phoneId = getPhoneIdFromSubId(subId);
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mOtaspMode[phoneId] = otaspMode;
@@ -1758,7 +1761,7 @@
         if (!checkNotifyPermission("notifyImsCallDisconnectCause()")) {
             return;
         }
-        int phoneId = SubscriptionManager.getPhoneId(subId);
+        int phoneId = getPhoneIdFromSubId(subId);
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mImsReasonInfo.set(phoneId, imsReasonInfo);
@@ -1818,7 +1821,7 @@
         if (VDBG) {
             log("notifySrvccStateChanged: subId=" + subId + " srvccState=" + state);
         }
-        int phoneId = SubscriptionManager.getPhoneId(subId);
+        int phoneId = getPhoneIdFromSubId(subId);
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 mSrvccState[phoneId] = state;
@@ -2226,7 +2229,7 @@
             intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
         }
         // If the phoneId is invalid, the broadcast is for overall call state.
-        if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+        if (phoneId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
             intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, phoneId);
             intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
         }
@@ -2304,7 +2307,7 @@
             return;
         }
 
-        TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(
+        TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mContext,
                 SubscriptionManager.getDefaultSubscriptionId(), method);
     }
 
@@ -2699,4 +2702,18 @@
     private static CallQuality createCallQuality() {
         return new CallQuality(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
     }
+
+    private int getPhoneIdFromSubId(int subId) {
+        SubscriptionManager subManager = (SubscriptionManager)
+                mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        if (subManager == null) return INVALID_SIM_SLOT_INDEX;
+
+        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+            subId = SubscriptionManager.getDefaultSubscriptionId();
+        }
+
+        SubscriptionInfo info = subManager.getActiveSubscriptionInfo(subId);
+        if (info == null) return INVALID_SIM_SLOT_INDEX;
+        return info.getSimSlotIndex();
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 01f3c26..b55d6ad 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -490,7 +490,7 @@
     static final String[] EMPTY_STRING_ARRAY = new String[0];
 
     // How many bytes to write into the dropbox log before truncating
-    static final int DROPBOX_MAX_SIZE = 192 * 1024;
+    static final int DROPBOX_DEFAULT_MAX_SIZE = 192 * 1024;
     // Assumes logcat entries average around 100 bytes; that's not perfect stack traces count
     // as one line, but close enough for now.
     static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100;
@@ -2386,7 +2386,8 @@
         mConstants = hasHandlerThread
                 ? new ActivityManagerConstants(mContext, this, mHandler) : null;
         final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
-        mProcessList.init(this, activeUids);
+        mPlatformCompat = null;
+        mProcessList.init(this, activeUids, mPlatformCompat);
         mLowMemDetector = null;
         mOomAdjuster = hasHandlerThread
                 ? new OomAdjuster(this, mProcessList, activeUids, handlerThread) : null;
@@ -2410,7 +2411,6 @@
         mFactoryTest = FACTORY_TEST_OFF;
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
         mInternal = new LocalService();
-        mPlatformCompat = null;
     }
 
     // Note: This method is invoked on the main thread but may need to attach various
@@ -2439,7 +2439,9 @@
 
         mConstants = new ActivityManagerConstants(mContext, this, mHandler);
         final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */);
-        mProcessList.init(this, activeUids);
+        mPlatformCompat = (PlatformCompat) ServiceManager.getService(
+                Context.PLATFORM_COMPAT_SERVICE);
+        mProcessList.init(this, activeUids, mPlatformCompat);
         mLowMemDetector = new LowMemDetector(this);
         mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
 
@@ -2547,9 +2549,6 @@
 
         mHiddenApiBlacklist = new HiddenApiSettings(mHandler, mContext);
 
-        mPlatformCompat = (PlatformCompat) ServiceManager.getService(
-                Context.PLATFORM_COMPAT_SERVICE);
-
         Watchdog.getInstance().addMonitor(this);
         Watchdog.getInstance().addThread(mHandler);
 
@@ -5032,9 +5031,7 @@
             bindApplicationTimeMillis = SystemClock.elapsedRealtime();
             mAtmInternal.preBindApplication(app.getWindowProcessController());
             final ActiveInstrumentation instr2 = app.getActiveInstrumentation();
-            long[] disabledCompatChanges = {};
             if (mPlatformCompat != null) {
-                disabledCompatChanges = mPlatformCompat.getDisabledChanges(app.info);
                 mPlatformCompat.resetReporting(app.info);
             }
             if (app.isolatedEntryPoint != null) {
@@ -5053,7 +5050,7 @@
                         app.compat, getCommonServicesLocked(app.isolated),
                         mCoreSettingsObserver.getCoreSettingsLocked(),
                         buildSerial, autofillOptions, contentCaptureOptions,
-                        disabledCompatChanges);
+                        app.mDisabledCompatChanges);
             } else {
                 thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                         null, null, null, testMode,
@@ -5063,7 +5060,7 @@
                         app.compat, getCommonServicesLocked(app.isolated),
                         mCoreSettingsObserver.getCoreSettingsLocked(),
                         buildSerial, autofillOptions, contentCaptureOptions,
-                        disabledCompatChanges);
+                        app.mDisabledCompatChanges);
             }
             if (profilerInfo != null) {
                 profilerInfo.closeFd();
@@ -9773,9 +9770,12 @@
                     sb.append(report);
                 }
 
-                String setting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
-                int lines = Settings.Global.getInt(mContext.getContentResolver(), setting, 0);
-                int maxDataFileSize = DROPBOX_MAX_SIZE - sb.length()
+                String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
+                String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
+                int lines = Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
+                int dropboxMaxSize = Settings.Global.getInt(
+                        mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
+                int maxDataFileSize = dropboxMaxSize - sb.length()
                         - lines * RESERVED_BYTES_PER_LOGCAT_LINE;
 
                 if (dataFile != null && maxDataFileSize > 0) {
@@ -18436,7 +18436,7 @@
         public ActivityPresentationInfo getActivityPresentationInfo(IBinder token) {
             int displayId = Display.INVALID_DISPLAY;
             try {
-                displayId = mActivityTaskManager.getActivityDisplayId(token);
+                displayId = mActivityTaskManager.getDisplayId(token);
             } catch (RemoteException e) {
             }
 
diff --git a/services/core/java/com/android/server/am/LmkdConnection.java b/services/core/java/com/android/server/am/LmkdConnection.java
index d1e09db..f41c364 100644
--- a/services/core/java/com/android/server/am/LmkdConnection.java
+++ b/services/core/java/com/android/server/am/LmkdConnection.java
@@ -18,6 +18,7 @@
 
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -35,9 +36,6 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Lmkd connection to communicate with lowmemorykiller daemon.
@@ -46,7 +44,7 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LmkdConnection" : TAG_AM;
 
     // lmkd reply max size in bytes
-    private static final int LMKD_REPLY_MAX_SIZE = 8;
+    private static final int LMKD_REPLY_MAX_SIZE = 12;
 
     // connection listener interface
     interface LmkdConnectionListener {
@@ -64,6 +62,15 @@
          */
         public boolean isReplyExpected(ByteBuffer replyBuf, ByteBuffer dataReceived,
             int receivedLen);
+
+        /**
+         * Handle the received message if it's unsolicited.
+         *
+         * @param dataReceived The buffer holding received data
+         * @param receivedLen Size of the data received
+         * @return True if the message has been handled correctly, false otherwise.
+         */
+        boolean handleUnsolicitedMessage(ByteBuffer dataReceived, int receivedLen);
     }
 
     private final MessageQueue mMsgQueue;
@@ -187,17 +194,17 @@
                         mReplyBuf.rewind();
                         // wakeup the waiting thread
                         mReplyBufLock.notifyAll();
-                    } else {
-                        // received asynchronous or unexpected packet
+                    } else if (!mListener.handleUnsolicitedMessage(mInputBuf, len)) {
+                        // received unexpected packet
                         // treat this as an error
                         mReplyBuf = null;
                         mReplyBufLock.notifyAll();
-                        Slog.e(TAG, "Received unexpected packet from lmkd");
+                        Slog.e(TAG, "Received an unexpected packet from lmkd");
                     }
-                } else {
+                } else if (!mListener.handleUnsolicitedMessage(mInputBuf, len)) {
                     // received asynchronous communication from lmkd
-                    // we don't support this yet
-                    Slog.w(TAG, "Received an asynchronous packet from lmkd");
+                    // but we don't recognize it.
+                    Slog.w(TAG, "Received an unexpected packet from lmkd");
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 32975d7..5378f43 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -101,6 +101,7 @@
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.Watchdog;
+import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.wm.ActivityServiceConnectionsHolder;
 import com.android.server.wm.WindowManagerService;
@@ -277,14 +278,16 @@
     // LMK_PROCREMOVE <pid>
     // LMK_PROCPURGE
     // LMK_GETKILLCNT
+    // LMK_PROCKILL
     static final byte LMK_TARGET = 0;
     static final byte LMK_PROCPRIO = 1;
     static final byte LMK_PROCREMOVE = 2;
     static final byte LMK_PROCPURGE = 3;
     static final byte LMK_GETKILLCNT = 4;
+    static final byte LMK_PROCKILL = 5; // Note: this is an unsolicated command
 
     // lmkd reconnect delay in msecs
-    private final static long LMDK_RECONNECT_DELAY_MS = 1000;
+    private static final long LMKD_RECONNECT_DELAY_MS = 1000;
 
     /**
      * How long between a process kill and we actually receive its death recipient
@@ -390,6 +393,12 @@
     ActiveUids mActiveUids;
 
     /**
+     * The listener who is intereted with the lmkd kills.
+     */
+    @GuardedBy("mService")
+    private LmkdKillListener mLmkdKillListener = null;
+
+    /**
      * The currently running isolated processes.
      */
     final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<>();
@@ -405,6 +414,15 @@
     final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses =
             new ArrayMap<AppZygote, ArrayList<ProcessRecord>>();
 
+    private PlatformCompat mPlatformCompat = null;
+
+    interface LmkdKillListener {
+        /**
+         * Called when there is a process kill by lmkd.
+         */
+        void onLmkdKillOccurred(int pid, int uid);
+    }
+
     final class IsolatedUidRange {
         @VisibleForTesting
         public final int mFirstUid;
@@ -557,7 +575,8 @@
 
     final class KillHandler extends Handler {
         static final int KILL_PROCESS_GROUP_MSG = 4000;
-        static final int LMDK_RECONNECT_MSG = 4001;
+        static final int LMKD_RECONNECT_MSG = 4001;
+        static final int LMKD_PROC_KILLED_MSG = 4002;
 
         public KillHandler(Looper looper) {
             super(looper, null, true);
@@ -571,15 +590,18 @@
                     Process.killProcessGroup(msg.arg1 /* uid */, msg.arg2 /* pid */);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
-                case LMDK_RECONNECT_MSG:
+                case LMKD_RECONNECT_MSG:
                     if (!sLmkdConnection.connect()) {
                         Slog.i(TAG, "Failed to connect to lmkd, retry after " +
-                                LMDK_RECONNECT_DELAY_MS + " ms");
-                        // retry after LMDK_RECONNECT_DELAY_MS
+                                LMKD_RECONNECT_DELAY_MS + " ms");
+                        // retry after LMKD_RECONNECT_DELAY_MS
                         sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage(
-                                KillHandler.LMDK_RECONNECT_MSG), LMDK_RECONNECT_DELAY_MS);
+                                KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
                     }
                     break;
+                case LMKD_PROC_KILLED_MSG:
+                    handleLmkdProcKilled(msg.arg1 /* pid */, msg.arg2 /* uid */);
+                    break;
 
                 default:
                     super.handleMessage(msg);
@@ -596,9 +618,11 @@
         updateOomLevels(0, 0, false);
     }
 
-    void init(ActivityManagerService service, ActiveUids activeUids) {
+    void init(ActivityManagerService service, ActiveUids activeUids,
+            PlatformCompat platformCompat) {
         mService = service;
         mActiveUids = activeUids;
+        mPlatformCompat = platformCompat;
 
         if (sKillHandler == null) {
             sKillThread = new ServiceThread(TAG + ":kill",
@@ -612,13 +636,15 @@
                             Slog.i(TAG, "Connection with lmkd established");
                             return onLmkdConnect(ostream);
                         }
+
                         @Override
                         public void onDisconnect() {
                             Slog.w(TAG, "Lost connection to lmkd");
                             // start reconnection after delay to let lmkd restart
                             sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage(
-                                    KillHandler.LMDK_RECONNECT_MSG), LMDK_RECONNECT_DELAY_MS);
+                                    KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
                         }
+
                         @Override
                         public boolean isReplyExpected(ByteBuffer replyBuf,
                                 ByteBuffer dataReceived, int receivedLen) {
@@ -627,6 +653,26 @@
                             return (receivedLen == replyBuf.array().length &&
                                     dataReceived.getInt(0) == replyBuf.getInt(0));
                         }
+
+                        @Override
+                        public boolean handleUnsolicitedMessage(ByteBuffer dataReceived,
+                                int receivedLen) {
+                            if (receivedLen < 4) {
+                                return false;
+                            }
+                            switch (dataReceived.getInt(0)) {
+                                case LMK_PROCKILL:
+                                    if (receivedLen != 12) {
+                                        return false;
+                                    }
+                                    sKillHandler.obtainMessage(KillHandler.LMKD_PROC_KILLED_MSG,
+                                            dataReceived.getInt(4), dataReceived.getInt(8))
+                                            .sendToTarget();
+                                    return true;
+                                default:
+                                    return false;
+                            }
+                        }
                     }
             );
         }
@@ -1303,10 +1349,10 @@
         if (!sLmkdConnection.isConnected()) {
             // try to connect immediately and then keep retrying
             sKillHandler.sendMessage(
-                sKillHandler.obtainMessage(KillHandler.LMDK_RECONNECT_MSG));
+                    sKillHandler.obtainMessage(KillHandler.LMKD_RECONNECT_MSG));
 
             // wait for connection retrying 3 times (up to 3 seconds)
-            if (!sLmkdConnection.waitForConnection(3 * LMDK_RECONNECT_DELAY_MS)) {
+            if (!sLmkdConnection.waitForConnection(3 * LMKD_RECONNECT_DELAY_MS)) {
                 return false;
             }
         }
@@ -1664,6 +1710,10 @@
             Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
                     + " with non-zero pid:" + app.pid);
         }
+        app.mDisabledCompatChanges = null;
+        if (mPlatformCompat != null) {
+            app.mDisabledCompatChanges = mPlatformCompat.getDisabledChanges(app.info);
+        }
         final long startSeq = app.startSeq = ++mProcStartSeqCounter;
         app.setStartParams(uid, hostingRecord, seInfo, startTime);
         app.setUsingWrapper(invokeWith != null
@@ -1826,8 +1876,8 @@
                 startResult = startWebView(entryPoint,
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
-                        app.info.dataDir, null, app.info.packageName,
-                        new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+                        app.info.dataDir, null, app.info.packageName, app.mDisabledCompatChanges,
+                        new String[]{PROC_START_SEQ_IDENT + app.startSeq});
             } else if (hostingRecord.usesAppZygote()) {
                 final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
 
@@ -1835,14 +1885,15 @@
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
-                        /*useUsapPool=*/ false, isTopApp,
-                        new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+                        /*useUsapPool=*/ false, isTopApp, app.mDisabledCompatChanges,
+                        new String[]{PROC_START_SEQ_IDENT + app.startSeq});
             } else {
                 startResult = Process.start(entryPoint,
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, invokeWith, app.info.packageName, isTopApp,
-                        new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+                        app.mDisabledCompatChanges,
+                        new String[]{PROC_START_SEQ_IDENT + app.startSeq});
             }
             checkSlow(startTime, "startProcess: returned from zygote!");
             return startResult;
@@ -3393,4 +3444,28 @@
             }
         }
     }
+
+    void setLmkdKillListener(final LmkdKillListener listener) {
+        synchronized (mService) {
+            mLmkdKillListener = listener;
+        }
+    }
+
+    private void handleLmkdProcKilled(final int pid, final int uid) {
+        // Log only now
+        if (DEBUG_PROCESSES) {
+            Slog.i(TAG, "lmkd kill: pid=" + pid + " uid=" + uid);
+        }
+
+        if (mService == null) {
+            return;
+        }
+        // Notify any interesed party regarding the lmkd kills
+        synchronized (mService) {
+            final LmkdKillListener listener = mLmkdKillListener;
+            if (listener != null) {
+                mService.mHandler.post(()-> listener.onLmkdKillOccurred(pid, uid));
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 6f6f193..24fc743 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -310,6 +310,8 @@
     long startTime;
     // This will be same as {@link #uid} usually except for some apps used during factory testing.
     int startUid;
+    // set of disabled compat changes for the process (all others are enabled)
+    long[] mDisabledCompatChanges;
 
     // Cached task info for OomAdjuster
     private static final int VALUE_INVALID = -1;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 366766e..14f9654 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -20,6 +20,7 @@
 import static android.app.AppOpsManager.MAX_PRIORITY_UID_STATE;
 import static android.app.AppOpsManager.MIN_PRIORITY_UID_STATE;
 import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_COARSE_LOCATION;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.AppOpsManager.OP_PLAY_AUDIO;
@@ -1642,6 +1643,13 @@
             return;
         }
 
+        // STOPSHIP: Remove this check once we are sure no one is doing it.
+        if (code == OP_COARSE_LOCATION && mode != AppOpsManager.opToDefaultMode(code)) {
+            Slog.wtf(TAG, "Trying to setMode() instead of setUidMode(), " + "code=" + code
+                    + ", uid=" + uid + ", packageName=" + packageName + ", mode=" + mode
+                    + ", callingUid=" + Binder.getCallingUid(), new RuntimeException());
+        }
+
         synchronized (this) {
             UidState uidState = getUidStateLocked(uid, false);
             Op op = getOpLocked(code, uid, packageName, isPrivileged, true);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 661451b..9061586 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -68,9 +68,6 @@
 
     private @NonNull AudioDeviceBroker mDeviceBroker;
 
-    // cache of the address of the last dock the device was connected to
-    private String mDockAddress;
-
     // Monitoring of audio routes.  Protected by mAudioRoutes.
     final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
     final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
@@ -187,7 +184,7 @@
         int a2dpVolume = btInfo.getVolume();
         if (AudioService.DEBUG_DEVICES) {
             Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
-                    + state + " is dock=" + btDevice.isBluetoothDock() + " vol=" + a2dpVolume);
+                    + state + " vol=" + a2dpVolume);
         }
         String address = btDevice.getAddress();
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
@@ -215,42 +212,17 @@
                         mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice);
                     }
                 } else {
-                    if (btDevice.isBluetoothDock()) {
-                        if (state == BluetoothProfile.STATE_DISCONNECTED) {
-                            // introduction of a delay for transient disconnections of docks when
-                            // power is rapidly turned off/on, this message will be canceled if
-                            // we reconnect the dock under a preset delay
-                            makeA2dpDeviceUnavailableLater(address,
-                                    AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
-                            // the next time isConnected is evaluated, it will be false for the dock
-                        }
-                    } else {
-                        makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
-                    }
+                    makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
                 }
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                if (btDevice.isBluetoothDock()) {
-                    // this could be a reconnection after a transient disconnection
-                    mDeviceBroker.cancelA2dpDockTimeout();
-                    mDockAddress = address;
-                } else {
-                    // this could be a connection of another A2DP device before the timeout of
-                    // a dock: cancel the dock timeout, and make the dock unavailable now
-                    if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
-                        mDeviceBroker.cancelA2dpDockTimeout();
-                        makeA2dpDeviceUnavailableNow(mDockAddress,
-                                AudioSystem.AUDIO_FORMAT_DEFAULT);
-                    }
-                }
-                if (a2dpVolume != -1) {
-                    mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
-                            // convert index to internal representation in VolumeStreamState
-                            a2dpVolume * 10,
-                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState");
-                }
-                makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice),
-                        "onSetA2dpSinkConnectionState", a2dpCodec);
             }
+            if (a2dpVolume != -1) {
+                mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
+                        // convert index to internal representation in VolumeStreamState
+                        a2dpVolume * 10,
+                        AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState");
+            }
+            makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice),
+                    "onSetA2dpSinkConnectionState", a2dpCodec);
         }
     }
 
@@ -723,9 +695,6 @@
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null);
-        if (mDockAddress == address) {
-            mDockAddress = null;
-        }
     }
 
     @GuardedBy("mConnectedDevices")
diff --git a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
index a91fe77..1cf27ff 100644
--- a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
+++ b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
@@ -231,7 +231,8 @@
                 // a Sim with a different mcc code was found
                 neededNow = false;
             }
-            String simOperator  = mTelephonyManager.getSimOperator(info.getSubscriptionId());
+            String simOperator = mTelephonyManager
+                    .createForSubscriptionId(info.getSubscriptionId()).getSimOperator();
             mcc = 0;
             if (simOperator != null && simOperator.length() >= 3) {
                 mcc = Integer.parseInt(simOperator.substring(0, 3));
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
index 52cede2..23b5c14 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
@@ -250,7 +250,11 @@
             new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_UP),
             // No Android keycode defined for CEC_KEYCODE_LEFT_DOWN
             new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_DOWN),
+            // Both KEYCODE_HOME and KEYCODE_MENU keys are sent as CEC_KEYCODE_ROOT_MENU
+            // NOTE that the HOME key is not usually forwarded.
+            // When CEC_KEYCODE_ROOT_MENU is received, it is translated to KEYCODE_HOME
             new KeycodeEntry(KeyEvent.KEYCODE_HOME, CEC_KEYCODE_ROOT_MENU),
+            new KeycodeEntry(KeyEvent.KEYCODE_MENU, CEC_KEYCODE_ROOT_MENU),
             new KeycodeEntry(KeyEvent.KEYCODE_SETTINGS, CEC_KEYCODE_SETUP_MENU),
             new KeycodeEntry(KeyEvent.KEYCODE_TV_CONTENTS_MENU, CEC_KEYCODE_CONTENTS_MENU, false),
             // No Android keycode defined for CEC_KEYCODE_FAVORITE_MENU
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
new file mode 100644
index 0000000..1b1a292
--- /dev/null
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.incremental;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.DataLoaderManager;
+import android.content.pm.IDataLoader;
+import android.content.pm.IDataLoaderStatusListener;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.incremental.IIncrementalManager;
+import android.os.incremental.IncrementalDataLoaderParamsParcel;
+import android.os.incremental.IncrementalFileSystemControlParcel;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+
+/**
+ * This service has the following purposes:
+ * 1) Starts the IIncrementalManager binder service.
+ * 1) Starts the native IIncrementalManagerService binder service.
+ * 2) Handles shell commands for "incremental" service.
+ * 3) Handles binder calls from the native IIncrementalManagerService binder service and pass
+ *    them to a data loader binder service.
+ */
+
+public class IncrementalManagerService extends IIncrementalManager.Stub  {
+    private static final String TAG = "IncrementalManagerService";
+    private static final String BINDER_SERVICE_NAME = "incremental";
+    // DataLoaderManagerService should have been started before us
+    private @NonNull DataLoaderManager mDataLoaderManager;
+    private long mNativeInstance;
+    private final @NonNull Context mContext;
+
+    /**
+     * Starts IIncrementalManager binder service and register to Service Manager.
+     * Starts the native IIncrementalManagerNative binder service.
+     */
+    public static IncrementalManagerService start(Context context) {
+        IncrementalManagerService self = new IncrementalManagerService(context);
+        if (self.mNativeInstance == 0) {
+            return null;
+        }
+        return self;
+    }
+
+    private IncrementalManagerService(Context context) {
+        mContext = context;
+        mDataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
+        ServiceManager.addService(BINDER_SERVICE_NAME, this);
+        // Starts and register IIncrementalManagerNative service
+        // TODO(b/136132412): add jni implementation
+    }
+    /**
+     * Notifies native IIncrementalManager service that system is ready.
+     */
+    public void systemReady() {
+        // TODO(b/136132412): add jni implementation
+    }
+
+    /**
+     * Finds data loader service provider and binds to it. This requires PackageManager.
+     */
+    @Override
+    public boolean prepareDataLoader(int mountId, IncrementalFileSystemControlParcel control,
+            IncrementalDataLoaderParamsParcel params,
+            IDataLoaderStatusListener listener) {
+        Bundle dataLoaderParams = new Bundle();
+        dataLoaderParams.putCharSequence("packageName", params.packageName);
+        dataLoaderParams.putParcelable("control", control);
+        dataLoaderParams.putParcelable("params", params);
+        DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
+        if (dataLoaderManager == null) {
+            Slog.e(TAG, "Failed to find data loader manager service");
+            return false;
+        }
+        if (!dataLoaderManager.initializeDataLoader(mountId, dataLoaderParams, listener)) {
+            Slog.e(TAG, "Failed to initialize data loader");
+            return false;
+        }
+        return true;
+    }
+
+
+    @Override
+    public boolean startDataLoader(int mountId) {
+        IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId);
+        if (dataLoader == null) {
+            Slog.e(TAG, "Start failed to retrieve data loader for ID=" + mountId);
+            return false;
+        }
+        try {
+            // TODO: fix file list
+            dataLoader.start(null);
+            return true;
+        } catch (RemoteException ex) {
+            return false;
+        }
+    }
+
+    @Override
+    public void destroyDataLoader(int mountId) {
+        IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId);
+        if (dataLoader == null) {
+            Slog.e(TAG, "Destroy failed to retrieve data loader for ID=" + mountId);
+            return;
+        }
+        try {
+            dataLoader.destroy();
+        } catch (RemoteException ex) {
+            return;
+        }
+    }
+
+    // TODO: remove this
+    @Override
+    public void newFileForDataLoader(int mountId, long inode, byte[] metadata) {
+        IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId);
+        if (dataLoader == null) {
+            Slog.e(TAG, "Failed to retrieve data loader for ID=" + mountId);
+            return;
+        }
+        try {
+            dataLoader.onFileCreated(inode, metadata);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    @Override
+    public void showHealthBlockedUI(int mountId) {
+        // TODO(b/136132412): implement this
+    }
+
+    @Override
+    public void onShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
+            FileDescriptor err, @NonNull String[] args, ShellCallback callback,
+            @NonNull ResultReceiver resultReceiver) {
+        (new IncrementalManagerShellCommand(mContext)).exec(
+                this, in, out, err, args, callback, resultReceiver);
+    }
+}
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
new file mode 100644
index 0000000..d35e806
--- /dev/null
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.incremental;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.InstallationFile;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ShellCommand;
+import android.os.incremental.IncrementalDataLoaderParams;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Defines actions to handle adb commands like "adb abb incremental ...".
+ */
+public final class IncrementalManagerShellCommand extends ShellCommand {
+    private static final String TAG = "IncrementalShellCommand";
+    // Assuming the adb data loader is always installed on the device
+    private static final String LOADER_PACKAGE_NAME = "com.android.incremental.nativeadb";
+    private final @NonNull Context mContext;
+
+    private static final int ERROR_INVALID_ARGUMENTS = -1;
+    private static final int ERROR_DATA_LOADER_INIT = -2;
+    private static final int ERROR_COMMAND_EXECUTION = -3;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ERROR_INVALID_ARGUMENTS, ERROR_DATA_LOADER_INIT, ERROR_COMMAND_EXECUTION})
+    public @interface IncrementalShellCommandErrorCode {
+    }
+
+    IncrementalManagerShellCommand(@NonNull Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public int onCommand(@Nullable String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(null);
+        }
+        switch (cmd) {
+            case "install-start":
+                return runInstallStart();
+            case "install-finish":
+                return runInstallFinish();
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Incremental Service Commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  install-start");
+        pw.println("    Opens an installation session");
+        pw.println("  install-finish SESSION_ID --file NAME:SIZE:INDEX --file NAME:SIZE:INDEX ...");
+        pw.println("    Commits an installation session specified by session ID for an APK ");
+        pw.println("      or a bundle of splits. Configures lib dirs or OBB files if specified.");
+    }
+
+    private int runInstallStart() {
+        final PrintWriter pw = getOutPrintWriter();
+        final PackageInstaller packageInstaller =
+                mContext.getPackageManager().getPackageInstaller();
+        if (packageInstaller == null) {
+            pw.println("Failed to get PackageInstaller.");
+            return ERROR_COMMAND_EXECUTION;
+        }
+
+        final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = getDataLoaderDynamicArgs();
+        if (dataLoaderDynamicArgs == null) {
+            pw.println("File names and sizes don't match.");
+            return ERROR_DATA_LOADER_INIT;
+        }
+        final IncrementalDataLoaderParams params = new IncrementalDataLoaderParams(
+                "", LOADER_PACKAGE_NAME, dataLoaderDynamicArgs);
+        PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        sessionParams.installFlags |= PackageManager.INSTALL_ALL_USERS;
+        // Replace existing if same package is already installed
+        sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+        sessionParams.setIncrementalParams(params);
+
+        try {
+            int sessionId = packageInstaller.createSession(sessionParams);
+            pw.println("Successfully opened session: sessionId = " + sessionId);
+        } catch (Exception ex) {
+            pw.println("Failed to create session.");
+            return ERROR_COMMAND_EXECUTION;
+        } finally {
+            try {
+                for (Map.Entry<String, ParcelFileDescriptor> nfd
+                        : dataLoaderDynamicArgs.entrySet()) {
+                    nfd.getValue().close();
+                }
+            } catch (IOException ignored) {
+            }
+        }
+        return 0;
+    }
+
+    private int runInstallFinish() {
+        final PrintWriter pw = getOutPrintWriter();
+        final int sessionId = parseInt(getNextArgRequired());
+        final List<InstallationFile> installationFiles = parseFileArgs(pw);
+        if (installationFiles == null) {
+            pw.println("Must specify at least one file to install.");
+            return ERROR_INVALID_ARGUMENTS;
+        }
+        final int numFiles = installationFiles.size();
+        if (numFiles == 0) {
+            pw.println("Must specify at least one file to install.");
+            return ERROR_INVALID_ARGUMENTS;
+        }
+
+        final PackageInstaller packageInstaller = mContext.getPackageManager()
+                .getPackageInstaller();
+        if (packageInstaller == null) {
+            pw.println("Failed to get PackageInstaller.");
+            return ERROR_COMMAND_EXECUTION;
+        }
+
+        final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
+        boolean success = false;
+
+        PackageInstaller.Session session = null;
+        try {
+            session = packageInstaller.openSession(sessionId);
+            for (int i = 0; i < numFiles; i++) {
+                InstallationFile file = installationFiles.get(i);
+                session.addFile(file.getName(), file.getSize(), file.getMetadata());
+            }
+            session.commit(localReceiver.getIntentSender());
+            final Intent result = localReceiver.getResult();
+            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            if (status == PackageInstaller.STATUS_SUCCESS) {
+                success = true;
+                pw.println("Success");
+                return 0;
+            } else {
+                pw.println("Failure ["
+                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+                return ERROR_COMMAND_EXECUTION;
+            }
+        } catch (Exception e) {
+            e.printStackTrace(pw);
+            return ERROR_COMMAND_EXECUTION;
+        } finally {
+            if (!success) {
+                try {
+                    if (session != null) {
+                        session.abandon();
+                    }
+                } catch (Exception ignore) {
+                }
+            }
+        }
+    }
+
+    private static class LocalIntentReceiver {
+        private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission,
+                    Bundle options) {
+                try {
+                    mResult.offer(intent, 5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public Intent getResult() {
+            try {
+                return mResult.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /** Helpers. */
+    private Map<String, ParcelFileDescriptor> getDataLoaderDynamicArgs() {
+        Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>();
+        final FileDescriptor outFd = getOutFileDescriptor();
+        final FileDescriptor inFd = getInFileDescriptor();
+        try {
+            dataLoaderDynamicArgs.put("inFd", ParcelFileDescriptor.dup(inFd));
+            dataLoaderDynamicArgs.put("outFd", ParcelFileDescriptor.dup(outFd));
+            return dataLoaderDynamicArgs;
+        } catch (Exception ex) {
+            Slog.e(TAG, "Failed to dup FDs");
+            return null;
+        }
+    }
+
+    private long parseLong(String arg) {
+        long result = -1;
+        try {
+            result = Long.parseLong(arg);
+        } catch (NumberFormatException e) {
+        }
+        return result;
+    }
+
+    private int parseInt(String arg) {
+        int result = -1;
+        try {
+            result = Integer.parseInt(arg);
+        } catch (NumberFormatException e) {
+        }
+        return result;
+    }
+
+    private List<InstallationFile> parseFileArgs(PrintWriter pw) {
+        List<InstallationFile> fileList = new ArrayList<>();
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--file": {
+                    final String fileArgs = getNextArgRequired();
+                    final String[] args = fileArgs.split(":");
+                    if (args.length != 3) {
+                        pw.println("Invalid file args: " + fileArgs);
+                        return null;
+                    }
+                    final String name = args[0];
+                    final long size = parseLong(args[1]);
+                    if (size < 0) {
+                        pw.println("Invalid file size in: " + fileArgs);
+                        return null;
+                    }
+                    final long index = parseLong(args[2]);
+                    if (index < 0) {
+                        pw.println("Invalid file index in: " + fileArgs);
+                        return null;
+                    }
+                    final byte[] metadata = String.valueOf(index).getBytes(StandardCharsets.UTF_8);
+                    fileList.add(new InstallationFile(name, size, metadata));
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+        return fileList;
+    }
+}
diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java
index 3d18d4a..f625452 100644
--- a/services/core/java/com/android/server/location/LocationSettingsStore.java
+++ b/services/core/java/com/android/server/location/LocationSettingsStore.java
@@ -34,6 +34,7 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemConfig;
@@ -113,6 +114,16 @@
                 () -> SystemConfig.getInstance().getAllowIgnoreLocationSettings(), handler);
     }
 
+    /** Called when system is ready. */
+    public synchronized void onSystemReady() {
+        mLocationMode.register();
+        mBackgroundThrottleIntervalMs.register();
+        mLocationPackageBlacklist.register();
+        mLocationPackageWhitelist.register();
+        mBackgroundThrottlePackageWhitelist.register();
+        mIgnoreSettingsPackageWhitelist.register();
+    }
+
     /**
      * Retrieve if location is enabled or not.
      */
@@ -300,13 +311,25 @@
     private abstract static class ObservingSetting extends ContentObserver {
 
         private final CopyOnWriteArrayList<UserSettingChangedListener> mListeners;
+        private boolean mRegistered;
 
-        private ObservingSetting(Context context, String settingName, Handler handler) {
+        private ObservingSetting(Handler handler) {
             super(handler);
             mListeners = new CopyOnWriteArrayList<>();
+        }
+
+        protected boolean isRegistered() {
+            return mRegistered;
+        }
+
+        protected void register(Context context, Uri uri) {
+            if (mRegistered) {
+                return;
+            }
 
             context.getContentResolver().registerContentObserver(
-                    getUriFor(settingName), false, this, UserHandle.USER_ALL);
+                    uri, false, this, UserHandle.USER_ALL);
+            mRegistered = true;
         }
 
         public void addListener(UserSettingChangedListener listener) {
@@ -317,8 +340,6 @@
             mListeners.remove(listener);
         }
 
-        protected abstract Uri getUriFor(String settingName);
-
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
             for (UserSettingChangedListener listener : mListeners) {
@@ -333,20 +354,19 @@
         private final String mSettingName;
 
         private IntegerSecureSetting(Context context, String settingName, Handler handler) {
-            super(context, settingName, handler);
+            super(handler);
             mContext = context;
             mSettingName = settingName;
         }
 
+        private void register() {
+            register(mContext, Settings.Secure.getUriFor(mSettingName));
+        }
+
         public int getValueForUser(int defaultValue, int userId) {
             return Settings.Secure.getIntForUser(mContext.getContentResolver(), mSettingName,
                     defaultValue, userId);
         }
-
-        @Override
-        protected Uri getUriFor(String settingName) {
-            return Settings.Secure.getUriFor(settingName);
-        }
     }
 
     private static class StringListCachedSecureSetting extends ObservingSetting {
@@ -354,31 +374,44 @@
         private final Context mContext;
         private final String mSettingName;
 
-        private int mCachedUserId = UserHandle.USER_NULL;
+        @GuardedBy("this")
+        private int mCachedUserId;
+        @GuardedBy("this")
         private List<String> mCachedValue;
 
         private StringListCachedSecureSetting(Context context, String settingName,
                 Handler handler) {
-            super(context, settingName, handler);
+            super(handler);
             mContext = context;
             mSettingName = settingName;
+
+            mCachedUserId = UserHandle.USER_NULL;
+        }
+
+        public synchronized void register() {
+            register(mContext, Settings.Secure.getUriFor(mSettingName));
         }
 
         public synchronized List<String> getValueForUser(int userId) {
             Preconditions.checkArgument(userId != UserHandle.USER_NULL);
 
+            List<String> value = mCachedValue;
             if (userId != mCachedUserId) {
                 String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                         mSettingName, userId);
                 if (TextUtils.isEmpty(setting)) {
-                    mCachedValue = Collections.emptyList();
+                    value = Collections.emptyList();
                 } else {
-                    mCachedValue = Arrays.asList(setting.split(","));
+                    value = Arrays.asList(setting.split(","));
                 }
-                mCachedUserId = userId;
+
+                if (isRegistered()) {
+                    mCachedUserId = userId;
+                    mCachedValue = value;
+                }
             }
 
-            return mCachedValue;
+            return value;
         }
 
         public synchronized void invalidateForUser(int userId) {
@@ -393,11 +426,6 @@
             invalidateForUser(userId);
             super.onChange(selfChange, uri, userId);
         }
-
-        @Override
-        protected Uri getUriFor(String settingName) {
-            return Settings.Secure.getUriFor(settingName);
-        }
     }
 
     private static class LongGlobalSetting extends ObservingSetting {
@@ -406,20 +434,19 @@
         private final String mSettingName;
 
         private LongGlobalSetting(Context context, String settingName, Handler handler) {
-            super(context, settingName, handler);
+            super(handler);
             mContext = context;
             mSettingName = settingName;
         }
 
+        public void register() {
+            register(mContext, Settings.Global.getUriFor(mSettingName));
+        }
+
         public long getValue(long defaultValue) {
             return Settings.Global.getLong(mContext.getContentResolver(), mSettingName,
                     defaultValue);
         }
-
-        @Override
-        protected Uri getUriFor(String settingName) {
-            return Settings.Global.getUriFor(settingName);
-        }
     }
 
     private static class StringSetCachedGlobalSetting extends ObservingSetting {
@@ -428,29 +455,42 @@
         private final String mSettingName;
         private final Supplier<ArraySet<String>> mBaseValuesSupplier;
 
+        @GuardedBy("this")
         private boolean mValid;
+        @GuardedBy("this")
         private ArraySet<String> mCachedValue;
 
         private StringSetCachedGlobalSetting(Context context, String settingName,
                 Supplier<ArraySet<String>> baseValuesSupplier, Handler handler) {
-            super(context, settingName, handler);
+            super(handler);
             mContext = context;
             mSettingName = settingName;
             mBaseValuesSupplier = baseValuesSupplier;
+
+            mValid = false;
+        }
+
+        public synchronized void register() {
+            register(mContext, Settings.Global.getUriFor(mSettingName));
         }
 
         public synchronized Set<String> getValue() {
+            ArraySet<String> value = mCachedValue;
             if (!mValid) {
-                mCachedValue = new ArraySet<>(mBaseValuesSupplier.get());
+                value = new ArraySet<>(mBaseValuesSupplier.get());
                 String setting = Settings.Global.getString(mContext.getContentResolver(),
                         mSettingName);
                 if (!TextUtils.isEmpty(setting)) {
-                    mCachedValue.addAll(Arrays.asList(setting.split(",")));
+                    value.addAll(Arrays.asList(setting.split(",")));
                 }
-                mValid = true;
+
+                if (isRegistered()) {
+                    mValid = true;
+                    mCachedValue = value;
+                }
             }
 
-            return mCachedValue;
+            return value;
         }
 
         public synchronized void invalidate() {
@@ -463,10 +503,5 @@
             invalidate();
             super.onChange(selfChange, uri, userId);
         }
-
-        @Override
-        protected Uri getUriFor(String settingName) {
-            return Settings.Global.getUriFor(settingName);
-        }
     }
 }
diff --git a/services/core/java/com/android/server/LocationUsageLogger.java b/services/core/java/com/android/server/location/LocationUsageLogger.java
similarity index 72%
rename from services/core/java/com/android/server/LocationUsageLogger.java
rename to services/core/java/com/android/server/location/LocationUsageLogger.java
index a8a3cc4..755438b 100644
--- a/services/core/java/com/android/server/LocationUsageLogger.java
+++ b/services/core/java/com/android/server/location/LocationUsageLogger.java
@@ -14,37 +14,113 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.location;
+
+import static com.android.server.LocationManagerService.TAG;
 
 import android.app.ActivityManager;
 import android.location.Geofence;
 import android.location.LocationManager;
 import android.location.LocationRequest;
-import android.os.SystemClock;
 import android.stats.location.LocationStatsEnums;
 import android.util.Log;
 import android.util.StatsLog;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.time.Instant;
 
 /**
  * Logger for Location API usage logging.
  */
 public class LocationUsageLogger {
-    private static final String TAG = "LocationUsageLogger";
-    private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int ONE_SEC_IN_MILLIS = 1000;
     private static final int ONE_MINUTE_IN_MILLIS = 60000;
     private static final int ONE_HOUR_IN_MILLIS = 3600000;
 
-    private long mLastApiUsageLogHour = 0;
-
-    private int mApiUsageLogHourlyCount = 0;
-
     private static final int API_USAGE_LOG_HOURLY_CAP = 60;
 
-    private static int providerNameToStatsdEnum(String provider) {
+    @GuardedBy("this")
+    private long mLastApiUsageLogHour = 0;
+    @GuardedBy("this")
+    private int mApiUsageLogHourlyCount = 0;
+
+    /**
+     * Log a location API usage event.
+     */
+    public void logLocationApiUsage(int usageType, int apiInUse,
+            String packageName, LocationRequest locationRequest,
+            boolean hasListener, boolean hasIntent,
+            Geofence geofence, int activityImportance) {
+        try {
+            if (hitApiUsageLogCap()) {
+                return;
+            }
+
+            boolean isLocationRequestNull = locationRequest == null;
+            boolean isGeofenceNull = geofence == null;
+
+            StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType,
+                    apiInUse, packageName,
+                    isLocationRequestNull
+                        ? LocationStatsEnums.PROVIDER_UNKNOWN
+                        : bucketizeProvider(locationRequest.getProvider()),
+                    isLocationRequestNull
+                        ? LocationStatsEnums.QUALITY_UNKNOWN
+                        : locationRequest.getQuality(),
+                    isLocationRequestNull
+                        ? LocationStatsEnums.INTERVAL_UNKNOWN
+                        : bucketizeInterval(locationRequest.getInterval()),
+                    isLocationRequestNull
+                        ? LocationStatsEnums.DISTANCE_UNKNOWN
+                        : bucketizeDistance(
+                                locationRequest.getSmallestDisplacement()),
+                    isLocationRequestNull ? 0 : locationRequest.getNumUpdates(),
+                    // only log expireIn for USAGE_STARTED
+                    isLocationRequestNull || usageType == LocationStatsEnums.USAGE_ENDED
+                        ? LocationStatsEnums.EXPIRATION_UNKNOWN
+                        : bucketizeExpireIn(locationRequest.getExpireIn()),
+                    getCallbackType(apiInUse, hasListener, hasIntent),
+                    isGeofenceNull
+                        ? LocationStatsEnums.RADIUS_UNKNOWN
+                        : bucketizeRadius(geofence.getRadius()),
+                    categorizeActivityImportance(activityImportance));
+        } catch (Exception e) {
+            // Swallow exceptions to avoid crashing LMS.
+            Log.w(TAG, "Failed to log API usage to statsd.", e);
+        }
+    }
+
+    /**
+     * Log a location API usage event.
+     */
+    public void logLocationApiUsage(int usageType, int apiInUse, String providerName) {
+        try {
+            if (hitApiUsageLogCap()) {
+                return;
+            }
+
+            StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType, apiInUse,
+                    /* package_name= */ null,
+                    bucketizeProvider(providerName),
+                    LocationStatsEnums.QUALITY_UNKNOWN,
+                    LocationStatsEnums.INTERVAL_UNKNOWN,
+                    LocationStatsEnums.DISTANCE_UNKNOWN,
+                    /* numUpdates= */ 0,
+                    LocationStatsEnums.EXPIRATION_UNKNOWN,
+                    getCallbackType(
+                            apiInUse,
+                            /* isListenerNull= */ true,
+                            /* isIntentNull= */ true),
+                    /* bucketizedRadius= */ 0,
+                    LocationStatsEnums.IMPORTANCE_UNKNOWN);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to log API usage to statsd.", e);
+        }
+    }
+
+    private static int bucketizeProvider(String provider) {
         if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
             return LocationStatsEnums.PROVIDER_NETWORK;
         } else if (LocationManager.GPS_PROVIDER.equals(provider)) {
@@ -58,8 +134,7 @@
         }
     }
 
-    private static int bucketizeIntervalToStatsdEnum(long interval) {
-        // LocationManager already converts negative values to 0.
+    private static int bucketizeInterval(long interval) {
         if (interval < ONE_SEC_IN_MILLIS) {
             return LocationStatsEnums.INTERVAL_BETWEEN_0_SEC_AND_1_SEC;
         } else if (interval < ONE_SEC_IN_MILLIS * 5) {
@@ -75,9 +150,8 @@
         }
     }
 
-    private static int bucketizeSmallestDisplacementToStatsdEnum(float smallestDisplacement) {
-        // LocationManager already converts negative values to 0.
-        if (smallestDisplacement == 0) {
+    private static int bucketizeDistance(float smallestDisplacement) {
+        if (smallestDisplacement <= 0) {
             return LocationStatsEnums.DISTANCE_ZERO;
         } else if (smallestDisplacement > 0 && smallestDisplacement <= 100) {
             return LocationStatsEnums.DISTANCE_BETWEEN_0_AND_100;
@@ -86,7 +160,7 @@
         }
     }
 
-    private static int bucketizeRadiusToStatsdEnum(float radius) {
+    private static int bucketizeRadius(float radius) {
         if (radius < 0) {
             return LocationStatsEnums.RADIUS_NEGATIVE;
         } else if (radius < 100) {
@@ -104,14 +178,11 @@
         }
     }
 
-    private static int getBucketizedExpireIn(long expireAt) {
-        if (expireAt == Long.MAX_VALUE) {
+    private static int bucketizeExpireIn(long expireIn) {
+        if (expireIn == Long.MAX_VALUE) {
             return LocationStatsEnums.EXPIRATION_NO_EXPIRY;
         }
 
-        long elapsedRealtime = SystemClock.elapsedRealtime();
-        long expireIn = Math.max(0, expireAt - elapsedRealtime);
-
         if (expireIn < 20 * ONE_SEC_IN_MILLIS) {
             return LocationStatsEnums.EXPIRATION_BETWEEN_0_AND_20_SEC;
         } else if (expireIn < ONE_MINUTE_IN_MILLIS) {
@@ -129,8 +200,8 @@
         if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
             return LocationStatsEnums.IMPORTANCE_TOP;
         } else if (importance == ActivityManager
-                                    .RunningAppProcessInfo
-                                    .IMPORTANCE_FOREGROUND_SERVICE) {
+                .RunningAppProcessInfo
+                .IMPORTANCE_FOREGROUND_SERVICE) {
             return LocationStatsEnums.IMPORTANCE_FORGROUND_SERVICE;
         } else {
             return LocationStatsEnums.IMPORTANCE_BACKGROUND;
@@ -154,117 +225,16 @@
         }
     }
 
-    // Update the hourly count of APIUsage log event.
-    // Returns false if hit the hourly log cap.
-    private boolean checkApiUsageLogCap() {
-        if (D) {
-            Log.d(TAG, "checking APIUsage log cap.");
-        }
-
+    private synchronized boolean hitApiUsageLogCap() {
         long currentHour = Instant.now().toEpochMilli() / ONE_HOUR_IN_MILLIS;
         if (currentHour > mLastApiUsageLogHour) {
             mLastApiUsageLogHour = currentHour;
             mApiUsageLogHourlyCount = 0;
-            return true;
+            return false;
         } else {
             mApiUsageLogHourlyCount = Math.min(
-                mApiUsageLogHourlyCount + 1, API_USAGE_LOG_HOURLY_CAP);
-            return mApiUsageLogHourlyCount < API_USAGE_LOG_HOURLY_CAP;
-        }
-    }
-
-    /**
-     * Log a Location API usage event to Statsd.
-     * Logging event is capped at 60 per hour. Usage events exceeding
-     * the cap will be dropped by LocationUsageLogger.
-     */
-    public void logLocationApiUsage(int usageType, int apiInUse,
-            String packageName, LocationRequest locationRequest,
-            boolean hasListener, boolean hasIntent,
-            Geofence geofence, int activityImportance) {
-        try {
-            if (!checkApiUsageLogCap()) {
-                return;
-            }
-
-            boolean isLocationRequestNull = locationRequest == null;
-            boolean isGeofenceNull = geofence == null;
-            if (D) {
-                Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: "
-                        + apiInUse + ", packageName: " + (packageName == null ? "" : packageName)
-                        + ", locationRequest: "
-                        + (isLocationRequestNull ? "" : locationRequest.toString())
-                        + ", hasListener: " + hasListener
-                        + ", hasIntent: " + hasIntent
-                        + ", geofence: "
-                        + (isGeofenceNull ? "" : geofence.toString())
-                        + ", importance: " + activityImportance);
-            }
-
-            StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType,
-                    apiInUse, packageName,
-                    isLocationRequestNull
-                        ? LocationStatsEnums.PROVIDER_UNKNOWN
-                        : providerNameToStatsdEnum(locationRequest.getProvider()),
-                    isLocationRequestNull
-                        ? LocationStatsEnums.QUALITY_UNKNOWN
-                        : locationRequest.getQuality(),
-                    isLocationRequestNull
-                        ? LocationStatsEnums.INTERVAL_UNKNOWN
-                        : bucketizeIntervalToStatsdEnum(locationRequest.getInterval()),
-                    isLocationRequestNull
-                        ? LocationStatsEnums.DISTANCE_UNKNOWN
-                        : bucketizeSmallestDisplacementToStatsdEnum(
-                                locationRequest.getSmallestDisplacement()),
-                    isLocationRequestNull ? 0 : locationRequest.getNumUpdates(),
-                    // only log expireIn for USAGE_STARTED
-                    isLocationRequestNull || usageType == LocationStatsEnums.USAGE_ENDED
-                        ? LocationStatsEnums.EXPIRATION_UNKNOWN
-                        : getBucketizedExpireIn(locationRequest.getExpireAt()),
-                    getCallbackType(apiInUse, hasListener, hasIntent),
-                    isGeofenceNull
-                        ? LocationStatsEnums.RADIUS_UNKNOWN
-                        : bucketizeRadiusToStatsdEnum(geofence.getRadius()),
-                    categorizeActivityImportance(activityImportance));
-        } catch (Exception e) {
-            // Swallow exceptions to avoid crashing LMS.
-            Log.w(TAG, "Failed to log API usage to statsd.", e);
-        }
-    }
-
-    /**
-     * Log a Location API usage event to Statsd.
-     * Logging event is capped at 60 per hour. Usage events exceeding
-     * the cap will be dropped by LocationUsageLogger.
-     */
-    public void logLocationApiUsage(int usageType, int apiInUse, String providerName) {
-        try {
-            if (!checkApiUsageLogCap()) {
-                return;
-            }
-
-            if (D) {
-                Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: "
-                        + apiInUse + ", providerName: " + providerName);
-            }
-
-            StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType, apiInUse,
-                    /* package_name= */ null,
-                    providerNameToStatsdEnum(providerName),
-                    LocationStatsEnums.QUALITY_UNKNOWN,
-                    LocationStatsEnums.INTERVAL_UNKNOWN,
-                    LocationStatsEnums.DISTANCE_UNKNOWN,
-                    /* numUpdates= */ 0,
-                    LocationStatsEnums.EXPIRATION_UNKNOWN,
-                    getCallbackType(
-                            apiInUse,
-                            /* isListenerNull= */ true,
-                            /* isIntentNull= */ true),
-                    /* bucketizedRadius= */ 0,
-                    LocationStatsEnums.IMPORTANCE_UNKNOWN);
-        } catch (Exception e) {
-            // Swallow exceptions to avoid crashing LMS.
-            Log.w(TAG, "Failed to log API usage to statsd.", e);
+                    mApiUsageLogHourlyCount + 1, API_USAGE_LOG_HOURLY_CAP);
+            return mApiUsageLogHourlyCount >= API_USAGE_LOG_HOURLY_CAP;
         }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index c25e206..2cb7e3b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -70,6 +70,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -1042,6 +1043,13 @@
         private boolean mVoiceButtonHandled = false;
 
         @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+            (new MediaShellCommand()).exec(this, in, out, err, args, callback,
+                    resultReceiver);
+        }
+
+        @Override
         public ISession createSession(String packageName, ISessionCallback cb, String tag,
                 Bundle sessionInfo, int userId) throws RemoteException {
             final int pid = Binder.getCallingPid();
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/services/core/java/com/android/server/media/MediaShellCommand.java
similarity index 64%
rename from cmds/media/src/com/android/commands/media/Media.java
rename to services/core/java/com/android/server/media/MediaShellCommand.java
index 1e915f8..20df271 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -1,29 +1,27 @@
 /*
-**
-** Copyright 2013, 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.
-*/
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-package com.android.commands.media;
+package com.android.server.media;
 
 import android.app.ActivityThread;
 import android.content.Context;
 import android.media.MediaMetadata;
 import android.media.session.ISessionManager;
 import android.media.session.MediaController;
-import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.MediaSession.QueueItem;
+import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
@@ -31,59 +29,41 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ShellCommand;
 import android.os.SystemClock;
-import android.util.AndroidException;
+import android.text.TextUtils;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 
-import com.android.internal.os.BaseCommand;
-
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.io.PrintStream;
+import java.io.PrintWriter;
 import java.util.List;
 
-public class Media extends BaseCommand {
+/**
+ * ShellCommand for MediaSessionService.
+ */
+public class MediaShellCommand extends ShellCommand {
     // This doesn't belongs to any package. Setting the package name to empty string.
     private static final String PACKAGE_NAME = "";
     private static ActivityThread sThread;
     private static MediaSessionManager sMediaSessionManager;
     private ISessionManager mSessionService;
-
-    /**
-     * Command-line entry point.
-     *
-     * @param args The command-line arguments
-     */
-    public static void main(String[] args) {
-        (new Media()).run(args);
-    }
+    private PrintWriter mWriter;
+    private PrintWriter mErrorWriter;
 
     @Override
-    public void onShowUsage(PrintStream out) {
-        out.println(
-                "usage: media [subcommand] [options]\n" +
-                "       media dispatch KEY\n" +
-                "       media list-sessions\n" +
-                "       media monitor <tag>\n" +
-                "       media volume [options]\n" +
-                "\n" +
-                "media dispatch: dispatch a media key to the system.\n" +
-                "                KEY may be: play, pause, play-pause, mute, headsethook,\n" +
-                "                stop, next, previous, rewind, record, fast-forword.\n" +
-                "media list-sessions: print a list of the current sessions.\n" +
-                        "media monitor: monitor updates to the specified session.\n" +
-                "                       Use the tag from list-sessions.\n" +
-                "media volume:  " + VolumeCtrl.USAGE
-        );
-    }
+    public int onCommand(String cmd) {
+        mWriter = getOutPrintWriter();
+        mErrorWriter = getErrPrintWriter();
 
-    @Override
-    public void onRun() throws Exception {
+        if (TextUtils.isEmpty(cmd)) {
+            return handleDefaultCommands(cmd);
+        }
         if (sThread == null) {
-            Looper.prepareMainLooper();
+            Looper.prepare();
             sThread = ActivityThread.systemMain();
             Context context = sThread.getSystemContext();
             sMediaSessionManager =
@@ -92,25 +72,47 @@
         mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService(
                 Context.MEDIA_SESSION_SERVICE));
         if (mSessionService == null) {
-            System.err.println(NO_SYSTEM_ERROR_CODE);
-            throw new AndroidException(
+            throw new IllegalStateException(
                     "Can't connect to media session service; is the system running?");
         }
 
-        String op = nextArgRequired();
-
-        if (op.equals("dispatch")) {
-            runDispatch();
-        } else if (op.equals("list-sessions")) {
-            runListSessions();
-        } else if (op.equals("monitor")) {
-            runMonitor();
-        } else if (op.equals("volume")) {
-            runVolume();
-        } else {
-            showError("Error: unknown command '" + op + "'");
-            return;
+        try {
+            if (cmd.equals("dispatch")) {
+                runDispatch();
+            } else if (cmd.equals("list-sessions")) {
+                runListSessions();
+            } else if (cmd.equals("monitor")) {
+                runMonitor();
+            } else if (cmd.equals("volume")) {
+                runVolume();
+            } else {
+                showError("Error: unknown command '" + cmd + "'");
+                return -1;
+            }
+        } catch (Exception e) {
+            showError(e.toString());
+            return -1;
         }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        mWriter.println("usage: media_session [subcommand] [options]");
+        mWriter.println("       media_session dispatch KEY");
+        mWriter.println("       media_session dispatch KEY");
+        mWriter.println("       media_session list-sessions");
+        mWriter.println("       media_session monitor <tag>");
+        mWriter.println("       media_session volume [options]");
+        mWriter.println();
+        mWriter.println("media_session dispatch: dispatch a media key to the system.");
+        mWriter.println("                KEY may be: play, pause, play-pause, mute, headsethook,");
+        mWriter.println("                stop, next, previous, rewind, record, fast-forword.");
+        mWriter.println("media_session list-sessions: print a list of the current sessions.");
+        mWriter.println("media_session monitor: monitor updates to the specified session.");
+        mWriter.println("                       Use the tag from list-sessions.");
+        mWriter.println("media_session volume:  " + VolumeCtrl.USAGE);
+        mWriter.println();
     }
 
     private void sendMediaKey(KeyEvent event) {
@@ -121,7 +123,7 @@
     }
 
     private void runMonitor() throws Exception {
-        String id = nextArgRequired();
+        String id = getNextArgRequired();
         if (id == null) {
             showError("Error: must include a session id");
             return;
@@ -133,7 +135,8 @@
             for (MediaController controller : controllers) {
                 try {
                     if (controller != null && id.equals(controller.getTag())) {
-                        ControllerMonitor monitor = new ControllerMonitor(controller);
+                        MediaShellCommand.ControllerMonitor monitor =
+                                new MediaShellCommand.ControllerMonitor(controller);
                         monitor.run();
                         success = true;
                         break;
@@ -143,15 +146,15 @@
                 }
             }
         } catch (Exception e) {
-            System.out.println("***Error monitoring session*** " + e.getMessage());
+            mErrorWriter.println("***Error monitoring session*** " + e.getMessage());
         }
         if (!success) {
-            System.out.println("No session found with id " + id);
+            mErrorWriter.println("No session found with id " + id);
         }
     }
 
     private void runDispatch() throws Exception {
-        String cmd = nextArgRequired();
+        String cmd = getNextArgRequired();
         int keycode;
         if ("play".equals(cmd)) {
             keycode = KeyEvent.KEYCODE_MEDIA_PLAY;
@@ -186,68 +189,73 @@
                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
     }
 
+    void showError(String errMsg) {
+        onHelp();
+        mErrorWriter.println(errMsg);
+    }
+
     class ControllerCallback extends MediaController.Callback {
         @Override
         public void onSessionDestroyed() {
-            System.out.println("onSessionDestroyed. Enter q to quit.");
+            mWriter.println("onSessionDestroyed. Enter q to quit.");
         }
 
         @Override
         public void onSessionEvent(String event, Bundle extras) {
-            System.out.println("onSessionEvent event=" + event + ", extras=" + extras);
+            mWriter.println("onSessionEvent event=" + event + ", extras=" + extras);
         }
 
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
-            System.out.println("onPlaybackStateChanged " + state);
+            mWriter.println("onPlaybackStateChanged " + state);
         }
 
         @Override
         public void onMetadataChanged(MediaMetadata metadata) {
             String mmString = metadata == null ? null : "title=" + metadata
                     .getDescription();
-            System.out.println("onMetadataChanged " + mmString);
+            mWriter.println("onMetadataChanged " + mmString);
         }
 
         @Override
-        public void onQueueChanged(List<QueueItem> queue) {
-            System.out.println("onQueueChanged, "
+        public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+            mWriter.println("onQueueChanged, "
                     + (queue == null ? "null queue" : " size=" + queue.size()));
         }
 
         @Override
         public void onQueueTitleChanged(CharSequence title) {
-            System.out.println("onQueueTitleChange " + title);
+            mWriter.println("onQueueTitleChange " + title);
         }
 
         @Override
         public void onExtrasChanged(Bundle extras) {
-            System.out.println("onExtrasChanged " + extras);
+            mWriter.println("onExtrasChanged " + extras);
         }
 
         @Override
-        public void onAudioInfoChanged(PlaybackInfo info) {
-            System.out.println("onAudioInfoChanged " + info);
+        public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
+            mWriter.println("onAudioInfoChanged " + info);
         }
     }
 
     private class ControllerMonitor {
         private final MediaController mController;
-        private final ControllerCallback mControllerCallback;
+        private final MediaShellCommand.ControllerCallback mControllerCallback;
 
         ControllerMonitor(MediaController controller) {
             mController = controller;
-            mControllerCallback = new ControllerCallback();
+            mControllerCallback = new MediaShellCommand.ControllerCallback();
         }
 
         void printUsageMessage() {
             try {
-                System.out.println("V2Monitoring session " + mController.getTag()
+                mWriter.println("V2Monitoring session " + mController.getTag()
                         + "...  available commands: play, pause, next, previous");
             } catch (RuntimeException e) {
-                System.out.println("Error trying to monitor session!");
+                mWriter.println("Error trying to monitor session!");
             }
-            System.out.println("(q)uit: finish monitoring");
+            mWriter.println("(q)uit: finish monitoring");
         }
 
         void run() throws RemoteException {
@@ -258,7 +266,7 @@
                     try {
                         mController.registerCallback(mControllerCallback);
                     } catch (RuntimeException e) {
-                        System.out.println("Error registering monitor callback");
+                        mErrorWriter.println("Error registering monitor callback");
                     }
                 }
             };
@@ -284,7 +292,7 @@
                     } else if ("previous".equals(line)) {
                         dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
                     } else {
-                        System.out.println("Invalid command: " + line);
+                        mErrorWriter.println("Invalid command: " + line);
                     }
 
                     synchronized (this) {
@@ -316,19 +324,19 @@
                 mController.dispatchMediaButtonEvent(down);
                 mController.dispatchMediaButtonEvent(up);
             } catch (RuntimeException e) {
-                System.out.println("Failed to dispatch " + keyCode);
+                mErrorWriter.println("Failed to dispatch " + keyCode);
             }
         }
     }
 
     private void runListSessions() {
-        System.out.println("Sessions:");
+        mWriter.println("Sessions:");
         try {
             List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
             for (MediaController controller : controllers) {
                 if (controller != null) {
                     try {
-                        System.out.println("  tag=" + controller.getTag()
+                        mWriter.println("  tag=" + controller.getTag()
                                 + ", package=" + controller.getPackageName());
                     } catch (RuntimeException e) {
                         // ignore
@@ -336,7 +344,7 @@
                 }
             }
         } catch (Exception e) {
-            System.out.println("***Error listing sessions***");
+            mErrorWriter.println("***Error listing sessions***");
         }
     }
 
diff --git a/cmds/media/src/com/android/commands/media/VolumeCtrl.java b/services/core/java/com/android/server/media/VolumeCtrl.java
old mode 100755
new mode 100644
similarity index 64%
rename from cmds/media/src/com/android/commands/media/VolumeCtrl.java
rename to services/core/java/com/android/server/media/VolumeCtrl.java
index 1629c6f..7a26665
--- a/cmds/media/src/com/android/commands/media/VolumeCtrl.java
+++ b/services/core/java/com/android/server/media/VolumeCtrl.java
@@ -1,21 +1,20 @@
 /*
-**
-** Copyright 2016, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-package com.android.commands.media;
+package com.android.server.media;
 
 import android.content.Context;
 import android.media.AudioManager;
@@ -26,45 +25,41 @@
 
 import com.android.internal.os.BaseCommand;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.lang.ArrayIndexOutOfBoundsException;
-
 /**
  * Command line tool to exercise AudioService.setStreamVolume()
  *                           and AudioService.adjustStreamVolume()
  */
 public class VolumeCtrl {
 
-    private final static String TAG = "VolumeCtrl";
+    private static final String TAG = "VolumeCtrl";
 
     // --stream affects --set, --adj or --get options.
     // --show affects --set and --adj options.
     // --get can be used with --set, --adj or by itself.
-    public final static String USAGE = new String(
-            "the options are as follows: \n" +
-            "\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n" +
-            "\t\t                controls AudioManager.STREAM_MUSIC if no stream is specified\n"+
-            "\t\t--set INDEX     sets the volume index value\n" +
-            "\t\t--adj DIRECTION adjusts the volume, use raise|same|lower for the direction\n" +
-            "\t\t--get           outputs the current volume\n" +
-            "\t\t--show          shows the UI during the volume change\n" +
-            "\texamples:\n" +
-            "\t\tadb shell media volume --show --stream 3 --set 11\n" +
-            "\t\tadb shell media volume --stream 0 --adj lower\n" +
-            "\t\tadb shell media volume --stream 3 --get\n"
-            );
+    public static final String USAGE = new String("the options are as follows: \n"
+            + "\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n"
+            + "\t\t                controls AudioManager.STREAM_MUSIC if no stream is specified\n"
+            + "\t\t--set INDEX     sets the volume index value\n"
+            + "\t\t--adj DIRECTION adjusts the volume, use raise|same|lower for the direction\n"
+            + "\t\t--get           outputs the current volume\n"
+            + "\t\t--show          shows the UI during the volume change\n"
+            + "\texamples:\n"
+            + "\t\tadb shell media volume --show --stream 3 --set 11\n"
+            + "\t\tadb shell media volume --stream 0 --adj lower\n"
+            + "\t\tadb shell media volume --stream 3 --get\n"
+    );
 
-    private final static int VOLUME_CONTROL_MODE_SET = 1;
-    private final static int VOLUME_CONTROL_MODE_ADJUST = 2;
+    private static final int VOLUME_CONTROL_MODE_SET = 1;
+    private static final int VOLUME_CONTROL_MODE_ADJUST = 2;
 
-    private final static String ADJUST_LOWER = "lower";
-    private final static String ADJUST_SAME = "same";
-    private final static String ADJUST_RAISE = "raise";
+    private static final String ADJUST_LOWER = "lower";
+    private static final String ADJUST_SAME = "same";
+    private static final String ADJUST_RAISE = "raise";
 
-    public static void run(BaseCommand cmd) throws Exception {
+    /**
+     * Runs a given MediaShellCommand
+     */
+    public static void run(MediaShellCommand cmd) throws Exception {
         //----------------------------------------
         // Default parameters
         int stream = AudioManager.STREAM_MUSIC;
@@ -78,7 +73,7 @@
         // read options
         String option;
         String adjustment = null;
-        while ((option = cmd.nextOption()) != null) {
+        while ((option = cmd.getNextOption()) != null) {
             switch (option) {
                 case "--show":
                     showUi = true;
@@ -88,17 +83,17 @@
                     log(LOG_V, "will get volume");
                     break;
                 case "--stream":
-                    stream = Integer.decode(cmd.nextArgRequired()).intValue();
+                    stream = Integer.decode(cmd.getNextArgRequired()).intValue();
                     log(LOG_V, "will control stream=" + stream + " (" + streamName(stream) + ")");
                     break;
                 case "--set":
-                    volIndex = Integer.decode(cmd.nextArgRequired()).intValue();
+                    volIndex = Integer.decode(cmd.getNextArgRequired()).intValue();
                     mode = VOLUME_CONTROL_MODE_SET;
                     log(LOG_V, "will set volume to index=" + volIndex);
                     break;
                 case "--adj":
                     mode = VOLUME_CONTROL_MODE_ADJUST;
-                    adjustment = cmd.nextArgRequired();
+                    adjustment = cmd.getNextArgRequired();
                     log(LOG_V, "will adjust volume");
                     break;
                 default:
@@ -140,7 +135,7 @@
             if ((volIndex > audioService.getStreamMaxVolume(stream))
                     || (volIndex < audioService.getStreamMinVolume(stream))) {
                 cmd.showError(String.format("Error: invalid volume index %d for stream %d "
-                        + "(should be in [%d..%d])", volIndex, stream,
+                                + "(should be in [%d..%d])", volIndex, stream,
                         audioService.getStreamMinVolume(stream),
                         audioService.getStreamMaxVolume(stream)));
                 return;
@@ -149,7 +144,7 @@
 
         //----------------------------------------
         // Non-interactive test
-        final int flag = showUi? AudioManager.FLAG_SHOW_UI : 0;
+        final int flag = showUi ? AudioManager.FLAG_SHOW_UI : 0;
         final String pack = cmd.getClass().getPackage().getName();
         if (mode == VOLUME_CONTROL_MODE_SET) {
             audioService.setStreamVolume(stream, volIndex, flag, pack/*callingPackage*/);
@@ -157,9 +152,9 @@
             audioService.adjustStreamVolume(stream, adjDir, flag, pack);
         }
         if (doGet) {
-            log(LOG_V, "volume is " + audioService.getStreamVolume(stream) +
-                       " in range [" + audioService.getStreamMinVolume(stream) +
-                       ".." + audioService.getStreamMaxVolume(stream) + "]");
+            log(LOG_V, "volume is " + audioService.getStreamVolume(stream)
+                    + " in range [" + audioService.getStreamMinVolume(stream)
+                    + ".." + audioService.getStreamMaxVolume(stream) + "]");
         }
     }
 
@@ -181,5 +176,4 @@
             return "invalid stream";
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index bc05154..7098435 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.provider.Settings;
 import android.telecom.TelecomManager;
 
 import com.android.internal.util.NotificationMessagingUtil;
@@ -55,14 +54,9 @@
         final boolean isLeftHighImportance = leftImportance >= IMPORTANCE_DEFAULT;
         final boolean isRightHighImportance = rightImportance >= IMPORTANCE_DEFAULT;
 
-        // With new interruption model, prefer importance bucket above all other criteria
-        // (to ensure buckets are contiguous)
-        if (Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) == 1) {
-            if (isLeftHighImportance != isRightHighImportance) {
-                // by importance bucket, high importance higher than low importance
-                return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
-            }
+        if (isLeftHighImportance != isRightHighImportance) {
+            // by importance bucket, high importance higher than low importance
+            return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
         }
 
         // first all colorized notifications
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index b57bfa0..e968fb70 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1289,6 +1289,18 @@
         return getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
     }
 
+    public boolean hasUndecoratedRemoteView() {
+        Notification notification = getNotification();
+        Class<? extends Notification.Style> style = notification.getNotificationStyle();
+        boolean hasDecoratedStyle = style != null
+                && (Notification.DecoratedCustomViewStyle.class.equals(style)
+                || Notification.DecoratedMediaCustomViewStyle.class.equals(style));
+        boolean hasCustomRemoteView = notification.contentView != null
+                || notification.bigContentView != null
+                || notification.headsUpContentView != null;
+        return hasCustomRemoteView && !hasDecoratedStyle;
+    }
+
     @VisibleForTesting
     static final class Light {
         public final int color;
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index ac8d1a9..d1fe0d9 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -149,7 +149,7 @@
             stats.numPostedByApp++;
             stats.updateInterarrivalEstimate(now);
             stats.countApiUse(notification);
-            stats.numUndecoratedRemoteViews += (isUndecoratedRemoteView(notification) ? 1 : 0);
+            stats.numUndecoratedRemoteViews += (notification.hasUndecoratedRemoteView() ? 1 : 0);
         }
         releaseAggregatedStatsLocked(aggregatedStatsArray);
         if (ENABLE_SQLITE_LOG) {
@@ -158,13 +158,6 @@
     }
 
     /**
-     * Does this notification use RemoveViews without a platform decoration?
-     */
-    protected static boolean isUndecoratedRemoteView(NotificationRecord notification) {
-        return (notification.getNotification().getNotificationStyle() == null);
-    }
-
-    /**
      * Called when a notification has been updated.
      */
     public synchronized void registerUpdatedByApp(NotificationRecord notification,
@@ -1303,7 +1296,7 @@
             } else {
                 putPosttimeVisibility(r, cv);
             }
-            cv.put(COL_UNDECORATED, (isUndecoratedRemoteView(r) ? 1 : 0));
+            cv.put(COL_UNDECORATED, (r.hasUndecoratedRemoteView() ? 1 : 0));
             SQLiteDatabase db = mHelper.getWritableDatabase();
             if (db.insert(TAB_LOG, null, cv) < 0) {
                 Log.wtf(TAG, "Error while trying to insert values: " + cv);
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 2b4b409..5ae8c58 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -17,11 +17,11 @@
 package com.android.server.pm;
 
 import android.annotation.IntDef;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.apex.ApexInfo;
 import android.apex.ApexInfoList;
 import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
 import android.apex.IApexService;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -176,13 +176,9 @@
      * enough for it to be activated at the next boot, the caller needs to call
      * {@link #markStagedSessionReady(int)}.
      *
-     * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
-     * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
-     *                        an array of identifiers of all the child sessions. Otherwise it should
-     *                        be an empty array.
      * @throws PackageManagerException if call to apexd fails
      */
-    abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
+    abstract ApexInfoList submitStagedSession(ApexSessionParams params)
             throws PackageManagerException;
 
     /**
@@ -450,11 +446,10 @@
         }
 
         @Override
-        ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
-                throws PackageManagerException {
+        ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException {
             try {
                 final ApexInfoList apexInfoList = new ApexInfoList();
-                mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
+                mApexService.submitStagedSession(params, apexInfoList);
                 return apexInfoList;
             } catch (RemoteException re) {
                 Slog.e(TAG, "Unable to contact apexservice", re);
@@ -686,7 +681,7 @@
         }
 
         @Override
-        ApexInfoList submitStagedSession(int sessionId, int[] childSessionIds)
+        ApexInfoList submitStagedSession(ApexSessionParams params)
                 throws PackageManagerException {
             throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                     "Device doesn't support updating APEX");
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index f2a2e65..13bd7e5 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -627,11 +627,13 @@
         final int providersSize = ArrayUtils.size(pkg.getProviders());
         StringBuilder r = null;
         for (int i = 0; i < providersSize; i++) {
-            EffectiveProvider p = new EffectiveProvider(pkg.getProviders().get(i));
+            ParsedProvider p = pkg.getProviders().get(i);
             mProviders.addProvider(p);
             if (p.getAuthority() != null) {
                 String[] names = p.getAuthority().split(";");
-                p.setEffectiveAuthority(null);
+
+                // TODO(b/135203078): Remove this mutation
+                p.setAuthority(null);
                 for (int j = 0; j < names.length; j++) {
                     if (j == 1 && p.isSyncable()) {
                         // We only want the first authority for a provider to possibly be
@@ -641,15 +643,15 @@
                         // to a provider that we don't want to change.
                         // Only do this for the second authority since the resulting provider
                         // object can be the same for all future authorities for this provider.
-                        p = new EffectiveProvider(p);
-                        p.setEffectiveSyncable(false);
+                        p = new ParsedProvider(p);
+                        p.setSyncable(false);
                     }
                     if (!mProvidersByAuthority.containsKey(names[j])) {
                         mProvidersByAuthority.put(names[j], p);
                         if (p.getAuthority() == null) {
-                            p.setEffectiveAuthority(names[j]);
+                            p.setAuthority(names[j]);
                         } else {
-                            p.setEffectiveAuthority(p.getAuthority() + ";" + names[j]);
+                            p.setAuthority(p.getAuthority() + ";" + names[j]);
                         }
                         if (DEBUG_PACKAGE_SCANNING && chatty) {
                             Log.d(TAG, "Registered content provider: " + names[j]
@@ -2113,35 +2115,4 @@
             return info.authoritiesIterator();
         }
     }
-
-    // TODO(b/135203078): Document or remove this if possible.
-    class EffectiveProvider extends ParsedProvider {
-
-        private String mEffectiveAuthority;
-        private boolean mEffectiveSyncable;
-
-        public EffectiveProvider(ParsedProvider parsedProvider) {
-            this.setFrom(parsedProvider);
-            this.mEffectiveAuthority = parsedProvider.getAuthority();
-            this.mEffectiveSyncable = parsedProvider.isSyncable();
-        }
-
-        public void setEffectiveAuthority(String authority) {
-            this.mEffectiveAuthority = authority;
-        }
-
-        public void setEffectiveSyncable(boolean syncable) {
-            this.mEffectiveSyncable = syncable;
-        }
-
-        @Override
-        public String getAuthority() {
-            return mEffectiveAuthority;
-        }
-
-        @Override
-        public boolean isSyncable() {
-            return mEffectiveSyncable;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 642b500..17b1daf 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -13487,10 +13487,12 @@
             }
             Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
             try {
-                if (bm.isBackupServiceActive(userId)) {
+                if (bm.isUserReadyForBackup(userId)) {
                     bm.restoreAtInstallForUser(
                             userId, res.pkg.getAppInfoPackageName(), token);
                 } else {
+                    Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
+                            + "didn't take place.");
                     return false;
                 }
             } catch (RemoteException e) {
@@ -14144,7 +14146,7 @@
                     verification.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);
 
                     verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
-                            installSource.installerPackageName);
+                            installSource.initiatingPackageName);
 
                     verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS,
                             installFlags);
@@ -15707,7 +15709,9 @@
                 // TODO(patb): create a more descriptive reason than unknown in future release
                 // mark all non-failure installs as UNKNOWN so we do not treat them as success
                 for (InstallRequest request : requests) {
-                    request.installResult.freezer.close();
+                    if (request.installResult.freezer != null) {
+                        request.installResult.freezer.close();
+                    }
                     if (request.installResult.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                         request.installResult.returnCode = PackageManager.INSTALL_UNKNOWN;
                     }
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 7ea4e98..688c34f 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -21,6 +21,7 @@
 import android.apex.ApexInfo;
 import android.apex.ApexInfoList;
 import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
@@ -36,6 +37,8 @@
 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.ParceledListSlice;
 import android.content.rollback.IRollbackManager;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -49,6 +52,7 @@
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.annotations.GuardedBy;
@@ -82,6 +86,9 @@
     @GuardedBy("mStagedSessions")
     private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
 
+    @GuardedBy("mStagedSessions")
+    private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
+
     StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
         mPi = pi;
         mApexManager = am;
@@ -166,18 +173,32 @@
 
     private List<PackageInfo> submitSessionToApexService(
             @NonNull PackageInstallerSession session) throws PackageManagerException {
-        final IntArray childSessionsIds = new IntArray();
+        final IntArray childSessionIds = new IntArray();
         if (session.isMultiPackage()) {
             for (int id : session.getChildSessionIds()) {
                 if (isApexSession(mStagedSessions.get(id))) {
-                    childSessionsIds.add(id);
+                    childSessionIds.add(id);
+                }
+            }
+        }
+        ApexSessionParams apexSessionParams = new ApexSessionParams();
+        apexSessionParams.sessionId = session.sessionId;
+        apexSessionParams.childSessionIds = childSessionIds.toArray();
+        if (session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
+            apexSessionParams.isRollback = true;
+            apexSessionParams.rollbackId = retrieveRollbackIdForCommitSession(session.sessionId);
+        } else {
+            synchronized (mStagedSessions) {
+                int rollbackId = mSessionRollbackIds.get(session.sessionId, -1);
+                if (rollbackId != -1) {
+                    apexSessionParams.hasRollbackEnabled = true;
+                    apexSessionParams.rollbackId = rollbackId;
                 }
             }
         }
         // submitStagedSession will throw a PackageManagerException if apexd verification fails,
         // which will be propagated to populate stagedSessionErrorMessage of this session.
-        final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId,
-                childSessionsIds.toArray());
+        final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
         final List<PackageInfo> result = new ArrayList<>();
         for (ApexInfo apexInfo : apexInfoList.apexInfos) {
             final PackageInfo packageInfo;
@@ -208,6 +229,19 @@
         return result;
     }
 
+    private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
+        RollbackManager rm = mContext.getSystemService(RollbackManager.class);
+
+        List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
+        for (RollbackInfo rollback : rollbacks) {
+            if (rollback.getCommittedSessionId() == sessionId) {
+                return rollback.getRollbackId();
+            }
+        }
+        throw new PackageManagerException(
+                "Could not find rollback id for commit session: " + sessionId);
+    }
+
     private void checkRequiredVersionCode(final PackageInstallerSession session,
             final PackageInfo activePackage) throws PackageManagerException {
         if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
@@ -633,6 +667,7 @@
     void abortSession(@NonNull PackageInstallerSession session) {
         synchronized (mStagedSessions) {
             mStagedSessions.remove(session.sessionId);
+            mSessionRollbackIds.delete(session.sessionId);
         }
     }
 
@@ -865,6 +900,28 @@
          */
         private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
             Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
+
+            if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
+                // If rollback is enabled for this session, we call through to the RollbackManager
+                // with the list of sessions it must enable rollback for. Note that
+                // notifyStagedSession is a synchronous operation.
+                final IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                        ServiceManager.getService(Context.ROLLBACK_SERVICE));
+                try {
+                    // NOTE: To stay consistent with the non-staged install flow, we don't fail the
+                    // entire install if rollbacks can't be enabled.
+                    int rollbackId = rm.notifyStagedSession(session.sessionId);
+                    if (rollbackId != -1) {
+                        synchronized (mStagedSessions) {
+                            mSessionRollbackIds.put(session.sessionId, rollbackId);
+                        }
+                    }
+                } catch (RemoteException re) {
+                    Slog.e(TAG, "Failed to notifyStagedSession for session: "
+                            + session.sessionId, re);
+                }
+            }
+
             notifyPreRebootVerification_Start_Complete(session.sessionId);
         }
 
@@ -929,25 +986,6 @@
          * </ul></p>
          */
         private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
-            if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
-                // If rollback is enabled for this session, we call through to the RollbackManager
-                // with the list of sessions it must enable rollback for. Note that
-                // notifyStagedSession is a synchronous operation.
-                final IRollbackManager rm = IRollbackManager.Stub.asInterface(
-                        ServiceManager.getService(Context.ROLLBACK_SERVICE));
-                try {
-                    // NOTE: To stay consistent with the non-staged install flow, we don't fail the
-                    // entire install if rollbacks can't be enabled.
-                    if (!rm.notifyStagedSession(session.sessionId)) {
-                        Slog.e(TAG, "Unable to enable rollback for session: "
-                                + session.sessionId);
-                    }
-                } catch (RemoteException re) {
-                    Slog.e(TAG, "Failed to notifyStagedSession for session: "
-                            + session.sessionId, re);
-                }
-            }
-
             // Proactively mark session as ready before calling apexd. Although this call order
             // looks counter-intuitive, this is the easiest way to ensure that session won't end up
             // in the inconsistent state:
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ad4411c..4d436c0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -579,15 +579,6 @@
             applyUserRestrictionsLR(UserHandle.USER_SYSTEM);
         }
 
-        UserInfo currentGuestUser = findCurrentGuestUser();
-        if (currentGuestUser != null && !hasUserRestriction(
-                UserManager.DISALLOW_CONFIG_WIFI, currentGuestUser.id)) {
-            // If a guest user currently exists, apply the DISALLOW_CONFIG_WIFI option
-            // to it, in case this guest was created in a previous version where this
-            // user restriction was not a default guest restriction.
-            setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, currentGuestUser.id);
-        }
-
         mContext.registerReceiver(mDisableQuietModeCallback,
                 new IntentFilter(ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK),
                 null, mHandler);
@@ -1118,11 +1109,6 @@
             info.flags ^= UserInfo.FLAG_ADMIN;
             writeUserLP(getUserDataLU(info.id));
         }
-
-        // Remove non-admin restrictions.
-        // Keep synchronized with createUserEvenWhenDisallowed.
-        setUserRestriction(UserManager.DISALLOW_SMS, false, userId);
-        setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, userId);
     }
 
     /**
@@ -1602,10 +1588,12 @@
     private void initDefaultGuestRestrictions() {
         synchronized (mGuestRestrictions) {
             if (mGuestRestrictions.isEmpty()) {
-                mGuestRestrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, true);
-                mGuestRestrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
-                mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
-                mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+                UserTypeDetails guestType = mUserTypes.get(UserManager.USER_TYPE_FULL_GUEST);
+                if (guestType == null) {
+                    Slog.wtf(LOG_TAG, "Can't set default guest restrictions: type doesn't exist.");
+                    return;
+                }
+                guestType.addDefaultRestrictionsTo(mGuestRestrictions);
             }
         }
     }
@@ -2494,6 +2482,12 @@
                         mDevicePolicyLocalUserRestrictions, mDevicePolicyGlobalUserRestrictions
                 );
             }
+            // DISALLOW_CONFIG_WIFI was made a default guest restriction some time during version 6.
+            final UserInfo currentGuestUser = findCurrentGuestUser();
+            if (currentGuestUser != null && !hasUserRestriction(
+                    UserManager.DISALLOW_CONFIG_WIFI, currentGuestUser.id)) {
+                setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, currentGuestUser.id);
+            }
             userVersion = 7;
         }
 
@@ -3245,12 +3239,15 @@
                 writeUserLP(userData);
             }
             updateUserIds();
+
             Bundle restrictions = new Bundle();
-            // TODO(b/142482943): Generalize this using UserTypeDetails default restrictions.
             if (isGuest) {
+                // Guest default restrictions can be modified via setDefaultGuestRestrictions.
                 synchronized (mGuestRestrictions) {
                     restrictions.putAll(mGuestRestrictions);
                 }
+            } else {
+                userTypeDetails.addDefaultRestrictionsTo(restrictions);
             }
             synchronized (mRestrictionsLock) {
                 mBaseUserRestrictions.append(userId, restrictions);
@@ -4666,14 +4663,8 @@
         @Override
         public UserInfo createUserEvenWhenDisallowed(String name, @NonNull String userType,
                 @UserInfoFlag int flags, String[] disallowedPackages) {
-            UserInfo user = createUserInternalUnchecked(name, userType, flags,
+            return createUserInternalUnchecked(name, userType, flags,
                     UserHandle.USER_NULL, /* preCreated= */ false, disallowedPackages);
-            // Keep this in sync with UserManager.createUser
-            if (user != null && !user.isAdmin() && !user.isDemo()) {
-                setUserRestriction(UserManager.DISALLOW_SMS, true, user.id);
-                setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, true, user.id);
-            }
-            return user;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 1631df3..826cd3f 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -19,18 +19,17 @@
 import android.annotation.ColorRes;
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
 import android.content.res.Resources;
+import android.os.Bundle;
 import android.os.UserManager;
 
 import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 
 /**
  * Contains the details about a multiuser "user type", such as a
@@ -75,8 +74,13 @@
     /** The {@link UserInfo.UserInfoFlag}s that all users of this type will automatically have. */
     private final @UserInfoFlag int mDefaultUserInfoPropertyFlags;
 
-    // TODO(b/142482943): Hook these up to something and set them for each type.
-    private final List<String> mDefaultRestrictions;
+    /**
+     * List of User Restrictions to apply by default to newly created users of this type.
+     * <p>Does not apply to SYSTEM users (since they are not formally created); for them use
+     * {@link com.android.internal.R.array#config_defaultFirstUserRestrictions} instead.
+     * The Bundle is of the form used by {@link UserRestrictionsUtils}.
+     */
+    private final @Nullable Bundle mDefaultRestrictions;
 
 
     // Fields for profiles only, controlling the nature of their badges.
@@ -99,7 +103,7 @@
      *
      * <p>Must be set if mIconBadge is set.
      */
-    private final int[] mBadgeLabels;
+    private final @Nullable int[] mBadgeLabels;
 
     /**
      * Resource ID ({@link ColorRes}) of the colors badge put on icons.
@@ -110,22 +114,21 @@
      *
      * <p>Must be set if mIconBadge is set.
      */
-    private final int[] mBadgeColors;
+    private final @Nullable int[] mBadgeColors;
 
     private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
             @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
             int maxAllowedPerParent,
             int iconBadge, int badgePlain, int badgeNoBackground,
-            int[] badgeLabels, int[] badgeColors,
-            ArrayList<String> defaultRestrictions) {
+            @Nullable int[] badgeLabels, @Nullable int[] badgeColors,
+            @Nullable Bundle defaultRestrictions) {
         this.mName = name;
         this.mEnabled = enabled;
         this.mMaxAllowed = maxAllowed;
         this.mMaxAllowedPerParent = maxAllowedPerParent;
         this.mBaseType = baseType;
         this.mDefaultUserInfoPropertyFlags = defaultUserInfoPropertyFlags;
-        this.mDefaultRestrictions =
-                Collections.unmodifiableList(new ArrayList<>(defaultRestrictions));
+        this.mDefaultRestrictions = defaultRestrictions;
 
         this.mIconBadge = iconBadge;
         this.mBadgePlain = badgePlain;
@@ -180,7 +183,7 @@
         return mIconBadge != Resources.ID_NULL;
     }
 
-    /** Resource ID of the badge put on icons. */
+    /** Resource ID of the badge to put on icons. */
     public @DrawableRes int getIconBadge() {
         return mIconBadge;
     }
@@ -231,9 +234,14 @@
         return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
     }
 
-    // TODO(b/142482943): Hook this up and don't return the original.
-    public List<String> getDefaultRestrictions() {
-        return mDefaultRestrictions;
+    /** Returns a Bundle representing the default user restrictions. */
+    @NonNull Bundle getDefaultRestrictions() {
+        return UserRestrictionsUtils.clone(mDefaultRestrictions);
+    }
+
+    /** Adds the default user restrictions to the given bundle of restrictions. */
+    public void addDefaultRestrictionsTo(@NonNull Bundle currentRestrictions) {
+        UserRestrictionsUtils.merge(currentRestrictions, mDefaultRestrictions);
     }
 
     /** Dumps details of the UserTypeDetails. Do not parse this. */
@@ -247,7 +255,8 @@
         pw.print(prefix); pw.print("mDefaultUserInfoFlags: ");
         pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags));
         pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel);
-        pw.print(prefix); pw.print("mDefaultRestrictions: "); pw.println(mDefaultRestrictions);
+        pw.print(prefix); pw.println("mDefaultRestrictions: ");
+        UserRestrictionsUtils.dumpRestrictions(pw, prefix + "    ", mDefaultRestrictions);
         pw.print(prefix); pw.print("mIconBadge: "); pw.println(mIconBadge);
         pw.print(prefix); pw.print("mBadgePlain: "); pw.println(mBadgePlain);
         pw.print(prefix); pw.print("mBadgeNoBackground: "); pw.println(mBadgeNoBackground);
@@ -265,14 +274,14 @@
         private int mMaxAllowed = UNLIMITED_NUMBER_OF_USERS;
         private int mMaxAllowedPerParent = UNLIMITED_NUMBER_OF_USERS;
         private int mDefaultUserInfoPropertyFlags = 0;
-        private ArrayList<String> mDefaultRestrictions = new ArrayList<>();
+        private @Nullable Bundle mDefaultRestrictions = null;
         private boolean mEnabled = true;
         private int mLabel = Resources.ID_NULL;
-        private int[] mBadgeLabels = null;
-        private int[] mBadgeColors = null;
-        private int mIconBadge = Resources.ID_NULL;
-        private int mBadgePlain = Resources.ID_NULL;
-        private int mBadgeNoBackground = Resources.ID_NULL;
+        private @Nullable int[] mBadgeLabels = null;
+        private @Nullable int[] mBadgeColors = null;
+        private @DrawableRes int mIconBadge = Resources.ID_NULL;
+        private @DrawableRes int mBadgePlain = Resources.ID_NULL;
+        private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
 
         public Builder setName(String name) {
             mName = name;
@@ -304,27 +313,27 @@
             return this;
         }
 
-        public Builder setBadgeLabels(int ... badgeLabels) {
+        public Builder setBadgeLabels(@StringRes int ... badgeLabels) {
             mBadgeLabels = badgeLabels;
             return this;
         }
 
-        public Builder setBadgeColors(int ... badgeColors) {
+        public Builder setBadgeColors(@ColorRes int ... badgeColors) {
             mBadgeColors = badgeColors;
             return this;
         }
 
-        public Builder setIconBadge(int badgeIcon) {
+        public Builder setIconBadge(@DrawableRes int badgeIcon) {
             mIconBadge = badgeIcon;
             return this;
         }
 
-        public Builder setBadgePlain(int badgePlain) {
+        public Builder setBadgePlain(@DrawableRes int badgePlain) {
             mBadgePlain = badgePlain;
             return this;
         }
 
-        public Builder setBadgeNoBackground(int badgeNoBackground) {
+        public Builder setBadgeNoBackground(@DrawableRes int badgeNoBackground) {
             mBadgeNoBackground = badgeNoBackground;
             return this;
         }
@@ -334,11 +343,15 @@
             return this;
         }
 
-        public Builder setDefaultRestrictions(ArrayList<String> restrictions) {
+        public Builder setDefaultRestrictions(@Nullable Bundle restrictions) {
             mDefaultRestrictions = restrictions;
             return this;
         }
 
+        @UserInfoFlag int getBaseType() {
+            return mBaseType;
+        }
+
         public UserTypeDetails createUserTypeDetails() {
             Preconditions.checkArgument(mName != null,
                     "Cannot create a UserTypeDetails with no name.");
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 43bbab1..7d45516 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -34,18 +34,36 @@
 
 import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
 
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
 import android.os.UserManager;
 import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.function.Consumer;
 
 /**
  * Class for creating all {@link UserTypeDetails} on the device.
  *
+ * This class is responsible both for defining the AOSP use types, as well as reading in customized
+ * user types from {@link com.android.internal.R.xml#config_user_types}.
+ *
  * Tests are located in UserManagerServiceUserTypeTest.java.
  * @hide
  */
 public final class UserTypeFactory {
 
+    private static final String LOG_TAG = "UserTypeFactory";
+
     /** This is a utility class, so no instantiable constructor. */
     private UserTypeFactory() {}
 
@@ -55,21 +73,25 @@
      * @return mapping from the name of each user type to its {@link UserTypeDetails} object
      */
     public static ArrayMap<String, UserTypeDetails> getUserTypes() {
-        final ArrayMap<String, UserTypeDetails> map = new ArrayMap<>();
-        // TODO(b/142482943): Read an xml file for OEM customized types.
-        //                    Remember to disallow "android." namespace
-        // TODO(b/142482943): Read an xml file to get any overrides for the built-in types.
-        final int maxManagedProfiles = 1;
-        map.put(USER_TYPE_PROFILE_MANAGED,
-                getDefaultTypeProfileManaged().setMaxAllowedPerParent(maxManagedProfiles)
-                        .createUserTypeDetails());
-        map.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeSystemFull().createUserTypeDetails());
-        map.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary().createUserTypeDetails());
-        map.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest().createUserTypeDetails());
-        map.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo().createUserTypeDetails());
-        map.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted().createUserTypeDetails());
-        map.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless().createUserTypeDetails());
-        return map;
+        final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+        builders.put(USER_TYPE_PROFILE_MANAGED, getDefaultTypeProfileManaged());
+        builders.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeFullSystem());
+        builders.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary());
+        builders.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest());
+        builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo());
+        builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted());
+        builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless());
+
+        try (XmlResourceParser parser =
+                     Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) {
+            customizeBuilders(builders, parser);
+        }
+
+        final ArrayMap<String, UserTypeDetails> types = new ArrayMap<>(builders.size());
+        for (int i = 0; i < builders.size(); i++) {
+            types.put(builders.keyAt(i), builders.valueAt(i).createUserTypeDetails());
+        }
+        return types;
     }
 
     /**
@@ -93,7 +115,8 @@
                 .setBadgeColors(
                         com.android.internal.R.color.profile_badge_1,
                         com.android.internal.R.color.profile_badge_2,
-                        com.android.internal.R.color.profile_badge_3);
+                        com.android.internal.R.color.profile_badge_3)
+                .setDefaultRestrictions(null);
     }
 
     /**
@@ -104,7 +127,8 @@
         return new UserTypeDetails.Builder()
                 .setName(USER_TYPE_FULL_SECONDARY)
                 .setBaseType(FLAG_FULL)
-                .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS);
+                .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
+                .setDefaultRestrictions(getDefaultSecondaryUserRestrictions());
     }
 
     /**
@@ -115,13 +139,12 @@
                 .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
         final int flags = FLAG_GUEST | (ephemeralGuests ? FLAG_EPHEMERAL : 0);
 
-        // TODO(b/142482943): Put UMS.initDefaultGuestRestrictions() here; then fetch them from here
-
         return new UserTypeDetails.Builder()
                 .setName(USER_TYPE_FULL_GUEST)
                 .setBaseType(FLAG_FULL)
                 .setDefaultUserInfoPropertyFlags(flags)
-                .setMaxAllowed(1);
+                .setMaxAllowed(1)
+                .setDefaultRestrictions(getDefaultGuestUserRestrictions());
     }
 
     /**
@@ -132,7 +155,8 @@
                 .setName(USER_TYPE_FULL_DEMO)
                 .setBaseType(FLAG_FULL)
                 .setDefaultUserInfoPropertyFlags(FLAG_DEMO)
-                .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS);
+                .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
+                .setDefaultRestrictions(null);
     }
 
     /**
@@ -144,13 +168,15 @@
                 .setName(USER_TYPE_FULL_RESTRICTED)
                 .setBaseType(FLAG_FULL)
                 .setDefaultUserInfoPropertyFlags(FLAG_RESTRICTED)
-                .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS);
+                .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
+                // NB: UserManagerService.createRestrictedProfile() applies hardcoded restrictions.
+                .setDefaultRestrictions(null);
     }
 
     /**
      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SYSTEM} configuration.
      */
-    private static UserTypeDetails.Builder getDefaultTypeSystemFull() {
+    private static UserTypeDetails.Builder getDefaultTypeFullSystem() {
         return new UserTypeDetails.Builder()
                 .setName(USER_TYPE_FULL_SYSTEM)
                 .setBaseType(FLAG_SYSTEM | FLAG_FULL);
@@ -165,4 +191,193 @@
                 .setName(USER_TYPE_SYSTEM_HEADLESS)
                 .setBaseType(FLAG_SYSTEM);
     }
+
+    private static Bundle getDefaultSecondaryUserRestrictions() {
+        final Bundle restrictions = new Bundle();
+        restrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
+        restrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+        return restrictions;
+    }
+
+    private static Bundle getDefaultGuestUserRestrictions() {
+        // Guest inherits the secondary user's restrictions, plus has some extra ones.
+        final Bundle restrictions = getDefaultSecondaryUserRestrictions();
+        restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, true);
+        restrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
+        return restrictions;
+    }
+
+    /**
+     * Reads the given xml parser to obtain device user-type customization, and updates the given
+     * map of {@link UserTypeDetails.Builder}s accordingly.
+     * <p>
+     * The xml file can specify the attributes according to the set... methods below.
+     */
+    @VisibleForTesting
+    static void customizeBuilders(ArrayMap<String, UserTypeDetails.Builder> builders,
+            XmlResourceParser parser) {
+        try {
+            XmlUtils.beginDocument(parser, "user-types");
+            for (XmlUtils.nextElement(parser);
+                    parser.getEventType() != XmlResourceParser.END_DOCUMENT;
+                    XmlUtils.nextElement(parser)) {
+                final boolean isProfile;
+                final String elementName = parser.getName();
+                if ("profile-type".equals(elementName)) {
+                    isProfile = true;
+                } else if ("full-type".equals(elementName)) {
+                    isProfile = false;
+                } else {
+                    Slog.w(LOG_TAG, "Skipping unknown element " + elementName + " in "
+                                + parser.getPositionDescription());
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+
+                String typeName = parser.getAttributeValue(null, "name");
+                if (typeName == null) {
+                    Slog.w(LOG_TAG, "Skipping user type with no name in "
+                            + parser.getPositionDescription());
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+                typeName.intern();
+
+                UserTypeDetails.Builder builder;
+                if (typeName.startsWith("android.")) {
+                    // typeName refers to a AOSP-defined type which we are modifying.
+                    Slog.i(LOG_TAG, "Customizing user type " + typeName);
+                    builder = builders.get(typeName);
+                    if (builder == null) {
+                        throw new IllegalArgumentException("Illegal custom user type name "
+                                + typeName + ": Non-AOSP user types cannot start with 'android.'");
+                    }
+                    final boolean isValid =
+                            (isProfile && builder.getBaseType() == UserInfo.FLAG_PROFILE)
+                            || (!isProfile && builder.getBaseType() == UserInfo.FLAG_FULL);
+                    if (!isValid) {
+                        throw new IllegalArgumentException("Wrong base type to customize user type "
+                                + "(" + typeName + "), which is type "
+                                + UserInfo.flagsToString(builder.getBaseType()));
+                    }
+                } else if (isProfile) {
+                    // typeName refers to a new OEM-defined profile type which we are defining.
+                    Slog.i(LOG_TAG, "Creating custom user type " + typeName);
+                    builder = new UserTypeDetails.Builder();
+                    builder.setName(typeName);
+                    builder.setBaseType(FLAG_PROFILE);
+                    builders.put(typeName, builder);
+                } else {
+                    throw new IllegalArgumentException("Creation of non-profile user type "
+                            + "(" + typeName + ") is not currently supported.");
+                }
+
+                // Process the attributes (other than name).
+                if (isProfile) {
+                    setIntAttribute(parser, "max-allowed-per-parent",
+                            builder::setMaxAllowedPerParent);
+                    setResAttribute(parser, "icon-badge", builder::setIconBadge);
+                    setResAttribute(parser, "badge-plain", builder::setBadgePlain);
+                    setResAttribute(parser, "badge-no-background", builder::setBadgeNoBackground);
+                }
+
+                // Process child elements.
+                final int depth = parser.getDepth();
+                while (XmlUtils.nextElementWithin(parser, depth)) {
+                    final String childName = parser.getName();
+                    if ("default-restrictions".equals(childName)) {
+                        final Bundle restrictions = UserRestrictionsUtils.readRestrictions(parser);
+                        builder.setDefaultRestrictions(restrictions);
+                    } else if (isProfile && "badge-labels".equals(childName)) {
+                        setResAttributeArray(parser, builder::setBadgeLabels);
+                    } else if (isProfile && "badge-colors".equals(childName)) {
+                        setResAttributeArray(parser, builder::setBadgeColors);
+                    } else {
+                        Slog.w(LOG_TAG, "Unrecognized tag " + childName + " in "
+                                + parser.getPositionDescription());
+                    }
+                }
+            }
+        } catch (XmlPullParserException | IOException e) {
+            Slog.w(LOG_TAG, "Cannot read user type configuration file.", e);
+        }
+    }
+
+    /**
+     * If the given attribute exists, gets the int stored in it and performs the given fcn using it.
+     * The stored value must be an int or NumberFormatException will be thrown.
+     *
+     * @param parser xml parser from which to read the attribute
+     * @param attributeName name of the attribute
+     * @param fcn one-int-argument function,
+     *            like {@link UserTypeDetails.Builder#setMaxAllowedPerParent(int)}
+     */
+    private static void setIntAttribute(XmlResourceParser parser, String attributeName,
+            Consumer<Integer> fcn) {
+        final String intValue = parser.getAttributeValue(null, attributeName);
+        if (intValue == null) {
+            return;
+        }
+        try {
+            fcn.accept(Integer.parseInt(intValue));
+        } catch (NumberFormatException e) {
+            Slog.e(LOG_TAG, "Cannot parse value of '" + intValue + "' for " + attributeName
+                    + " in " + parser.getPositionDescription(), e);
+            throw e;
+        }
+    }
+
+    /**
+     * If the given attribute exists, gets the resId stored in it (or 0 if it is not a valid resId)
+     * and performs the given fcn using it.
+     *
+     * @param parser xml parser from which to read the attribute
+     * @param attributeName name of the attribute
+     * @param fcn one-argument function, like {@link UserTypeDetails.Builder#setIconBadge(int)}
+     */
+    private static void setResAttribute(XmlResourceParser parser, String attributeName,
+            Consumer<Integer> fcn) {
+        if (parser.getAttributeValue(null, attributeName) == null) {
+            // Attribute is not present, i.e. use the default value.
+            return;
+        }
+        final int resId = parser.getAttributeResourceValue(null, attributeName, Resources.ID_NULL);
+        fcn.accept(resId);
+    }
+
+    /**
+     * Gets the resIds stored in "item" elements (in their "res" attribute) at the current depth.
+     * Then performs the given fcn using the int[] array of these resIds.
+     * <p>
+     * Each xml element is expected to be of the form {@code <item res="someResValue" />}.
+     *
+     * @param parser xml parser from which to read the elements and their attributes
+     * @param fcn function, like {@link UserTypeDetails.Builder#setBadgeColors(int...)}
+     */
+    private static void setResAttributeArray(XmlResourceParser parser, Consumer<int[]> fcn)
+            throws IOException, XmlPullParserException {
+
+        ArrayList<Integer> resList = new ArrayList<>();
+        final int depth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, depth)) {
+            final String elementName = parser.getName();
+            if (!"item".equals(elementName)) {
+                Slog.w(LOG_TAG, "Skipping unknown child element " + elementName + " in "
+                        + parser.getPositionDescription());
+                XmlUtils.skipCurrentTag(parser);
+                continue;
+            }
+            final int resId = parser.getAttributeResourceValue(null, "res", -1);
+            if (resId == -1) {
+                continue;
+            }
+            resList.add(resId);
+        }
+
+        int[] result = new int[resList.size()];
+        for (int i = 0; i < resList.size(); i++) {
+            result[i] = resList.get(i);
+        }
+        fcn.accept(result);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5adb648..8ce1a52 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -321,7 +321,10 @@
         public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
                 int uid) {
             onPermissionUpdated(updatedUserIds, sync);
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+            for (int i = 0; i < updatedUserIds.length; i++) {
+                int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid));
+                mOnPermissionChangeListeners.onPermissionsChanged(userUid);
+            }
         }
         public void onInstallPermissionUpdatedNotifyListener(int uid) {
             onInstallPermissionUpdated();
@@ -733,7 +736,8 @@
             // Install and runtime permissions are stored in different places,
             // so figure out what permission changed and persist the change.
             if (permissionsState.getInstallPermissionState(permName) != null) {
-                callback.onInstallPermissionUpdatedNotifyListener(pkg.getUid());
+                int userUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+                callback.onInstallPermissionUpdatedNotifyListener(userUid);
             } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
                     || hadState) {
                 callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8435a82..c05b704 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -157,6 +157,7 @@
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UEventObserver;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
@@ -4603,6 +4604,7 @@
     public void screenTurningOn(final ScreenOnListener screenOnListener) {
         if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turning on...");
 
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */);
         updateScreenOffSleepToken(false);
         mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
 
@@ -4665,6 +4667,7 @@
         if (!mDefaultDisplayPolicy.finishScreenTurningOn()) {
             return; // Spurious or not ready yet.
         }
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */);
 
         final boolean enableScreen;
         final boolean awake = mDefaultDisplayPolicy.isAwake();
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 6da8fb4..cc1cddd 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -41,12 +41,12 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.TimingsTraceLog;
 import android.view.WindowManager;
 
-import com.android.internal.telephony.ITelephony;
 import com.android.server.LocalServices;
 import com.android.server.RescueParty;
 import com.android.server.pm.PackageManagerService;
@@ -586,19 +586,15 @@
                 TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog();
                 boolean radioOff;
 
-                final ITelephony phone =
-                        ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+                TelephonyManager telephonyManager = mContext.getSystemService(
+                        TelephonyManager.class);
 
-                try {
-                    radioOff = phone == null || !phone.needMobileRadioShutdown();
-                    if (!radioOff) {
-                        Log.w(TAG, "Turning off cellular radios...");
-                        metricStarted(METRIC_RADIO);
-                        phone.shutdownMobileRadios();
-                    }
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "RemoteException during radio shutdown", ex);
-                    radioOff = true;
+                radioOff = telephonyManager == null
+                        || !telephonyManager.isAnyRadioPoweredOn();
+                if (!radioOff) {
+                    Log.w(TAG, "Turning off cellular radios...");
+                    metricStarted(METRIC_RADIO);
+                    telephonyManager.shutdownAllRadios();
                 }
 
                 Log.i(TAG, "Waiting for Radio...");
@@ -613,12 +609,7 @@
                     }
 
                     if (!radioOff) {
-                        try {
-                            radioOff = !phone.needMobileRadioShutdown();
-                        } catch (RemoteException ex) {
-                            Log.e(TAG, "RemoteException during radio shutdown", ex);
-                            radioOff = true;
-                        }
+                        radioOff = !telephonyManager.isAnyRadioPoweredOn();
                         if (radioOff) {
                             Log.i(TAG, "Radio turned off.");
                             metricEnded(METRIC_RADIO);
diff --git a/services/core/java/com/android/server/role/FinancialSmsManager.java b/services/core/java/com/android/server/role/FinancialSmsManager.java
deleted file mode 100644
index 2ec3993..0000000
--- a/services/core/java/com/android/server/role/FinancialSmsManager.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.role;
-
-import android.Manifest;
-import android.annotation.MainThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.sms.FinancialSmsService;
-import android.service.sms.IFinancialSmsService;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * This class binds to {@code FinancialSmsService}.
- */
-final class FinancialSmsManager {
-
-    private static final String TAG = "FinancialSmsManager";
-
-    private final Context mContext;
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private ServiceConnection mServiceConnection;
-
-    @GuardedBy("mLock")
-    private IFinancialSmsService mRemoteService;
-
-    @GuardedBy("mLock")
-    private ArrayList<Command> mQueuedCommands;
-
-    FinancialSmsManager(Context context) {
-        mContext = context;
-    }
-
-    @Nullable
-    ServiceInfo getServiceInfo() {
-        final String packageName =
-                mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
-        if (packageName == null) {
-            Slog.w(TAG, "no external services package!");
-            return null;
-        }
-
-        final Intent intent = new Intent(FinancialSmsService.ACTION_FINANCIAL_SERVICE_INTENT);
-        intent.setPackage(packageName);
-        final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
-                PackageManager.GET_SERVICES);
-        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
-            Slog.w(TAG, "No valid components found.");
-            return null;
-        }
-        return resolveInfo.serviceInfo;
-    }
-
-    @Nullable
-    private ComponentName getServiceComponentName() {
-        final ServiceInfo serviceInfo = getServiceInfo();
-        if (serviceInfo == null) return null;
-
-        final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
-        if (!Manifest.permission.BIND_FINANCIAL_SMS_SERVICE.equals(serviceInfo.permission)) {
-            Slog.w(TAG, name.flattenToShortString() + " does not require permission "
-                    + Manifest.permission.BIND_FINANCIAL_SMS_SERVICE);
-            return null;
-        }
-
-        return name;
-    }
-
-    void reset() {
-        synchronized (mLock) {
-            if (mServiceConnection != null) {
-                mContext.unbindService(mServiceConnection);
-                mServiceConnection = null;
-            } else {
-                Slog.d(TAG, "reset(): service is not bound. Do nothing.");
-            }
-        }
-    }
-
-    /**
-     * Run a command, starting the service connection if necessary.
-     */
-    private void connectAndRun(@NonNull Command command) {
-        synchronized (mLock) {
-            if (mRemoteService != null) {
-                try {
-                    command.run(mRemoteService);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "exception calling service: " + e);
-                }
-                return;
-            } else {
-                if (mQueuedCommands == null) {
-                    mQueuedCommands = new ArrayList<>(1);
-                }
-                mQueuedCommands.add(command);
-                // If we're already connected, don't create a new connection, just leave - the
-                // command will be run when the service connects
-                if (mServiceConnection != null) return;
-            }
-
-            // Create the connection
-            mServiceConnection = new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName name, IBinder service) {
-                    synchronized (mLock) {
-                        mRemoteService = IFinancialSmsService.Stub.asInterface(service);
-                        if (mQueuedCommands != null) {
-                            final int size = mQueuedCommands.size();
-                            for (int i = 0; i < size; i++) {
-                                final Command queuedCommand = mQueuedCommands.get(i);
-                                try {
-                                    queuedCommand.run(mRemoteService);
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "exception calling " + name + ": " + e);
-                                }
-                            }
-                            mQueuedCommands = null;
-                        }
-                    }
-                }
-
-                @Override
-                @MainThread
-                public void onServiceDisconnected(ComponentName name) {
-                    synchronized (mLock) {
-                        mRemoteService = null;
-                    }
-                }
-
-                @Override
-                public void onBindingDied(ComponentName name) {
-                    synchronized (mLock) {
-                        mRemoteService = null;
-                    }
-                }
-
-                @Override
-                public void onNullBinding(ComponentName name) {
-                    synchronized (mLock) {
-                        mRemoteService = null;
-                    }
-                }
-            };
-
-            final ComponentName component = getServiceComponentName();
-            if (component != null) {
-                final Intent intent = new Intent();
-                intent.setComponent(component);
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE,
-                            UserHandle.getUserHandleForUid(UserHandle.getCallingUserId()));
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-            }
-        }
-    }
-
-    void getSmsMessages(RemoteCallback callback, @Nullable Bundle params) {
-        connectAndRun((service) -> service.getSmsMessages(callback, params));
-    }
-
-    void dump(String prefix, PrintWriter pw) {
-        final ComponentName impl = getServiceComponentName();
-        pw.print(prefix); pw.print("User ID: "); pw.println(UserHandle.getCallingUserId());
-        pw.print(prefix); pw.print("Queued commands: ");
-        if (mQueuedCommands == null) {
-            pw.println("N/A");
-        } else {
-            pw.println(mQueuedCommands.size());
-        }
-        pw.print(prefix); pw.print("Implementation: ");
-        if (impl == null) {
-            pw.println("N/A");
-            return;
-        }
-        pw.println(impl.flattenToShortString());
-    }
-
-    private interface Command {
-        void run(IFinancialSmsService service) throws RemoteException;
-    }
-}
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index a4eef9b..c4522e0 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -50,7 +50,6 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
-import android.service.sms.FinancialSmsService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 8b79c3f..6898e1c 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -418,6 +418,7 @@
                 if (isStaged()) {
                     parentParams.setStaged();
                 }
+                parentParams.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
 
                 int parentSessionId = packageInstaller.createSession(parentParams);
                 PackageInstaller.Session parentSession = packageInstaller.openSession(
@@ -484,6 +485,7 @@
                                 synchronized (mLock) {
                                     mState = ROLLBACK_STATE_AVAILABLE;
                                     mRestoreUserDataInProgress = false;
+                                    info.setCommittedSessionId(-1);
                                 }
                                 sendFailure(context, statusReceiver,
                                         RollbackManager.STATUS_FAILURE_INSTALL,
@@ -500,7 +502,6 @@
                                     mRestoreUserDataInProgress = false;
                                 }
 
-                                info.setCommittedSessionId(parentSessionId);
                                 info.getCausePackages().addAll(causePackages);
                                 RollbackStore.deletePackageCodePaths(this);
                                 RollbackStore.saveRollback(this);
@@ -528,6 +529,7 @@
                 );
 
                 mState = ROLLBACK_STATE_COMMITTED;
+                info.setCommittedSessionId(parentSessionId);
                 mRestoreUserDataInProgress = true;
                 parentSession.commit(receiver.getIntentSender());
             } catch (IOException e) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 3f5e2a4..9324870 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -897,11 +897,11 @@
     }
 
     @Override
-    public boolean notifyStagedSession(int sessionId) {
+    public int notifyStagedSession(int sessionId) {
         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
             throw new SecurityException("notifyStagedSession may only be called by the system.");
         }
-        final LinkedBlockingQueue<Boolean> result = new LinkedBlockingQueue<>();
+        final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>();
 
         // NOTE: We post this runnable on the RollbackManager's binder thread because we'd prefer
         // to preserve the invariant that all operations that modify state happen there.
@@ -911,7 +911,7 @@
             final PackageInstaller.SessionInfo session = installer.getSessionInfo(sessionId);
             if (session == null) {
                 Slog.e(TAG, "No matching install session for: " + sessionId);
-                result.offer(false);
+                result.offer(-1);
                 return;
             }
 
@@ -923,7 +923,7 @@
             if (!session.isMultiPackage()) {
                 if (!enableRollbackForPackageSession(newRollback.rollback, session)) {
                     Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
-                    result.offer(false);
+                    result.offer(-1);
                     return;
                 }
             } else {
@@ -932,25 +932,30 @@
                             installer.getSessionInfo(childSessionId);
                     if (childSession == null) {
                         Slog.e(TAG, "No matching child install session for: " + childSessionId);
-                        result.offer(false);
+                        result.offer(-1);
                         return;
                     }
                     if (!enableRollbackForPackageSession(newRollback.rollback, childSession)) {
                         Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
-                        result.offer(false);
+                        result.offer(-1);
                         return;
                     }
                 }
             }
 
-            result.offer(completeEnableRollback(newRollback, true) != null);
+            Rollback rollback = completeEnableRollback(newRollback, true);
+            if (rollback == null) {
+                result.offer(-1);
+            } else {
+                result.offer(rollback.info.getRollbackId());
+            }
         });
 
         try {
             return result.take();
         } catch (InterruptedException ie) {
             Slog.e(TAG, "Interrupted while waiting for notifyStagedSession response");
-            return false;
+            return -1;
         }
     }
 
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index b84fd79..bb09584 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -61,7 +61,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 
 /**
  * {@link PackageHealthObserver} for {@link RollbackManagerService}.
@@ -74,10 +73,6 @@
     private static final String TAG = "RollbackPackageHealthObserver";
     private static final String NAME = "rollback-observer";
     private static final int INVALID_ROLLBACK_ID = -1;
-    // TODO: make the following values configurable via DeviceConfig
-    private static final long NATIVE_CRASH_POLLING_INTERVAL_MILLIS =
-            TimeUnit.SECONDS.toMillis(30);
-    private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
 
     private final Context mContext;
     private final Handler mHandler;
@@ -85,13 +80,9 @@
     // Staged rollback ids that have been committed but their session is not yet ready
     @GuardedBy("mPendingStagedRollbackIds")
     private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>();
-    // this field is initialized in the c'tor and then only accessed from mHandler thread, so
-    // no need to guard with a lock
-    private long mNumberOfNativeCrashPollsRemaining;
 
     RollbackPackageHealthObserver(Context context) {
         mContext = context;
-        mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
         HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
         handlerThread.start();
         mHandler = handlerThread.getThreadHandler();
@@ -102,7 +93,14 @@
     }
 
     @Override
-    public int onHealthCheckFailed(VersionedPackage failedPackage) {
+    public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
+            @FailureReasons int failureReason) {
+        // For native crashes, we will roll back any available rollbacks
+        if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
+                && !mContext.getSystemService(RollbackManager.class)
+                .getAvailableRollbacks().isEmpty()) {
+            return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+        }
         if (getAvailableRollback(failedPackage) == null) {
             // Don't handle the notification, no rollbacks available for the package
             return PackageHealthObserverImpact.USER_IMPACT_NONE;
@@ -113,7 +111,13 @@
     }
 
     @Override
-    public boolean execute(VersionedPackage failedPackage, @FailureReasons int rollbackReason) {
+    public boolean execute(@Nullable VersionedPackage failedPackage,
+            @FailureReasons int rollbackReason) {
+        if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+            rollbackAll();
+            return true;
+        }
+
         RollbackInfo rollback = getAvailableRollback(failedPackage);
         if (rollback == null) {
             Slog.w(TAG, "Expected rollback but no valid rollback found for package: [ "
@@ -152,7 +156,8 @@
         String moduleMetadataPackageName = getModuleMetadataPackageName();
 
         if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
-            scheduleCheckAndMitigateNativeCrashes();
+            // TODO(gavincorkery): Call into Package Watchdog from outside the observer
+            PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes();
         }
 
         int rollbackId = popLastStagedRollbackId();
@@ -343,24 +348,6 @@
         }
     }
 
-    /**
-     * This method should be only called on mHandler thread, since it modifies
-     * {@link #mNumberOfNativeCrashPollsRemaining} and we want to keep this class lock free.
-     */
-    private void checkAndMitigateNativeCrashes() {
-        mNumberOfNativeCrashPollsRemaining--;
-        // Check if native watchdog reported a crash
-        if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
-            rollbackAll();
-            // we stop polling after an attempt to execute rollback, regardless of whether the
-            // attempt succeeds or not
-        } else {
-            if (mNumberOfNativeCrashPollsRemaining > 0) {
-                mHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
-                        NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
-            }
-        }
-    }
 
     /**
      * Returns true if the package name is the name of a module.
@@ -456,16 +443,6 @@
         }
     }
 
-    /**
-     * Since this method can eventually trigger a RollbackManager rollback, it should be called
-     * only once boot has completed {@code onBootCompleted} and not earlier, because the install
-     * session must be entirely completed before we try to rollback.
-     */
-    private void scheduleCheckAndMitigateNativeCrashes() {
-        Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
-                + "and mitigate native crashes");
-        mHandler.post(()->checkAndMitigateNativeCrashes());
-    }
 
     private int mapFailureReasonToMetric(@FailureReasons int failureReason) {
         switch (failureReason) {
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index 46dd366..aa3ab63 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -308,34 +308,6 @@
     }
 
     /**
-     * Throws an {@link IllegalStateException} if {@code path} is not ready to be accessed by
-     * {@code userId}.
-     */
-    // TODO(b/144332951): This is not used because it is racy. Right after checking a path
-    // we can call into vold with that path and the FUSE daemon can go down. Improve or remove
-    public void checkPathReadyForUser(int userId, String path) {
-        if (!mIsFuseEnabled) {
-            return;
-        }
-
-        if (mIsResetting) {
-            throw new IllegalStateException("Connection resetting for user " + userId
-                    + " with path " + path);
-        }
-
-        StorageUserConnection connection = null;
-        synchronized (mLock) {
-            connection = mConnections.get(userId);
-        }
-
-        if (connection == null) {
-            throw new IllegalStateException("Connection not ready for user " + userId
-                    + " with path " + path);
-        }
-        connection.checkPathReady(path);
-    }
-
-    /**
      * Returns {@code true} if {@code vol} is an emulated or public volume,
      * {@code false} otherwise
      **/
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index 5c44eee..10514ad 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -163,20 +163,6 @@
         mActiveConnection.close();
     }
 
-    /** Throws an {@link IllegalArgumentException} if {@code path} is not ready for access */
-    public void checkPathReady(String path) {
-        synchronized (mLock) {
-            for (Session session : mSessions.values()) {
-                if (session.upperPath != null && path.startsWith(session.upperPath)) {
-                    if (mActiveConnection.isActiveLocked(session)) {
-                        return;
-                    }
-                }
-            }
-            throw new IllegalStateException("Path not ready " + path);
-        }
-    }
-
     /** Returns all created sessions. */
     public Set<String> getAllSessionIds() {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
deleted file mode 100644
index 4e8ba07..0000000
--- a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.timedetector;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.app.timedetector.ManualTimeSuggestion;
-import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
-import android.util.LocalLog;
-import android.util.Slog;
-import android.util.TimestampedValue;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.IndentingPrintWriter;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * An implementation of TimeDetectorStrategy that passes only NITZ suggestions to
- * {@link AlarmManager}.
- *
- * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
- */
-public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
-
-    private static final boolean DBG = false;
-    private static final String LOG_TAG = "SimpleTimeDetectorStrategy";
-
-    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface Origin {}
-
-    /** Used when a time value originated from a telephony signal. */
-    @Origin
-    private static final int ORIGIN_PHONE = 1;
-
-    /** Used when a time value originated from a user / manual settings. */
-    @Origin
-    private static final int ORIGIN_MANUAL = 2;
-
-    /**
-     * CLOCK_PARANOIA: The maximum difference allowed between the expected system clock time and the
-     * actual system clock time before a warning is logged. Used to help identify situations where
-     * there is something other than this class setting the system clock automatically.
-     */
-    private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
-
-    // A log for changes made to the system clock and why.
-    @NonNull
-    private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
-
-    // @NonNull after initialize()
-    private Callback mCallback;
-
-    // Last phone suggestion.
-    @Nullable private PhoneTimeSuggestion mLastPhoneSuggestion;
-
-    // Information about the last time signal received: Used when toggling auto-time.
-    @Nullable private TimestampedValue<Long> mLastAutoSystemClockTime;
-    private boolean mLastAutoSystemClockTimeSendNetworkBroadcast;
-
-    // System clock state.
-    @Nullable private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
-
-    @Override
-    public void initialize(@NonNull Callback callback) {
-        mCallback = callback;
-    }
-
-    @Override
-    public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
-        // NITZ logic
-
-        // Empty suggestions are just ignored as we don't currently keep track of suggestion origin.
-        if (timeSuggestion.getUtcTime() == null) {
-            return;
-        }
-
-        boolean timeSuggestionIsValid =
-                validateNewPhoneSuggestion(timeSuggestion, mLastPhoneSuggestion);
-        if (!timeSuggestionIsValid) {
-            return;
-        }
-        // Always store the last NITZ value received, regardless of whether we go on to use it to
-        // update the system clock. This is so that we can validate future phone suggestions.
-        mLastPhoneSuggestion = timeSuggestion;
-
-        // System clock update logic.
-        final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
-        setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, timeSuggestion);
-    }
-
-    @Override
-    public synchronized void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
-        final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
-        setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion);
-    }
-
-    private static boolean validateNewPhoneSuggestion(@NonNull PhoneTimeSuggestion newSuggestion,
-            @Nullable PhoneTimeSuggestion lastSuggestion) {
-
-        if (lastSuggestion != null) {
-            long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
-                    newSuggestion.getUtcTime(), lastSuggestion.getUtcTime());
-            if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) {
-                // Out of order or bogus.
-                Slog.w(LOG_TAG, "Bad NITZ signal received."
-                        + " referenceTimeDifference=" + referenceTimeDifference
-                        + " lastSuggestion=" + lastSuggestion
-                        + " newSuggestion=" + newSuggestion);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @GuardedBy("this")
-    private void setSystemClockIfRequired(
-            @Origin int origin, TimestampedValue<Long> time, Object cause) {
-        // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only
-        // when setting the time using NITZ.
-        boolean sendNetworkBroadcast = origin == ORIGIN_PHONE;
-
-        boolean isOriginAutomatic = isOriginAutomatic(origin);
-        if (isOriginAutomatic) {
-            // Store the last auto time candidate we've seen in all cases so we can set the system
-            // clock when/if time detection is off but later enabled.
-            mLastAutoSystemClockTime = time;
-            mLastAutoSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast;
-
-            if (!mCallback.isAutoTimeDetectionEnabled()) {
-                if (DBG) {
-                    Slog.d(LOG_TAG, "Auto time detection is not enabled."
-                            + " origin=" + origin
-                            + ", time=" + time
-                            + ", cause=" + cause);
-                }
-                return;
-            }
-        } else {
-            if (mCallback.isAutoTimeDetectionEnabled()) {
-                if (DBG) {
-                    Slog.d(LOG_TAG, "Auto time detection is enabled."
-                            + " origin=" + origin
-                            + ", time=" + time
-                            + ", cause=" + cause);
-                }
-                return;
-            }
-        }
-
-        mCallback.acquireWakeLock();
-        try {
-            long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
-            long actualTimeMillis = mCallback.systemClockMillis();
-
-            if (isOriginAutomatic) {
-                // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
-                // may be setting the clock.
-                if (mLastAutoSystemClockTimeSet != null) {
-                    long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
-                            mLastAutoSystemClockTimeSet, elapsedRealtimeMillis);
-                    long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis);
-                    if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
-                        Slog.w(LOG_TAG,
-                                "System clock has not tracked elapsed real time clock. A clock may"
-                                        + " be inaccurate or something unexpectedly set the system"
-                                        + " clock."
-                                        + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
-                                        + " expectedTimeMillis=" + expectedTimeMillis
-                                        + " actualTimeMillis=" + actualTimeMillis);
-                    }
-                }
-            }
-
-            adjustAndSetDeviceSystemClock(
-                    time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, cause);
-        } finally {
-            mCallback.releaseWakeLock();
-        }
-    }
-
-    private static boolean isOriginAutomatic(@Origin int origin) {
-        return origin == ORIGIN_PHONE;
-    }
-
-    @Override
-    public synchronized void handleAutoTimeDetectionChanged() {
-        // If automatic time detection is enabled we update the system clock instantly if we can.
-        // Conversely, if automatic time detection is disabled we leave the clock as it is.
-        boolean enabled = mCallback.isAutoTimeDetectionEnabled();
-        if (enabled) {
-            if (mLastAutoSystemClockTime != null) {
-                // Only send the network broadcast if the last candidate would have caused one.
-                final boolean sendNetworkBroadcast = mLastAutoSystemClockTimeSendNetworkBroadcast;
-
-                mCallback.acquireWakeLock();
-                try {
-                    long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
-                    long actualTimeMillis = mCallback.systemClockMillis();
-
-                    final String reason = "Automatic time detection enabled.";
-                    adjustAndSetDeviceSystemClock(mLastAutoSystemClockTime, sendNetworkBroadcast,
-                            elapsedRealtimeMillis, actualTimeMillis, reason);
-                } finally {
-                    mCallback.releaseWakeLock();
-                }
-            }
-        } else {
-            // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
-            // it should be in future.
-            mLastAutoSystemClockTimeSet = null;
-        }
-    }
-
-    @Override
-    public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
-        ipw.println("TimeDetectorStrategy:");
-        ipw.increaseIndent(); // level 1
-
-        ipw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion);
-        ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
-        ipw.println("mLastAutoSystemClockTime=" + mLastAutoSystemClockTime);
-        ipw.println("mLastAutoSystemClockTimeSendNetworkBroadcast="
-                + mLastAutoSystemClockTimeSendNetworkBroadcast);
-
-
-        ipw.println("Time change log:");
-        ipw.increaseIndent(); // level 2
-        mTimeChangesLog.dump(ipw);
-        ipw.decreaseIndent(); // level 2
-
-        ipw.decreaseIndent(); // level 1
-        ipw.flush();
-    }
-
-    @GuardedBy("this")
-    private void adjustAndSetDeviceSystemClock(
-            TimestampedValue<Long> newTime, boolean sendNetworkBroadcast,
-            long elapsedRealtimeMillis, long actualSystemClockMillis, Object cause) {
-
-        // Adjust for the time that has elapsed since the signal was received.
-        long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
-
-        // Check if the new signal would make sufficient difference to the system clock. If it's
-        // below the threshold then ignore it.
-        long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis);
-        long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis();
-        if (absTimeDifference < systemClockUpdateThreshold) {
-            if (DBG) {
-                Slog.d(LOG_TAG, "Not setting system clock. New time and"
-                        + " system clock are close enough."
-                        + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
-                        + " newTime=" + newTime
-                        + " cause=" + cause
-                        + " systemClockUpdateThreshold=" + systemClockUpdateThreshold
-                        + " absTimeDifference=" + absTimeDifference);
-            }
-            return;
-        }
-
-        mCallback.setSystemClock(newSystemClockMillis);
-        String logMsg = "Set system clock using time=" + newTime
-                + " cause=" + cause
-                + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
-                + " newSystemClockMillis=" + newSystemClockMillis;
-        if (DBG) {
-            Slog.d(LOG_TAG, logMsg);
-        }
-        mTimeChangesLog.log(logMsg);
-
-        // CLOCK_PARANOIA : Record the last time this class set the system clock.
-        mLastAutoSystemClockTimeSet = newTime;
-
-        if (sendNetworkBroadcast) {
-            // Send a broadcast that telephony code used to send after setting the clock.
-            // TODO Remove this broadcast as soon as there are no remaining listeners.
-            Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
-            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-            intent.putExtra("time", newSystemClockMillis);
-            mCallback.sendStickyBroadcast(intent);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 34400ff..172367a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -31,7 +31,6 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
 import com.android.server.SystemService;
-import com.android.server.timedetector.TimeDetectorStrategy.Callback;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -58,17 +57,16 @@
 
     @NonNull private final Handler mHandler;
     @NonNull private final Context mContext;
-    @NonNull private final Callback mCallback;
     @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
 
     private static TimeDetectorService create(@NonNull Context context) {
-        TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy();
+        TimeDetectorStrategy timeDetectorStrategy = new TimeDetectorStrategyImpl();
         TimeDetectorStrategyCallbackImpl callback = new TimeDetectorStrategyCallbackImpl(context);
-        timeDetector.initialize(callback);
+        timeDetectorStrategy.initialize(callback);
 
         Handler handler = FgThread.getHandler();
         TimeDetectorService timeDetectorService =
-                new TimeDetectorService(context, handler, callback, timeDetector);
+                new TimeDetectorService(context, handler, timeDetectorStrategy);
 
         // Wire up event listening.
         ContentResolver contentResolver = context.getContentResolver();
@@ -85,10 +83,9 @@
 
     @VisibleForTesting
     public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
-            @NonNull Callback callback, @NonNull TimeDetectorStrategy timeDetectorStrategy) {
+            @NonNull TimeDetectorStrategy timeDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
-        mCallback = Objects.requireNonNull(callback);
         mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
     }
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
new file mode 100644
index 0000000..1b1ac6d
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -0,0 +1,549 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timedetector;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.PhoneTimeSuggestion;
+import android.content.Intent;
+import android.util.ArrayMap;
+import android.util.LocalLog;
+import android.util.Slog;
+import android.util.TimestampedValue;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * An implementation of TimeDetectorStrategy that passes phone and manual suggestions to
+ * {@link AlarmManager}. When there are multiple phone sources, the one with the lowest ID is used
+ * unless the data becomes too stale.
+ *
+ * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
+ */
+public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
+
+    private static final boolean DBG = false;
+    private static final String LOG_TAG = "SimpleTimeDetectorStrategy";
+
+    /** A score value used to indicate "no score", either due to validation failure or age. */
+    private static final int PHONE_INVALID_SCORE = -1;
+    /** The number of buckets phone suggestions can be put in by age. */
+    private static final int PHONE_BUCKET_COUNT = 24;
+    /** Each bucket is this size. All buckets are equally sized. */
+    @VisibleForTesting
+    static final int PHONE_BUCKET_SIZE_MILLIS = 60 * 60 * 1000;
+    /** Phone suggestions older than this value are considered too old. */
+    @VisibleForTesting
+    static final long PHONE_MAX_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS;
+
+    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Origin {}
+
+    /** Used when a time value originated from a telephony signal. */
+    @Origin
+    private static final int ORIGIN_PHONE = 1;
+
+    /** Used when a time value originated from a user / manual settings. */
+    @Origin
+    private static final int ORIGIN_MANUAL = 2;
+
+    /**
+     * CLOCK_PARANOIA: The maximum difference allowed between the expected system clock time and the
+     * actual system clock time before a warning is logged. Used to help identify situations where
+     * there is something other than this class setting the system clock.
+     */
+    private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
+
+    /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
+    private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
+
+    // A log for changes made to the system clock and why.
+    @NonNull
+    private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
+
+    // @NonNull after initialize()
+    private Callback mCallback;
+
+    // Used to store the last time the system clock state was set automatically. It is used to
+    // detect (and log) issues with the realtime clock or whether the clock is being set without
+    // going through this strategy code.
+    @GuardedBy("this")
+    @Nullable
+    private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
+
+    /**
+     * A mapping from phoneId to a linked list of time suggestions (the "first" being the latest).
+     * We typically expect one or two entries in this Map: devices will have a small number
+     * of telephony devices and phoneIds are assumed to be stable. The LinkedList associated with
+     * the ID will not exceed {@link #KEEP_SUGGESTION_HISTORY_SIZE} in size.
+     */
+    @GuardedBy("this")
+    private ArrayMap<Integer, LinkedList<PhoneTimeSuggestion>> mSuggestionByPhoneId =
+            new ArrayMap<>();
+
+    @Override
+    public void initialize(@NonNull Callback callback) {
+        mCallback = callback;
+    }
+
+    @Override
+    public synchronized void suggestManualTime(@NonNull ManualTimeSuggestion suggestion) {
+        final TimestampedValue<Long> newUtcTime = suggestion.getUtcTime();
+
+        if (!validateSuggestionTime(newUtcTime, suggestion)) {
+            return;
+        }
+
+        String cause = "Manual time suggestion received: suggestion=" + suggestion;
+        setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, cause);
+    }
+
+    @Override
+    public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
+        // Empty time suggestion means that telephony network connectivity has been lost.
+        // The passage of time is relentless, and we don't expect our users to use a time machine,
+        // so we can continue relying on previous suggestions when we lose connectivity. This is
+        // unlike time zone, where a user may lose connectivity when boarding a flight and where we
+        // do want to "forget" old signals. Suggestions that are too old are discarded later in the
+        // detection algorithm.
+        if (timeSuggestion.getUtcTime() == null) {
+            return;
+        }
+
+        // Perform validation / input filtering and record the validated suggestion against the
+        // phoneId.
+        if (!validateAndStorePhoneSuggestion(timeSuggestion)) {
+            return;
+        }
+
+        // Now perform auto time detection. The new suggestion may be used to modify the system
+        // clock.
+        String reason = "New phone time suggested. timeSuggestion=" + timeSuggestion;
+        doAutoTimeDetection(reason);
+    }
+
+    @Override
+    public synchronized void handleAutoTimeDetectionChanged() {
+        boolean enabled = mCallback.isAutoTimeDetectionEnabled();
+        // When automatic time detection is enabled we update the system clock instantly if we can.
+        // Conversely, when automatic time detection is disabled we leave the clock as it is.
+        if (enabled) {
+            String reason = "Auto time zone detection setting enabled.";
+            doAutoTimeDetection(reason);
+        } else {
+            // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
+            // it should be in future.
+            mLastAutoSystemClockTimeSet = null;
+        }
+    }
+
+    @Override
+    public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+        ipw.println("TimeDetectorStrategy:");
+        ipw.increaseIndent(); // level 1
+
+        ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
+
+        ipw.println("Time change log:");
+        ipw.increaseIndent(); // level 2
+        mTimeChangesLog.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+
+        ipw.println("Phone suggestion history:");
+        ipw.increaseIndent(); // level 2
+        for (Map.Entry<Integer, LinkedList<PhoneTimeSuggestion>> entry
+                : mSuggestionByPhoneId.entrySet()) {
+            ipw.println("Phone " + entry.getKey());
+
+            ipw.increaseIndent(); // level 3
+            for (PhoneTimeSuggestion suggestion : entry.getValue()) {
+                ipw.println(suggestion);
+            }
+            ipw.decreaseIndent(); // level 3
+        }
+        ipw.decreaseIndent(); // level 2
+
+        ipw.decreaseIndent(); // level 1
+        ipw.flush();
+    }
+
+    @GuardedBy("this")
+    private boolean validateAndStorePhoneSuggestion(@NonNull PhoneTimeSuggestion suggestion) {
+        TimestampedValue<Long> newUtcTime = suggestion.getUtcTime();
+        if (!validateSuggestionTime(newUtcTime, suggestion)) {
+            // There's probably nothing useful we can do: elsewhere we assume that reference
+            // times are in the past so just stop here.
+            return false;
+        }
+
+        int phoneId = suggestion.getPhoneId();
+        LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.get(phoneId);
+        if (phoneSuggestions == null) {
+            // The first time we've seen this phoneId.
+            phoneSuggestions = new LinkedList<>();
+            mSuggestionByPhoneId.put(phoneId, phoneSuggestions);
+        } else if (phoneSuggestions.isEmpty()) {
+            Slog.w(LOG_TAG, "Suggestions unexpectedly empty when adding suggestion=" + suggestion);
+        }
+
+        if (!phoneSuggestions.isEmpty()) {
+            // We can log / discard suggestions with obvious issues with the reference time clock.
+            PhoneTimeSuggestion previousSuggestion = phoneSuggestions.getFirst();
+            if (previousSuggestion == null
+                    || previousSuggestion.getUtcTime() == null
+                    || previousSuggestion.getUtcTime().getValue() == null) {
+                // This should be impossible given we only store validated suggestions.
+                Slog.w(LOG_TAG, "Previous suggestion is null or has a null time."
+                        + " previousSuggestion=" + previousSuggestion
+                        + ", suggestion=" + suggestion);
+                return false;
+            }
+
+            long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
+                    newUtcTime, previousSuggestion.getUtcTime());
+            if (referenceTimeDifference < 0) {
+                // The reference time is before the previously received suggestion. Ignore it.
+                Slog.w(LOG_TAG, "Out of order phone suggestion received."
+                        + " referenceTimeDifference=" + referenceTimeDifference
+                        + " previousSuggestion=" + previousSuggestion
+                        + " suggestion=" + suggestion);
+                return false;
+            }
+        }
+
+        // Store the latest suggestion.
+        phoneSuggestions.addFirst(suggestion);
+        if (phoneSuggestions.size() > KEEP_SUGGESTION_HISTORY_SIZE) {
+            phoneSuggestions.removeLast();
+        }
+        return true;
+    }
+
+    private boolean validateSuggestionTime(
+            @NonNull TimestampedValue<Long> newUtcTime, @NonNull Object suggestion) {
+        if (newUtcTime.getValue() == null) {
+            Slog.w(LOG_TAG, "Suggested time value is null. suggestion=" + suggestion);
+            return false;
+        }
+
+        // We can validate the suggestion against the reference time clock.
+        long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+        if (elapsedRealtimeMillis < newUtcTime.getReferenceTimeMillis()) {
+            // elapsedRealtime clock went backwards?
+            Slog.w(LOG_TAG, "New reference time is in the future? Ignoring."
+                    + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+                    + ", suggestion=" + suggestion);
+            return false;
+        }
+        return true;
+    }
+
+    @GuardedBy("this")
+    private void doAutoTimeDetection(@NonNull String detectionReason) {
+        if (!mCallback.isAutoTimeDetectionEnabled()) {
+            // Avoid doing unnecessary work with this (race-prone) check.
+            return;
+        }
+
+        PhoneTimeSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
+
+        // Work out what to do with the best suggestion.
+        if (bestPhoneSuggestion == null) {
+            // There is no good phone suggestion.
+            if (DBG) {
+                Slog.d(LOG_TAG, "Could not determine time: No best phone suggestion."
+                        + " detectionReason=" + detectionReason);
+            }
+            return;
+        }
+
+        final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime();
+        String cause = "Found good suggestion."
+                + ", bestPhoneSuggestion=" + bestPhoneSuggestion
+                + ", detectionReason=" + detectionReason;
+        setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause);
+    }
+
+    @GuardedBy("this")
+    @Nullable
+    private PhoneTimeSuggestion findBestPhoneSuggestion() {
+        long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+
+        // Phone time suggestions are assumed to be derived from NITZ or NITZ-like signals. These
+        // have a number of limitations:
+        // 1) No guarantee of accuracy ("accuracy of the time information is in the order of
+        // minutes") [1]
+        // 2) No guarantee of regular signals ("dependent on the handset crossing radio network
+        // boundaries") [1]
+        //
+        // [1] https://en.wikipedia.org/wiki/NITZ
+        //
+        // Generally, when there are suggestions from multiple phoneIds they should usually
+        // approximately agree. In cases where signals *are* inaccurate we don't want to vacillate
+        // between signals from two phoneIds. However, it is known for NITZ signals to be incorrect
+        // occasionally, which means we also don't want to stick forever with one phoneId. Without
+        // cross-referencing across sources (e.g. the current device time, NTP), or doing some kind
+        // of statistical analysis of consistency within and across phoneIds, we can't know which
+        // suggestions are more correct.
+        //
+        // For simplicity, we try to value recency, then consistency of phoneId.
+        //
+        // The heuristic works as follows:
+        // Recency: The most recent suggestion from each phone is scored. The score is based on a
+        // discrete age bucket, i.e. so signals received around the same time will be in the same
+        // bucket, thus applying a loose reference time ordering. The suggestion with the highest
+        // score is used.
+        // Consistency: If there a multiple suggestions with the same score, the suggestion with the
+        // lowest phoneId is always taken.
+        //
+        // In the trivial case with a single ID this will just mean that the latest received
+        // suggestion is used.
+
+        PhoneTimeSuggestion bestSuggestion = null;
+        int bestScore = PHONE_INVALID_SCORE;
+        for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
+            Integer phoneId = mSuggestionByPhoneId.keyAt(i);
+            LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.valueAt(i);
+            if (phoneSuggestions == null) {
+                // Unexpected - map is missing a value.
+                Slog.w(LOG_TAG, "Suggestions unexpectedly missing for phoneId."
+                        + " phoneId=" + phoneId);
+                continue;
+            }
+
+            PhoneTimeSuggestion candidateSuggestion = phoneSuggestions.getFirst();
+            if (candidateSuggestion == null) {
+                // Unexpected - null suggestions should never be stored.
+                Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for phoneId."
+                        + " phoneId=" + phoneId);
+                continue;
+            } else if (candidateSuggestion.getUtcTime() == null) {
+                // Unexpected - we do not store empty suggestions.
+                Slog.w(LOG_TAG, "Latest suggestion unexpectedly empty. "
+                        + " candidateSuggestion=" + candidateSuggestion);
+                continue;
+            }
+
+            int candidateScore = scorePhoneSuggestion(elapsedRealtimeMillis, candidateSuggestion);
+            if (candidateScore == PHONE_INVALID_SCORE) {
+                // Expected: This means the suggestion is obviously invalid or just too old.
+                continue;
+            }
+
+            // Higher scores are better.
+            if (bestSuggestion == null || bestScore < candidateScore) {
+                bestSuggestion = candidateSuggestion;
+                bestScore = candidateScore;
+            } else if (bestScore == candidateScore) {
+                // Tie! Use the suggestion with the lowest phoneId.
+                int candidatePhoneId = candidateSuggestion.getPhoneId();
+                int bestPhoneId = bestSuggestion.getPhoneId();
+                if (candidatePhoneId < bestPhoneId) {
+                    bestSuggestion = candidateSuggestion;
+                }
+            }
+        }
+        return bestSuggestion;
+    }
+
+    private static int scorePhoneSuggestion(
+            long elapsedRealtimeMillis, @NonNull PhoneTimeSuggestion timeSuggestion) {
+        // The score is based on the age since receipt. Suggestions are bucketed so two
+        // suggestions in the same bucket from different phoneIds are scored the same.
+        TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime();
+        long referenceTimeMillis = utcTime.getReferenceTimeMillis();
+        if (referenceTimeMillis > elapsedRealtimeMillis) {
+            // Future times are ignored. They imply the reference time was wrong, or the elapsed
+            // realtime clock has gone backwards, neither of which are supportable situations.
+            Slog.w(LOG_TAG, "Existing suggestion found to be in the future. "
+                    + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+                    + ", timeSuggestion=" + timeSuggestion);
+            return PHONE_INVALID_SCORE;
+        }
+
+        long ageMillis = elapsedRealtimeMillis - referenceTimeMillis;
+
+        // Any suggestion > MAX_AGE_MILLIS is treated as too old. Although time is relentless and
+        // predictable, the accuracy of the reference time clock may be poor over long periods which
+        // would lead to errors creeping in. Also, in edge cases where a bad suggestion has been
+        // made and never replaced, it could also mean that the time detection code remains
+        // opinionated using a bad invalid suggestion. This caps that edge case at MAX_AGE_MILLIS.
+        if (ageMillis > PHONE_MAX_AGE_MILLIS) {
+            return PHONE_INVALID_SCORE;
+        }
+
+        // Turn the age into a discrete value: 0 <= bucketIndex < MAX_AGE_HOURS.
+        int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS);
+
+        // We want the lowest bucket index to have the highest score. 0 > score >= BUCKET_COUNT.
+        return PHONE_BUCKET_COUNT - bucketIndex;
+    }
+
+    @GuardedBy("this")
+    private void setSystemClockIfRequired(
+            @Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) {
+
+        boolean isOriginAutomatic = isOriginAutomatic(origin);
+        if (isOriginAutomatic) {
+            if (!mCallback.isAutoTimeDetectionEnabled()) {
+                if (DBG) {
+                    Slog.d(LOG_TAG, "Auto time detection is not enabled."
+                            + " origin=" + origin
+                            + ", time=" + time
+                            + ", cause=" + cause);
+                }
+                return;
+            }
+        } else {
+            if (mCallback.isAutoTimeDetectionEnabled()) {
+                if (DBG) {
+                    Slog.d(LOG_TAG, "Auto time detection is enabled."
+                            + " origin=" + origin
+                            + ", time=" + time
+                            + ", cause=" + cause);
+                }
+                return;
+            }
+        }
+
+        mCallback.acquireWakeLock();
+        try {
+            setSystemClockUnderWakeLock(origin, time, cause);
+        } finally {
+            mCallback.releaseWakeLock();
+        }
+    }
+
+    private static boolean isOriginAutomatic(@Origin int origin) {
+        return origin == ORIGIN_PHONE;
+    }
+
+    @GuardedBy("this")
+    private void setSystemClockUnderWakeLock(
+            int origin, @NonNull TimestampedValue<Long> newTime, @NonNull Object cause) {
+
+        long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+        boolean isOriginAutomatic = isOriginAutomatic(origin);
+        long actualSystemClockMillis = mCallback.systemClockMillis();
+        if (isOriginAutomatic) {
+            // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
+            // may be setting the clock.
+            if (mLastAutoSystemClockTimeSet != null) {
+                long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
+                        mLastAutoSystemClockTimeSet, elapsedRealtimeMillis);
+                long absSystemClockDifference =
+                        Math.abs(expectedTimeMillis - actualSystemClockMillis);
+                if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
+                    Slog.w(LOG_TAG,
+                            "System clock has not tracked elapsed real time clock. A clock may"
+                                    + " be inaccurate or something unexpectedly set the system"
+                                    + " clock."
+                                    + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+                                    + " expectedTimeMillis=" + expectedTimeMillis
+                                    + " actualTimeMillis=" + actualSystemClockMillis
+                                    + " cause=" + cause);
+                }
+            }
+        }
+
+        // Adjust for the time that has elapsed since the signal was received.
+        long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
+
+        // Check if the new signal would make sufficient difference to the system clock. If it's
+        // below the threshold then ignore it.
+        long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis);
+        long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis();
+        if (absTimeDifference < systemClockUpdateThreshold) {
+            if (DBG) {
+                Slog.d(LOG_TAG, "Not setting system clock. New time and"
+                        + " system clock are close enough."
+                        + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+                        + " newTime=" + newTime
+                        + " cause=" + cause
+                        + " systemClockUpdateThreshold=" + systemClockUpdateThreshold
+                        + " absTimeDifference=" + absTimeDifference);
+            }
+            return;
+        }
+
+        mCallback.setSystemClock(newSystemClockMillis);
+        String logMsg = "Set system clock using time=" + newTime
+                + " cause=" + cause
+                + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+                + " newSystemClockMillis=" + newSystemClockMillis;
+        if (DBG) {
+            Slog.d(LOG_TAG, logMsg);
+        }
+        mTimeChangesLog.log(logMsg);
+
+        // CLOCK_PARANOIA : Record the last time this class set the system clock due to an auto-time
+        // signal, or clear the record it is being done manually.
+        if (isOriginAutomatic(origin)) {
+            mLastAutoSystemClockTimeSet = newTime;
+        } else {
+            mLastAutoSystemClockTimeSet = null;
+        }
+
+        // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only
+        // when setting the time using NITZ.
+        if (origin == ORIGIN_PHONE) {
+            // Send a broadcast that telephony code used to send after setting the clock.
+            // TODO Remove this broadcast as soon as there are no remaining listeners.
+            Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+            intent.putExtra("time", newSystemClockMillis);
+            mCallback.sendStickyBroadcast(intent);
+        }
+    }
+
+    /**
+     * Returns the current best phone suggestion. Not intended for general use: it is used during
+     * tests to check strategy behavior.
+     */
+    @VisibleForTesting
+    @Nullable
+    public synchronized PhoneTimeSuggestion findBestPhoneSuggestionForTests() {
+        return findBestPhoneSuggestion();
+    }
+
+    /**
+     * A method used to inspect state during tests. Not intended for general use.
+     */
+    @VisibleForTesting
+    @Nullable
+    public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) {
+        LinkedList<PhoneTimeSuggestion> suggestions = mSuggestionByPhoneId.get(phoneId);
+        if (suggestions == null) {
+            return null;
+        }
+        return suggestions.getFirst();
+    }
+}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
old mode 100644
new mode 100755
index 8e66b14..18ed51a
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2943,6 +2943,16 @@
                 if (state != null) {
                     setStateLocked(inputId, state, mCurrentUserId);
                 }
+                UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
+                // Broadcast the event to all hardware inputs.
+                for (ServiceState serviceState : userState.serviceStateMap.values()) {
+                    if (!serviceState.isHardware || serviceState.service == null) continue;
+                    try {
+                        serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in notifyHdmiDeviceUpdated", e);
+                    }
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
deleted file mode 100644
index 35b64e7..0000000
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ /dev/null
@@ -1,1384 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.os.Build.VERSION_CODES.N;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.FLAG_PRIVATE;
-import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
-
-import static com.android.server.am.ActivityDisplayProto.DISPLAY;
-import static com.android.server.am.ActivityDisplayProto.FOCUSED_STACK_ID;
-import static com.android.server.am.ActivityDisplayProto.ID;
-import static com.android.server.am.ActivityDisplayProto.RESUMED_ACTIVITY;
-import static com.android.server.am.ActivityDisplayProto.SINGLE_TASK_INSTANCE;
-import static com.android.server.am.ActivityDisplayProto.STACKS;
-import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.RootActivityContainer.FindTaskResult;
-import static com.android.server.wm.RootActivityContainer.TAG_STATES;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.app.ActivityOptions;
-import android.app.WindowConfiguration;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.IntArray;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.Display;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.protolog.common.ProtoLog;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Exactly one of these classes per Display in the system. Capable of holding zero or more
- * attached {@link ActivityStack}s.
- */
-class ActivityDisplay extends DisplayContent {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_ATM;
-    private static final String TAG_STACK = TAG + POSTFIX_STACK;
-
-    static final int POSITION_TOP = Integer.MAX_VALUE;
-    static final int POSITION_BOTTOM = Integer.MIN_VALUE;
-
-    /**
-     * Counter for next free stack ID to use for dynamic activity stacks. Unique across displays.
-     */
-    private static int sNextFreeStackId = 0;
-
-    private RootActivityContainer mRootActivityContainer;
-    /** Actual Display this object tracks. */
-    int mDisplayId;
-    Display mDisplay;
-
-    /**
-     * All of the stacks on this display. Order matters, topmost stack is in front of all other
-     * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls
-     * changing the list should also call {@link #onStackOrderChanged()}.
-     */
-    private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>();
-
-    /** Array of all UIDs that are present on the display. */
-    private IntArray mDisplayAccessUIDs = new IntArray();
-
-    /** All tokens used to put activities on this stack to sleep (including mOffToken) */
-    final ArrayList<ActivityTaskManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
-    /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
-    ActivityTaskManagerInternal.SleepToken mOffToken;
-
-    private boolean mSleeping;
-
-    /** We started the process of removing the display from the system. */
-    private boolean mRemoving;
-
-    /**
-     * The display is removed from the system and we are just waiting for all activities on it to be
-     * finished before removing this object.
-     */
-    private boolean mRemoved;
-
-    /** The display can only contain one task. */
-    private boolean mSingleTaskInstance;
-
-    /**
-     * Non-null if the last size compatibility mode activity is using non-native screen
-     * configuration. The activity is not able to put in multi-window mode, so it exists only one
-     * per display.
-     */
-    private ActivityRecord mLastCompatModeActivity;
-
-    /**
-     * A focusable stack that is purposely to be positioned at the top. Although the stack may not
-     * have the topmost index, it is used as a preferred candidate to prevent being unable to resume
-     * target stack properly when there are other focusable always-on-top stacks.
-     */
-    private ActivityStack mPreferredTopFocusableStack;
-
-    /**
-     * If this is the same as {@link #getFocusedStack} then the activity on the top of the focused
-     * stack has been resumed. If stacks are changing position this will hold the old stack until
-     * the new stack becomes resumed after which it will be set to current focused stack.
-     */
-    private ActivityStack mLastFocusedStack;
-
-    // Used in updating the display size
-    private Point mTmpDisplaySize = new Point();
-
-    // Used in updating override configurations
-    private final Configuration mTempConfig = new Configuration();
-
-    private final FindTaskResult mTmpFindTaskResult = new FindTaskResult();
-
-    ActivityDisplay(RootActivityContainer root, Display display) {
-        super(display, root.mWindowManager);
-        mRootActivityContainer = root;
-        mDisplayId = display.getDisplayId();
-        mDisplay = display;
-
-        if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
-
-        mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
-
-        if (mWmService.mDisplayManagerInternal != null) {
-            mWmService.mDisplayManagerInternal
-                .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
-            configureDisplayPolicy();
-        }
-
-        reconfigureDisplayLocked();
-        onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
-        mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
-    }
-
-    void onDisplayChanged() {
-        // The window policy is responsible for stopping activities on the default display.
-        final int displayId = mDisplay.getDisplayId();
-        if (displayId != DEFAULT_DISPLAY) {
-            final int displayState = mDisplay.getState();
-            if (displayState == Display.STATE_OFF && mOffToken == null) {
-                mOffToken = mAtmService.acquireSleepToken("Display-off", displayId);
-            } else if (displayState == Display.STATE_ON && mOffToken != null) {
-                mOffToken.release();
-                mOffToken = null;
-            }
-        }
-
-        mDisplay.getRealSize(mTmpDisplaySize);
-        setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
-        updateDisplayInfo();
-        mWmService.requestTraversal();
-    }
-
-    void addStack(ActivityStack stack, int position) {
-        setStackOnDisplay(stack, position);
-        positionStackAt(stack, position);
-        mAtmService.updateSleepIfNeededLocked();
-    }
-
-    void onStackRemoved(ActivityStack stack) {
-        if (DEBUG_STACK) {
-            Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId=" + mDisplayId);
-        }
-        if (mPreferredTopFocusableStack == stack) {
-            mPreferredTopFocusableStack = null;
-        }
-        releaseSelfIfNeeded();
-        mAtmService.updateSleepIfNeededLocked();
-        onStackOrderChanged(stack);
-    }
-
-    void positionStackAtTop(ActivityStack stack, boolean includingParents) {
-        positionStackAtTop(stack, includingParents, null /* updateLastFocusedStackReason */);
-    }
-
-    void positionStackAtTop(ActivityStack stack, boolean includingParents,
-            String updateLastFocusedStackReason) {
-        positionStackAt(stack, getStackCount(), includingParents, updateLastFocusedStackReason);
-    }
-
-    void positionStackAtBottom(ActivityStack stack) {
-        positionStackAtBottom(stack, null /* updateLastFocusedStackReason */);
-    }
-
-    void positionStackAtBottom(ActivityStack stack, String updateLastFocusedStackReason) {
-        positionStackAt(stack, 0, false /* includingParents */, updateLastFocusedStackReason);
-    }
-
-    private void positionStackAt(ActivityStack stack, int position) {
-        positionStackAt(stack, position, false /* includingParents */,
-                null /* updateLastFocusedStackReason */);
-    }
-
-    private void positionStackAt(ActivityStack stack, int position, boolean includingParents,
-            String updateLastFocusedStackReason) {
-        // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust
-        //       the position internally, also update the logic here
-        final ActivityStack prevFocusedStack = updateLastFocusedStackReason != null
-                ? getFocusedStack() : null;
-        final boolean wasContained = getIndexOf(stack) >= 0;
-        if (mSingleTaskInstance && getStackCount() == 1 && !wasContained) {
-            throw new IllegalStateException(
-                    "positionStackAt: Can only have one task on display=" + this);
-        }
-
-        // Since positionChildAt() is called during the creation process of pinned stacks,
-        // ActivityStack#getStack() can be null.
-        positionStackAt(position, stack, includingParents);
-
-        // The insert position may be adjusted to non-top when there is always-on-top stack. Since
-        // the original position is preferred to be top, the stack should have higher priority when
-        // we are looking for top focusable stack. The condition {@code wasContained} restricts the
-        // preferred stack is set only when moving an existing stack to top instead of adding a new
-        // stack that may be too early (e.g. in the middle of launching or reparenting).
-        if (wasContained && position >= getStackCount() - 1 && stack.isFocusableAndVisible()) {
-            mPreferredTopFocusableStack = stack;
-        } else if (mPreferredTopFocusableStack == stack) {
-            mPreferredTopFocusableStack = null;
-        }
-
-        if (updateLastFocusedStackReason != null) {
-            final ActivityStack currentFocusedStack = getFocusedStack();
-            if (currentFocusedStack != prevFocusedStack) {
-                mLastFocusedStack = prevFocusedStack;
-                EventLogTags.writeWmFocusedStack(mRootActivityContainer.mCurrentUser, mDisplayId,
-                        currentFocusedStack == null ? -1 : currentFocusedStack.getStackId(),
-                        mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(),
-                        updateLastFocusedStackReason);
-            }
-        }
-
-        onStackOrderChanged(stack);
-    }
-
-    ActivityStack getStack(int stackId) {
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = getStackAt(i);
-            if (stack.mStackId == stackId) {
-                return stack;
-            }
-        }
-        return null;
-    }
-
-    boolean alwaysCreateStack(int windowingMode, int activityType) {
-        // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
-        // modes so that we can manage visual ordering and return types correctly.
-        return activityType == ACTIVITY_TYPE_STANDARD
-                && (windowingMode == WINDOWING_MODE_FULLSCREEN
-                || windowingMode == WINDOWING_MODE_FREEFORM
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-    }
-
-    /**
-     * Returns an existing stack compatible with the windowing mode and activity type or creates one
-     * if a compatible stack doesn't exist.
-     * @see #getStack(int, int)
-     * @see #createStack(int, int, boolean)
-     */
-    ActivityStack getOrCreateStack(int windowingMode, int activityType,
-            boolean onTop) {
-        if (!alwaysCreateStack(windowingMode, activityType)) {
-            ActivityStack stack = getStack(windowingMode, activityType);
-            if (stack != null) {
-                return stack;
-            }
-        }
-        return createStack(windowingMode, activityType, onTop);
-    }
-
-    /**
-     * Returns an existing stack compatible with the input params or creates one
-     * if a compatible stack doesn't exist.
-     * @see #getOrCreateStack(int, int, boolean)
-     */
-    ActivityStack getOrCreateStack(@Nullable ActivityRecord r,
-            @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType,
-            boolean onTop) {
-        // First preference is the windowing mode in the activity options if set.
-        int windowingMode = (options != null)
-                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-        // Validate that our desired windowingMode will work under the current conditions.
-        // UNDEFINED windowing mode is a valid result and means that the new stack will inherit
-        // it's display's windowing mode.
-        windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
-        return getOrCreateStack(windowingMode, activityType, onTop);
-    }
-
-    @VisibleForTesting
-    int getNextStackId() {
-        return sNextFreeStackId++;
-    }
-
-    /**
-     * Creates a stack matching the input windowing mode and activity type on this display.
-     * @param windowingMode The windowing mode the stack should be created in. If
-     *                      {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
-     *                      inherit it's parent's windowing mode.
-     * @param activityType The activityType the stack should be created in. If
-     *                     {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
-     *                     be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
-     * @param onTop If true the stack will be created at the top of the display, else at the bottom.
-     * @return The newly created stack.
-     */
-    ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
-
-        if (mSingleTaskInstance && getStackCount() > 0) {
-            // Create stack on default display instead since this display can only contain 1 stack.
-            // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
-            // this goes away once ActivityView is no longer using virtual displays.
-            return mRootActivityContainer.getDefaultDisplay().createStack(
-                    windowingMode, activityType, onTop);
-        }
-
-        if (activityType == ACTIVITY_TYPE_UNDEFINED) {
-            // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
-            // anything else should be passing it in anyways...
-            activityType = ACTIVITY_TYPE_STANDARD;
-        }
-
-        if (activityType != ACTIVITY_TYPE_STANDARD) {
-            // For now there can be only one stack of a particular non-standard activity type on a
-            // display. So, get that ignoring whatever windowing mode it is currently in.
-            ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
-            if (stack != null) {
-                throw new IllegalArgumentException("Stack=" + stack + " of activityType="
-                        + activityType + " already on display=" + this + ". Can't have multiple.");
-            }
-        }
-
-        if (!isWindowingModeSupported(windowingMode, mAtmService.mSupportsMultiWindow,
-                mAtmService.mSupportsSplitScreenMultiWindow,
-                mAtmService.mSupportsFreeformWindowManagement,
-                mAtmService.mSupportsPictureInPicture, activityType)) {
-            throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
-                    + windowingMode);
-        }
-
-        final int stackId = getNextStackId();
-        return createStackUnchecked(windowingMode, activityType, stackId, onTop);
-    }
-
-    @VisibleForTesting
-    ActivityStack createStackUnchecked(int windowingMode, int activityType,
-            int stackId, boolean onTop) {
-        if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
-            throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
-                    + "activity type.");
-        }
-        return new ActivityStack(this, stackId,
-                mRootActivityContainer.mStackSupervisor, windowingMode, activityType, onTop);
-    }
-
-    /**
-     * Get the preferred focusable stack in priority. If the preferred stack does not exist, find a
-     * focusable and visible stack from the top of stacks in this display.
-     */
-    ActivityStack getFocusedStack() {
-        if (mPreferredTopFocusableStack != null) {
-            return mPreferredTopFocusableStack;
-        }
-
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = getStackAt(i);
-            if (stack.isFocusableAndVisible()) {
-                return stack;
-            }
-        }
-
-        return null;
-    }
-
-    ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) {
-        final int currentWindowingMode = currentFocus != null
-                ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
-        ActivityStack candidate = null;
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = getStackAt(i);
-            if (ignoreCurrent && stack == currentFocus) {
-                continue;
-            }
-            if (!stack.isFocusableAndVisible()) {
-                continue;
-            }
-
-            if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                    && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) {
-                // If the currently focused stack is in split-screen secondary we save off the
-                // top primary split-screen stack as a candidate for focus because we might
-                // prefer focus to move to an other stack to avoid primary split-screen stack
-                // overlapping with a fullscreen stack when a fullscreen stack is higher in z
-                // than the next split-screen stack. Assistant stack, I am looking at you...
-                // We only move the focus to the primary-split screen stack if there isn't a
-                // better alternative.
-                candidate = stack;
-                continue;
-            }
-            if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) {
-                // Use the candidate stack since we are now at the secondary split-screen.
-                return candidate;
-            }
-            return stack;
-        }
-        return candidate;
-    }
-
-    ActivityRecord getResumedActivity() {
-        final ActivityStack focusedStack = getFocusedStack();
-        if (focusedStack == null) {
-            return null;
-        }
-        // TODO(b/111541062): Move this into ActivityStack#getResumedActivity()
-        // Check if the focused stack has the resumed activity
-        ActivityRecord resumedActivity = focusedStack.getResumedActivity();
-        if (resumedActivity == null || resumedActivity.app == null) {
-            // If there is no registered resumed activity in the stack or it is not running -
-            // try to use previously resumed one.
-            resumedActivity = focusedStack.mPausingActivity;
-            if (resumedActivity == null || resumedActivity.app == null) {
-                // If previously resumed activity doesn't work either - find the topmost running
-                // activity that can be focused.
-                resumedActivity = focusedStack.topRunningActivity(true /* focusableOnly */);
-            }
-        }
-        return resumedActivity;
-    }
-
-    ActivityStack getLastFocusedStack() {
-        return mLastFocusedStack;
-    }
-
-    boolean allResumedActivitiesComplete() {
-        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityRecord r = getStackAt(stackNdx).getResumedActivity();
-            if (r != null && !r.isState(RESUMED)) {
-                return false;
-            }
-        }
-        final ActivityStack currentFocusedStack = getFocusedStack();
-        if (DEBUG_STACK) {
-            Slog.d(TAG_STACK, "allResumedActivitiesComplete: mLastFocusedStack changing from="
-                    + mLastFocusedStack + " to=" + currentFocusedStack);
-        }
-        mLastFocusedStack = currentFocusedStack;
-        return true;
-    }
-
-    /**
-     * Pause all activities in either all of the stacks or just the back stacks. This is done before
-     * resuming a new activity and to make sure that previously active activities are
-     * paused in stacks that are no longer visible or in pinned windowing mode. This does not
-     * pause activities in visible stacks, so if an activity is launched within the same stack/task,
-     * then we should explicitly pause that stack's top activity.
-     * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
-     * @param resuming The resuming activity.
-     * @return {@code true} if any activity was paused as a result of this call.
-     */
-    boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) {
-        boolean someActivityPaused = false;
-        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = getStackAt(stackNdx);
-            final ActivityRecord resumedActivity = stack.getResumedActivity();
-            if (resumedActivity != null
-                    && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
-                        || !stack.isFocusable())) {
-                if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
-                        " mResumedActivity=" + resumedActivity);
-                someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
-                        resuming);
-            }
-        }
-        return someActivityPaused;
-    }
-
-    /**
-     * Find task for putting the Activity in.
-     */
-    void findTaskLocked(final ActivityRecord r, final boolean isPreferredDisplay,
-            FindTaskResult result) {
-        mTmpFindTaskResult.clear();
-        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = getStackAt(stackNdx);
-            if (!r.hasCompatibleActivityType(stack)) {
-                if (DEBUG_TASKS) {
-                    Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack);
-                }
-                continue;
-            }
-
-            mTmpFindTaskResult.process(r, stack);
-            // It is possible to have tasks in multiple stacks with the same root affinity, so
-            // we should keep looking after finding an affinity match to see if there is a
-            // better match in another stack. Also, task affinity isn't a good enough reason
-            // to target a display which isn't the source of the intent, so skip any affinity
-            // matches not on the specified display.
-            if (mTmpFindTaskResult.mRecord != null) {
-                if (mTmpFindTaskResult.mIdealMatch) {
-                    result.setTo(mTmpFindTaskResult);
-                    return;
-                } else if (isPreferredDisplay) {
-                    // Note: since the traversing through the stacks is top down, the floating
-                    // tasks should always have lower priority than any affinity-matching tasks
-                    // in the fullscreen stacks
-                    result.setTo(mTmpFindTaskResult);
-                }
-            }
-        }
-    }
-
-    /**
-     * Removes stacks in the input windowing modes from the system if they are of activity type
-     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
-     */
-    void removeStacksInWindowingModes(int... windowingModes) {
-        if (windowingModes == null || windowingModes.length == 0) {
-            return;
-        }
-
-        // Collect the stacks that are necessary to be removed instead of performing the removal
-        // by looping mStacks, so that we don't miss any stacks after the stack size changed or
-        // stacks reordered.
-        final ArrayList<ActivityStack> stacks = new ArrayList<>();
-        for (int j = windowingModes.length - 1 ; j >= 0; --j) {
-            final int windowingMode = windowingModes[j];
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = getStackAt(i);
-                if (!stack.isActivityTypeStandardOrUndefined()) {
-                    continue;
-                }
-                if (stack.getWindowingMode() != windowingMode) {
-                    continue;
-                }
-                stacks.add(stack);
-            }
-        }
-
-        for (int i = stacks.size() - 1; i >= 0; --i) {
-            mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i));
-        }
-    }
-
-    void removeStacksWithActivityTypes(int... activityTypes) {
-        if (activityTypes == null || activityTypes.length == 0) {
-            return;
-        }
-
-        // Collect the stacks that are necessary to be removed instead of performing the removal
-        // by looping mStacks, so that we don't miss any stacks after the stack size changed or
-        // stacks reordered.
-        final ArrayList<ActivityStack> stacks = new ArrayList<>();
-        for (int j = activityTypes.length - 1 ; j >= 0; --j) {
-            final int activityType = activityTypes[j];
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = getStackAt(i);
-                if (stack.getActivityType() == activityType) {
-                    stacks.add(stack);
-                }
-            }
-        }
-
-        for (int i = stacks.size() - 1; i >= 0; --i) {
-            mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i));
-        }
-    }
-
-    void onSplitScreenModeDismissed() {
-        mAtmService.deferWindowLayout();
-        try {
-            // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack otherStack = getStackAt(i);
-                if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
-                    continue;
-                }
-                otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED, false /* animate */,
-                        false /* showRecents */, false /* enteringSplitScreenMode */,
-                        true /* deferEnsuringVisibility */, false /* creating */);
-            }
-        } finally {
-            final ActivityStack topFullscreenStack =
-                    getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
-            final ActivityStack homeStack = getHomeStack();
-            if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
-                // Whenever split-screen is dismissed we want the home stack directly behind the
-                // current top fullscreen stack so it shows up when the top stack is finished.
-                // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
-                // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
-                // once we have that.
-                homeStack.moveToFront("onSplitScreenModeDismissed");
-                topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
-            }
-            mAtmService.continueWindowLayout();
-        }
-    }
-
-    void onSplitScreenModeActivated() {
-        mAtmService.deferWindowLayout();
-        try {
-            // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
-            final ActivityStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack otherStack = getStackAt(i);
-                if (otherStack == splitScreenPrimaryStack
-                        || !otherStack.affectedBySplitScreenResize()) {
-                    continue;
-                }
-                otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
-                        false /* animate */, false /* showRecents */,
-                        true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */,
-                        false /* creating */);
-            }
-        } finally {
-            mAtmService.continueWindowLayout();
-        }
-    }
-
-    /**
-     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
-     * @param windowingMode The windowing mode we are checking support for.
-     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
-     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
-     * @param supportsFreeform If we should consider support for freeform multi-window.
-     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
-     * @param activityType The activity type under consideration.
-     * @return true if the windowing mode is supported.
-     */
-    private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
-            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
-            int activityType) {
-
-        if (windowingMode == WINDOWING_MODE_UNDEFINED
-                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
-            return true;
-        }
-        if (!supportsMultiWindow) {
-            return false;
-        }
-
-        final int displayWindowingMode = getWindowingMode();
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-            return supportsSplitScreen
-                    && WindowConfiguration.supportSplitScreenWindowingMode(activityType)
-                    // Freeform windows and split-screen windows don't mix well, so prevent
-                    // split windowing modes on freeform displays.
-                    && displayWindowingMode != WINDOWING_MODE_FREEFORM;
-        }
-
-        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
-            return false;
-        }
-
-        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this
-     * display with the provided parameters.
-     *
-     * @param r The ActivityRecord in question.
-     * @param options Options to start with.
-     * @param task The task within-which the activity would start.
-     * @param activityType The type of activity to start.
-     * @return The resolved (not UNDEFINED) windowing-mode that the activity would be in.
-     */
-    int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
-            @Nullable Task task, int activityType) {
-
-        // First preference if the windowing mode in the activity options if set.
-        int windowingMode = (options != null)
-                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
-        // If windowing mode is unset, then next preference is the candidate task, then the
-        // activity record.
-        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
-            if (task != null) {
-                windowingMode = task.getWindowingMode();
-            }
-            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
-                windowingMode = r.getWindowingMode();
-            }
-            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
-                // Use the display's windowing mode.
-                windowingMode = getWindowingMode();
-            }
-        }
-        windowingMode = validateWindowingMode(windowingMode, r, task, activityType);
-        return windowingMode != WINDOWING_MODE_UNDEFINED
-                ? windowingMode : WINDOWING_MODE_FULLSCREEN;
-    }
-
-    /**
-     * Check that the requested windowing-mode is appropriate for the specified task and/or activity
-     * on this display.
-     *
-     * @param windowingMode The windowing-mode to validate.
-     * @param r The {@link ActivityRecord} to check against.
-     * @param task The {@link Task} to check against.
-     * @param activityType An activity type.
-     * @return The provided windowingMode or the closest valid mode which is appropriate.
-     */
-    int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
-            int activityType) {
-        // Make sure the windowing mode we are trying to use makes sense for what is supported.
-        boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
-        boolean supportsSplitScreen = mAtmService.mSupportsSplitScreenMultiWindow;
-        boolean supportsFreeform = mAtmService.mSupportsFreeformWindowManagement;
-        boolean supportsPip = mAtmService.mSupportsPictureInPicture;
-        if (supportsMultiWindow) {
-            if (task != null) {
-                supportsMultiWindow = task.isResizeable();
-                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
-                // TODO: Do we need to check for freeform and Pip support here?
-            } else if (r != null) {
-                supportsMultiWindow = r.isResizeable();
-                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
-                supportsFreeform = r.supportsFreeform();
-                supportsPip = r.supportsPictureInPicture();
-            }
-        }
-
-        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
-        if (!inSplitScreenMode
-                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
-            // Switch to the display's windowing mode if we are not in split-screen mode and we are
-            // trying to launch in split-screen secondary.
-            windowingMode = WINDOWING_MODE_UNDEFINED;
-        } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN
-                        || windowingMode == WINDOWING_MODE_UNDEFINED)
-                && supportsSplitScreen) {
-            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-        }
-
-        if (windowingMode != WINDOWING_MODE_UNDEFINED
-                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
-                        supportsFreeform, supportsPip, activityType)) {
-            return windowingMode;
-        }
-        return WINDOWING_MODE_UNDEFINED;
-    }
-
-    boolean isTopStack(ActivityStack stack) {
-        return stack == getTopStack();
-    }
-
-    boolean isTopNotPinnedStack(ActivityStack stack) {
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack current = getStackAt(i);
-            if (!current.inPinnedWindowingMode()) {
-                return current == stack;
-            }
-        }
-        return false;
-    }
-
-    ActivityStack getTopStackInWindowingMode(int windowingMode) {
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack current = getStackAt(i);
-            if (windowingMode == current.getWindowingMode()) {
-                return current;
-            }
-        }
-        return null;
-    }
-
-    ActivityRecord topRunningActivity() {
-        return topRunningActivity(false /* considerKeyguardState */);
-    }
-
-    /**
-     * Returns the top running activity in the focused stack. In the case the focused stack has no
-     * such activity, the next focusable stack on this display is returned.
-     *
-     * @param considerKeyguardState Indicates whether the locked state should be considered. if
-     *                              {@code true} and the keyguard is locked, only activities that
-     *                              can be shown on top of the keyguard will be considered.
-     * @return The top running activity. {@code null} if none is available.
-     */
-    ActivityRecord topRunningActivity(boolean considerKeyguardState) {
-        ActivityRecord topRunning = null;
-        final ActivityStack focusedStack = getFocusedStack();
-        if (focusedStack != null) {
-            topRunning = focusedStack.topRunningActivity();
-        }
-
-        // Look in other focusable stacks.
-        if (topRunning == null) {
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = getStackAt(i);
-                // Only consider focusable stacks other than the current focused one.
-                if (stack == focusedStack || !stack.isFocusable()) {
-                    continue;
-                }
-                topRunning = stack.topRunningActivity();
-                if (topRunning != null) {
-                    break;
-                }
-            }
-        }
-
-        // This activity can be considered the top running activity if we are not considering
-        // the locked state, the keyguard isn't locked, or we can show when locked.
-        if (topRunning != null && considerKeyguardState
-                && mRootActivityContainer.mStackSupervisor.getKeyguardController().isKeyguardLocked()
-                && !topRunning.canShowWhenLocked()) {
-            return null;
-        }
-
-        return topRunning;
-    }
-
-    boolean updateDisplayOverrideConfigurationLocked() {
-        Configuration values = new Configuration();
-        computeScreenConfiguration(values);
-
-        mAtmService.mH.sendMessage(PooledLambda.obtainMessage(
-                ActivityManagerInternal::updateOomLevelsForDisplay, mAtmService.mAmInternal,
-                mDisplayId));
-
-        Settings.System.clearConfiguration(values);
-        updateDisplayOverrideConfigurationLocked(values, null /* starting */,
-                false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
-        return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
-    }
-
-    /**
-     * Updates override configuration specific for the selected display. If no config is provided,
-     * new one will be computed in WM based on current display info.
-     */
-    boolean updateDisplayOverrideConfigurationLocked(Configuration values,
-            ActivityRecord starting, boolean deferResume,
-            ActivityTaskManagerService.UpdateConfigurationResult result) {
-
-        int changes = 0;
-        boolean kept = true;
-
-        mAtmService.deferWindowLayout();
-        try {
-            if (values != null) {
-                if (mDisplayId == DEFAULT_DISPLAY) {
-                    // Override configuration of the default display duplicates global config, so
-                    // we're calling global config update instead for default display. It will also
-                    // apply the correct override config.
-                    changes = mAtmService.updateGlobalConfigurationLocked(values,
-                            false /* initLocale */, false /* persistent */,
-                            UserHandle.USER_NULL /* userId */, deferResume);
-                } else {
-                    changes = performDisplayOverrideConfigUpdate(values, deferResume);
-                }
-            }
-
-            kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
-        } finally {
-            mAtmService.continueWindowLayout();
-        }
-
-        if (result != null) {
-            result.changes = changes;
-            result.activityRelaunched = !kept;
-        }
-        return kept;
-    }
-
-    int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume) {
-        mTempConfig.setTo(getRequestedOverrideConfiguration());
-        final int changes = mTempConfig.updateFrom(values);
-        if (changes != 0) {
-            Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
-                    + mTempConfig + " for displayId=" + mDisplayId);
-            onRequestedOverrideConfigurationChanged(mTempConfig);
-
-            final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
-            if (isDensityChange && mDisplayId == DEFAULT_DISPLAY) {
-                mAtmService.mAppWarnings.onDensityChanged();
-
-                // Post message to start process to avoid possible deadlock of calling into AMS with
-                // the ATMS lock held.
-                final Message msg = PooledLambda.obtainMessage(
-                        ActivityManagerInternal::killAllBackgroundProcessesExcept,
-                        mAtmService.mAmInternal, N,
-                        ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
-                mAtmService.mH.sendMessage(msg);
-            }
-            mWmService.mDisplayNotificationController.dispatchDisplayChanged(
-                    this, getConfiguration());
-        }
-        return changes;
-    }
-
-    @Override
-    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
-        final int currRotation =
-                getRequestedOverrideConfiguration().windowConfiguration.getRotation();
-        if (currRotation != ROTATION_UNDEFINED
-                && currRotation != overrideConfiguration.windowConfiguration.getRotation()) {
-            applyRotationLocked(currRotation,
-                    overrideConfiguration.windowConfiguration.getRotation());
-        }
-        super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
-        mWmService.setNewDisplayOverrideConfiguration(overrideConfiguration, this);
-        mAtmService.addWindowLayoutReasons(
-                ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newParentConfig) {
-        // update resources before cascade so that docked/pinned stacks use the correct info
-        preOnConfigurationChanged();
-        super.onConfigurationChanged(newParentConfig);
-    }
-
-    /** Checks whether the given activity is in size compatibility mode and notifies the change. */
-    void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) {
-        if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
-            // The callback is only interested in the foreground changes of fullscreen activity.
-            return;
-        }
-        if (!r.inSizeCompatMode()) {
-            if (mLastCompatModeActivity != null) {
-                mAtmService.getTaskChangeNotificationController()
-                        .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */);
-            }
-            mLastCompatModeActivity = null;
-            return;
-        }
-        if (mLastCompatModeActivity == r) {
-            return;
-        }
-        mLastCompatModeActivity = r;
-        mAtmService.getTaskChangeNotificationController()
-                .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken);
-    }
-
-    @Override
-    public String toString() {
-        return "ActivityDisplay={" + mDisplayId + " numStacks=" + getStackCount() + "}";
-    }
-
-    boolean isPrivate() {
-        return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
-    }
-
-    boolean isUidPresent(int uid) {
-        final PooledPredicate p = PooledLambda.obtainPredicate(
-                ActivityRecord::isUid, PooledLambda.__(ActivityRecord.class), uid);
-        final boolean isUidPresent = mDisplayContent.getActivity(p) != null;
-        p.recycle();
-        return isUidPresent;
-    }
-
-    /**
-     * @see #mRemoved
-     */
-    boolean isRemoved() {
-        return mRemoved;
-    }
-
-    /**
-     * @see #mRemoving
-     */
-    boolean isRemoving() {
-        return mRemoving;
-    }
-
-    void remove() {
-        mRemoving = true;
-        final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove();
-        ActivityStack lastReparentedStack = null;
-        mPreferredTopFocusableStack = null;
-
-        // Stacks could be reparented from the removed display to other display. While
-        // reparenting the last stack of the removed display, the remove display is ready to be
-        // released (no more ActivityStack). But, we cannot release it at that moment or the
-        // related WindowContainer will also be removed. So, we set display as removed after
-        // reparenting stack finished.
-        final ActivityDisplay toDisplay = mRootActivityContainer.getDefaultDisplay();
-        mRootActivityContainer.mStackSupervisor.beginDeferResume();
-        try {
-            int numStacks = getStackCount();
-            // Keep the order from bottom to top.
-            for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
-                final ActivityStack stack = getStackAt(stackNdx);
-                // Always finish non-standard type stacks.
-                if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) {
-                    stack.finishAllActivitiesImmediately();
-                } else {
-                    // If default display is in split-window mode, set windowing mode of the stack
-                    // to split-screen secondary. Otherwise, set the windowing mode to undefined by
-                    // default to let stack inherited the windowing mode from the new display.
-                    final int windowingMode = toDisplay.hasSplitScreenPrimaryStack()
-                            ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                            : WINDOWING_MODE_UNDEFINED;
-                    stack.reparent(toDisplay, true /* onTop */);
-                    stack.setWindowingMode(windowingMode);
-                    lastReparentedStack = stack;
-                }
-                // Stacks may be removed from this display. Ensure each stack will be processed and
-                // the loop will end.
-                stackNdx -= numStacks - getStackCount();
-                numStacks = getStackCount();
-            }
-        } finally {
-            mRootActivityContainer.mStackSupervisor.endDeferResume();
-        }
-        mRemoved = true;
-
-        // Only update focus/visibility for the last one because there may be many stacks are
-        // reparented and the intermediate states are unnecessary.
-        if (lastReparentedStack != null) {
-            lastReparentedStack.postReparent();
-        }
-        releaseSelfIfNeeded();
-
-        if (!mAllSleepTokens.isEmpty()) {
-            mRootActivityContainer.mSleepTokens.removeAll(mAllSleepTokens);
-            mAllSleepTokens.clear();
-            mAtmService.updateSleepIfNeededLocked();
-        }
-    }
-
-    private void releaseSelfIfNeeded() {
-        if (!mRemoved) {
-            return;
-        }
-
-        final ActivityStack stack = getStackCount() == 1 ? getStackAt(0) : null;
-        if (stack != null && stack.isActivityTypeHome() && !stack.hasChild()) {
-            // Release this display if an empty home stack is the only thing left.
-            // Since it is the last stack, this display will be released along with the stack
-            // removal.
-            stack.removeIfPossible();
-        } else if (getTopStack() == null) {
-            removeIfPossible();
-            mRootActivityContainer.removeChild(this);
-            mRootActivityContainer.mStackSupervisor
-                    .getKeyguardController().onDisplayRemoved(mDisplayId);
-        }
-    }
-
-    /** Update and get all UIDs that are present on the display and have access to it. */
-    IntArray getPresentUIDs() {
-        mDisplayAccessUIDs.clear();
-        final PooledConsumer c = PooledLambda.obtainConsumer(ActivityDisplay::addActivityUid,
-                PooledLambda.__(ActivityRecord.class), mDisplayAccessUIDs);
-        mDisplayContent.forAllActivities(c);
-        c.recycle();
-        return mDisplayAccessUIDs;
-    }
-
-    private static void addActivityUid(ActivityRecord r, IntArray uids) {
-        uids.add(r.getUid());
-    }
-
-    @VisibleForTesting
-    boolean shouldDestroyContentOnRemove() {
-        return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
-    }
-
-    boolean shouldSleep() {
-        return (getStackCount() == 0 || !mAllSleepTokens.isEmpty())
-                && (mAtmService.mRunningVoice == null);
-    }
-
-    void setFocusedApp(ActivityRecord r, boolean moveFocusNow) {
-        final ActivityRecord newFocus;
-        final IBinder token = r.appToken;
-        if (token == null) {
-            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Clearing focused app, displayId=%d",
-                    mDisplayId);
-            newFocus = null;
-        } else {
-            newFocus = mWmService.mRoot.getActivityRecord(token);
-            if (newFocus == null) {
-                Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token
-                        + ", displayId=" + mDisplayId);
-            }
-            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
-                    "Set focused app to: %s moveFocusNow=%b displayId=%d", newFocus,
-                            moveFocusNow, mDisplayId);
-        }
-
-        final boolean changed = setFocusedApp(newFocus);
-        if (moveFocusNow && changed) {
-            mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
-                    true /*updateInputWindows*/);
-        }
-    }
-
-    /**
-     * @return the stack currently above the {@param stack}.  Can be null if the {@param stack} is
-     *         already top-most.
-     */
-    ActivityStack getStackAbove(ActivityStack stack) {
-        final int stackIndex = getIndexOf(stack) + 1;
-        return (stackIndex < getStackCount()) ? getStackAt(stackIndex) : null;
-    }
-
-    /**
-     * Adjusts the {@param stack} behind the last visible stack in the display if necessary.
-     * Generally used in conjunction with {@link #moveStackBehindStack}.
-     */
-    void moveStackBehindBottomMostVisibleStack(ActivityStack stack) {
-        if (stack.shouldBeVisible(null)) {
-            // Skip if the stack is already visible
-            return;
-        }
-
-        // Move the stack to the bottom to not affect the following visibility checks
-        positionStackAtBottom(stack);
-
-        // Find the next position where the stack should be placed
-        final int numStacks = getStackCount();
-        for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
-            final ActivityStack s = getStackAt(stackNdx);
-            if (s == stack) {
-                continue;
-            }
-            final int winMode = s.getWindowingMode();
-            final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN ||
-                    winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-            if (s.shouldBeVisible(null) && isValidWindowingMode) {
-                // Move the provided stack to behind this stack
-                positionStackAt(stack, Math.max(0, stackNdx - 1));
-                break;
-            }
-        }
-    }
-
-    /**
-     * Moves the {@param stack} behind the given {@param behindStack} if possible. If
-     * {@param behindStack} is not currently in the display, then then the stack is moved to the
-     * back. Generally used in conjunction with {@link #moveStackBehindBottomMostVisibleStack}.
-     */
-    void moveStackBehindStack(ActivityStack stack, ActivityStack behindStack) {
-        if (behindStack == null || behindStack == stack) {
-            return;
-        }
-
-        // Note that positionChildAt will first remove the given stack before inserting into the
-        // list, so we need to adjust the insertion index to account for the removed index
-        // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
-        //       position internally
-        final int stackIndex = getIndexOf(stack);
-        final int behindStackIndex = getIndexOf(behindStack);
-        final int insertIndex = stackIndex <= behindStackIndex
-                ? behindStackIndex - 1 : behindStackIndex;
-        positionStackAt(stack, Math.max(0, insertIndex));
-    }
-
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
-        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = getStackAt(stackNdx);
-            stack.ensureActivitiesVisible(starting, configChanges, preserveWindows,
-                    notifyClients);
-        }
-    }
-
-    void moveHomeStackToFront(String reason) {
-        final ActivityStack homeStack = getHomeStack();
-        if (homeStack != null) {
-            homeStack.moveToFront(reason);
-        }
-    }
-
-    /**
-     * Moves the focusable home activity to top. If there is no such activity, the home stack will
-     * still move to top.
-     */
-    void moveHomeActivityToTop(String reason) {
-        final ActivityRecord top = getHomeActivity();
-        if (top == null) {
-            moveHomeStackToFront(reason);
-            return;
-        }
-        top.moveFocusableActivityToTop(reason);
-    }
-
-    @Nullable
-    ActivityRecord getHomeActivity() {
-        return getHomeActivityForUser(mRootActivityContainer.mCurrentUser);
-    }
-
-    @Nullable
-    ActivityRecord getHomeActivityForUser(int userId) {
-        final ActivityStack homeStack = getHomeStack();
-        if (homeStack == null) {
-            return null;
-        }
-
-        final PooledPredicate p = PooledLambda.obtainPredicate(
-                ActivityDisplay::isHomeActivityForUser, PooledLambda.__(ActivityRecord.class),
-                userId);
-        final ActivityRecord r = homeStack.getActivity(p);
-        p.recycle();
-        return r;
-    }
-
-    private static boolean isHomeActivityForUser(ActivityRecord r, int userId) {
-        return r.isActivityTypeHome() && (userId == UserHandle.USER_ALL || r.mUserId == userId);
-    }
-
-    boolean isSleeping() {
-        return mSleeping;
-    }
-
-    void setIsSleeping(boolean asleep) {
-        mSleeping = asleep;
-    }
-
-    /**
-     * Adds a listener to be notified whenever the stack order in the display changes. Currently
-     * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
-     * current animation when the system state changes.
-     */
-    void registerStackOrderChangedListener(OnStackOrderChangedListener listener) {
-        if (!mStackOrderChangedCallbacks.contains(listener)) {
-            mStackOrderChangedCallbacks.add(listener);
-        }
-    }
-
-    /**
-     * Removes a previously registered stack order change listener.
-     */
-    void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) {
-        mStackOrderChangedCallbacks.remove(listener);
-    }
-
-    /**
-     * Notifies of a stack order change
-     * @param stack The stack which triggered the order change
-     */
-    private void onStackOrderChanged(ActivityStack stack) {
-        for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
-            mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack);
-        }
-    }
-
-    void setDisplayToSingleTaskInstance() {
-        final int childCount = getStackCount();
-        if (childCount > 1) {
-            throw new IllegalArgumentException("Display already has multiple stacks. display="
-                    + this);
-        }
-        if (childCount > 0) {
-            final ActivityStack stack = getStackAt(0);
-            if (stack.getChildCount() > 1) {
-                throw new IllegalArgumentException("Display stack already has multiple tasks."
-                        + " display=" + this + " stack=" + stack);
-            }
-        }
-
-        mSingleTaskInstance = true;
-    }
-
-    /** Returns true if the display can only contain one task */
-    boolean isSingleTaskInstance() {
-        return mSingleTaskInstance;
-    }
-
-    @VisibleForTesting
-    void removeAllTasks() {
-        mDisplayContent.forAllTasks((t) -> { t.getStack().removeChild(t, "removeAllTasks"); });
-    }
-
-    public void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + getStackCount()
-                + (mSingleTaskInstance ? " mSingleTaskInstance" : ""));
-        final String myPrefix = prefix + " ";
-        ActivityStack stack = getHomeStack();
-        if (stack != null) {
-            pw.println(myPrefix + "mHomeStack=" + stack);
-        }
-        stack = getRecentsStack();
-        if (stack != null) {
-            pw.println(myPrefix + "mRecentsStack=" + stack);
-        }
-        stack = getPinnedStack();
-        if (stack != null) {
-            pw.println(myPrefix + "mPinnedStack=" + stack);
-        }
-        stack = getSplitScreenPrimaryStack();
-        if (stack != null) {
-            pw.println(myPrefix + "mSplitScreenPrimaryStack=" + stack);
-        }
-        if (mPreferredTopFocusableStack != null) {
-            pw.println(myPrefix + "mPreferredTopFocusableStack=" + mPreferredTopFocusableStack);
-        }
-        if (mLastFocusedStack != null) {
-            pw.println(myPrefix + "mLastFocusedStack=" + mLastFocusedStack);
-        }
-    }
-
-    public void dumpStacks(PrintWriter pw) {
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            pw.print(getStackAt(i).mStackId);
-            if (i > 0) {
-                pw.print(",");
-            }
-        }
-    }
-
-    public void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
-        final long token = proto.start(fieldId);
-        dumpDebugInner(proto, DISPLAY, logLevel);
-        proto.write(ID, mDisplayId);
-        proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance);
-        final ActivityStack focusedStack = getFocusedStack();
-        if (focusedStack != null) {
-            proto.write(FOCUSED_STACK_ID, focusedStack.mStackId);
-            final ActivityRecord focusedActivity = focusedStack.getDisplay().getResumedActivity();
-            if (focusedActivity != null) {
-                focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
-            }
-        } else {
-            proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
-        }
-        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = getStackAt(stackNdx);
-            stack.dumpDebug(proto, STACKS, logLevel);
-        }
-        proto.end(token);
-    }
-
-    /**
-     * Callback for when the order of the stacks in the display changes.
-     */
-    interface OnStackOrderChangedListener {
-        void onStackOrderChanged(ActivityStack stack);
-    }
-}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 20d1d1c..c8357e2 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -190,6 +190,8 @@
         final int mTransitionType;
         /** Whether the process was already running when the transition started. */
         final boolean mProcessRunning;
+        /** whether the process of the launching activity didn't have any active activity. */
+        final boolean mProcessSwitch;
         /** The activities that should be drawn. */
         final LinkedList<ActivityRecord> mPendingDrawActivities = new LinkedList<>();
         /** The latest activity to have been launched. */
@@ -218,7 +220,8 @@
         /** @return Non-null if there will be a window drawn event for the launch. */
         @Nullable
         static TransitionInfo create(@NonNull ActivityRecord r,
-                @NonNull LaunchingState launchingState, boolean processRunning, int startResult) {
+                @NonNull LaunchingState launchingState, boolean processRunning,
+                boolean processSwitch, int startResult) {
             int transitionType = INVALID_TRANSITION_TYPE;
             if (processRunning) {
                 if (startResult == START_SUCCESS) {
@@ -235,16 +238,18 @@
                 // That means the startResult is neither START_SUCCESS nor START_TASK_TO_FRONT.
                 return null;
             }
-            return new TransitionInfo(r, launchingState, transitionType, processRunning);
+            return new TransitionInfo(r, launchingState, transitionType, processRunning,
+                    processSwitch);
         }
 
         /** Use {@link TransitionInfo#create} instead to ensure the transition type is valid. */
         private TransitionInfo(ActivityRecord r, LaunchingState launchingState, int transitionType,
-                boolean processRunning) {
+                boolean processRunning, boolean processSwitch) {
             mLaunchingState = launchingState;
             mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
             mTransitionType = transitionType;
             mProcessRunning = processRunning;
+            mProcessSwitch = processSwitch;
             mCurrentTransitionDeviceUptime =
                     (int) TimeUnit.MILLISECONDS.toSeconds(SystemClock.uptimeMillis());
             setLatestLaunchedActivity(r);
@@ -282,6 +287,14 @@
             return mPendingDrawActivities.isEmpty();
         }
 
+        /**
+         * @return {@code true} if the transition info should be sent to MetricsLogger, StatsLog, or
+         *         LaunchObserver.
+         */
+        boolean isInterestingToLoggerAndObserver() {
+            return mProcessSwitch;
+        }
+
         int calculateCurrentDelay() {
             return calculateDelay(SystemClock.elapsedRealtimeNanos());
         }
@@ -540,13 +553,8 @@
             return;
         }
 
-        if (!processSwitch) {
-            abort(info, "not a process switch");
-            return;
-        }
-
         final TransitionInfo newInfo = TransitionInfo.create(launchedActivity, launchingState,
-                processRunning, resultCode);
+                processRunning, processSwitch, resultCode);
         if (newInfo == null) {
             abort(info, "unrecognized launch");
             return;
@@ -557,7 +565,12 @@
         mTransitionInfoList.add(newInfo);
         mLastTransitionInfo.put(launchedActivity, newInfo);
         startLaunchTrace(newInfo);
-        launchObserverNotifyActivityLaunched(newInfo);
+        if (newInfo.isInterestingToLoggerAndObserver()) {
+            launchObserverNotifyActivityLaunched(newInfo);
+        } else {
+            // As abort for no process switch.
+            launchObserverNotifyIntentFailed();
+        }
     }
 
     /**
@@ -733,7 +746,9 @@
             launchObserverNotifyActivityLaunchCancelled(info);
         } else {
             logAppTransitionFinished(info);
-            launchObserverNotifyActivityLaunchFinished(info, timestampNs);
+            if (info.isInterestingToLoggerAndObserver()) {
+                launchObserverNotifyActivityLaunchFinished(info, timestampNs);
+            }
         }
         info.mPendingDrawActivities.clear();
         mTransitionInfoList.remove(info);
@@ -768,8 +783,11 @@
         // Take a snapshot of the transition info before sending it to the handler for logging.
         // This will avoid any races with other operations that modify the ActivityRecord.
         final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
-        BackgroundThread.getHandler().post(() -> logAppTransition(
-                info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs, infoSnapshot));
+        if (info.isInterestingToLoggerAndObserver()) {
+            BackgroundThread.getHandler().post(() -> logAppTransition(
+                    info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs,
+                    infoSnapshot));
+        }
         BackgroundThread.getHandler().post(() -> logAppDisplayed(infoSnapshot));
         if (info.mPendingFullyDrawn != null) {
             info.mPendingFullyDrawn.run();
@@ -905,6 +923,18 @@
             return null;
         }
 
+        final long currentTimestampNs = SystemClock.elapsedRealtimeNanos();
+        final long startupTimeMs = info.mPendingFullyDrawn != null
+                ? info.mWindowsDrawnDelayMs
+                : TimeUnit.NANOSECONDS.toMillis(currentTimestampNs - info.mTransitionStartTimeNs);
+        final TransitionInfoSnapshot infoSnapshot =
+                new TransitionInfoSnapshot(info, r, (int) startupTimeMs);
+        BackgroundThread.getHandler().post(() -> logAppFullyDrawn(infoSnapshot));
+
+        if (!info.isInterestingToLoggerAndObserver()) {
+            return infoSnapshot;
+        }
+
         // Record the handling of the reportFullyDrawn callback in the trace system. This is not
         // actually used to trace this function, but instead the logical task that this function
         // fullfils (handling reportFullyDrawn() callbacks).
@@ -914,10 +944,6 @@
         final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
         builder.setPackageName(r.packageName);
         builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
-        final long currentTimestampNs = SystemClock.elapsedRealtimeNanos();
-        final long startupTimeMs = info.mPendingFullyDrawn != null
-                ? info.mWindowsDrawnDelayMs
-                : TimeUnit.NANOSECONDS.toMillis(currentTimestampNs - info.mTransitionStartTimeNs);
         builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
         builder.setType(restoredFromBundle
                 ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
@@ -940,10 +966,6 @@
         // the trace slice to have a noticable duration.
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
-        final TransitionInfoSnapshot infoSnapshot =
-                new TransitionInfoSnapshot(info, r, (int) startupTimeMs);
-        BackgroundThread.getHandler().post(() -> logAppFullyDrawn(infoSnapshot));
-
         // Notify reportFullyDrawn event.
         launchObserverNotifyReportFullyDrawn(r, currentTimestampNs);
 
@@ -1069,7 +1091,8 @@
             return;
         }
         info.mLaunchTraceName = "launching: " + info.mLastLaunchedActivity.packageName;
-        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, info.mLaunchTraceName, 0);
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, info.mLaunchTraceName,
+                (int) info.mTransitionStartTimeNs /* cookie */);
     }
 
     /** Stops trace for the launch is completed or cancelled. */
@@ -1078,7 +1101,8 @@
         if (info.mLaunchTraceName == null) {
             return;
         }
-        Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, info.mLaunchTraceName, 0);
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, info.mLaunchTraceName,
+                (int) info.mTransitionStartTimeNs /* cookie */);
         info.mLaunchTraceName = null;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4e39dae..6de9dbd 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1640,6 +1640,17 @@
                 allowTaskSnapshot, activityCreated, fromRecents, snapshot);
 
         if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
+            if (isActivityTypeHome()) {
+                // The snapshot of home is only used once because it won't be updated while screen
+                // is on (see {@link TaskSnapshotController#screenTurningOff}).
+                mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
+                // TODO(b/9684093): Use more general condition to specify the case.
+                if (mDisplayContent.mAppTransition
+                        .getAppTransition() != WindowManager.TRANSIT_KEYGUARD_GOING_AWAY) {
+                    // Only use snapshot of home as starting window when unlocking.
+                    return false;
+                }
+            }
             return createSnapshot(snapshot);
         }
 
@@ -1800,11 +1811,6 @@
         } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
             return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else if (taskSwitch && allowTaskSnapshot) {
-            if (mWmService.mLowRamTaskSnapshotsAndRecents) {
-                // For low RAM devices, we use the splash screen starting window instead of the
-                // task snapshot starting window.
-                return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-            }
             return snapshot == null ? STARTING_WINDOW_TYPE_NONE
                     : snapshotOrientationSameAsTask(snapshot) || fromRecents
                             ? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -1996,7 +2002,7 @@
         return getActivityStack() != null ? getActivityStack().mStackId : INVALID_STACK_ID;
     }
 
-    ActivityDisplay getDisplay() {
+    DisplayContent getDisplay() {
         final ActivityStack stack = getActivityStack();
         return stack != null ? stack.getDisplay() : null;
     }
@@ -2153,7 +2159,7 @@
         boolean isKeyguardLocked = mAtmService.isKeyguardLocked();
         boolean isCurrentAppLocked =
                 mAtmService.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         boolean hasPinnedStack = display != null && display.hasPinnedStack();
         // Don't return early if !isNotLocked, since we want to throw an exception if the activity
         // is in an incorrect state
@@ -2217,11 +2223,15 @@
         return (info.flags & FLAG_ALWAYS_FOCUSABLE) != 0;
     }
 
+    boolean windowsAreFocusable() {
+        return windowsAreFocusable(false /* fromUserTouch */);
+    }
+
     // TODO: Does this really need to be different from isAlwaysFocusable()? For the activity side
     // focusable means resumeable. I guess with that in mind maybe we should rename the other
     // method to isResumeable() or something like that.
-    boolean windowsAreFocusable() {
-        if (mTargetSdk < Build.VERSION_CODES.Q) {
+    boolean windowsAreFocusable(boolean fromUserTouch) {
+        if (!fromUserTouch && mTargetSdk < Build.VERSION_CODES.Q) {
             final int pid = getPid();
             final ActivityRecord topFocusedAppOfMyProcess =
                     mWmService.mRoot.mTopFocusedAppByProcess.get(pid);
@@ -2235,7 +2245,12 @@
                 && getDisplay() != null;
     }
 
-    /** Move activity with its stack to front and make the stack focused. */
+    /**
+     * Move activity with its stack to front and make the stack focused.
+     * @param reason the reason to move to top
+     * @return {@code true} if the stack is focusable and has been moved to top or the activity
+     *         is not yet resumed while the stack is already on top, {@code false} otherwise.
+     */
     boolean moveFocusableActivityToTop(String reason) {
         if (!isFocusable()) {
             if (DEBUG_FOCUS) {
@@ -2256,7 +2271,7 @@
             if (DEBUG_FOCUS) {
                 Slog.d(TAG_FOCUS, "moveActivityStackToFront: already on top, activity=" + this);
             }
-            return false;
+            return !isState(RESUMED);
         }
 
         if (DEBUG_FOCUS) {
@@ -2413,7 +2428,7 @@
                     stack.adjustFocusToNextFocusableStack("finish-top");
                 } else {
                     // Only move the next stack to top in its display.
-                    final ActivityDisplay display = stack.getDisplay();
+                    final DisplayContent display = stack.getDisplay();
                     next = display.topRunningActivity();
                     if (next != null) {
                         display.positionStackAtTop(next.getActivityStack(),
@@ -2587,9 +2602,9 @@
         mStackSupervisor.mGoingToSleepActivities.remove(this);
 
         final ActivityStack stack = getActivityStack();
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         // TODO(b/137329632): Exclude current activity when looking for the next one with
-        //  ActivityDisplay#topRunningActivity().
+        // DisplayContent#topRunningActivity().
         final ActivityRecord next = display.topRunningActivity();
         final boolean isLastStackOverEmptyHome =
                 next == null && stack.isFocusedStackOnDisplay() && display.getHomeStack() != null;
@@ -4656,7 +4671,7 @@
         }
         r.setSavedState(null /* savedState */);
 
-        final ActivityDisplay display = r.getDisplay();
+        final DisplayContent display = r.getDisplay();
         if (display != null) {
             display.handleActivitySizeCompatModeIfNeeded(r);
         }
@@ -6253,7 +6268,7 @@
         }
 
         // The role of CompatDisplayInsets is like the override bounds.
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         if (display != null) {
             mCompatDisplayInsets = new CompatDisplayInsets(display.mDisplayContent,
                     getWindowConfiguration().getBounds(),
@@ -6523,7 +6538,7 @@
             onMergedOverrideConfigurationChanged();
         }
 
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         if (display == null) {
             return;
         }
@@ -7272,7 +7287,7 @@
      * otherwise.
      */
     boolean isResumedActivityOnDisplay() {
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         return display != null && this == display.getResumedActivity();
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index eb1f638..677d2a1 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -131,8 +131,6 @@
 import android.app.IActivityController;
 import android.app.RemoteAction;
 import android.app.ResultInfo;
-import android.app.WindowConfiguration.ActivityType;
-import android.app.WindowConfiguration.WindowingMode;
 import android.app.servertransaction.ActivityResultItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.NewIntentItem;
@@ -144,7 +142,6 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.Region;
 import android.os.Binder;
 import android.os.Debug;
 import android.os.Handler;
@@ -154,9 +151,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
-import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Slog;
@@ -732,7 +727,7 @@
         }
     }
 
-    ActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+    ActivityStack(DisplayContent display, int stackId, ActivityStackSupervisor supervisor,
             int windowingMode, int activityType, boolean onTop) {
         super(supervisor.mService.mWindowManager);
         mStackId = stackId;
@@ -816,7 +811,7 @@
             }
         }
 
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         if (display == null ) {
             return;
         }
@@ -946,7 +941,7 @@
             boolean creating) {
         final int currentMode = getWindowingMode();
         final int currentOverrideMode = getRequestedOverrideWindowingMode();
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         final Task topTask = getTopMostTask();
         final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
         int windowingMode = preferredWindowingMode;
@@ -1116,8 +1111,8 @@
                 !PRESERVE_WINDOWS);
     }
 
-    ActivityDisplay getDisplay() {
-        return mRootActivityContainer.getActivityDisplay(mDisplayId);
+    DisplayContent getDisplay() {
+        return getDisplayContent();
     }
 
     /**
@@ -1250,7 +1245,7 @@
 
     /** @return true if the stack can only contain one task */
     boolean isSingleTaskInstance() {
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         return display != null && display.isSingleTaskInstance();
     }
 
@@ -1291,7 +1286,7 @@
             return;
         }
 
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
 
         if (inSplitScreenSecondaryWindowingMode()) {
             // If the stack is in split-screen seconardy mode, we need to make sure we move the
@@ -1358,7 +1353,7 @@
 
     @Override
     public boolean isAttached() {
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         return display != null && !display.isRemoved();
     }
 
@@ -1777,7 +1772,7 @@
     }
 
     boolean isTopStackOnDisplay() {
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         return display != null && display.isTopStack(this);
     }
 
@@ -1786,7 +1781,7 @@
      * otherwise.
      */
     boolean isFocusedStackOnDisplay() {
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         return display != null && this == display.getFocusedStack();
     }
 
@@ -1815,7 +1810,7 @@
             return STACK_VISIBILITY_INVISIBLE;
         }
 
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         boolean gotSplitScreenStack = false;
         boolean gotOpaqueSplitScreenPrimary = false;
         boolean gotOpaqueSplitScreenSecondary = false;
@@ -2049,13 +2044,13 @@
      * {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
      */
     boolean canShowWithInsecureKeyguard() {
-        final ActivityDisplay activityDisplay = getDisplay();
-        if (activityDisplay == null) {
+        final DisplayContent displayContent = getDisplay();
+        if (displayContent == null) {
             throw new IllegalStateException("Stack is not attached to any display, stackId="
                     + mStackId);
         }
 
-        final int flags = activityDisplay.mDisplay.getFlags();
+        final int flags = displayContent.mDisplay.getFlags();
         return (flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0;
     }
 
@@ -2224,7 +2219,7 @@
         }
 
         next.delayedResume = false;
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
 
         // If the top activity is the resumed one, nothing to do.
         if (mResumedActivity == next && next.isState(RESUMED)
@@ -2966,7 +2961,7 @@
 
     /** @return true if the stack behind this one is a standard activity type. */
     private boolean inFrontOfStandardStack() {
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         if (display == null) {
             return false;
         }
@@ -3665,7 +3660,7 @@
             return;
         }
 
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         final boolean topFocused = mRootActivityContainer.isTopDisplayFocusedStack(this);
         if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeChild: task=" + child);
 
@@ -3693,7 +3688,7 @@
     }
 
     void moveHomeStackToFrontIfNeeded(
-            boolean wasTopFocusedStack, ActivityDisplay display, String reason) {
+            boolean wasTopFocusedStack, DisplayContent display, String reason) {
         if (!hasChild() && wasTopFocusedStack) {
             // We only need to adjust focused stack if this stack is in focus and we are not in the
             // process of moving the task to the top of the stack that will be focused.
@@ -3790,11 +3785,11 @@
             return;
         }
         super.setAlwaysOnTop(alwaysOnTop);
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
         // positionChildAtTop() must be called even when always on top gets turned off because we
         // need to make sure that the stack is moved from among always on top windows to below other
         // always on top windows. Since the position the stack should be inserted into is calculated
-        // properly in {@link ActivityDisplay#getTopInsertPosition()} in both cases, we can just
+        // properly in {@link DisplayContent#getTopInsertPosition()} in both cases, we can just
         // request that the stack is put at top here.
         display.positionStackAtTop(this, false /* includingParents */);
     }
@@ -4352,11 +4347,10 @@
     @Override
     protected void onParentChanged(
             ConfigurationContainer newParent, ConfigurationContainer oldParent) {
-        // TODO(display-merge): Remove cast
-        final ActivityDisplay display = newParent != null
-                ? (ActivityDisplay) ((WindowContainer) newParent).getDisplayContent() : null;
-        final ActivityDisplay oldDisplay = oldParent != null
-                ? (ActivityDisplay) ((WindowContainer) oldParent).getDisplayContent() : null;
+        final DisplayContent display = newParent != null
+                ? ((WindowContainer) newParent).getDisplayContent() : null;
+        final DisplayContent oldDisplay = oldParent != null
+                ? ((WindowContainer) oldParent).getDisplayContent() : null;
 
         mDisplayId = (display != null) ? display.mDisplayId : INVALID_DISPLAY;
         mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY;
@@ -5408,7 +5402,7 @@
     }
 
     boolean shouldSleepActivities() {
-        final ActivityDisplay display = getDisplay();
+        final DisplayContent display = getDisplay();
 
         // Do not sleep activities in this stack if we're marked as focused and the keyguard
         // is in the process of going away.
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 8c5fd8c..43dce73 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -401,12 +401,12 @@
     private final MoveTaskToFullscreenHelper mMoveTaskToFullscreenHelper =
             new MoveTaskToFullscreenHelper();
     private class MoveTaskToFullscreenHelper {
-        private ActivityDisplay mToDisplay;
+        private DisplayContent mToDisplay;
         private boolean mOnTop;
         private Task mTopTask;
         private boolean mSchedulePictureInPictureModeChange;
 
-        void process(ActivityStack fromStack, ActivityDisplay toDisplay, boolean onTop,
+        void process(ActivityStack fromStack, DisplayContent toDisplay, boolean onTop,
                 boolean schedulePictureInPictureModeChange) {
             mSchedulePictureInPictureModeChange = schedulePictureInPictureModeChange;
             mToDisplay = toDisplay;
@@ -1122,9 +1122,9 @@
             return true;
         }
 
-        final ActivityDisplay activityDisplay =
-                mRootActivityContainer.getActivityDisplayOrCreate(launchDisplayId);
-        if (activityDisplay == null || activityDisplay.isRemoved()) {
+        final DisplayContent displayContent =
+                mRootActivityContainer.getDisplayContentOrCreate(launchDisplayId);
+        if (displayContent == null || displayContent.isRemoved()) {
             Slog.w(TAG, "Launch on display check: display not found");
             return false;
         }
@@ -1140,10 +1140,10 @@
         }
 
         // Check if caller is already present on display
-        final boolean uidPresentOnDisplay = activityDisplay.isUidPresent(callingUid);
+        final boolean uidPresentOnDisplay = displayContent.isUidPresent(callingUid);
 
-        final int displayOwnerUid = activityDisplay.mDisplay.getOwnerUid();
-        if (activityDisplay.mDisplay.getType() == TYPE_VIRTUAL && displayOwnerUid != SYSTEM_UID
+        final int displayOwnerUid = displayContent.mDisplay.getOwnerUid();
+        if (displayContent.mDisplay.getType() == TYPE_VIRTUAL && displayOwnerUid != SYSTEM_UID
                 && displayOwnerUid != aInfo.applicationInfo.uid) {
             // Limit launching on virtual displays, because their contents can be read from Surface
             // by apps that created them.
@@ -1161,7 +1161,7 @@
             }
         }
 
-        if (!activityDisplay.isPrivate()) {
+        if (!displayContent.isPrivate()) {
             // Anyone can launch on a public display.
             if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
                     + " allow launch on public display");
@@ -1482,7 +1482,7 @@
                 currentStack, forceNonResizeable);
     }
 
-    private void moveHomeStackToFrontIfNeeded(int flags, ActivityDisplay display, String reason) {
+    private void moveHomeStackToFrontIfNeeded(int flags, DisplayContent display, String reason) {
         final ActivityStack focusedStack = display.getFocusedStack();
 
         if ((display.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
@@ -1542,8 +1542,8 @@
         mService.deferWindowLayout();
         try {
             final int windowingMode = fromStack.getWindowingMode();
-            final ActivityDisplay toDisplay =
-                    mRootActivityContainer.getActivityDisplay(toDisplayId);
+            final DisplayContent toDisplay =
+                    mRootActivityContainer.getDisplayContent(toDisplayId);
 
             if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 // We are moving all tasks from the docked stack to the fullscreen stack,
@@ -1668,7 +1668,7 @@
                 // static stacks need to be adjusted so they don't overlap with the docked stack.
                 // We get the bounds to use from window manager which has been adjusted for any
                 // screen controls and is also the same for all stacks.
-                final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+                final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
                 final Rect otherTaskRect = new Rect();
                 for (int i = display.getStackCount() - 1; i >= 0; --i) {
                     final ActivityStack current = display.getStackAt(i);
@@ -1934,7 +1934,7 @@
      * Returns the reparent target stack, creating the stack if necessary.  This call also enforces
      * the various checks on tasks that are going to be reparented from one stack to another.
      */
-    // TODO: Look into changing users to this method to ActivityDisplay.resolveWindowingMode()
+    // TODO: Look into changing users to this method to DisplayContent.resolveWindowingMode()
     ActivityStack getReparentTargetStack(Task task, ActivityStack stack, boolean toTop) {
         final ActivityStack prevStack = task.getStack();
         final int stackId = stack.mStackId;
@@ -2426,8 +2426,8 @@
                 throw new IllegalStateException("Task resolved to incompatible display");
             }
 
-            final ActivityDisplay preferredDisplay =
-                    mRootActivityContainer.getActivityDisplay(preferredDisplayId);
+            final DisplayContent preferredDisplay =
+                    mRootActivityContainer.getDisplayContent(preferredDisplayId);
 
             final boolean singleTaskInstance = preferredDisplay != null
                     && preferredDisplay.isSingleTaskInstance();
@@ -2818,7 +2818,7 @@
                 // call this at the end to make sure that tasks exists on the window manager side.
                 setResizingDuringAnimation(task);
 
-                final ActivityDisplay display = task.getStack().getDisplay();
+                final DisplayContent display = task.getStack().getDisplay();
                 final ActivityStack topSecondaryStack =
                         display.getTopStackInWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
                 if (topSecondaryStack.isActivityTypeHome()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 695f58c..4c165df 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -43,6 +43,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.RemoteAnimationAdapter;
 
@@ -180,8 +181,8 @@
         }
         options.setLaunchDisplayId(displayId);
 
-        final ActivityDisplay display =
-                mService.mRootActivityContainer.getActivityDisplay(displayId);
+        final DisplayContent display =
+                mService.mRootActivityContainer.getDisplayContent(displayId);
         // The home activity will be started later, defer resuming to avoid unneccerary operations
         // (e.g. start home recursively) when creating home stack.
         mSupervisor.beginDeferResume();
@@ -385,6 +386,7 @@
         } else {
             callingPid = callingUid = -1;
         }
+        final SparseArray<String> startingUidPkgs = new SparseArray<>();
         final long origId = Binder.clearCallingIdentity();
         try {
             intents = ArrayUtils.filterNotNull(intents, Intent[]::new);
@@ -411,9 +413,14 @@
                                 callingUid, realCallingUid, UserHandle.USER_NULL));
                 aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
 
-                if (aInfo != null && (aInfo.applicationInfo.privateFlags
-                        & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
-                    throw new IllegalArgumentException("FLAG_CANT_SAVE_STATE not supported here");
+                if (aInfo != null) {
+                    if ((aInfo.applicationInfo.privateFlags
+                            & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
+                        throw new IllegalArgumentException(
+                                "FLAG_CANT_SAVE_STATE not supported here");
+                    }
+                    startingUidPkgs.put(aInfo.applicationInfo.uid,
+                            aInfo.applicationInfo.packageName);
                 }
 
                 final boolean top = i == intents.length - 1;
@@ -439,6 +446,16 @@
                         .setOriginatingPendingIntent(originatingPendingIntent)
                         .setAllowBackgroundActivityStart(allowBackgroundActivityStart);
             }
+            // Log if the activities to be started have different uids.
+            if (startingUidPkgs.size() > 1) {
+                final StringBuilder sb = new StringBuilder("startActivities: different apps [");
+                final int size = startingUidPkgs.size();
+                for (int i = 0; i < size; i++) {
+                    sb.append(startingUidPkgs.valueAt(i)).append(i == size - 1 ? "]" : ", ");
+                }
+                sb.append(" from ").append(callingPackage);
+                Slog.wtf(TAG, sb.toString());
+            }
 
             final ActivityRecord[] outActivity = new ActivityRecord[1];
             // Lock the loop to ensure the activities launched in a sequence.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 46c4d87..f0bc412 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2038,7 +2038,7 @@
     }
 
     @Override
-    public int getActivityDisplayId(IBinder activityToken) throws RemoteException {
+    public int getDisplayId(IBinder activityToken) throws RemoteException {
         synchronized (mGlobalLock) {
             final ActivityStack stack = ActivityRecord.getStackLocked(activityToken);
             if (stack != null && stack.mDisplayId != INVALID_DISPLAY) {
@@ -4572,6 +4572,17 @@
         }
     }
 
+    @Override
+    public void invalidateHomeTaskSnapshot(IBinder token) {
+        synchronized (mGlobalLock) {
+            final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null || !r.isActivityTypeHome()) {
+                return;
+            }
+            mWindowManager.mTaskSnapshotController.removeSnapshotCache(r.getTask().mTaskId);
+        }
+    }
+
     /** Return the user id of the last resumed activity. */
     @Override
     public @UserIdInt
@@ -4691,7 +4702,7 @@
                 "registerRemoteAnimations");
         definition.setCallingPidUid(Binder.getCallingPid(), Binder.getCallingUid());
         synchronized (mGlobalLock) {
-            final ActivityDisplay display = mRootActivityContainer.getActivityDisplay(displayId);
+            final DisplayContent display = mRootActivityContainer.getDisplayContent(displayId);
             if (display == null) {
                 Slog.e(TAG, "Couldn't find display with id: " + displayId);
                 return;
@@ -4899,8 +4910,8 @@
                 "setDisplayToSingleTaskInstance");
         final long origId = Binder.clearCallingIdentity();
         try {
-            final ActivityDisplay display =
-                    mRootActivityContainer.getActivityDisplayOrCreate(displayId);
+            final DisplayContent display =
+                    mRootActivityContainer.getDisplayContentOrCreate(displayId);
             if (display != null) {
                 display.setDisplayToSingleTaskInstance();
             }
@@ -5198,8 +5209,8 @@
     int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
             boolean persistent, int userId, boolean deferResume) {
 
-        final ActivityDisplay defaultDisplay =
-                mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY);
+        final DisplayContent defaultDisplay =
+                mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY);
 
         mTempConfig.setTo(getGlobalConfiguration());
         final int changes = mTempConfig.updateFrom(values);
@@ -6205,12 +6216,12 @@
 
                 // We might change the visibilities here, so prepare an empty app transition which
                 // might be overridden later if we actually change visibilities.
-                final ActivityDisplay activityDisplay =
-                        mRootActivityContainer.getActivityDisplay(displayId);
-                if (activityDisplay == null) {
+                final DisplayContent displayContent =
+                        mRootActivityContainer.getDisplayContent(displayId);
+                if (displayContent == null) {
                     return;
                 }
-                final DisplayContent dc = activityDisplay.mDisplayContent;
+                final DisplayContent dc = displayContent.mDisplayContent;
                 final boolean wasTransitionSet =
                         dc.mAppTransition.getAppTransition() != TRANSIT_NONE;
                 if (!wasTransitionSet) {
@@ -6557,9 +6568,9 @@
                 return;
             }
             synchronized (mGlobalLock) {
-                final ActivityDisplay activityDisplay =
-                        mRootActivityContainer.getActivityDisplay(displayId);
-                if (activityDisplay == null) {
+                final DisplayContent displayContent =
+                        mRootActivityContainer.getDisplayContent(displayId);
+                if (displayContent == null) {
                     // Call might come when display is not yet added or has been removed.
                     if (DEBUG_CONFIGURATION) {
                         Slog.w(TAG, "Trying to update display configuration for non-existing "
@@ -6576,7 +6587,7 @@
                     return;
                 }
                 process.mIsImeProcess = true;
-                process.registerDisplayConfigurationListenerLocked(activityDisplay);
+                process.registerDisplayConfigurationListenerLocked(displayContent);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index bd0ea3d..dd3365c 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -384,7 +384,6 @@
      *  When you call this function, make sure that the following functions are called as well to
      *  keep proper z-order.
      *  - {@Link DisplayContent#positionStackAt(POSITION_TOP, TaskStack)};
-     *  - {@Link ActivityDisplay#positionChildAtTop(ActivityStack)};
      * */
     public void setAlwaysOnTop(boolean alwaysOnTop) {
         mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index aa0e973..e3ea2c5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,24 +16,33 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.Build.VERSION_CODES.N;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_PRIVATE;
 import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
 import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
@@ -75,10 +84,20 @@
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
 
+import static com.android.server.am.ActivityDisplayProto.DISPLAY;
+import static com.android.server.am.ActivityDisplayProto.FOCUSED_STACK_ID;
+import static com.android.server.am.ActivityDisplayProto.RESUMED_ACTIVITY;
+import static com.android.server.am.ActivityDisplayProto.SINGLE_TASK_INSTANCE;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
 import static com.android.server.wm.DisplayContentProto.ABOVE_APP_WINDOWS;
 import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
 import static com.android.server.wm.DisplayContentProto.BELOW_APP_WINDOWS;
@@ -105,6 +124,7 @@
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.server.wm.RootActivityContainer.TAG_STATES;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
@@ -124,6 +144,7 @@
 import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
 import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
@@ -141,6 +162,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -158,15 +184,19 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
+import android.util.IntArray;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
@@ -174,6 +204,7 @@
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.ISystemGestureExclusionListener;
+import android.view.IWindow;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputWindowHandle;
@@ -196,6 +227,7 @@
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.AnimationThread;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.protolog.common.ProtoLog;
@@ -223,6 +255,7 @@
 class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>
         implements WindowManagerPolicy.DisplayContentInfo {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM;
+    private static final String TAG_STACK = TAG + POSTFIX_STACK;
 
     /** The default scaling mode that scales content automatically. */
     static final int FORCE_SCALING_MODE_AUTO = 0;
@@ -239,7 +272,7 @@
     ActivityTaskManagerService mAtmService;
 
     /** Unique identifier of this display. */
-    private final int mDisplayId;
+    final int mDisplayId;
 
     /**
      * Most surfaces will be a child of this window. There are some special layers and windows
@@ -328,8 +361,8 @@
      * @see WindowManagerService#setForcedDisplayScalingMode(int, int)
      */
     boolean mDisplayScalingDisabled;
+    final Display mDisplay;
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
-    private final Display mDisplay;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private final DisplayPolicy mDisplayPolicy;
     private final DisplayRotation mDisplayRotation;
@@ -563,6 +596,74 @@
     /** Corner radius that windows should have in order to match the display. */
     private final float mWindowCornerRadius;
 
+    private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
+
+    /**
+     * Counter for next free stack ID to use for dynamic activity stacks. Unique across displays.
+     */
+    private static int sNextFreeStackId = 0;
+
+    private RootActivityContainer mRootActivityContainer;
+
+    /**
+     * All of the stacks on this display. Order matters, topmost stack is in front of all other
+     * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls
+     * changing the list should also call {@link #onStackOrderChanged()}.
+     */
+    private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>();
+
+    /** Array of all UIDs that are present on the display. */
+    private IntArray mDisplayAccessUIDs = new IntArray();
+
+    /** All tokens used to put activities on this stack to sleep (including mOffToken) */
+    final ArrayList<ActivityTaskManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
+    /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
+    ActivityTaskManagerInternal.SleepToken mOffToken;
+
+    private boolean mSleeping;
+
+    /** We started the process of removing the display from the system. */
+    private boolean mRemoving;
+
+    /**
+     * The display is removed from the system and we are just waiting for all activities on it to be
+     * finished before removing this object.
+     */
+    private boolean mRemoved;
+
+    /** The display can only contain one task. */
+    private boolean mSingleTaskInstance;
+
+    /**
+     * Non-null if the last size compatibility mode activity is using non-native screen
+     * configuration. The activity is not able to put in multi-window mode, so it exists only one
+     * per display.
+     */
+    private ActivityRecord mLastCompatModeActivity;
+
+    /**
+     * A focusable stack that is purposely to be positioned at the top. Although the stack may not
+     * have the topmost index, it is used as a preferred candidate to prevent being unable to resume
+     * target stack properly when there are other focusable always-on-top stacks.
+     */
+    private ActivityStack mPreferredTopFocusableStack;
+
+    /**
+     * If this is the same as {@link #getFocusedStack} then the activity on the top of the focused
+     * stack has been resumed. If stacks are changing position this will hold the old stack until
+     * the new stack becomes resumed after which it will be set to current focused stack.
+     */
+    private ActivityStack mLastFocusedStack;
+
+    // Used in updating the display size
+    private Point mTmpDisplaySize = new Point();
+
+    // Used in updating override configurations
+    private final Configuration mTempConfig = new Configuration();
+
+    private final RootActivityContainer.FindTaskResult
+            mTmpFindTaskResult = new RootActivityContainer.FindTaskResult();
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         final ActivityRecord activity = w.mActivityRecord;
@@ -842,17 +943,18 @@
      * Create new {@link DisplayContent} instance, add itself to the root window container and
      * initialize direct children.
      * @param display May not be null.
-     * @param service You know.
-     * @param activityDisplay The ActivityDisplay for the display container.
+     * @param root {@link RootActivityContainer}
      */
-    DisplayContent(Display display, WindowManagerService service) {
-        super(service);
-        if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
+    DisplayContent(Display display, RootActivityContainer root) {
+        super(root.mWindowManager);
+        if (mWmService.mRoot.getDisplayContent(display.getDisplayId()) != null) {
             throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
-                    + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
+                    + " already exists="
+                    + mWmService.mRoot.getDisplayContent(display.getDisplayId())
                     + " new=" + display);
         }
 
+        mRootActivityContainer = root;
         mAtmService = mWmService.mAtmService;
         mDisplay = display;
         mDisplayId = display.getDisplayId();
@@ -866,13 +968,13 @@
                 calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
         initializeDisplayBaseInfo();
 
-        mAppTransition = new AppTransition(service.mContext, service, this);
-        mAppTransition.registerListenerLocked(service.mActivityManagerAppTransitionNotifier);
-        mAppTransitionController = new AppTransitionController(service, this);
-        mUnknownAppVisibilityController = new UnknownAppVisibilityController(service, this);
+        mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
+        mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
+        mAppTransitionController = new AppTransitionController(mWmService, this);
+        mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
 
         AnimationHandler animationHandler = new AnimationHandler();
-        mBoundsAnimationController = new BoundsAnimationController(service.mContext,
+        mBoundsAnimationController = new BoundsAnimationController(mWmService.mContext,
                 mAppTransition, AnimationThread.getHandler(), animationHandler);
 
         final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
@@ -890,9 +992,9 @@
                     mWmService.mAtmService.getRecentTasks().getInputListener());
         }
 
-        mDisplayPolicy = new DisplayPolicy(service, this);
-        mDisplayRotation = new DisplayRotation(service, this);
-        mCloseToSquareMaxAspectRatio = service.mContext.getResources().getFloat(
+        mDisplayPolicy = new DisplayPolicy(mWmService, this);
+        mDisplayRotation = new DisplayRotation(mWmService, this);
+        mCloseToSquareMaxAspectRatio = mWmService.mContext.getResources().getFloat(
                 com.android.internal.R.dimen.config_closeToSquareDisplayMaxAspectRatio);
         if (isDefaultDisplay) {
             // The policy may be invoked right after here, so it requires the necessary default
@@ -906,8 +1008,8 @@
             mDisplayPolicy.systemReady();
         }
         mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius();
-        mDividerControllerLocked = new DockedStackDividerController(service, this);
-        mPinnedStackControllerLocked = new PinnedStackController(service, this);
+        mDividerControllerLocked = new DockedStackDividerController(mWmService, this);
+        mPinnedStackControllerLocked = new PinnedStackController(mWmService, this);
 
         final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
                 .setOpaque(true)
@@ -937,9 +1039,23 @@
         mDisplayReady = true;
 
         mWmService.mAnimator.addDisplayLocked(mDisplayId);
-        mInputMonitor = new InputMonitor(service, mDisplayId);
+        mInputMonitor = new InputMonitor(mWmService, mDisplayId);
         mInsetsStateController = new InsetsStateController(this);
         mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
+
+        if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
+
+        mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
+
+        if (mWmService.mDisplayManagerInternal != null) {
+            mWmService.mDisplayManagerInternal
+                    .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
+            configureDisplayPolicy();
+        }
+
+        reconfigureDisplayLocked();
+        onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+        mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
     }
 
     boolean isReady() {
@@ -1017,6 +1133,37 @@
         return token;
     }
 
+    SurfaceControl addShellRoot(@NonNull IWindow client, int windowType) {
+        ShellRoot root = mShellRoots.get(windowType);
+        if (root != null) {
+            if (root.getClient() == client) {
+                return root.getSurfaceControl();
+            }
+            root.clear();
+            mShellRoots.remove(windowType);
+        }
+        root = new ShellRoot(client, this, windowType);
+        SurfaceControl rootLeash = root.getSurfaceControl();
+        if (rootLeash == null) {
+            // Root didn't finish initializing, so don't add it.
+            root.clear();
+            return null;
+        }
+        mShellRoots.put(windowType, root);
+        SurfaceControl out = new SurfaceControl();
+        out.copyFrom(rootLeash);
+        return out;
+    }
+
+    void removeShellRoot(int windowType) {
+        ShellRoot root = mShellRoots.get(windowType);
+        if (root == null) {
+            return;
+        }
+        root.clear();
+        mShellRoots.remove(windowType);
+    }
+
     /** Changes the display the input window token is housed on to this one. */
     void reParentWindowToken(WindowToken token) {
         final DisplayContent prevDc = token.getDisplayContent();
@@ -1154,9 +1301,7 @@
         if (mDisplayRotation.isWaitingForRemoteRotation()) {
             return;
         }
-        // TODO(display-merge): Remove cast
-        final boolean configUpdated =
-                ((ActivityDisplay) this).updateDisplayOverrideConfigurationLocked();
+        final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
         if (configUpdated) {
             return;
         }
@@ -1187,9 +1332,8 @@
 
         if (handled && requestingContainer instanceof ActivityRecord) {
             final ActivityRecord activityRecord = (ActivityRecord) requestingContainer;
-            // TODO(display-merge): Remove cast
-            final boolean kept = ((ActivityDisplay) this).updateDisplayOverrideConfigurationLocked(
-                    config, activityRecord, false /* deferResume */, null /* result */);
+            final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord,
+                    false /* deferResume */, null /* result */);
             activityRecord.frozenBeforeDestroy = true;
             if (!kept) {
                 mWmService.mAtmService.mRootActivityContainer.resumeFocusedStacksTopActivities();
@@ -1197,9 +1341,8 @@
         } else {
             // We have a new configuration to push so we need to update ATMS for now.
             // TODO: Clean up display configuration push between ATMS and WMS after unification.
-            // TODO(display-merge): Remove cast
-            ((ActivityDisplay) this.mDisplayContent).updateDisplayOverrideConfigurationLocked(
-                    config, null /* starting */, false /* deferResume */, null);
+            updateDisplayOverrideConfigurationLocked(config, null /* starting */,
+                    false /* deferResume */, null);
         }
         return handled;
     }
@@ -1880,15 +2023,9 @@
     }
 
     @Override
-    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
-        mCurrentOverrideConfigurationChanges =
-                getRequestedOverrideConfiguration().diff(overrideConfiguration);
-        super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
-        mCurrentOverrideConfigurationChanges = 0;
-    }
-
-    @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
+        // update resources before cascade so that docked/pinned stacks use the correct info
+        preOnConfigurationChanged();
         final int lastOrientation = getConfiguration().orientation;
         super.onConfigurationChanged(newParentConfig);
         if (mDisplayPolicy != null) {
@@ -1912,8 +2049,7 @@
     /**
      * Updates the resources used by docked/pinned controllers. This needs to be called at the
      * beginning of a configuration update cascade since the metrics from these resources are used
-     * for bounds calculations. Since ActivityDisplay initiates the configuration update, this
-     * should be called from there instead of DisplayContent's onConfigurationChanged.
+     * for bounds calculations.
      */
     void preOnConfigurationChanged() {
         final DockedStackDividerController dividerController = getDockedDividerController();
@@ -2395,9 +2531,7 @@
             setRotationAnimation(null);
             mWmService.mAnimator.removeDisplayLocked(mDisplayId);
             mInputMonitor.onDisplayRemoved();
-            // TODO(display-merge): Remove cast
-            mWmService.mDisplayNotificationController
-                .dispatchDisplayRemoved((ActivityDisplay) this);
+            mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
         } finally {
             mDisplayReady = false;
             mRemovingDisplay = false;
@@ -2610,6 +2744,29 @@
         }
     }
 
+    public void dumpDebug(ProtoOutputStream proto, long fieldId,
+            @WindowTraceLogLevel int logLevel) {
+        final long token = proto.start(fieldId);
+        dumpDebugInner(proto, DISPLAY, logLevel);
+        proto.write(com.android.server.am.ActivityDisplayProto.ID, mDisplayId);
+        proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance);
+        final ActivityStack focusedStack = getFocusedStack();
+        if (focusedStack != null) {
+            proto.write(FOCUSED_STACK_ID, focusedStack.mStackId);
+            final ActivityRecord focusedActivity = focusedStack.getDisplay().getResumedActivity();
+            if (focusedActivity != null) {
+                focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+            }
+        } else {
+            proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
+        }
+        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = getStackAt(stackNdx);
+            stack.dumpDebug(proto, com.android.server.am.ActivityDisplayProto.STACKS, logLevel);
+        }
+        proto.end(token);
+    }
+
     // TODO(proto-merge): Remove once protos for ActivityDisplay and DisplayContent are merged.
     public void dumpDebugInner(ProtoOutputStream proto, long fieldId,
             @WindowTraceLogLevel int logLevel) {
@@ -2670,7 +2827,9 @@
     @Override
     public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         super.dump(pw, prefix, dumpAll);
-        pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
+        pw.print(prefix);
+        pw.println("Display: mDisplayId=" + mDisplayId + " stacks=" + getStackCount() + (
+                mSingleTaskInstance ? " mSingleTaskInstance" : ""));
         final String subPrefix = "  " + prefix;
         pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
         pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
@@ -2706,6 +2865,12 @@
         if (mLastFocus != mCurrentFocus) {
             pw.print("  mLastFocus="); pw.println(mLastFocus);
         }
+        if (mPreferredTopFocusableStack != null) {
+            pw.println(prefix + "mPreferredTopFocusableStack=" + mPreferredTopFocusableStack);
+        }
+        if (mLastFocusedStack != null) {
+            pw.println(prefix + "mLastFocusedStack=" + mLastFocusedStack);
+        }
         if (mLosingFocus.size() > 0) {
             pw.println();
             pw.println("  Windows losing focus:");
@@ -2783,6 +2948,10 @@
         if (splitScreenPrimaryStack != null) {
             pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
         }
+        final ActivityStack recentsStack = getRecentsStack();
+        if (recentsStack != null) {
+            pw.println(prefix + "recentsStack=" + recentsStack.getName());
+        }
 
         pw.println();
         mDividerControllerLocked.dump(prefix, pw);
@@ -4068,8 +4237,7 @@
                             + " already exist on display=" + this + " stack=" + stack);
                 }
                 mSplitScreenPrimaryStack = stack;
-                // TODO(display-merge): Remove cast
-                ((ActivityDisplay) this.mDisplayContent).onSplitScreenModeActivated();
+                mDisplayContent.onSplitScreenModeActivated();
                 mDividerControllerLocked.notifyDockedStackExistsChanged(true);
             }
         }
@@ -4083,8 +4251,7 @@
                 mPinnedStack = null;
             } else if (stack == mSplitScreenPrimaryStack) {
                 mSplitScreenPrimaryStack = null;
-                // TODO(display-merge): Remove cast
-                ((ActivityDisplay) this.mDisplayContent).onSplitScreenModeDismissed();
+                mDisplayContent.onSplitScreenModeDismissed();
                 // Re-set the split-screen create mode whenever the split-screen stack is removed.
                 mWmService.setDockedStackCreateStateLocked(
                         SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
@@ -4109,8 +4276,7 @@
         @Override
         protected void removeChild(ActivityStack stack) {
             super.removeChild(stack);
-            // TODO(display-merge): Remove cast
-            ((ActivityDisplay) this.mDisplayContent).onStackRemoved(stack);
+            mDisplayContent.onStackRemoved(stack);
             removeStackReferenceIfNeeded(stack);
         }
 
@@ -5342,4 +5508,1132 @@
         }
         return mMetricsLogger;
     }
+
+    void onDisplayChanged() {
+        // The window policy is responsible for stopping activities on the default display.
+        final int displayId = mDisplay.getDisplayId();
+        if (displayId != DEFAULT_DISPLAY) {
+            final int displayState = mDisplay.getState();
+            if (displayState == Display.STATE_OFF && mOffToken == null) {
+                mOffToken = mAtmService.acquireSleepToken("Display-off", displayId);
+            } else if (displayState == Display.STATE_ON && mOffToken != null) {
+                mOffToken.release();
+                mOffToken = null;
+            }
+        }
+
+        mDisplay.getRealSize(mTmpDisplaySize);
+        setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
+        updateDisplayInfo();
+        mWmService.requestTraversal();
+    }
+
+    void addStack(ActivityStack stack, int position) {
+        setStackOnDisplay(stack, position);
+        positionStackAt(stack, position);
+        mAtmService.updateSleepIfNeededLocked();
+    }
+
+    void onStackRemoved(ActivityStack stack) {
+        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
+            Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId=" + mDisplayId);
+        }
+        if (mPreferredTopFocusableStack == stack) {
+            mPreferredTopFocusableStack = null;
+        }
+        releaseSelfIfNeeded();
+        mAtmService.updateSleepIfNeededLocked();
+        onStackOrderChanged(stack);
+    }
+
+    void positionStackAtTop(ActivityStack stack, boolean includingParents) {
+        positionStackAtTop(stack, includingParents, null /* updateLastFocusedStackReason */);
+    }
+
+    void positionStackAtTop(ActivityStack stack, boolean includingParents,
+            String updateLastFocusedStackReason) {
+        positionStackAt(stack, getStackCount(), includingParents, updateLastFocusedStackReason);
+    }
+
+    void positionStackAtBottom(ActivityStack stack) {
+        positionStackAtBottom(stack, null /* updateLastFocusedStackReason */);
+    }
+
+    void positionStackAtBottom(ActivityStack stack, String updateLastFocusedStackReason) {
+        positionStackAt(stack, 0, false /* includingParents */, updateLastFocusedStackReason);
+    }
+
+    private void positionStackAt(ActivityStack stack, int position) {
+        positionStackAt(stack, position, false /* includingParents */,
+                null /* updateLastFocusedStackReason */);
+    }
+
+    private void positionStackAt(ActivityStack stack, int position, boolean includingParents,
+            String updateLastFocusedStackReason) {
+        // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust
+        //       the position internally, also update the logic here
+        final ActivityStack prevFocusedStack = updateLastFocusedStackReason != null
+                ? getFocusedStack() : null;
+        final boolean wasContained = getIndexOf(stack) >= 0;
+        if (mSingleTaskInstance && getStackCount() == 1 && !wasContained) {
+            throw new IllegalStateException(
+                    "positionStackAt: Can only have one task on display=" + this);
+        }
+
+        // Since positionChildAt() is called during the creation process of pinned stacks,
+        // ActivityStack#getStack() can be null.
+        positionStackAt(position, stack, includingParents);
+
+        // The insert position may be adjusted to non-top when there is always-on-top stack. Since
+        // the original position is preferred to be top, the stack should have higher priority when
+        // we are looking for top focusable stack. The condition {@code wasContained} restricts the
+        // preferred stack is set only when moving an existing stack to top instead of adding a new
+        // stack that may be too early (e.g. in the middle of launching or reparenting).
+        if (wasContained && position >= getStackCount() - 1 && stack.isFocusableAndVisible()) {
+            mPreferredTopFocusableStack = stack;
+        } else if (mPreferredTopFocusableStack == stack) {
+            mPreferredTopFocusableStack = null;
+        }
+
+        if (updateLastFocusedStackReason != null) {
+            final ActivityStack currentFocusedStack = getFocusedStack();
+            if (currentFocusedStack != prevFocusedStack) {
+                mLastFocusedStack = prevFocusedStack;
+                EventLogTags.writeWmFocusedStack(mRootActivityContainer.mCurrentUser, mDisplayId,
+                        currentFocusedStack == null ? -1 : currentFocusedStack.getStackId(),
+                        mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(),
+                        updateLastFocusedStackReason);
+            }
+        }
+
+        onStackOrderChanged(stack);
+    }
+
+    ActivityStack getStack(int stackId) {
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = getStackAt(i);
+            if (stack.mStackId == stackId) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    boolean alwaysCreateStack(int windowingMode, int activityType) {
+        // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
+        // modes so that we can manage visual ordering and return types correctly.
+        return activityType == ACTIVITY_TYPE_STANDARD
+                && (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_FREEFORM
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+    }
+
+    /**
+     * Returns an existing stack compatible with the windowing mode and activity type or creates one
+     * if a compatible stack doesn't exist.
+     * @see #getStack(int, int)
+     * @see #createStack(int, int, boolean)
+     */
+    ActivityStack getOrCreateStack(int windowingMode, int activityType,
+            boolean onTop) {
+        if (!alwaysCreateStack(windowingMode, activityType)) {
+            ActivityStack stack = getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return createStack(windowingMode, activityType, onTop);
+    }
+
+    /**
+     * Returns an existing stack compatible with the input params or creates one
+     * if a compatible stack doesn't exist.
+     * @see #getOrCreateStack(int, int, boolean)
+     */
+    ActivityStack getOrCreateStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType,
+            boolean onTop) {
+        // First preference is the windowing mode in the activity options if set.
+        int windowingMode = (options != null)
+                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+        // Validate that our desired windowingMode will work under the current conditions.
+        // UNDEFINED windowing mode is a valid result and means that the new stack will inherit
+        // it's display's windowing mode.
+        windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
+        return getOrCreateStack(windowingMode, activityType, onTop);
+    }
+
+    @VisibleForTesting
+    int getNextStackId() {
+        return sNextFreeStackId++;
+    }
+
+    /**
+     * Creates a stack matching the input windowing mode and activity type on this display.
+     * @param windowingMode The windowing mode the stack should be created in. If
+     *                      {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
+     *                      inherit its parent's windowing mode.
+     * @param activityType The activityType the stack should be created in. If
+     *                     {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
+     *                     be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+     * @param onTop If true the stack will be created at the top of the display, else at the bottom.
+     * @return The newly created stack.
+     */
+    ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
+        if (mSingleTaskInstance && getStackCount() > 0) {
+            // Create stack on default display instead since this display can only contain 1 stack.
+            // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
+            // this goes away once ActivityView is no longer using virtual displays.
+            return mRootActivityContainer.getDefaultDisplay().createStack(
+                    windowingMode, activityType, onTop);
+        }
+
+        if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+            // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
+            // anything else should be passing it in anyways...
+            activityType = ACTIVITY_TYPE_STANDARD;
+        }
+
+        if (activityType != ACTIVITY_TYPE_STANDARD) {
+            // For now there can be only one stack of a particular non-standard activity type on a
+            // display. So, get that ignoring whatever windowing mode it is currently in.
+            ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
+            if (stack != null) {
+                throw new IllegalArgumentException("Stack=" + stack + " of activityType="
+                        + activityType + " already on display=" + this + ". Can't have multiple.");
+            }
+        }
+
+        if (!isWindowingModeSupported(windowingMode, mAtmService.mSupportsMultiWindow,
+                mAtmService.mSupportsSplitScreenMultiWindow,
+                mAtmService.mSupportsFreeformWindowManagement,
+                mAtmService.mSupportsPictureInPicture, activityType)) {
+            throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
+                    + windowingMode);
+        }
+
+        final int stackId = getNextStackId();
+        return createStackUnchecked(windowingMode, activityType, stackId, onTop);
+    }
+
+    @VisibleForTesting
+    ActivityStack createStackUnchecked(int windowingMode, int activityType,
+            int stackId, boolean onTop) {
+        if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
+            throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
+                    + "activity type.");
+        }
+        return new ActivityStack(this, stackId, mRootActivityContainer.mStackSupervisor,
+                windowingMode, activityType, onTop);
+    }
+
+    /**
+     * Get the preferred focusable stack in priority. If the preferred stack does not exist, find a
+     * focusable and visible stack from the top of stacks in this display.
+     */
+    ActivityStack getFocusedStack() {
+        if (mPreferredTopFocusableStack != null) {
+            return mPreferredTopFocusableStack;
+        }
+
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = getStackAt(i);
+            if (stack.isFocusableAndVisible()) {
+                return stack;
+            }
+        }
+
+        return null;
+    }
+
+    ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) {
+        final int currentWindowingMode = currentFocus != null
+                ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+        ActivityStack candidate = null;
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = getStackAt(i);
+            if (ignoreCurrent && stack == currentFocus) {
+                continue;
+            }
+            if (!stack.isFocusableAndVisible()) {
+                continue;
+            }
+
+            if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                    && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) {
+                // If the currently focused stack is in split-screen secondary we save off the
+                // top primary split-screen stack as a candidate for focus because we might
+                // prefer focus to move to an other stack to avoid primary split-screen stack
+                // overlapping with a fullscreen stack when a fullscreen stack is higher in z
+                // than the next split-screen stack. Assistant stack, I am looking at you...
+                // We only move the focus to the primary-split screen stack if there isn't a
+                // better alternative.
+                candidate = stack;
+                continue;
+            }
+            if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) {
+                // Use the candidate stack since we are now at the secondary split-screen.
+                return candidate;
+            }
+            return stack;
+        }
+        return candidate;
+    }
+
+    ActivityRecord getResumedActivity() {
+        final ActivityStack focusedStack = getFocusedStack();
+        if (focusedStack == null) {
+            return null;
+        }
+        // TODO(b/111541062): Move this into ActivityStack#getResumedActivity()
+        // Check if the focused stack has the resumed activity
+        ActivityRecord resumedActivity = focusedStack.getResumedActivity();
+        if (resumedActivity == null || resumedActivity.app == null) {
+            // If there is no registered resumed activity in the stack or it is not running -
+            // try to use previously resumed one.
+            resumedActivity = focusedStack.mPausingActivity;
+            if (resumedActivity == null || resumedActivity.app == null) {
+                // If previously resumed activity doesn't work either - find the topmost running
+                // activity that can be focused.
+                resumedActivity = focusedStack.topRunningActivity(true /* focusableOnly */);
+            }
+        }
+        return resumedActivity;
+    }
+
+    ActivityStack getLastFocusedStack() {
+        return mLastFocusedStack;
+    }
+
+    boolean allResumedActivitiesComplete() {
+        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityRecord r = getStackAt(stackNdx).getResumedActivity();
+            if (r != null && !r.isState(RESUMED)) {
+                return false;
+            }
+        }
+        final ActivityStack currentFocusedStack = getFocusedStack();
+        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
+            Slog.d(TAG_STACK, "allResumedActivitiesComplete: mLastFocusedStack changing from="
+                    + mLastFocusedStack + " to=" + currentFocusedStack);
+        }
+        mLastFocusedStack = currentFocusedStack;
+        return true;
+    }
+
+    /**
+     * Pause all activities in either all of the stacks or just the back stacks. This is done before
+     * resuming a new activity and to make sure that previously active activities are
+     * paused in stacks that are no longer visible or in pinned windowing mode. This does not
+     * pause activities in visible stacks, so if an activity is launched within the same stack/task,
+     * then we should explicitly pause that stack's top activity.
+     * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
+     * @param resuming The resuming activity.
+     * @return {@code true} if any activity was paused as a result of this call.
+     */
+    boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) {
+        boolean someActivityPaused = false;
+        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = getStackAt(stackNdx);
+            final ActivityRecord resumedActivity = stack.getResumedActivity();
+            if (resumedActivity != null
+                    && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
+                    || !stack.isFocusable())) {
+                if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
+                        + " mResumedActivity=" + resumedActivity);
+                someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
+                        resuming);
+            }
+        }
+        return someActivityPaused;
+    }
+
+    /**
+     * Find task for putting the Activity in.
+     */
+    void findTaskLocked(final ActivityRecord r, final boolean isPreferredDisplay,
+            RootActivityContainer.FindTaskResult result) {
+        mTmpFindTaskResult.clear();
+        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = getStackAt(stackNdx);
+            if (!r.hasCompatibleActivityType(stack)) {
+                if (DEBUG_TASKS) {
+                    Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack);
+                }
+                continue;
+            }
+
+            mTmpFindTaskResult.process(r, stack);
+            // It is possible to have tasks in multiple stacks with the same root affinity, so
+            // we should keep looking after finding an affinity match to see if there is a
+            // better match in another stack. Also, task affinity isn't a good enough reason
+            // to target a display which isn't the source of the intent, so skip any affinity
+            // matches not on the specified display.
+            if (mTmpFindTaskResult.mRecord != null) {
+                if (mTmpFindTaskResult.mIdealMatch) {
+                    result.setTo(mTmpFindTaskResult);
+                    return;
+                } else if (isPreferredDisplay) {
+                    // Note: since the traversing through the stacks is top down, the floating
+                    // tasks should always have lower priority than any affinity-matching tasks
+                    // in the fullscreen stacks
+                    result.setTo(mTmpFindTaskResult);
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    void removeStacksInWindowingModes(int... windowingModes) {
+        if (windowingModes == null || windowingModes.length == 0) {
+            return;
+        }
+
+        // Collect the stacks that are necessary to be removed instead of performing the removal
+        // by looping mStacks, so that we don't miss any stacks after the stack size changed or
+        // stacks reordered.
+        final ArrayList<ActivityStack> stacks = new ArrayList<>();
+        for (int j = windowingModes.length - 1; j >= 0; --j) {
+            final int windowingMode = windowingModes[j];
+            for (int i = getStackCount() - 1; i >= 0; --i) {
+                final ActivityStack stack = getStackAt(i);
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    continue;
+                }
+                if (stack.getWindowingMode() != windowingMode) {
+                    continue;
+                }
+                stacks.add(stack);
+            }
+        }
+
+        for (int i = stacks.size() - 1; i >= 0; --i) {
+            mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i));
+        }
+    }
+
+    void removeStacksWithActivityTypes(int... activityTypes) {
+        if (activityTypes == null || activityTypes.length == 0) {
+            return;
+        }
+
+        // Collect the stacks that are necessary to be removed instead of performing the removal
+        // by looping mStacks, so that we don't miss any stacks after the stack size changed or
+        // stacks reordered.
+        final ArrayList<ActivityStack> stacks = new ArrayList<>();
+        for (int j = activityTypes.length - 1; j >= 0; --j) {
+            final int activityType = activityTypes[j];
+            for (int i = getStackCount() - 1; i >= 0; --i) {
+                final ActivityStack stack = getStackAt(i);
+                if (stack.getActivityType() == activityType) {
+                    stacks.add(stack);
+                }
+            }
+        }
+
+        for (int i = stacks.size() - 1; i >= 0; --i) {
+            mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i));
+        }
+    }
+
+    void onSplitScreenModeDismissed() {
+        mAtmService.deferWindowLayout();
+        try {
+            // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
+            for (int i = getStackCount() - 1; i >= 0; --i) {
+                final ActivityStack otherStack = getStackAt(i);
+                if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
+                    continue;
+                }
+                otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED, false /* animate */,
+                        false /* showRecents */, false /* enteringSplitScreenMode */,
+                        true /* deferEnsuringVisibility */, false /* creating */);
+            }
+        } finally {
+            final ActivityStack topFullscreenStack =
+                    getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
+            final ActivityStack homeStack = getHomeStack();
+            if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
+                // Whenever split-screen is dismissed we want the home stack directly behind the
+                // current top fullscreen stack so it shows up when the top stack is finished.
+                // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
+                // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
+                // once we have that.
+                homeStack.moveToFront("onSplitScreenModeDismissed");
+                topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
+            }
+            mAtmService.continueWindowLayout();
+        }
+    }
+
+    void onSplitScreenModeActivated() {
+        mAtmService.deferWindowLayout();
+        try {
+            // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
+            final ActivityStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
+            for (int i = getStackCount() - 1; i >= 0; --i) {
+                final ActivityStack otherStack = getStackAt(i);
+                if (otherStack == splitScreenPrimaryStack
+                        || !otherStack.affectedBySplitScreenResize()) {
+                    continue;
+                }
+                otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                        false /* animate */, false /* showRecents */,
+                        true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */,
+                        false /* creating */);
+            }
+        } finally {
+            mAtmService.continueWindowLayout();
+        }
+    }
+
+    /**
+     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+     * @param windowingMode The windowing mode we are checking support for.
+     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+     * @param supportsFreeform If we should consider support for freeform multi-window.
+     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+     * @param activityType The activity type under consideration.
+     * @return true if the windowing mode is supported.
+     */
+    private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+            int activityType) {
+
+        if (windowingMode == WINDOWING_MODE_UNDEFINED
+                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            return true;
+        }
+        if (!supportsMultiWindow) {
+            return false;
+        }
+
+        final int displayWindowingMode = getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            return supportsSplitScreen
+                    && WindowConfiguration.supportSplitScreenWindowingMode(activityType)
+                    // Freeform windows and split-screen windows don't mix well, so prevent
+                    // split windowing modes on freeform displays.
+                    && displayWindowingMode != WINDOWING_MODE_FREEFORM;
+        }
+
+        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+            return false;
+        }
+
+        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this
+     * display with the provided parameters.
+     *
+     * @param r The ActivityRecord in question.
+     * @param options Options to start with.
+     * @param task The task within-which the activity would start.
+     * @param activityType The type of activity to start.
+     * @return The resolved (not UNDEFINED) windowing-mode that the activity would be in.
+     */
+    int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable Task task, int activityType) {
+
+        // First preference if the windowing mode in the activity options if set.
+        int windowingMode = (options != null)
+                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+        // If windowing mode is unset, then next preference is the candidate task, then the
+        // activity record.
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            if (task != null) {
+                windowingMode = task.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+                windowingMode = r.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+                // Use the display's windowing mode.
+                windowingMode = getWindowingMode();
+            }
+        }
+        windowingMode = validateWindowingMode(windowingMode, r, task, activityType);
+        return windowingMode != WINDOWING_MODE_UNDEFINED
+                ? windowingMode : WINDOWING_MODE_FULLSCREEN;
+    }
+
+    /**
+     * Check that the requested windowing-mode is appropriate for the specified task and/or activity
+     * on this display.
+     *
+     * @param windowingMode The windowing-mode to validate.
+     * @param r The {@link ActivityRecord} to check against.
+     * @param task The {@link Task} to check against.
+     * @param activityType An activity type.
+     * @return The provided windowingMode or the closest valid mode which is appropriate.
+     */
+    int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
+            int activityType) {
+        // Make sure the windowing mode we are trying to use makes sense for what is supported.
+        boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
+        boolean supportsSplitScreen = mAtmService.mSupportsSplitScreenMultiWindow;
+        boolean supportsFreeform = mAtmService.mSupportsFreeformWindowManagement;
+        boolean supportsPip = mAtmService.mSupportsPictureInPicture;
+        if (supportsMultiWindow) {
+            if (task != null) {
+                supportsMultiWindow = task.isResizeable();
+                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+                // TODO: Do we need to check for freeform and Pip support here?
+            } else if (r != null) {
+                supportsMultiWindow = r.isResizeable();
+                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+                supportsFreeform = r.supportsFreeform();
+                supportsPip = r.supportsPictureInPicture();
+            }
+        }
+
+        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+        if (!inSplitScreenMode
+                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+            // Switch to the display's windowing mode if we are not in split-screen mode and we are
+            // trying to launch in split-screen secondary.
+            windowingMode = WINDOWING_MODE_UNDEFINED;
+        } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_UNDEFINED)
+                && supportsSplitScreen) {
+            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+        }
+
+        if (windowingMode != WINDOWING_MODE_UNDEFINED
+                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+                supportsFreeform, supportsPip, activityType)) {
+            return windowingMode;
+        }
+        return WINDOWING_MODE_UNDEFINED;
+    }
+
+    boolean isTopStack(ActivityStack stack) {
+        return stack == getTopStack();
+    }
+
+    boolean isTopNotPinnedStack(ActivityStack stack) {
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack current = getStackAt(i);
+            if (!current.inPinnedWindowingMode()) {
+                return current == stack;
+            }
+        }
+        return false;
+    }
+
+    ActivityRecord topRunningActivity() {
+        return topRunningActivity(false /* considerKeyguardState */);
+    }
+
+    /**
+     * Returns the top running activity in the focused stack. In the case the focused stack has no
+     * such activity, the next focusable stack on this display is returned.
+     *
+     * @param considerKeyguardState Indicates whether the locked state should be considered. if
+     *                              {@code true} and the keyguard is locked, only activities that
+     *                              can be shown on top of the keyguard will be considered.
+     * @return The top running activity. {@code null} if none is available.
+     */
+    ActivityRecord topRunningActivity(boolean considerKeyguardState) {
+        ActivityRecord topRunning = null;
+        final ActivityStack focusedStack = getFocusedStack();
+        if (focusedStack != null) {
+            topRunning = focusedStack.topRunningActivity();
+        }
+
+        // Look in other focusable stacks.
+        if (topRunning == null) {
+            for (int i = getStackCount() - 1; i >= 0; --i) {
+                final ActivityStack stack = getStackAt(i);
+                // Only consider focusable stacks other than the current focused one.
+                if (stack == focusedStack || !stack.isFocusable()) {
+                    continue;
+                }
+                topRunning = stack.topRunningActivity();
+                if (topRunning != null) {
+                    break;
+                }
+            }
+        }
+
+        // This activity can be considered the top running activity if we are not considering
+        // the locked state, the keyguard isn't locked, or we can show when locked.
+        if (topRunning != null && considerKeyguardState
+                && mRootActivityContainer.mStackSupervisor.getKeyguardController()
+                        .isKeyguardLocked()
+                && !topRunning.canShowWhenLocked()) {
+            return null;
+        }
+
+        return topRunning;
+    }
+
+    boolean updateDisplayOverrideConfigurationLocked() {
+        Configuration values = new Configuration();
+        computeScreenConfiguration(values);
+
+        mAtmService.mH.sendMessage(PooledLambda.obtainMessage(
+                ActivityManagerInternal::updateOomLevelsForDisplay, mAtmService.mAmInternal,
+                mDisplayId));
+
+        Settings.System.clearConfiguration(values);
+        updateDisplayOverrideConfigurationLocked(values, null /* starting */,
+                false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
+        return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
+    }
+
+    /**
+     * Updates override configuration specific for the selected display. If no config is provided,
+     * new one will be computed in WM based on current display info.
+     */
+    boolean updateDisplayOverrideConfigurationLocked(Configuration values,
+            ActivityRecord starting, boolean deferResume,
+            ActivityTaskManagerService.UpdateConfigurationResult result) {
+
+        int changes = 0;
+        boolean kept = true;
+
+        mAtmService.deferWindowLayout();
+        try {
+            if (values != null) {
+                if (mDisplayId == DEFAULT_DISPLAY) {
+                    // Override configuration of the default display duplicates global config, so
+                    // we're calling global config update instead for default display. It will also
+                    // apply the correct override config.
+                    changes = mAtmService.updateGlobalConfigurationLocked(values,
+                            false /* initLocale */, false /* persistent */,
+                            UserHandle.USER_NULL /* userId */, deferResume);
+                } else {
+                    changes = performDisplayOverrideConfigUpdate(values, deferResume);
+                }
+            }
+
+            kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
+        } finally {
+            mAtmService.continueWindowLayout();
+        }
+
+        if (result != null) {
+            result.changes = changes;
+            result.activityRelaunched = !kept;
+        }
+        return kept;
+    }
+
+    int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume) {
+        mTempConfig.setTo(getRequestedOverrideConfiguration());
+        final int changes = mTempConfig.updateFrom(values);
+        if (changes != 0) {
+            Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
+                    + mTempConfig + " for displayId=" + mDisplayId);
+            onRequestedOverrideConfigurationChanged(mTempConfig);
+
+            final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
+            if (isDensityChange && mDisplayId == DEFAULT_DISPLAY) {
+                mAtmService.mAppWarnings.onDensityChanged();
+
+                // Post message to start process to avoid possible deadlock of calling into AMS with
+                // the ATMS lock held.
+                final Message msg = PooledLambda.obtainMessage(
+                        ActivityManagerInternal::killAllBackgroundProcessesExcept,
+                        mAtmService.mAmInternal, N,
+                        ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+                mAtmService.mH.sendMessage(msg);
+            }
+            mWmService.mDisplayNotificationController.dispatchDisplayChanged(
+                    this, getConfiguration());
+        }
+        return changes;
+    }
+
+    @Override
+    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        final int currRotation =
+                getRequestedOverrideConfiguration().windowConfiguration.getRotation();
+        if (currRotation != ROTATION_UNDEFINED
+                && currRotation != overrideConfiguration.windowConfiguration.getRotation()) {
+            applyRotationLocked(currRotation,
+                    overrideConfiguration.windowConfiguration.getRotation());
+        }
+        mCurrentOverrideConfigurationChanges =
+            getRequestedOverrideConfiguration().diff(overrideConfiguration);
+        super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
+        mCurrentOverrideConfigurationChanges = 0;
+        mWmService.setNewDisplayOverrideConfiguration(overrideConfiguration, this);
+        mAtmService.addWindowLayoutReasons(
+                ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
+    }
+
+    /** Checks whether the given activity is in size compatibility mode and notifies the change. */
+    void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) {
+        if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+            // The callback is only interested in the foreground changes of fullscreen activity.
+            return;
+        }
+        if (!r.inSizeCompatMode()) {
+            if (mLastCompatModeActivity != null) {
+                mAtmService.getTaskChangeNotificationController()
+                        .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */);
+            }
+            mLastCompatModeActivity = null;
+            return;
+        }
+        if (mLastCompatModeActivity == r) {
+            return;
+        }
+        mLastCompatModeActivity = r;
+        mAtmService.getTaskChangeNotificationController()
+                .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken);
+    }
+
+    boolean isUidPresent(int uid) {
+        final PooledPredicate p = PooledLambda.obtainPredicate(
+                ActivityRecord::isUid, PooledLambda.__(ActivityRecord.class), uid);
+        final boolean isUidPresent = mDisplayContent.getActivity(p) != null;
+        p.recycle();
+        return isUidPresent;
+    }
+
+    /**
+     * @see #mRemoved
+     */
+    boolean isRemoved() {
+        return mRemoved;
+    }
+
+    /**
+     * @see #mRemoving
+     */
+    boolean isRemoving() {
+        return mRemoving;
+    }
+
+    void remove() {
+        mRemoving = true;
+        final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove();
+        ActivityStack lastReparentedStack = null;
+        mPreferredTopFocusableStack = null;
+
+        // Stacks could be reparented from the removed display to other display. While
+        // reparenting the last stack of the removed display, the remove display is ready to be
+        // released (no more ActivityStack). But, we cannot release it at that moment or the
+        // related WindowContainer will also be removed. So, we set display as removed after
+        // reparenting stack finished.
+        final DisplayContent toDisplay = mRootActivityContainer.getDefaultDisplay();
+        mRootActivityContainer.mStackSupervisor.beginDeferResume();
+        try {
+            int numStacks = getStackCount();
+            // Keep the order from bottom to top.
+            for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
+                final ActivityStack stack = getStackAt(stackNdx);
+                // Always finish non-standard type stacks.
+                if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) {
+                    stack.finishAllActivitiesImmediately();
+                } else {
+                    // If default display is in split-window mode, set windowing mode of the stack
+                    // to split-screen secondary. Otherwise, set the windowing mode to undefined by
+                    // default to let stack inherited the windowing mode from the new display.
+                    final int windowingMode = toDisplay.hasSplitScreenPrimaryStack()
+                            ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                            : WINDOWING_MODE_UNDEFINED;
+                    stack.reparent(toDisplay, true /* onTop */);
+                    stack.setWindowingMode(windowingMode);
+                    lastReparentedStack = stack;
+                }
+                // Stacks may be removed from this display. Ensure each stack will be processed and
+                // the loop will end.
+                stackNdx -= numStacks - getStackCount();
+                numStacks = getStackCount();
+            }
+        } finally {
+            mRootActivityContainer.mStackSupervisor.endDeferResume();
+        }
+        mRemoved = true;
+
+        // Only update focus/visibility for the last one because there may be many stacks are
+        // reparented and the intermediate states are unnecessary.
+        if (lastReparentedStack != null) {
+            lastReparentedStack.postReparent();
+        }
+        releaseSelfIfNeeded();
+
+        if (!mAllSleepTokens.isEmpty()) {
+            mRootActivityContainer.mSleepTokens.removeAll(mAllSleepTokens);
+            mAllSleepTokens.clear();
+            mAtmService.updateSleepIfNeededLocked();
+        }
+    }
+
+    private void releaseSelfIfNeeded() {
+        if (!mRemoved) {
+            return;
+        }
+
+        final ActivityStack stack = getStackCount() == 1 ? getStackAt(0) : null;
+        if (stack != null && stack.isActivityTypeHome() && !stack.hasChild()) {
+            // Release this display if an empty home stack is the only thing left.
+            // Since it is the last stack, this display will be released along with the stack
+            // removal.
+            stack.removeIfPossible();
+        } else if (getTopStack() == null) {
+            removeIfPossible();
+            mRootActivityContainer.removeChild(this);
+            mRootActivityContainer.mStackSupervisor
+                    .getKeyguardController().onDisplayRemoved(mDisplayId);
+        }
+    }
+
+    /** Update and get all UIDs that are present on the display and have access to it. */
+    IntArray getPresentUIDs() {
+        mDisplayAccessUIDs.clear();
+        final PooledConsumer c = PooledLambda.obtainConsumer(DisplayContent::addActivityUid,
+                PooledLambda.__(ActivityRecord.class), mDisplayAccessUIDs);
+        mDisplayContent.forAllActivities(c);
+        c.recycle();
+        return mDisplayAccessUIDs;
+    }
+
+    private static void addActivityUid(ActivityRecord r, IntArray uids) {
+        uids.add(r.getUid());
+    }
+
+    @VisibleForTesting
+    boolean shouldDestroyContentOnRemove() {
+        return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
+    }
+
+    boolean shouldSleep() {
+        return (getStackCount() == 0 || !mAllSleepTokens.isEmpty())
+                && (mAtmService.mRunningVoice == null);
+    }
+
+    void setFocusedApp(ActivityRecord r, boolean moveFocusNow) {
+        final ActivityRecord newFocus;
+        final IBinder token = r.appToken;
+        if (token == null) {
+            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Clearing focused app, displayId=%d",
+                    mDisplayId);
+            newFocus = null;
+        } else {
+            newFocus = mWmService.mRoot.getActivityRecord(token);
+            if (newFocus == null) {
+                Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token
+                        + ", displayId=" + mDisplayId);
+            }
+            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
+                    "Set focused app to: %s moveFocusNow=%b displayId=%d", newFocus,
+                    moveFocusNow, mDisplayId);
+        }
+
+        final boolean changed = setFocusedApp(newFocus);
+        if (moveFocusNow && changed) {
+            mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+                    true /*updateInputWindows*/);
+        }
+    }
+
+    /**
+     * @return the stack currently above the {@param stack}.  Can be null if the {@param stack} is
+     *         already top-most.
+     */
+    ActivityStack getStackAbove(ActivityStack stack) {
+        final int stackIndex = getIndexOf(stack) + 1;
+        return (stackIndex < getStackCount()) ? getStackAt(stackIndex) : null;
+    }
+
+    /**
+     * Adjusts the {@param stack} behind the last visible stack in the display if necessary.
+     * Generally used in conjunction with {@link #moveStackBehindStack}.
+     */
+    void moveStackBehindBottomMostVisibleStack(ActivityStack stack) {
+        if (stack.shouldBeVisible(null)) {
+            // Skip if the stack is already visible
+            return;
+        }
+
+        // Move the stack to the bottom to not affect the following visibility checks
+        positionStackAtBottom(stack);
+
+        // Find the next position where the stack should be placed
+        final int numStacks = getStackCount();
+        for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
+            final ActivityStack s = getStackAt(stackNdx);
+            if (s == stack) {
+                continue;
+            }
+            final int winMode = s.getWindowingMode();
+            final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN
+                    || winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+            if (s.shouldBeVisible(null) && isValidWindowingMode) {
+                // Move the provided stack to behind this stack
+                positionStackAt(stack, Math.max(0, stackNdx - 1));
+                break;
+            }
+        }
+    }
+
+    /**
+     * Moves the {@param stack} behind the given {@param behindStack} if possible. If
+     * {@param behindStack} is not currently in the display, then then the stack is moved to the
+     * back. Generally used in conjunction with {@link #moveStackBehindBottomMostVisibleStack}.
+     */
+    void moveStackBehindStack(ActivityStack stack, ActivityStack behindStack) {
+        if (behindStack == null || behindStack == stack) {
+            return;
+        }
+
+        // Note that positionChildAt will first remove the given stack before inserting into the
+        // list, so we need to adjust the insertion index to account for the removed index
+        // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
+        //       position internally
+        final int stackIndex = getIndexOf(stack);
+        final int behindStackIndex = getIndexOf(behindStack);
+        final int insertIndex = stackIndex <= behindStackIndex
+                ? behindStackIndex - 1 : behindStackIndex;
+        positionStackAt(stack, Math.max(0, insertIndex));
+    }
+
+    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
+            boolean preserveWindows, boolean notifyClients) {
+        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = getStackAt(stackNdx);
+            stack.ensureActivitiesVisible(starting, configChanges, preserveWindows,
+                    notifyClients);
+        }
+    }
+
+    void moveHomeStackToFront(String reason) {
+        final ActivityStack homeStack = getHomeStack();
+        if (homeStack != null) {
+            homeStack.moveToFront(reason);
+        }
+    }
+
+    /**
+     * Moves the focusable home activity to top. If there is no such activity, the home stack will
+     * still move to top.
+     */
+    void moveHomeActivityToTop(String reason) {
+        final ActivityRecord top = getHomeActivity();
+        if (top == null) {
+            moveHomeStackToFront(reason);
+            return;
+        }
+        top.moveFocusableActivityToTop(reason);
+    }
+
+    @Nullable
+    ActivityRecord getHomeActivity() {
+        return getHomeActivityForUser(mRootActivityContainer.mCurrentUser);
+    }
+
+    @Nullable
+    ActivityRecord getHomeActivityForUser(int userId) {
+        final ActivityStack homeStack = getHomeStack();
+        if (homeStack == null) {
+            return null;
+        }
+
+        final PooledPredicate p = PooledLambda.obtainPredicate(
+                DisplayContent::isHomeActivityForUser, PooledLambda.__(ActivityRecord.class),
+                userId);
+        final ActivityRecord r = homeStack.getActivity(p);
+        p.recycle();
+        return r;
+    }
+
+    private static boolean isHomeActivityForUser(ActivityRecord r, int userId) {
+        return r.isActivityTypeHome() && (userId == UserHandle.USER_ALL || r.mUserId == userId);
+    }
+
+    boolean isSleeping() {
+        return mSleeping;
+    }
+
+    void setIsSleeping(boolean asleep) {
+        mSleeping = asleep;
+    }
+
+    /**
+     * Adds a listener to be notified whenever the stack order in the display changes. Currently
+     * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
+     * current animation when the system state changes.
+     */
+    void registerStackOrderChangedListener(OnStackOrderChangedListener listener) {
+        if (!mStackOrderChangedCallbacks.contains(listener)) {
+            mStackOrderChangedCallbacks.add(listener);
+        }
+    }
+
+    /**
+     * Removes a previously registered stack order change listener.
+     */
+    void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) {
+        mStackOrderChangedCallbacks.remove(listener);
+    }
+
+    /**
+     * Notifies of a stack order change
+     * @param stack The stack which triggered the order change
+     */
+    private void onStackOrderChanged(ActivityStack stack) {
+        for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
+            mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack);
+        }
+    }
+
+    void setDisplayToSingleTaskInstance() {
+        final int childCount = getStackCount();
+        if (childCount > 1) {
+            throw new IllegalArgumentException("Display already has multiple stacks. display="
+                    + this);
+        }
+        if (childCount > 0) {
+            final ActivityStack stack = getStackAt(0);
+            if (stack.getChildCount() > 1) {
+                throw new IllegalArgumentException("Display stack already has multiple tasks."
+                        + " display=" + this + " stack=" + stack);
+            }
+        }
+
+        mSingleTaskInstance = true;
+    }
+
+    /** Returns true if the display can only contain one task */
+    boolean isSingleTaskInstance() {
+        return mSingleTaskInstance;
+    }
+
+    @VisibleForTesting
+    void removeAllTasks() {
+        forAllTasks((t) -> { t.getStack().removeChild(t, "removeAllTasks"); });
+    }
+
+    /**
+     * Callback for when the order of the stacks in the display changes.
+     */
+    interface OnStackOrderChangedListener {
+        void onStackOrderChanged(ActivityStack stack);
+    }
+
+    public void dumpStacks(PrintWriter pw) {
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            pw.print(getStackAt(i).mStackId);
+            if (i > 0) {
+                pw.print(",");
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index 78fea74..bb31d45 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -41,7 +41,7 @@
             try {
                 for (int i = 0; i < mService.mAtmService.mRootActivityContainer.getChildCount();
                         ++i) {
-                    ActivityDisplay d = mService.mAtmService.mRootActivityContainer.getChildAt(i);
+                    DisplayContent d = mService.mAtmService.mRootActivityContainer.getChildAt(i);
                     listener.onDisplayAdded(d.mDisplayId);
                 }
             } catch (RemoteException e) { }
@@ -52,7 +52,7 @@
         mDisplayListeners.unregister(listener);
     }
 
-    void dispatchDisplayAdded(ActivityDisplay display) {
+    void dispatchDisplayAdded(DisplayContent display) {
         int count = mDisplayListeners.beginBroadcast();
         for (int i = 0; i < count; ++i) {
             try {
@@ -85,7 +85,7 @@
         mDisplayListeners.finishBroadcast();
     }
 
-    void dispatchDisplayRemoved(ActivityDisplay display) {
+    void dispatchDisplayRemoved(DisplayContent display) {
         int count = mDisplayListeners.beginBroadcast();
         for (int i = 0; i < count; ++i) {
             try {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 3a0696f..6f81957 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -296,7 +296,7 @@
         boolean requestDismissKeyguard = false;
         for (int displayNdx = mRootActivityContainer.getChildCount() - 1;
              displayNdx >= 0; displayNdx--) {
-            final ActivityDisplay display = mRootActivityContainer.getChildAt(displayNdx);
+            final DisplayContent display = mRootActivityContainer.getChildAt(displayNdx);
             final KeyguardDisplayState state = getDisplay(display.mDisplayId);
             state.visibilitiesUpdated(this, display);
             requestDismissKeyguard |= state.mRequestDismissKeyguard;
@@ -420,7 +420,7 @@
     private void updateKeyguardSleepToken() {
         for (int displayNdx = mRootActivityContainer.getChildCount() - 1;
              displayNdx >= 0; displayNdx--) {
-            final ActivityDisplay display = mRootActivityContainer.getChildAt(displayNdx);
+            final DisplayContent display = mRootActivityContainer.getChildAt(displayNdx);
             updateKeyguardSleepToken(display.mDisplayId);
         }
     }
@@ -483,7 +483,7 @@
             }
         }
 
-        void visibilitiesUpdated(KeyguardController controller, ActivityDisplay display) {
+        void visibilitiesUpdated(KeyguardController controller, DisplayContent display) {
             final boolean lastOccluded = mOccluded;
             final ActivityRecord lastDismissActivity = mDismissingKeyguardActivity;
             mRequestDismissKeyguard = false;
@@ -530,7 +530,7 @@
          * Only the top non-pinned activity of the focusable stack on each display can control its
          * occlusion state.
          */
-        private ActivityStack getStackForControllingOccluding(ActivityDisplay display) {
+        private ActivityStack getStackForControllingOccluding(DisplayContent display) {
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 if (stack != null && stack.isFocusableAndVisible()
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index c3bcc74..8cf3dc8 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -223,8 +223,8 @@
     private boolean saveTaskToLaunchParam(Task task, PersistableLaunchParams params) {
         final ActivityStack stack = task.getStack();
         final int displayId = stack.mDisplayId;
-        final ActivityDisplay display =
-                mSupervisor.mRootActivityContainer.getActivityDisplay(displayId);
+        final DisplayContent display =
+                mSupervisor.mRootActivityContainer.getDisplayContent(displayId);
         final DisplayInfo info = new DisplayInfo();
         display.mDisplay.getDisplayInfo(info);
 
@@ -260,7 +260,7 @@
             return;
         }
 
-        final ActivityDisplay display = mSupervisor.mRootActivityContainer.getActivityDisplay(
+        final DisplayContent display = mSupervisor.mRootActivityContainer.getDisplayContent(
                 persistableParams.mDisplayUniqueId);
         if (display != null) {
             outParams.mPreferredDisplayId =  display.mDisplayId;
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index c54fbd6..71bbb70 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -208,7 +208,7 @@
                 synchronized (mService.mGlobalLock) {
                     // Unfreeze the task list once we touch down in a task
                     final RootActivityContainer rac = mService.mRootActivityContainer;
-                    final DisplayContent dc = rac.getActivityDisplay(displayId).mDisplayContent;
+                    final DisplayContent dc = rac.getDisplayContent(displayId).mDisplayContent;
                     if (dc.pointWithinAppWindow(x, y)) {
                         final ActivityStack stack = mService.getTopDisplayFocusedStack();
                         final Task topTask = stack != null ? stack.getTopMostTask() : null;
@@ -1144,6 +1144,7 @@
 
         // Trim the set of tasks to the active set
         trimInactiveRecentTasks();
+        notifyTaskPersisterLocked(task, false /* flush */);
     }
 
     /**
@@ -1316,7 +1317,7 @@
         // TODO(b/126185105): Find a different signal to use besides isSingleTaskInstance
         final ActivityStack stack = task.getStack();
         if (stack != null) {
-            ActivityDisplay display = stack.getDisplay();
+            DisplayContent display = stack.getDisplay();
             if (display != null && display.isSingleTaskInstance()) {
                 return false;
             }
@@ -1386,7 +1387,7 @@
         }
 
         // Trim tasks that are in stacks that are behind the home stack
-        final ActivityDisplay display = stack.getDisplay();
+        final DisplayContent display = stack.getDisplay();
         return display.getIndexOf(stack) < display.getIndexOf(display.getHomeStack());
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index b398626..0a8e747 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -53,14 +53,14 @@
  * cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
  */
 class RecentsAnimation implements RecentsAnimationCallbacks,
-        ActivityDisplay.OnStackOrderChangedListener {
+        DisplayContent.OnStackOrderChangedListener {
     private static final String TAG = RecentsAnimation.class.getSimpleName();
 
     private final ActivityTaskManagerService mService;
     private final ActivityStackSupervisor mStackSupervisor;
     private final ActivityStartController mActivityStartController;
     private final WindowManagerService mWindowManager;
-    private final ActivityDisplay mDefaultDisplay;
+    private final DisplayContent mDefaultDisplay;
     private final Intent mTargetIntent;
     private final ComponentName mRecentsComponent;
     private final int mRecentsUid;
@@ -180,7 +180,7 @@
         ActivityRecord targetActivity = getTargetActivity(targetStack);
         final boolean hasExistingActivity = targetActivity != null;
         if (hasExistingActivity) {
-            final ActivityDisplay display = targetActivity.getDisplay();
+            final DisplayContent display = targetActivity.getDisplay();
             mRestoreTargetBehindStack = display.getStackAbove(targetStack);
             if (mRestoreTargetBehindStack == null) {
                 notifyAnimationCancelBeforeStart(recentsAnimationRunner);
@@ -250,7 +250,7 @@
             mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION,
                     "startRecentsActivity");
             mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
-                    this, mDefaultDisplay.mDisplayId,
+                    this, mDefaultDisplay.getDisplayId(),
                     mStackSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
 
             // If we updated the launch-behind state, update the visibility of the activities after
@@ -351,7 +351,7 @@
                         }
                     } else if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION){
                         // Restore the target stack to its previous position
-                        final ActivityDisplay display = targetActivity.getDisplay();
+                        final DisplayContent display = targetActivity.getDisplay();
                         display.moveStackBehindStack(targetStack, mRestoreTargetBehindStack);
                         if (WM_DEBUG_RECENTS_ANIMATIONS.isLogToAny()) {
                             final ActivityStack aboveTargetStack =
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 3aa91d5..d787cbc 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -258,7 +258,7 @@
     private void processCreatedTasks() {
         if (mCreatedTasks.isEmpty()) return;
 
-        ActivityDisplay display = mParent.getDisplay();
+        DisplayContent display = mParent.getDisplay();
         final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
         if (singleTaskInstanceDisplay) {
             display = mParent.mRootActivityContainer.getDefaultDisplay();
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 704a4b7..7dd9790 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -109,7 +109,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
-import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -176,10 +175,10 @@
      * List of displays which contain activities, sorted by z-order.
      * The last entry in the list is the topmost.
      */
-    private final ArrayList<ActivityDisplay> mActivityDisplays = new ArrayList<>();
+    private final ArrayList<DisplayContent> mDisplayContents = new ArrayList<>();
 
     /** Reference to default display so we can quickly look it up. */
-    private ActivityDisplay mDefaultDisplay;
+    private DisplayContent mDefaultDisplay;
     private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
 
     /** The current user */
@@ -355,35 +354,35 @@
         final Display[] displays = mDisplayManager.getDisplays();
         for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
             final Display display = displays[displayNdx];
-            final ActivityDisplay activityDisplay = new ActivityDisplay(this, display);
-            if (activityDisplay.mDisplayId == DEFAULT_DISPLAY) {
-                mDefaultDisplay = activityDisplay;
+            final DisplayContent displayContent = new DisplayContent(display, this);
+            if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
+                mDefaultDisplay = displayContent;
             }
-            addChild(activityDisplay, ActivityDisplay.POSITION_TOP);
+            addChild(displayContent, DisplayContent.POSITION_TOP);
         }
         calculateDefaultMinimalSizeOfResizeableTasks();
 
-        final ActivityDisplay defaultDisplay = getDefaultDisplay();
+        final DisplayContent defaultDisplay = getDefaultDisplay();
 
         defaultDisplay.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
-        positionChildAt(defaultDisplay, ActivityDisplay.POSITION_TOP);
+        positionChildAt(defaultDisplay, DisplayContent.POSITION_TOP);
     }
 
     // TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display.
-    ActivityDisplay getDefaultDisplay() {
+    DisplayContent getDefaultDisplay() {
         return mDefaultDisplay;
     }
 
     /**
-     * Get an existing instance of {@link ActivityDisplay} that has the given uniqueId. Unique ID is
+     * Get an existing instance of {@link DisplayContent} that has the given uniqueId. Unique ID is
      * defined in {@link DisplayInfo#uniqueId}.
      *
      * @param uniqueId the unique ID of the display
-     * @return the {@link ActivityDisplay} or {@code null} if nothing is found.
+     * @return the {@link DisplayContent} or {@code null} if nothing is found.
      */
-    ActivityDisplay getActivityDisplay(String uniqueId) {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityDisplay display = mActivityDisplays.get(i);
+    DisplayContent getDisplayContent(String uniqueId) {
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final DisplayContent display = mDisplayContents.get(i);
             final boolean isValid = display.mDisplay.isValid();
             if (isValid && display.mDisplay.getUniqueId().equals(uniqueId)) {
                 return display;
@@ -393,26 +392,26 @@
         return null;
     }
 
-    // TODO: Look into consolidating with getActivityDisplayOrCreate()
-    ActivityDisplay getActivityDisplay(int displayId) {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityDisplay activityDisplay = mActivityDisplays.get(i);
-            if (activityDisplay.mDisplayId == displayId) {
-                return activityDisplay;
+    // TODO: Look into consolidating with getDisplayContentOrCreate()
+    DisplayContent getDisplayContent(int displayId) {
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final DisplayContent displayContent = mDisplayContents.get(i);
+            if (displayContent.mDisplayId == displayId) {
+                return displayContent;
             }
         }
         return null;
     }
 
     /**
-     * Get an existing instance of {@link ActivityDisplay} or create new if there is a
+     * Get an existing instance of {@link DisplayContent} or create new if there is a
      * corresponding record in display manager.
      */
-    // TODO: Look into consolidating with getActivityDisplay()
-    @Nullable ActivityDisplay getActivityDisplayOrCreate(int displayId) {
-        ActivityDisplay activityDisplay = getActivityDisplay(displayId);
-        if (activityDisplay != null) {
-            return activityDisplay;
+    // TODO: Look into consolidating with getDisplayContent()
+    @Nullable DisplayContent getDisplayContentOrCreate(int displayId) {
+        DisplayContent displayContent = getDisplayContent(displayId);
+        if (displayContent != null) {
+            return displayContent;
         }
         if (mDisplayManager == null) {
             // The system isn't fully initialized yet.
@@ -424,9 +423,9 @@
             return null;
         }
         // The display hasn't been added to ActivityManager yet, create a new record now.
-        activityDisplay = new ActivityDisplay(this, display);
-        addChild(activityDisplay, ActivityDisplay.POSITION_BOTTOM);
-        return activityDisplay;
+        displayContent = new DisplayContent(display, this);
+        addChild(displayContent, DisplayContent.POSITION_BOTTOM);
+        return displayContent;
     }
 
     ActivityRecord getDefaultDisplayHomeActivity() {
@@ -434,21 +433,21 @@
     }
 
     ActivityRecord getDefaultDisplayHomeActivityForUser(int userId) {
-        return getActivityDisplay(DEFAULT_DISPLAY).getHomeActivityForUser(userId);
+        return getDisplayContent(DEFAULT_DISPLAY).getHomeActivityForUser(userId);
     }
 
     boolean startHomeOnAllDisplays(int userId, String reason) {
         boolean homeStarted = false;
-        for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
-            final int displayId = mActivityDisplays.get(i).mDisplayId;
+        for (int i = mDisplayContents.size() - 1; i >= 0; i--) {
+            final int displayId = mDisplayContents.get(i).mDisplayId;
             homeStarted |= startHomeOnDisplay(userId, reason, displayId);
         }
         return homeStarted;
     }
 
     void startHomeOnEmptyDisplays(String reason) {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
-            final ActivityDisplay display = mActivityDisplays.get(i);
+        for (int i = mDisplayContents.size() - 1; i >= 0; i--) {
+            final DisplayContent display = mDisplayContents.get(i);
             if (display.topRunningActivity() == null) {
                 startHomeOnDisplay(mCurrentUser, reason, display.mDisplayId);
             }
@@ -630,7 +629,7 @@
             displayId = DEFAULT_DISPLAY;
         }
 
-        final ActivityRecord r = getActivityDisplay(displayId).getHomeActivity();
+        final ActivityRecord r = getDisplayContent(displayId).getHomeActivity();
         final String myReason = reason + " resumeHomeActivity";
 
         // Only resume home activity if isn't finishing.
@@ -672,7 +671,7 @@
             return false;
         }
 
-        final ActivityDisplay display = getActivityDisplay(displayId);
+        final DisplayContent display = getDisplayContent(displayId);
         if (display == null || display.isRemoved() || !display.supportsSystemDecorations()) {
             // Can't launch home on display that doesn't support system decorations.
             return false;
@@ -772,10 +771,8 @@
 
         if (displayContent != null) {
             // Update the configuration of the activities on the display.
-            // TODO(display-merge): Remove cast
-            return ((ActivityDisplay) displayContent)
-                .updateDisplayOverrideConfigurationLocked(config, starting, deferResume,
-                    null /* result */);
+            return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
+                    deferResume, null /* result */);
         } else {
             return true;
         }
@@ -789,8 +786,8 @@
         final ArrayList<IBinder> topActivityTokens = new ArrayList<>();
         final ActivityStack topFocusedStack = getTopDisplayFocusedStack();
         // Traverse all displays.
-        for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
-            final ActivityDisplay display = mActivityDisplays.get(i);
+        for (int i = mDisplayContents.size() - 1; i >= 0; i--) {
+            final DisplayContent display = mDisplayContents.get(i);
             // Traverse all stacks on a display.
             for (int j = display.getStackCount() - 1; j >= 0; --j) {
                 final ActivityStack stack = display.getStackAt(j);
@@ -811,8 +808,8 @@
     }
 
     ActivityStack getTopDisplayFocusedStack() {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityStack focusedStack = mActivityDisplays.get(i).getFocusedStack();
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final ActivityStack focusedStack = mDisplayContents.get(i).getFocusedStack();
             if (focusedStack != null) {
                 return focusedStack;
             }
@@ -831,8 +828,8 @@
         }
         // The top focused stack might not have a resumed activity yet - look on all displays in
         // focus order.
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityDisplay display = mActivityDisplays.get(i);
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final DisplayContent display = mDisplayContents.get(i);
             final ActivityRecord resumedActivityOnDisplay = display.getResumedActivity();
             if (resumedActivityOnDisplay != null) {
                 return resumedActivityOnDisplay;
@@ -861,8 +858,8 @@
         // previous app if this activity is being hosted by the process that is actually still the
         // foreground.
         WindowProcessController fgApp = null;
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 if (isTopDisplayFocusedStack(stack)) {
@@ -889,8 +886,8 @@
     boolean attachApplication(WindowProcessController app) throws RemoteException {
         final String processName = app.mName;
         boolean didSomething = false;
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             final ActivityStack stack = display.getFocusedStack();
             if (stack == null) {
                 continue;
@@ -958,8 +955,8 @@
         try {
             mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
             // First the front stacks. In case any are not fullscreen and are in front of home.
-            for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-                final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+            for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+                final DisplayContent display = mDisplayContents.get(displayNdx);
                 display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
                         notifyClients);
             }
@@ -988,8 +985,8 @@
         mCurrentUser = userId;
 
         mStackSupervisor.mStartingUsers.add(uss);
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 stack.switchUser(userId);
@@ -1036,8 +1033,8 @@
      * @param onTop Indicates whether container should be place on top or on bottom.
      */
     void moveStackToDisplay(int stackId, int displayId, boolean onTop) {
-        final ActivityDisplay activityDisplay = getActivityDisplayOrCreate(displayId);
-        if (activityDisplay == null) {
+        final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
+        if (displayContent == null) {
             throw new IllegalArgumentException("moveStackToDisplay: Unknown displayId="
                     + displayId);
         }
@@ -1047,7 +1044,7 @@
                     + stackId);
         }
 
-        final ActivityDisplay currentDisplay = stack.getDisplay();
+        final DisplayContent currentDisplay = stack.getDisplay();
         if (currentDisplay == null) {
             throw new IllegalStateException("moveStackToDisplay: Stack with stack=" + stack
                     + " is not attached to any display.");
@@ -1058,14 +1055,14 @@
                     + " to its current displayId=" + displayId);
         }
 
-        if (activityDisplay.isSingleTaskInstance() && activityDisplay.getStackCount() > 0) {
+        if (displayContent.isSingleTaskInstance() && displayContent.getStackCount() > 0) {
             // We don't allow moving stacks to single instance display that already has a child.
             Slog.e(TAG, "Can not move stack=" + stack
-                    + " to single task instance display=" + activityDisplay);
+                    + " to single task instance display=" + displayContent);
             return;
         }
 
-        stack.reparent(activityDisplay.mDisplayContent, onTop);
+        stack.reparent(displayContent.mDisplayContent, onTop);
         // TODO(multi-display): resize stacks properly if moved from split-screen.
     }
 
@@ -1098,7 +1095,7 @@
             String reason) {
         mService.deferWindowLayout();
 
-        final ActivityDisplay display = r.getActivityStack().getDisplay();
+        final DisplayContent display = r.getActivityStack().getDisplay();
 
         try {
             final Task task = r.getTask();
@@ -1168,8 +1165,8 @@
     }
 
     void executeAppTransitionForAllDisplay() {
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             display.mDisplayContent.executeAppTransition();
         }
     }
@@ -1194,7 +1191,7 @@
         mTmpFindTaskResult.clear();
 
         // Looking up task on preferred display first
-        final ActivityDisplay preferredDisplay = getActivityDisplay(preferredDisplayId);
+        final DisplayContent preferredDisplay = getDisplayContent(preferredDisplayId);
         if (preferredDisplay != null) {
             preferredDisplay.findTaskLocked(r, true /* isPreferredDisplay */, mTmpFindTaskResult);
             if (mTmpFindTaskResult.mIdealMatch) {
@@ -1202,8 +1199,8 @@
             }
         }
 
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             if (display.mDisplayId == preferredDisplayId) {
                 continue;
             }
@@ -1227,8 +1224,8 @@
     int finishTopCrashedActivities(WindowProcessController app, String reason) {
         Task finishedTask = null;
         ActivityStack focusedStack = getTopDisplayFocusedStack();
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             // It is possible that request to finish activity might also remove its task and stack,
             // so we need to be careful with indexes in the loop and check child count every time.
             for (int stackNdx = 0; stackNdx < display.getStackCount(); ++stackNdx) {
@@ -1259,9 +1256,9 @@
             result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
         }
 
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
             boolean resumedOnDisplay = false;
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 final ActivityRecord topRunningActivity = stack.topRunningActivity();
@@ -1291,7 +1288,7 @@
                 final ActivityStack focusedStack = display.getFocusedStack();
                 if (focusedStack != null) {
                     result |= focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
-                } else if (targetStack == null && !display.hasChild()) {
+                } else if (targetStack == null && display.getStackCount() == 0) {
                     result |= resumeHomeActivity(null /* prev */, "empty-display",
                             display.mDisplayId);
                 }
@@ -1302,9 +1299,9 @@
     }
 
     void applySleepTokens(boolean applyToStacks) {
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
             // Set the sleeping state of the display.
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             final boolean displayShouldSleep = display.shouldSleep();
             if (displayShouldSleep == display.isSleeping()) {
                 continue;
@@ -1358,8 +1355,8 @@
     }
 
     protected ActivityStack getStack(int stackId) {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityStack stack = mActivityDisplays.get(i).getStack(stackId);
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final ActivityStack stack = mDisplayContents.get(i).getStack(stackId);
             if (stack != null) {
                 return stack;
             }
@@ -1367,11 +1364,11 @@
         return null;
     }
 
-    /** @see ActivityDisplay#getStack(int, int) */
+    /** @see DisplayContent#getStack(int, int) */
     ActivityStack getStack(int windowingMode, int activityType) {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
             final ActivityStack stack =
-                    mActivityDisplays.get(i).getStack(windowingMode, activityType);
+                    mDisplayContents.get(i).getStack(windowingMode, activityType);
             if (stack != null) {
                 return stack;
             }
@@ -1381,7 +1378,7 @@
 
     private ActivityStack getStack(int windowingMode, int activityType,
             int displayId) {
-        ActivityDisplay display = getActivityDisplay(displayId);
+        DisplayContent display = getDisplayContent(displayId);
         if (display == null) {
             return null;
         }
@@ -1390,7 +1387,7 @@
 
     private ActivityManager.StackInfo getStackInfo(ActivityStack stack) {
         final int displayId = stack.mDisplayId;
-        final ActivityDisplay display = getActivityDisplay(displayId);
+        final DisplayContent display = getDisplayContent(displayId);
         ActivityManager.StackInfo info = new ActivityManager.StackInfo();
         stack.getBounds(info.bounds);
         info.displayId = displayId;
@@ -1455,8 +1452,8 @@
     ArrayList<ActivityManager.StackInfo> getAllStackInfos(int displayId) {
         ArrayList<ActivityManager.StackInfo> list = new ArrayList<>();
         if (displayId == INVALID_DISPLAY) {
-            for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
-                final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+            for (int displayNdx = 0; displayNdx < mDisplayContents.size(); ++displayNdx) {
+                final DisplayContent display = mDisplayContents.get(displayNdx);
                 for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                     final ActivityStack stack = display.getStackAt(stackNdx);
                     list.add(getStackInfo(stack));
@@ -1464,7 +1461,7 @@
             }
             return list;
         }
-        final ActivityDisplay display = getActivityDisplay(displayId);
+        final DisplayContent display = getDisplayContent(displayId);
         if (display == null) {
             return list;
         }
@@ -1493,7 +1490,7 @@
     public void onDisplayAdded(int displayId) {
         if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId);
         synchronized (mService.mGlobalLock) {
-            final ActivityDisplay display = getActivityDisplayOrCreate(displayId);
+            final DisplayContent display = getDisplayContentOrCreate(displayId);
             if (display == null) {
                 return;
             }
@@ -1519,12 +1516,12 @@
         }
 
         synchronized (mService.mGlobalLock) {
-            final ActivityDisplay activityDisplay = getActivityDisplay(displayId);
-            if (activityDisplay == null) {
+            final DisplayContent displayContent = getDisplayContent(displayId);
+            if (displayContent == null) {
                 return;
             }
 
-            activityDisplay.remove();
+            displayContent.remove();
         }
     }
 
@@ -1532,9 +1529,9 @@
     public void onDisplayChanged(int displayId) {
         if (DEBUG_STACK) Slog.v(TAG, "Display changed displayId=" + displayId);
         synchronized (mService.mGlobalLock) {
-            final ActivityDisplay activityDisplay = getActivityDisplay(displayId);
-            if (activityDisplay != null) {
-                activityDisplay.onDisplayChanged();
+            final DisplayContent displayContent = getDisplayContent(displayId);
+            if (displayContent != null) {
+                displayContent.onDisplayChanged();
             }
         }
     }
@@ -1542,12 +1539,12 @@
     /** Update lists of UIDs that are present on displays and have access to them. */
     void updateUIDsPresentOnDisplay() {
         mDisplayAccessUIDs.clear();
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent displayContent = mDisplayContents.get(displayNdx);
             // Only bother calculating the whitelist for private displays
-            if (activityDisplay.isPrivate()) {
+            if (displayContent.isPrivate()) {
                 mDisplayAccessUIDs.append(
-                        activityDisplay.mDisplayId, activityDisplay.getPresentUIDs());
+                        displayContent.mDisplayId, displayContent.getPresentUIDs());
             }
         }
         // Store updated lists in DisplayManager. Callers from outside of AM should get them there.
@@ -1555,7 +1552,7 @@
     }
 
     ActivityStack findStackBehind(ActivityStack stack) {
-        final ActivityDisplay display = getActivityDisplay(stack.mDisplayId);
+        final DisplayContent display = getDisplayContent(stack.mDisplayId);
         if (display != null) {
             for (int i = display.getStackCount() - 1; i >= 0; i--) {
                 if (display.getStackAt(i) == stack && i > 0) {
@@ -1569,12 +1566,12 @@
 
     @Override
     protected int getChildCount() {
-        return mActivityDisplays.size();
+        return mDisplayContents.size();
     }
 
     @Override
-    protected ActivityDisplay getChildAt(int index) {
-        return mActivityDisplays.get(index);
+    protected DisplayContent getChildAt(int index) {
+        return mDisplayContents.get(index);
     }
 
     @Override
@@ -1592,61 +1589,59 @@
 
     /** Change the z-order of the given display. */
     private void positionChildAt(DisplayContent display, int position) {
-        if (position >= mActivityDisplays.size()) {
-            position = mActivityDisplays.size() - 1;
+        if (position >= mDisplayContents.size()) {
+            position = mDisplayContents.size() - 1;
         } else if (position < 0) {
             position = 0;
         }
 
-        // TODO(display-merge): Remove cast
-        final ActivityDisplay activityDisplay = (ActivityDisplay) display;
-        if (mActivityDisplays.isEmpty()) {
-            mActivityDisplays.add(activityDisplay);
-        } else if (mActivityDisplays.get(position) != display) {
-            mActivityDisplays.remove(display);
-            mActivityDisplays.add(position, activityDisplay);
+        if (mDisplayContents.isEmpty()) {
+            mDisplayContents.add(display);
+        } else if (mDisplayContents.get(position) != display) {
+            mDisplayContents.remove(display);
+            mDisplayContents.add(position, display);
         }
         mStackSupervisor.updateTopResumedActivityIfNeeded();
     }
 
     @VisibleForTesting
-    void addChild(ActivityDisplay activityDisplay, int position) {
-        positionChildAt(activityDisplay, position);
-        mRootWindowContainer.positionChildAt(position, activityDisplay.mDisplayContent);
+    void addChild(DisplayContent displayContent, int position) {
+        positionChildAt(displayContent, position);
+        mRootWindowContainer.positionChildAt(position, displayContent);
     }
 
-    void removeChild(ActivityDisplay activityDisplay) {
-        // The caller must tell the controller of {@link ActivityDisplay} to release its container
-        // {@link DisplayContent}. That is done in {@link ActivityDisplay#releaseSelfIfNeeded}).
-        mActivityDisplays.remove(activityDisplay);
+    void removeChild(DisplayContent displayContent) {
+        // The caller must tell the controller of {@link DisplayContent} to release its container
+        // {@link DisplayContent}. That is done in {@link DisplayContent#releaseSelfIfNeeded}).
+        mDisplayContents.remove(displayContent);
     }
 
     Configuration getDisplayOverrideConfiguration(int displayId) {
-        final ActivityDisplay activityDisplay = getActivityDisplayOrCreate(displayId);
-        if (activityDisplay == null) {
+        final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
+        if (displayContent == null) {
             throw new IllegalArgumentException("No display found with id: " + displayId);
         }
 
-        return activityDisplay.getRequestedOverrideConfiguration();
+        return displayContent.getRequestedOverrideConfiguration();
     }
 
     void setDisplayOverrideConfiguration(Configuration overrideConfiguration, int displayId) {
-        final ActivityDisplay activityDisplay = getActivityDisplayOrCreate(displayId);
-        if (activityDisplay == null) {
+        final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
+        if (displayContent == null) {
             throw new IllegalArgumentException("No display found with id: " + displayId);
         }
 
-        activityDisplay.onRequestedOverrideConfigurationChanged(overrideConfiguration);
+        displayContent.onRequestedOverrideConfigurationChanged(overrideConfiguration);
     }
 
     void prepareForShutdown() {
-        for (int i = 0; i < mActivityDisplays.size(); i++) {
-            createSleepToken("shutdown", mActivityDisplays.get(i).mDisplayId);
+        for (int i = 0; i < mDisplayContents.size(); i++) {
+            createSleepToken("shutdown", mDisplayContents.get(i).mDisplayId);
         }
     }
 
     ActivityTaskManagerInternal.SleepToken createSleepToken(String tag, int displayId) {
-        final ActivityDisplay display = getActivityDisplay(displayId);
+        final DisplayContent display = getDisplayContent(displayId);
         if (display == null) {
             throw new IllegalArgumentException("Invalid display: " + displayId);
         }
@@ -1660,7 +1655,7 @@
     private void removeSleepToken(SleepTokenImpl token) {
         mSleepTokens.remove(token);
 
-        final ActivityDisplay display = getActivityDisplay(token.mDisplayId);
+        final DisplayContent display = getDisplayContent(token.mDisplayId);
         if (display != null) {
             display.mAllSleepTokens.remove(token);
             if (display.mAllSleepTokens.isEmpty()) {
@@ -1717,8 +1712,8 @@
     }
 
     void scheduleDestroyAllActivities(WindowProcessController app, String reason) {
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 stack.scheduleDestroyActivities(app, reason);
@@ -1730,8 +1725,8 @@
     // successfully put to sleep.
     boolean putStacksToSleep(boolean allowDelay, boolean shuttingDown) {
         boolean allSleep = true;
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 // Stacks and activities could be removed while putting activities to sleep if
                 // the app process was gone. This prevents us getting exception by accessing an
@@ -1801,8 +1796,8 @@
     }
 
     boolean hasAwakeDisplay() {
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             if (!display.shouldSleep()) {
                 return true;
             }
@@ -1878,7 +1873,7 @@
                     return stack;
                 }
             }
-            final ActivityDisplay display = getActivityDisplayOrCreate(displayId);
+            final DisplayContent display = getDisplayContentOrCreate(displayId);
             if (display != null) {
                 stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
                 if (stack != null) {
@@ -1890,7 +1885,7 @@
         // Give preference to the stack and display of the input task and activity if they match the
         // mode we want to launch into.
         stack = null;
-        ActivityDisplay display = null;
+        DisplayContent display = null;
         if (candidateTask != null) {
             stack = candidateTask.getStack();
         }
@@ -1948,8 +1943,8 @@
     private ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
             @Nullable Task candidateTask, @Nullable ActivityOptions options,
             @Nullable LaunchParamsController.LaunchParams launchParams) {
-        final ActivityDisplay activityDisplay = getActivityDisplayOrCreate(displayId);
-        if (activityDisplay == null) {
+        final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
+        if (displayContent == null) {
             throw new IllegalArgumentException(
                     "Display with displayId=" + displayId + " not found.");
         }
@@ -1978,12 +1973,12 @@
             windowingMode = options != null ? options.getLaunchWindowingMode()
                     : r.getWindowingMode();
         }
-        windowingMode = activityDisplay.validateWindowingMode(windowingMode, r, candidateTask,
+        windowingMode = displayContent.validateWindowingMode(windowingMode, r, candidateTask,
                 r.getActivityType());
 
         // Return the topmost valid stack on the display.
-        for (int i = activityDisplay.getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = activityDisplay.getStackAt(i);
+        for (int i = displayContent.getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = displayContent.getStackAt(i);
             if (isValidLaunchStack(stack, r, windowingMode)) {
                 return stack;
             }
@@ -1994,7 +1989,7 @@
             final int activityType =
                     options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED
                             ? options.getLaunchActivityType() : r.getActivityType();
-            return activityDisplay.createStack(windowingMode, activityType, true /*onTop*/);
+            return displayContent.createStack(windowingMode, activityType, true /*onTop*/);
         }
 
         return null;
@@ -2056,11 +2051,11 @@
     ActivityStack getNextFocusableStack(@NonNull ActivityStack currentFocus,
             boolean ignoreCurrent) {
         // First look for next focusable stack on the same display
-        ActivityDisplay preferredDisplay = currentFocus.getDisplay();
+        DisplayContent preferredDisplay = currentFocus.getDisplay();
         if (preferredDisplay == null) {
             // Stack is currently detached because it is being removed. Use the previous display it
             // was on.
-            preferredDisplay = getActivityDisplay(currentFocus.mPrevDisplayId);
+            preferredDisplay = getDisplayContent(currentFocus.mPrevDisplayId);
         }
         final ActivityStack preferredFocusableStack = preferredDisplay.getNextFocusableStack(
                 currentFocus, ignoreCurrent);
@@ -2075,8 +2070,8 @@
         }
 
         // Now look through all displays
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityDisplay display = mActivityDisplays.get(i);
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final DisplayContent display = mDisplayContents.get(i);
             if (display == preferredDisplay) {
                 // We've already checked this one
                 continue;
@@ -2102,8 +2097,8 @@
      * @return Next valid {@link ActivityStack}, null if not found.
      */
     ActivityStack getNextValidLaunchStack(@NonNull ActivityRecord r, int currentFocus) {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityDisplay display = mActivityDisplays.get(i);
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final DisplayContent display = mDisplayContents.get(i);
             if (display.mDisplayId == currentFocus) {
                 continue;
             }
@@ -2118,8 +2113,8 @@
 
     boolean handleAppDied(WindowProcessController app) {
         boolean hasVisibleActivities = false;
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 hasVisibleActivities |= stack.handleAppDiedLocked(app);
@@ -2231,8 +2226,8 @@
     }
 
     void finishVoiceTask(IVoiceInteractionSession session) {
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             final int numStacks = display.getStackCount();
             for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
@@ -2246,20 +2241,20 @@
      * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
      */
     void removeStacksInWindowingModes(int... windowingModes) {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            mActivityDisplays.get(i).removeStacksInWindowingModes(windowingModes);
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            mDisplayContents.get(i).removeStacksInWindowingModes(windowingModes);
         }
     }
 
     void removeStacksWithActivityTypes(int... activityTypes) {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            mActivityDisplays.get(i).removeStacksWithActivityTypes(activityTypes);
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            mDisplayContents.get(i).removeStacksWithActivityTypes(activityTypes);
         }
     }
 
     ActivityRecord topRunningActivity() {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityRecord topActivity = mActivityDisplays.get(i).topRunningActivity();
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final ActivityRecord topActivity = mDisplayContents.get(i).topRunningActivity();
             if (topActivity != null) {
                 return topActivity;
             }
@@ -2268,9 +2263,9 @@
     }
 
     boolean allResumedActivitiesIdle() {
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
             // TODO(b/117135575): Check resumed activities on all visible stacks.
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             if (display.isSleeping()) {
                 // No resumed activities while display is sleeping.
                 continue;
@@ -2298,8 +2293,8 @@
 
     boolean allResumedActivitiesVisible() {
         boolean foundResumed = false;
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 final ActivityRecord r = stack.getResumedActivity();
@@ -2316,8 +2311,8 @@
 
     boolean allPausedActivitiesComplete() {
         boolean pausing = true;
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 final ActivityRecord r = stack.mPausingActivity;
@@ -2381,8 +2376,8 @@
     }
 
     void cancelInitializingActivities() {
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 stack.cancelInitializingActivities();
@@ -2467,9 +2462,9 @@
     }
 
     ActivityRecord isInAnyStack(IBinder token) {
-        int numDisplays = mActivityDisplays.size();
+        int numDisplays = mDisplayContents.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 final ActivityRecord r = stack.isInStackLocked(token);
@@ -2503,9 +2498,9 @@
             // activity on all displays, or if there are no resumed activities in the system.
             boolean noResumedActivities = true;
             boolean allFocusedProcessesDiffer = true;
-            for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
-                final ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx);
-                final ActivityRecord resumedActivity = activityDisplay.getResumedActivity();
+            for (int displayNdx = 0; displayNdx < mDisplayContents.size(); ++displayNdx) {
+                final DisplayContent displayContent = mDisplayContents.get(displayNdx);
+                final ActivityRecord resumedActivity = displayContent.getResumedActivity();
                 final WindowProcessController resumedActivityProcess =
                         resumedActivity == null ? null : resumedActivity.app;
 
@@ -2550,9 +2545,9 @@
             return getTopDisplayFocusedStack().getDumpActivitiesLocked(name);
         } else {
             ArrayList<ActivityRecord> activities = new ArrayList<>();
-            int numDisplays = mActivityDisplays.size();
+            int numDisplays = mDisplayContents.size();
             for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-                final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+                final DisplayContent display = mDisplayContents.get(displayNdx);
                 for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                     final ActivityStack stack = display.getStackAt(stackNdx);
                     if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) {
@@ -2567,9 +2562,9 @@
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix);
         pw.println("topDisplayFocusedStack=" + getTopDisplayFocusedStack());
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityDisplay display = mActivityDisplays.get(i);
-            display.dump(pw, prefix);
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final DisplayContent display = mDisplayContents.get(i);
+            display.dump(pw, prefix, true /* dumpAll */);
         }
     }
 
@@ -2579,17 +2574,17 @@
      */
     void dumpDisplayConfigs(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.println("Display override configurations:");
-        final int displayCount = mActivityDisplays.size();
+        final int displayCount = mDisplayContents.size();
         for (int i = 0; i < displayCount; i++) {
-            final ActivityDisplay activityDisplay = mActivityDisplays.get(i);
-            pw.print(prefix); pw.print("  "); pw.print(activityDisplay.mDisplayId); pw.print(": ");
-            pw.println(activityDisplay.getRequestedOverrideConfiguration());
+            final DisplayContent displayContent = mDisplayContents.get(i);
+            pw.print(prefix); pw.print("  "); pw.print(displayContent.mDisplayId); pw.print(": ");
+            pw.println(displayContent.getRequestedOverrideConfiguration());
         }
     }
 
     public void dumpDisplays(PrintWriter pw) {
-        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
-            final ActivityDisplay display = mActivityDisplays.get(i);
+        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
+            final DisplayContent display = mDisplayContents.get(i);
             pw.print("[id:" + display.mDisplayId + " stacks:");
             display.dumpStacks(pw);
             pw.print("]");
@@ -2600,18 +2595,18 @@
             String dumpPackage) {
         boolean printed = false;
         boolean needSep = false;
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx);
-            pw.print("Display #"); pw.print(activityDisplay.mDisplayId);
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            DisplayContent displayContent = mDisplayContents.get(displayNdx);
+            pw.print("Display #"); pw.print(displayContent.mDisplayId);
             pw.println(" (activities from top to bottom):");
-            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+            final DisplayContent display = mDisplayContents.get(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 pw.println();
                 printed = stack.dump(fd, pw, dumpAll, dumpClient, dumpPackage, needSep);
                 needSep = printed;
             }
-            printThisActivity(pw, activityDisplay.getResumedActivity(), dumpPackage, needSep,
+            printThisActivity(pw, displayContent.getResumedActivity(), dumpPackage, needSep,
                     " ResumedActivity:");
         }
 
@@ -2632,9 +2627,9 @@
             @WindowTraceLogLevel int logLevel) {
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, CONFIGURATION_CONTAINER, logLevel);
-        for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
-            final ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx);
-            activityDisplay.dumpDebug(proto, DISPLAYS, logLevel);
+        for (int displayNdx = 0; displayNdx < mDisplayContents.size(); ++displayNdx) {
+            final DisplayContent displayContent = mDisplayContents.get(displayNdx);
+            displayContent.dumpDebug(proto, DISPLAYS, logLevel);
         }
         mStackSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
         // TODO(b/111541062): Update tests to look for resumed activities on all displays
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
new file mode 100644
index 0000000..9732637
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+
+/**
+ * Represents a piece of the hierarchy under which a client Shell can manage sub-windows.
+ */
+public class ShellRoot {
+    private static final String TAG = "ShellRoot";
+    private final DisplayContent mDisplayContent;
+    private IWindow mClient;
+    private WindowToken mToken;
+    private final IBinder.DeathRecipient mDeathRecipient;
+    private SurfaceControl mSurfaceControl = null;
+
+    ShellRoot(@NonNull IWindow client, @NonNull DisplayContent dc, final int windowType) {
+        mDisplayContent = dc;
+        mDeathRecipient = () -> mDisplayContent.removeShellRoot(windowType);
+        try {
+            client.asBinder().linkToDeath(mDeathRecipient, 0);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to add shell root for layer " + windowType + " on display "
+                    + dc.getDisplayId(), e);
+            return;
+        }
+        mClient = client;
+        mToken = new WindowToken(
+                dc.mWmService, client.asBinder(), windowType, true, dc, true, false);
+        mSurfaceControl = mToken.makeChildSurface(null)
+                .setContainerLayer().setName("Shell Root Leash " + dc.getDisplayId()).build();
+        mToken.getPendingTransaction().show(mSurfaceControl);
+    }
+
+    void clear() {
+        if (mClient != null) {
+            mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
+            mClient = null;
+        }
+        if (mToken != null) {
+            mToken.removeImmediately();
+            mToken = null;
+        }
+    }
+
+    SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
+    IWindow getClient() {
+        return mClient;
+    }
+}
+
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0e07e52..1b2274a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -135,7 +135,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
@@ -1583,8 +1582,8 @@
         if (!inPinnedWindowingMode() && mStack != null) {
             final int defaultMinSizeDp =
                     mAtmService.mRootActivityContainer.mDefaultMinSizeOfResizeableTaskDp;
-            final ActivityDisplay display =
-                    mAtmService.mRootActivityContainer.getActivityDisplay(mStack.mDisplayId);
+            final DisplayContent display =
+                    mAtmService.mRootActivityContainer.getDisplayContent(mStack.mDisplayId);
             final float density =
                     (float) display.getConfiguration().densityDpi / DisplayMetrics.DENSITY_DEFAULT;
             final int defaultMinSize = (int) (defaultMinSizeDp * density);
@@ -1995,7 +1994,7 @@
                     ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
             final Rect parentBounds =
                     new Rect(newParentConfig.windowConfiguration.getBounds());
-            final ActivityDisplay display = mStack.getDisplay();
+            final DisplayContent display = mStack.getDisplay();
             if (display != null && display.mDisplayContent != null) {
                 // If a freeform window moves below system bar, there is no way to move it again
                 // by touch. Because its caption is covered by system bar. So we exclude them
@@ -2188,7 +2187,7 @@
         final ActivityStack prevStack = getTaskStack();
         final boolean wasTopFocusedStack =
                 mAtmService.mRootActivityContainer.isTopDisplayFocusedStack(prevStack);
-        final ActivityDisplay prevStackDisplay = prevStack.getDisplay();
+        final DisplayContent prevStackDisplay = prevStack.getDisplay();
 
         position = stack.findPositionForTask(this, position, showForAllUsers());
 
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 9d19cfe..4de61f0 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -137,7 +137,7 @@
         // STEP 1: Determine the display to launch the activity/task.
         final int displayId = getPreferredLaunchDisplay(task, options, source, currentParams);
         outParams.mPreferredDisplayId = displayId;
-        ActivityDisplay display = mSupervisor.mRootActivityContainer.getActivityDisplay(displayId);
+        DisplayContent display = mSupervisor.mRootActivityContainer.getDisplayContent(displayId);
         if (DEBUG) {
             appendLog("display-id=" + outParams.mPreferredDisplayId + " display-windowing-mode="
                     + display.getWindowingMode());
@@ -282,7 +282,7 @@
             if (source != null && source.inFreeformWindowingMode()
                     && resolvedMode == WINDOWING_MODE_FREEFORM
                     && outParams.mBounds.isEmpty()
-                    && source.getDisplayId() == display.mDisplayId) {
+                    && source.getDisplayId() == display.getDisplayId()) {
                 // Set bounds to be not very far from source activity.
                 cascadeBounds(source.getConfiguration().windowConfiguration.getBounds(),
                         display, outParams.mBounds);
@@ -333,17 +333,17 @@
         }
 
         if (displayId != INVALID_DISPLAY
-                && mSupervisor.mRootActivityContainer.getActivityDisplay(displayId) == null) {
+                && mSupervisor.mRootActivityContainer.getDisplayContent(displayId) == null) {
             displayId = currentParams.mPreferredDisplayId;
         }
         displayId = (displayId == INVALID_DISPLAY) ? currentParams.mPreferredDisplayId : displayId;
 
         return (displayId != INVALID_DISPLAY
-                && mSupervisor.mRootActivityContainer.getActivityDisplay(displayId) != null)
+                && mSupervisor.mRootActivityContainer.getDisplayContent(displayId) != null)
                 ? displayId : DEFAULT_DISPLAY;
     }
 
-    private boolean canInheritWindowingModeFromSource(@NonNull ActivityDisplay display,
+    private boolean canInheritWindowingModeFromSource(@NonNull DisplayContent display,
             @Nullable ActivityRecord source) {
         if (source == null) {
             return false;
@@ -365,10 +365,10 @@
         // Only inherit windowing mode if both source and target activities are on the same display.
         // Otherwise we may have unintended freeform windows showing up if an activity in freeform
         // window launches an activity on a fullscreen display by specifying display ID.
-        return display.mDisplayId == source.getDisplayId();
+        return display.getDisplayId() == source.getDisplayId();
     }
 
-    private boolean canApplyFreeformWindowPolicy(@NonNull ActivityDisplay display, int launchMode) {
+    private boolean canApplyFreeformWindowPolicy(@NonNull DisplayContent display, int launchMode) {
         return mSupervisor.mService.mSupportsFreeformWindowManagement
                 && (display.inFreeformWindowingMode() || launchMode == WINDOWING_MODE_FREEFORM);
     }
@@ -378,7 +378,7 @@
                 && launchMode == WINDOWING_MODE_PINNED;
     }
 
-    private void getLayoutBounds(@NonNull ActivityDisplay display, @NonNull ActivityRecord root,
+    private void getLayoutBounds(@NonNull DisplayContent display, @NonNull ActivityRecord root,
             @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect outBounds) {
         final int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
         final int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
@@ -488,7 +488,7 @@
         return orientation;
     }
 
-    private void cascadeBounds(@NonNull Rect srcBounds, @NonNull ActivityDisplay display,
+    private void cascadeBounds(@NonNull Rect srcBounds, @NonNull DisplayContent display,
             @NonNull Rect outBounds) {
         outBounds.set(srcBounds);
         float density = (float) display.getConfiguration().densityDpi / DENSITY_DEFAULT;
@@ -500,7 +500,7 @@
         outBounds.offset(dx, dy);
     }
 
-    private void getTaskBounds(@NonNull ActivityRecord root, @NonNull ActivityDisplay display,
+    private void getTaskBounds(@NonNull ActivityRecord root, @NonNull DisplayContent display,
             @NonNull ActivityInfo.WindowLayout layout, int resolvedMode, boolean hasInitialBounds,
             @NonNull Rect inOutBounds) {
         if (resolvedMode == WINDOWING_MODE_FULLSCREEN) {
@@ -567,7 +567,7 @@
         }
     }
 
-    private int resolveOrientation(@NonNull ActivityRecord root, @NonNull ActivityDisplay display,
+    private int resolveOrientation(@NonNull ActivityRecord root, @NonNull DisplayContent display,
             @NonNull Rect bounds) {
         int orientation = resolveOrientation(root);
 
@@ -593,7 +593,7 @@
         return orientation;
     }
 
-    private void getDefaultFreeformSize(@NonNull ActivityDisplay display,
+    private void getDefaultFreeformSize(@NonNull DisplayContent display,
             @NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) {
         // Default size, which is letterboxing/pillarboxing in display. That's to say the large
         // dimension of default size is the small dimension of display size, and the small dimension
@@ -637,7 +637,7 @@
      * Gets centered bounds of width x height. If inOutBounds is not empty, the result bounds
      * centers at its center or display's app bounds center if inOutBounds is empty.
      */
-    private void centerBounds(@NonNull ActivityDisplay display, int width, int height,
+    private void centerBounds(@NonNull DisplayContent display, int width, int height,
             @NonNull Rect inOutBounds) {
         if (inOutBounds.isEmpty()) {
             inOutBounds.set(display.mDisplayContent.mDisplayFrames.mStable);
@@ -647,7 +647,7 @@
         inOutBounds.set(left, top, left + width, top + height);
     }
 
-    private void adjustBoundsToFitInDisplay(@NonNull ActivityDisplay display,
+    private void adjustBoundsToFitInDisplay(@NonNull DisplayContent display,
             @NonNull Rect inOutBounds) {
         final Rect displayStableBounds = display.mDisplayContent.mDisplayFrames.mStable;
 
@@ -702,7 +702,7 @@
      * @param display the display which tasks are to check
      * @param inOutBounds the bounds used to input initial bounds and output result bounds
      */
-    private void adjustBoundsToAvoidConflictInDisplay(@NonNull ActivityDisplay display,
+    private void adjustBoundsToAvoidConflictInDisplay(@NonNull DisplayContent display,
             @NonNull Rect inOutBounds) {
         final List<Rect> taskBoundsToCheck = new ArrayList<>();
         for (int i = 0; i < display.getStackCount(); ++i) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 5cbab5d..7b0d841 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -109,7 +109,7 @@
         removeRunningEntry(taskId);
     }
 
-    private void removeRunningEntry(int taskId) {
+    void removeRunningEntry(int taskId) {
         final CacheEntry entry = mRunningCache.get(taskId);
         if (entry != null) {
             mAppTaskMap.remove(entry.topApp);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c1a36c4..12b2845 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -171,22 +171,30 @@
     }
 
     void snapshotTasks(ArraySet<Task> tasks) {
+        snapshotTasks(mTmpTasks, false /* allowSnapshotHome */);
+    }
+
+    private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
         for (int i = tasks.size() - 1; i >= 0; i--) {
             final Task task = tasks.valueAt(i);
-            final int mode = getSnapshotMode(task);
             final TaskSnapshot snapshot;
-            switch (mode) {
-                case SNAPSHOT_MODE_NONE:
-                    continue;
-                case SNAPSHOT_MODE_APP_THEME:
-                    snapshot = drawAppThemeSnapshot(task);
-                    break;
-                case SNAPSHOT_MODE_REAL:
-                    snapshot = snapshotTask(task);
-                    break;
-                default:
-                    snapshot = null;
-                    break;
+            final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
+            if (snapshotHome) {
+                snapshot = snapshotTask(task);
+            } else {
+                switch (getSnapshotMode(task)) {
+                    case SNAPSHOT_MODE_NONE:
+                        continue;
+                    case SNAPSHOT_MODE_APP_THEME:
+                        snapshot = drawAppThemeSnapshot(task);
+                        break;
+                    case SNAPSHOT_MODE_REAL:
+                        snapshot = snapshotTask(task);
+                        break;
+                    default:
+                        snapshot = null;
+                        break;
+                }
             }
             if (snapshot != null) {
                 final GraphicBuffer buffer = snapshot.getSnapshot();
@@ -196,8 +204,11 @@
                             + buffer.getHeight());
                 } else {
                     mCache.putSnapshot(task, snapshot);
-                    mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
-                    task.onSnapshotChanged(snapshot);
+                    // Don't persist or notify the change for the temporal snapshot.
+                    if (!snapshotHome) {
+                        mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+                        task.onSnapshotChanged(snapshot);
+                    }
                 }
             }
         }
@@ -450,6 +461,10 @@
         mPersister.onTaskRemovedFromRecents(taskId, userId);
     }
 
+    void removeSnapshotCache(int taskId) {
+        mCache.removeRunningEntry(taskId);
+    }
+
     /**
      * See {@link TaskSnapshotPersister#removeObsoleteFiles}
      */
@@ -485,7 +500,9 @@
                             mTmpTasks.add(task);
                         }
                     });
-                    snapshotTasks(mTmpTasks);
+                    // Allow taking snapshot of home when turning screen off to reduce the delay of
+                    // unlocking/waking to home.
+                    snapshotTasks(mTmpTasks, true /* allowSnapshotHome */);
                 }
             } finally {
                 listener.onScreenOff();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 5915590..10f2996 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -54,7 +54,6 @@
     private static final String REDUCED_POSTFIX = "_reduced";
     private static final float REDUCED_SCALE = .5f;
     private static final float LOW_RAM_REDUCED_SCALE = .6f;
-    private static final float LOW_RAM_RECENTS_REDUCED_SCALE = .1f;
     static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
     private static final long DELAY_MS = 100;
     private static final int QUALITY = 95;
@@ -85,14 +84,8 @@
 
     TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
         mDirectoryResolver = resolver;
-        if (service.mLowRamTaskSnapshotsAndRecents) {
-            // Use very low res snapshots if we are using Go version of recents.
-            mReducedScale = LOW_RAM_RECENTS_REDUCED_SCALE;
-        } else {
-            // TODO(122671846) Replace the low RAM value scale with the above when it is fully built
-            mReducedScale = ActivityManager.isLowRamDeviceStatic()
-                    ? LOW_RAM_REDUCED_SCALE : REDUCED_SCALE;
-        }
+        mReducedScale = ActivityManager.isLowRamDeviceStatic()
+                ? LOW_RAM_REDUCED_SCALE : REDUCED_SCALE;
         mUse16BitFormat = service.mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index 10c8ef0..74d5c04 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -19,6 +19,8 @@
 import static android.provider.AndroidDeviceConfig.KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE;
 import static android.provider.AndroidDeviceConfig.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.provider.AndroidDeviceConfig;
 import android.provider.DeviceConfig;
 
@@ -72,8 +74,8 @@
     WindowManagerConstants(WindowManagerGlobalLock globalLock,
             Runnable updateSystemGestureExclusionCallback,
             DeviceConfigInterface deviceConfig) {
-        mGlobalLock = globalLock;
-        mUpdateSystemGestureExclusionCallback = updateSystemGestureExclusionCallback;
+        mGlobalLock = checkNotNull(globalLock);
+        mUpdateSystemGestureExclusionCallback = checkNotNull(updateSystemGestureExclusionCallback);
         mDeviceConfig = deviceConfig;
         mListenerAndroid = this::onAndroidPropertiesChanged;
         mListenerWindowManager = this::onWindowPropertiesChanged;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f45eb50..acaaed9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -79,7 +79,6 @@
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_BOOT;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -406,8 +405,7 @@
 
     private static final int ANIMATION_COMPLETED_TIMEOUT_MS = 5000;
 
-    final WindowManagerConstants mConstants = new WindowManagerConstants(this,
-            DeviceConfigInterface.REAL);
+    final WindowManagerConstants mConstants;
 
     final WindowTracing mWindowTracing;
 
@@ -491,13 +489,6 @@
     final long mDrawLockTimeoutMillis;
     final boolean mAllowAnimationsInLowPowerMode;
 
-    // TODO(b/122671846) Remove the flag below in favor of isLowRam once feature is stable
-    /**
-     * Use very low resolution task snapshots. Replaces task snapshot starting windows with
-     * splashscreen starting windows. Used on low RAM devices to save memory.
-     */
-    final boolean mLowRamTaskSnapshotsAndRecents;
-
     final boolean mAllowBootMessages;
 
     final boolean mLimitedAlphaCompositing;
@@ -1114,8 +1105,6 @@
                 com.android.internal.R.bool.config_disableTransitionAnimation);
         mPerDisplayFocusEnabled = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_perDisplayFocusEnabled);
-        mLowRamTaskSnapshotsAndRecents = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_lowRamTaskSnapshotsAndRecents);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
 
@@ -1242,6 +1231,7 @@
 
         mHighRefreshRateBlacklist = HighRefreshRateBlacklist.create(context.getResources());
 
+        mConstants = new WindowManagerConstants(this, DeviceConfigInterface.REAL);
         mConstants.start(new HandlerExecutor(mH));
 
         LocalServices.addService(WindowManagerInternal.class, new LocalService());
@@ -1731,7 +1721,7 @@
             }
         }
 
-        return mAtmService.mRootActivityContainer.getActivityDisplayOrCreate(displayId);
+        return mAtmService.mRootActivityContainer.getDisplayContentOrCreate(displayId);
     }
 
     private boolean doesAddToastWindowRequireToken(String packageName, int callingUid,
@@ -3721,6 +3711,26 @@
     }
 
     @Override
+    public SurfaceControl addShellRoot(int displayId, IWindow client, int windowType) {
+        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null) {
+                    return null;
+                }
+                return dc.addShellRoot(client, windowType);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
     public int watchRotation(IRotationWatcher watcher, int displayId) {
         final DisplayContent displayContent;
         synchronized (mGlobalLock) {
@@ -7639,7 +7649,11 @@
             return;
         }
 
-        handleDisplayFocusChange(touchedWindow);
+        final DisplayContent displayContent = touchedWindow.getDisplayContent();
+        if (!displayContent.isOnTop()) {
+            displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent,
+                    true /* includingParents */);
+        }
         handleTaskFocusChange(touchedWindow.getTask());
     }
 
@@ -7662,29 +7676,6 @@
         }
     }
 
-    private void handleDisplayFocusChange(WindowState window) {
-        final DisplayContent displayContent = window.getDisplayContent();
-        if (displayContent == null) {
-            return;
-        }
-
-        final WindowContainer parent = displayContent.getParent();
-        if (parent != null && parent.getTopChild() != displayContent) {
-            parent.positionChildAt(WindowContainer.POSITION_TOP, displayContent,
-                    true /* includingParents */);
-            // For compatibility, only the topmost activity is allowed to be resumed for pre-Q
-            // app. Ensure the topmost activities are resumed whenever a display is moved to top.
-            // TODO(b/123761773): Investigate whether we can move this into
-            // RootActivityContainer#updateTopResumedActivityIfNeeded(). Currently, it is risky
-            // to do so because it seems possible to resume activities as part of a larger
-            // transaction and it's too early to resume based on current order when performing
-            // updateTopResumedActivityIfNeeded().
-            // TODO(display-merge): Remove cast
-            ((ActivityDisplay) displayContent).ensureActivitiesVisible(null /* starting */,
-                    0 /* configChanges */, !PRESERVE_WINDOWS, true /* notifyClients */);
-        }
-    }
-
     /**
      * Assigns an InputChannel to a SurfaceControl and configures it to receive
      * touch input according to it's on-screen geometry.
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index ddf8e9b..1a41006 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -575,14 +575,14 @@
             return true;
         }
 
-        final ActivityDisplay display = activity.getDisplay();
+        final DisplayContent display = activity.getDisplay();
         if (display == null) {
             // No need to update if the activity hasn't attach to any display.
             return false;
         }
 
         boolean canUpdate = false;
-        final ActivityDisplay topDisplay =
+        final DisplayContent topDisplay =
                 mPreQTopResumedActivity != null ? mPreQTopResumedActivity.getDisplay() : null;
         // Update the topmost activity if current top activity is
         // - not on any display OR
@@ -938,15 +938,15 @@
         mAtm.mH.sendMessage(m);
     }
 
-    void registerDisplayConfigurationListenerLocked(ActivityDisplay activityDisplay) {
-        if (activityDisplay == null) {
+    void registerDisplayConfigurationListenerLocked(DisplayContent displayContent) {
+        if (displayContent == null) {
             return;
         }
         // A process can only register to one display to listener to the override configuration
         // change. Unregister existing listener if it has one before register the new one.
         unregisterDisplayConfigurationListenerLocked();
-        mDisplayId = activityDisplay.mDisplayId;
-        activityDisplay.registerConfigurationChangeListener(this);
+        mDisplayId = displayContent.mDisplayId;
+        displayContent.registerConfigurationChangeListener(this);
     }
 
     @VisibleForTesting
@@ -954,10 +954,10 @@
         if (mDisplayId == INVALID_DISPLAY) {
             return;
         }
-        final ActivityDisplay activityDisplay =
-                mAtm.mRootActivityContainer.getActivityDisplay(mDisplayId);
-        if (activityDisplay != null) {
-            activityDisplay.unregisterConfigurationChangeListener(this);
+        final DisplayContent displayContent =
+                mAtm.mRootActivityContainer.getDisplayContent(mDisplayId);
+        if (displayContent != null) {
+            displayContent.unregisterConfigurationChangeListener(this);
         }
         mDisplayId = INVALID_DISPLAY;
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5b7f36d..eab8d05 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2611,7 +2611,7 @@
         final boolean canReceiveKeys = isVisibleOrAdding()
                 && (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
                 && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
-                && (mActivityRecord == null || mActivityRecord.windowsAreFocusable())
+                && (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
                 && !cantReceiveTouchInput();
         if (!canReceiveKeys) {
             return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ee0449d..eb1753b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -990,6 +990,7 @@
                 "cross-profile-calendar-packages";
         private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
                 "cross-profile-calendar-packages-null";
+        private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
 
         DeviceAdminInfo info;
 
@@ -1104,6 +1105,11 @@
         // is whitelisted.
         List<String> mCrossProfileCalendarPackages = Collections.emptyList();
 
+        // The whitelist of packages that the admin has enabled to be able to request consent from
+        // the user to communicate cross-profile. By default, no packages are whitelisted, which is
+        // represented as an empty list.
+        List<String> mCrossProfilePackages = Collections.emptyList();
+
         ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
             info = _info;
             isParent = parent;
@@ -1329,6 +1335,7 @@
                 writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
                         mCrossProfileCalendarPackages);
             }
+            writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
         }
 
         void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
@@ -1560,6 +1567,8 @@
                     mCrossProfileCalendarPackages = readPackageList(parser, tag);
                 } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
                     mCrossProfileCalendarPackages = null;
+                } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
+                    mCrossProfilePackages = readPackageList(parser, tag);
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -1783,6 +1792,8 @@
                 pw.print("mCrossProfileCalendarPackages=");
                 pw.println(mCrossProfileCalendarPackages);
             }
+            pw.print("mCrossProfilePackages=");
+            pw.println(mCrossProfilePackages);
         }
     }
 
@@ -14645,6 +14656,35 @@
     }
 
     @Override
+    public void setCrossProfilePackages(ComponentName who, List<String> packageNames) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkNotNull(packageNames, "Package names is null");
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin =
+                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            admin.mCrossProfilePackages = packageNames;
+            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+        }
+    }
+
+    @Override
+    public List<String> getCrossProfilePackages(ComponentName who) {
+        if (!mHasFeature) {
+            return Collections.emptyList();
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            return admin.mCrossProfilePackages;
+        }
+    }
+
+    @Override
     public boolean isManagedKiosk() {
         if (!mHasFeature) {
             return false;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b6e501a..401a094 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -126,6 +126,7 @@
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.CrossProfileAppsService;
+import com.android.server.pm.DataLoaderManagerService;
 import com.android.server.pm.DynamicCodeLoggingService;
 import com.android.server.pm.Installer;
 import com.android.server.pm.LauncherAppsService;
@@ -321,6 +322,7 @@
     private PackageManager mPackageManager;
     private ContentResolver mContentResolver;
     private EntropyMixer mEntropyMixer;
+    private DataLoaderManagerService mDataLoaderManagerService;
 
     private boolean mOnlyCore;
     private boolean mFirstBoot;
@@ -493,6 +495,9 @@
             // Initialize the system context.
             createSystemContext();
 
+            // Call per-process mainline module initialization.
+            ActivityThread.initializeMainlineModules();
+
             // Create the system service manager.
             mSystemServiceManager = new SystemServiceManager(mSystemContext);
             mSystemServiceManager.setStartInfo(mRuntimeRestart,
@@ -694,6 +699,12 @@
         mWindowManagerGlobalLock = atm.getGlobalLock();
         t.traceEnd();
 
+        // Data loader manager service needs to be started before package manager
+        t.traceBegin("StartDataLoaderManagerService");
+        mDataLoaderManagerService = mSystemServiceManager.startService(
+                DataLoaderManagerService.class);
+        t.traceEnd();
+
         // Power manager needs to be started early because other services need it.
         // Native daemons may be watching for it to be registered so it must be ready
         // to handle incoming binder calls immediately (including being able to verify
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 8e6114a..4635c08 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -142,7 +142,7 @@
         sService.mConstants = new ActivityManagerConstants(sContext, sService,
                 sContext.getMainThreadHandler());
         ProcessList pr = new ProcessList();
-        pr.init(sService, new ActiveUids(sService, false));
+        pr.init(sService, new ActiveUids(sService, false), null);
         setFieldValue(ActivityManagerService.class, sService, "mProcessList",
                 pr);
         setFieldValue(ActivityManagerService.class, sService, "mHandler",
diff --git a/services/tests/servicestests/res/xml/usertypes_test_eraseArray.xml b/services/tests/servicestests/res/xml/usertypes_test_eraseArray.xml
new file mode 100644
index 0000000..02ac48e
--- /dev/null
+++ b/services/tests/servicestests/res/xml/usertypes_test_eraseArray.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<user-types>
+    <profile-type
+        name='android.test'
+        max-allowed-per-parent='2' >
+        <badge-colors>
+        </badge-colors>
+        <default-restrictions />
+    </profile-type>
+</user-types>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/usertypes_test_full.xml b/services/tests/servicestests/res/xml/usertypes_test_full.xml
new file mode 100644
index 0000000..a281dca
--- /dev/null
+++ b/services/tests/servicestests/res/xml/usertypes_test_full.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<user-types>
+    <full-type
+        name='android.test.1'
+        max-allowed-per-parent='12' >
+        <default-restrictions no_remove_user='true' no_bluetooth='true' />
+        <badge-colors>
+            <item res='@*android:color/profile_badge_1' />
+        </badge-colors>
+    </full-type>
+</user-types>
diff --git a/services/tests/servicestests/res/xml/usertypes_test_illegalOemName.xml b/services/tests/servicestests/res/xml/usertypes_test_illegalOemName.xml
new file mode 100644
index 0000000..f91df1f
--- /dev/null
+++ b/services/tests/servicestests/res/xml/usertypes_test_illegalOemName.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<user-types>
+    <profile-type name='android.aosp.legal' max-allowed-per-parent='12' />
+    <profile-type name='android.oem.illegal.name' max-allowed-per-parent='14' />
+</user-types>
diff --git a/services/tests/servicestests/res/xml/usertypes_test_illegalUserBaseType.xml b/services/tests/servicestests/res/xml/usertypes_test_illegalUserBaseType.xml
new file mode 100644
index 0000000..0785655
--- /dev/null
+++ b/services/tests/servicestests/res/xml/usertypes_test_illegalUserBaseType.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<user-types>
+    <profile-type name='android.test' max-allowed-per-parent='12' />
+</user-types>
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
new file mode 100644
index 0000000..b6c8fbd
--- /dev/null
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<user-types>
+    <profile-type
+        name='android.test.2'
+        max-allowed-per-parent='12'
+        icon-badge='@*android:drawable/ic_corp_icon_badge_case'
+        badge-plain='garbage'
+        badge-no-background='@*android:drawable/ic_corp_badge_no_background'
+        >
+        <badge-labels>
+            <item res='@*android:string/managed_profile_label_badge' />
+            <item res='@*android:string/managed_profile_label_badge_2' />
+        </badge-labels>
+        <badge-colors>
+            <item res='@*android:color/profile_badge_1' />
+            <item res='@*android:color/profile_badge_2' />
+        </badge-colors>
+        <default-restrictions no_remove_user='true' no_bluetooth='true' />
+    </profile-type>
+    <profile-type name='custom.test.1' max-allowed-per-parent='14' />
+</user-types>
diff --git a/services/tests/servicestests/src/com/android/server/GnssManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/GnssManagerServiceTest.java
index 9692c25..8b5444c 100644
--- a/services/tests/servicestests/src/com/android/server/GnssManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GnssManagerServiceTest.java
@@ -61,6 +61,7 @@
 import com.android.server.location.GnssNavigationMessageProvider;
 import com.android.server.location.GnssNavigationMessageProvider.GnssNavigationMessageProviderNative;
 import com.android.server.location.GnssStatusListenerHelper;
+import com.android.server.location.LocationUsageLogger;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index b5e5deb..67075ed 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -705,6 +705,26 @@
         assertTrue(displayList.equals(mExpectedDisplayList));
     }
 
+    @Test
+    public void setAccessibilityWindowIdToSurfaceMetadata()
+            throws RemoteException {
+        final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+                true, USER_SYSTEM_ID);
+        int windowId = -1;
+        for (int i = 0; i < mA11yWindowTokens.size(); i++) {
+            if (mA11yWindowTokens.valueAt(i).equals(token)) {
+                windowId = mA11yWindowTokens.keyAt(i);
+            }
+        }
+        assertNotEquals("Returned token is not found in mA11yWindowTokens", -1, windowId);
+        verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
+                token.asBinder(), windowId);
+
+        mA11yWindowManager.removeAccessibilityInteractionConnection(token);
+        verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
+                token.asBinder(), -1);
+    }
+
     private void startTrackingPerDisplay(int displayId) throws RemoteException {
         ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>();
         // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 43d8f92..3f09f57 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5548,6 +5548,38 @@
         mServiceContext.binder.restoreCallingIdentity(ident);
     }
 
+    public void testGetCrossProfilePackages_notSet_returnsEmpty() {
+        setAsProfileOwner(admin1);
+        assertTrue(dpm.getCrossProfilePackages(admin1).isEmpty());
+    }
+
+    public void testGetCrossProfilePackages_notSet_dpmsReinitialized_returnsEmpty() {
+        setAsProfileOwner(admin1);
+
+        initializeDpms();
+
+        assertTrue(dpm.getCrossProfilePackages(admin1).isEmpty());
+    }
+
+    public void testGetCrossProfilePackages_whenSet_returnsEqual() {
+        setAsProfileOwner(admin1);
+        Set<String> packages = Collections.singleton("TEST_PACKAGE");
+
+        dpm.setCrossProfilePackages(admin1, packages);
+
+        assertEquals(packages, dpm.getCrossProfilePackages(admin1));
+    }
+
+    public void testGetCrossProfilePackages_whenSet_dpmsReinitialized_returnsEqual() {
+        setAsProfileOwner(admin1);
+        Set<String> packages = Collections.singleton("TEST_PACKAGE");
+
+        dpm.setCrossProfilePackages(admin1, packages);
+        initializeDpms();
+
+        assertEquals(packages, dpm.getCrossProfilePackages(admin1));
+    }
+
     // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
     private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
         writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index fb9c68a..40ada2a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -30,6 +30,7 @@
 
 import android.apex.ApexInfo;
 import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
 import android.apex.IApexService;
 import android.content.Context;
 import android.content.pm.PackageInfo;
@@ -183,19 +184,18 @@
     public void testSubmitStagedSession_throwPackageManagerException() throws RemoteException {
         doAnswer(invocation -> {
             throw new Exception();
-        }).when(mApexService).submitStagedSession(anyInt(), any(), any());
+        }).when(mApexService).submitStagedSession(any(), any());
 
         assertThrows(PackageManagerException.class,
-                () -> mApexManager.submitStagedSession(TEST_SESSION_ID, TEST_CHILD_SESSION_ID));
+                () -> mApexManager.submitStagedSession(testParamsWithChildren()));
     }
 
     @Test
     public void testSubmitStagedSession_throwRunTimeException() throws RemoteException {
-        doThrow(RemoteException.class).when(mApexService).submitStagedSession(anyInt(), any(),
-                any());
+        doThrow(RemoteException.class).when(mApexService).submitStagedSession(any(), any());
 
         assertThrows(RuntimeException.class,
-                () -> mApexManager.submitStagedSession(TEST_SESSION_ID, TEST_CHILD_SESSION_ID));
+                () -> mApexManager.submitStagedSession(testParamsWithChildren()));
     }
 
     @Test
@@ -272,6 +272,13 @@
         return stagedSessionInfo;
     }
 
+    private static ApexSessionParams testParamsWithChildren() {
+        ApexSessionParams params = new ApexSessionParams();
+        params.sessionId = TEST_SESSION_ID;
+        params.childSessionIds = TEST_CHILD_SESSION_ID;
+        return params;
+    }
+
     /**
      * Copies a specified {@code resourceId} to a temp file. Returns a non-null file if the copy
      * succeeded
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 7aadd87..8e74c90 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -25,24 +25,32 @@
 import static android.content.pm.UserInfo.FLAG_RESTRICTED;
 import static android.content.pm.UserInfo.FLAG_SYSTEM;
 
+import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
 import static org.testng.Assert.assertThrows;
 
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
 import android.os.UserManager;
+import android.util.ArrayMap;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.frameworks.servicestests.R;
+
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-
 /**
  * Tests for {@link UserTypeDetails} and {@link UserTypeFactory}.
  *
@@ -52,9 +60,17 @@
 @MediumTest
 public class UserManagerServiceUserTypeTest {
 
+    private Resources mResources;
+
+    @Before
+    public void setup() {
+        mResources = InstrumentationRegistry.getTargetContext().getResources();
+    }
+
     @Test
     public void testUserTypeBuilder_createUserType() {
-        UserTypeDetails type = new UserTypeDetails.Builder()
+        final Bundle restrictions = makeRestrictionsBundle("r1", "r2");
+        final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
                 .setEnabled(true)
                 .setMaxAllowed(21)
@@ -67,7 +83,7 @@
                 .setBadgeNoBackground(30)
                 .setLabel(31)
                 .setMaxAllowedPerParent(32)
-                .setDefaultRestrictions(new ArrayList<>(Arrays.asList("r1", "r2")))
+                .setDefaultRestrictions(restrictions)
                 .createUserTypeDetails();
 
         assertEquals("a.name", type.getName());
@@ -79,7 +95,8 @@
         assertEquals(30, type.getBadgeNoBackground());
         assertEquals(31, type.getLabel());
         assertEquals(32, type.getMaxAllowedPerParent());
-        assertEquals(new ArrayList<>(Arrays.asList("r1", "r2")), type.getDefaultRestrictions());
+        assertTrue(UserRestrictionsUtils.areEqual(restrictions, type.getDefaultRestrictions()));
+        assertNotSame(restrictions, type.getDefaultRestrictions());
 
 
         assertEquals(23, type.getBadgeLabel(0));
@@ -106,8 +123,8 @@
                 .createUserTypeDetails();
 
         assertTrue(type.isEnabled());
-        assertEquals(UserTypeDetails.UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowed());
-        assertEquals(UserTypeDetails.UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowedPerParent());
+        assertEquals(UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowed());
+        assertEquals(UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowedPerParent());
         assertEquals(FLAG_FULL, type.getDefaultUserInfoFlags());
         assertEquals(Resources.ID_NULL, type.getIconBadge());
         assertEquals(Resources.ID_NULL, type.getBadgePlain());
@@ -185,8 +202,173 @@
                 UserInfo.getDefaultUserType(FLAG_EPHEMERAL));
     }
 
+    /** Tests {@link UserTypeFactory#customizeBuilders} for a reasonable xml file. */
+    @Test
+    public void testUserTypeFactoryCustomize_profile() throws Exception {
+        final String userTypeAosp1 = "android.test.1"; // Profile user that is not customized
+        final String userTypeAosp2 = "android.test.2"; // Profile user that is customized
+        final String userTypeOem1 = "custom.test.1"; // Custom-defined profile
+
+        // Mock some "AOSP defaults".
+        final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
+        final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+        builders.put(userTypeAosp1, new UserTypeDetails.Builder()
+                .setName(userTypeAosp1)
+                .setBaseType(FLAG_PROFILE)
+                .setMaxAllowedPerParent(31)
+                .setDefaultRestrictions(restrictions));
+        builders.put(userTypeAosp2, new UserTypeDetails.Builder()
+                .setName(userTypeAosp1)
+                .setBaseType(FLAG_PROFILE)
+                .setMaxAllowedPerParent(32)
+                .setIconBadge(401)
+                .setBadgeColors(402, 403, 404)
+                .setDefaultRestrictions(restrictions));
+
+        final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_profile);
+        UserTypeFactory.customizeBuilders(builders, parser);
+
+        // userTypeAosp1 should not be modified.
+        UserTypeDetails aospType = builders.get(userTypeAosp1).createUserTypeDetails();
+        assertEquals(31, aospType.getMaxAllowedPerParent());
+        assertEquals(Resources.ID_NULL, aospType.getIconBadge());
+        assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
+
+        // userTypeAosp2 should be modified.
+        aospType = builders.get(userTypeAosp2).createUserTypeDetails();
+        assertEquals(12, aospType.getMaxAllowedPerParent());
+        assertEquals(com.android.internal.R.drawable.ic_corp_icon_badge_case,
+                aospType.getIconBadge());
+        assertEquals(Resources.ID_NULL, aospType.getBadgePlain()); // No resId for 'garbage'
+        assertEquals(com.android.internal.R.drawable.ic_corp_badge_no_background,
+                aospType.getBadgeNoBackground());
+        assertEquals(com.android.internal.R.string.managed_profile_label_badge,
+                aospType.getBadgeLabel(0));
+        assertEquals(com.android.internal.R.string.managed_profile_label_badge_2,
+                aospType.getBadgeLabel(1));
+        assertEquals(com.android.internal.R.string.managed_profile_label_badge_2,
+                aospType.getBadgeLabel(2));
+        assertEquals(com.android.internal.R.string.managed_profile_label_badge_2,
+                aospType.getBadgeLabel(3));
+        assertEquals(com.android.internal.R.color.profile_badge_1,
+                aospType.getBadgeColor(0));
+        assertEquals(com.android.internal.R.color.profile_badge_2,
+                aospType.getBadgeColor(1));
+        assertEquals(com.android.internal.R.color.profile_badge_2,
+                aospType.getBadgeColor(2));
+        assertEquals(com.android.internal.R.color.profile_badge_2,
+                aospType.getBadgeColor(3));
+        assertTrue(UserRestrictionsUtils.areEqual(
+                makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
+                aospType.getDefaultRestrictions()));
+
+        // userTypeOem1 should be created.
+        UserTypeDetails.Builder customType = builders.get(userTypeOem1);
+        assertNotNull(customType);
+        assertEquals(14, customType.createUserTypeDetails().getMaxAllowedPerParent());
+    }
+
+    /** Tests {@link UserTypeFactory#customizeBuilders} for customizing a FULL user. */
+    @Test
+    public void testUserTypeFactoryCustomize_full() throws Exception {
+        final String userTypeFull = "android.test.1";
+
+        // Mock "AOSP default".
+        final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
+        final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+        builders.put(userTypeFull, new UserTypeDetails.Builder()
+                .setName(userTypeFull)
+                .setBaseType(FLAG_FULL)
+                .setDefaultRestrictions(restrictions));
+
+        final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_full);
+        UserTypeFactory.customizeBuilders(builders, parser);
+
+        UserTypeDetails details = builders.get(userTypeFull).createUserTypeDetails();
+        assertEquals(UNLIMITED_NUMBER_OF_USERS, details.getMaxAllowedPerParent());
+        assertTrue(UserRestrictionsUtils.areEqual(
+                makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
+                details.getDefaultRestrictions()));
+        assertEquals(Resources.ID_NULL, details.getBadgeColor(0));
+    }
+
+    /**
+     * Tests {@link UserTypeFactory#customizeBuilders} when custom user type deletes the
+     * badge-colors and restrictions.
+     */
+    @Test
+    public void testUserTypeFactoryCustomize_eraseArray() throws Exception {
+        final String typeName = "android.test";
+
+        final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+        builders.put(typeName, new UserTypeDetails.Builder()
+                .setName(typeName)
+                .setBaseType(FLAG_PROFILE)
+                .setMaxAllowedPerParent(1)
+                .setBadgeColors(501, 502)
+                .setDefaultRestrictions(makeRestrictionsBundle("r1")));
+
+        final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_eraseArray);
+        UserTypeFactory.customizeBuilders(builders, parser);
+
+        UserTypeDetails typeDetails =  builders.get(typeName).createUserTypeDetails();
+        assertEquals(2, typeDetails.getMaxAllowedPerParent());
+        assertEquals(Resources.ID_NULL, typeDetails.getBadgeColor(0));
+        assertEquals(Resources.ID_NULL, typeDetails.getBadgeColor(1));
+        assertTrue(typeDetails.getDefaultRestrictions().isEmpty());
+    }
+
+    /** Tests {@link UserTypeFactory#customizeBuilders} when custom user type has illegal name. */
+    @Test
+    public void testUserTypeFactoryCustomize_illegalOemName() throws Exception {
+        final String userTypeAosp = "android.aosp.legal";
+        final String userTypeOem = "android.oem.illegal.name"; // Custom-defined profile
+
+        final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+        builders.put(userTypeAosp, new UserTypeDetails.Builder()
+                .setName(userTypeAosp)
+                .setBaseType(FLAG_PROFILE)
+                .setMaxAllowedPerParent(21));
+
+        final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_illegalOemName);
+
+        // parser is illegal because non-AOSP user types cannot be prefixed with "android.".
+        assertThrows(IllegalArgumentException.class,
+                () -> UserTypeFactory.customizeBuilders(builders, parser));
+    }
+
+    /**
+     * Tests {@link UserTypeFactory#customizeBuilders} when illegally customizing a non-profile as
+     * a profile.
+     */
+    @Test
+    public void testUserTypeFactoryCustomize_illegalUserBaseType() throws Exception {
+        final String userTypeFull = "android.test";
+
+        final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+        builders.put(userTypeFull, new UserTypeDetails.Builder()
+                .setName(userTypeFull)
+                .setBaseType(FLAG_FULL)
+                .setMaxAllowedPerParent(21));
+
+        XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_illegalUserBaseType);
+
+        // parser is illegal because userTypeFull is FULL but the tag is for profile-type.
+        assertThrows(IllegalArgumentException.class,
+                () -> UserTypeFactory.customizeBuilders(builders, parser));
+    }
+
     /** Returns a minimal {@link UserTypeDetails.Builder} that can legitimately be created. */
     private UserTypeDetails.Builder getMinimalBuilder() {
         return new UserTypeDetails.Builder().setName("name").setBaseType(FLAG_FULL);
     }
+
+    /** Creates a Bundle of the given String restrictions, each set to true. */
+    private Bundle makeRestrictionsBundle(String ... restrictions) {
+        final Bundle bundle = new Bundle();
+        for (String restriction : restrictions) {
+            bundle.putBoolean(restriction, true);
+        }
+        return bundle;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index dee79bb..b3f1bcd6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -278,26 +278,12 @@
     @MediumTest
     public void testSetUserAdmin() throws Exception {
         UserInfo userInfo = createUser("SecondaryUser", /*flags=*/ 0);
-
-        // Assert user is not admin and has SMS and calls restrictions.
         assertFalse(userInfo.isAdmin());
-        assertTrue(mUserManager.hasUserRestriction(UserManager.DISALLOW_SMS,
-                userInfo.getUserHandle()));
-        assertTrue(mUserManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                userInfo.getUserHandle()));
 
-        // Assign admin privileges.
         mUserManager.setUserAdmin(userInfo.id);
 
-        // Refresh.
         userInfo = mUserManager.getUserInfo(userInfo.id);
-
-        // Verify user became admin and SMS and call restrictions are lifted.
         assertTrue(userInfo.isAdmin());
-        assertFalse(mUserManager.hasUserRestriction(UserManager.DISALLOW_SMS,
-                userInfo.getUserHandle()));
-        assertFalse(mUserManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                userInfo.getUserHandle()));
     }
 
     @MediumTest
@@ -631,6 +617,21 @@
     }
 
     @MediumTest
+    public void testDefaultRestrictionsApplied() throws Exception {
+        final UserInfo userInfo = createUser("Useroid", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+        final UserTypeDetails userTypeDetails =
+                UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_FULL_SECONDARY);
+        final Bundle expectedRestrictions = userTypeDetails.getDefaultRestrictions();
+        // Note this can fail if DO unset those restrictions.
+        for (String restriction : expectedRestrictions.keySet()) {
+            if (expectedRestrictions.getBoolean(restriction)) {
+                assertTrue(
+                        mUserManager.hasUserRestriction(restriction, UserHandle.of(userInfo.id)));
+            }
+        }
+    }
+
+    @MediumTest
     public void testSetDefaultGuestRestrictions() {
         final Bundle origGuestRestrictions = mUserManager.getDefaultGuestRestrictions();
         Bundle restrictions = new Bundle();
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java
deleted file mode 100644
index 7a0a28d..0000000
--- a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java
+++ /dev/null
@@ -1,661 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.timedetector;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.timedetector.ManualTimeSuggestion;
-import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
-import android.icu.util.Calendar;
-import android.icu.util.GregorianCalendar;
-import android.icu.util.TimeZone;
-import android.util.TimestampedValue;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.time.Duration;
-
-@RunWith(AndroidJUnit4.class)
-public class SimpleTimeDetectorStrategyTest {
-
-    private static final Scenario SCENARIO_1 = new Scenario.Builder()
-            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
-            .setInitialDeviceRealtimeMillis(123456789L)
-            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
-            .build();
-
-    private static final int ARBITRARY_PHONE_ID = 123456;
-
-    private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis();
-
-    private Script mScript;
-
-    @Before
-    public void setUp() {
-        mScript = new Script();
-    }
-
-    @Test
-    public void testSuggestPhoneTime_autoTimeEnabled() {
-        Scenario scenario = SCENARIO_1;
-        mScript.pokeFakeClocks(scenario)
-                .pokeTimeDetectionEnabled(true);
-
-        PhoneTimeSuggestion timeSuggestion =
-                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
-        final int clockIncrement = 1000;
-        long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
-
-        mScript.simulateTimePassing(clockIncrement)
-                .simulatePhoneTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectSystemClockMillis, true /* expectNetworkBroadcast */);
-    }
-
-    @Test
-    public void testSuggestPhoneTime_emptySuggestionIgnored() {
-        Scenario scenario = SCENARIO_1;
-        mScript.pokeFakeClocks(scenario)
-                .pokeTimeDetectionEnabled(true);
-
-        PhoneTimeSuggestion timeSuggestion = createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, null);
-
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasNotSetAndResetCallTracking();
-    }
-
-    @Test
-    public void testSuggestPhoneTime_systemClockThreshold() {
-        Scenario scenario = SCENARIO_1;
-        final int systemClockUpdateThresholdMillis = 1000;
-        mScript.pokeFakeClocks(scenario)
-                .pokeThresholds(systemClockUpdateThresholdMillis)
-                .pokeTimeDetectionEnabled(true);
-
-        PhoneTimeSuggestion timeSuggestion1 =
-                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
-        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
-
-        final int clockIncrement = 100;
-        // Increment the the device clocks to simulate the passage of time.
-        mScript.simulateTimePassing(clockIncrement);
-
-        long expectSystemClockMillis1 =
-                TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
-
-        // Send the first time signal. It should be used.
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectSystemClockMillis1, true /* expectNetworkBroadcast */);
-
-        // Now send another time signal, but one that is too similar to the last one and should be
-        // ignored.
-        int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
-        TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
-                mScript.peekElapsedRealtimeMillis(),
-                mScript.peekSystemClockMillis() + underThresholdMillis);
-        PhoneTimeSuggestion timeSuggestion2 =
-                createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
-        mScript.simulateTimePassing(clockIncrement)
-                .simulatePhoneTimeSuggestion(timeSuggestion2)
-                .verifySystemClockWasNotSetAndResetCallTracking();
-
-        // Now send another time signal, but one that is on the threshold and so should be used.
-        TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
-                mScript.peekElapsedRealtimeMillis(),
-                mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
-
-        PhoneTimeSuggestion timeSuggestion3 =
-                createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
-        mScript.simulateTimePassing(clockIncrement);
-
-        long expectSystemClockMillis3 =
-                TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
-
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectSystemClockMillis3, true /* expectNetworkBroadcast */);
-    }
-
-    @Test
-    public void testSuggestPhoneTime_autoTimeDisabled() {
-        Scenario scenario = SCENARIO_1;
-        mScript.pokeFakeClocks(scenario)
-                .pokeTimeDetectionEnabled(false);
-
-        PhoneTimeSuggestion timeSuggestion =
-                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasNotSetAndResetCallTracking();
-    }
-
-    @Test
-    public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
-        Scenario scenario = SCENARIO_1;
-        final int systemClockUpdateThreshold = 2000;
-        mScript.pokeFakeClocks(scenario)
-                .pokeThresholds(systemClockUpdateThreshold)
-                .pokeTimeDetectionEnabled(true);
-        PhoneTimeSuggestion timeSuggestion1 =
-                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
-        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
-
-        // Initialize the strategy / device with a time set from NITZ.
-        mScript.simulateTimePassing(100);
-        long expectedSystemClockMillis1 =
-                TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
-
-        // The UTC time increment should be larger than the system clock update threshold so we
-        // know it shouldn't be ignored for other reasons.
-        long validUtcTimeMillis = utcTime1.getValue() + (2 * systemClockUpdateThreshold);
-
-        // Now supply a new signal that has an obviously bogus reference time : older than the last
-        // one.
-        long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1;
-        TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
-                referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
-        PhoneTimeSuggestion timeSuggestion2 =
-                createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
-                .verifySystemClockWasNotSetAndResetCallTracking();
-
-        // Now supply a new signal that has an obviously bogus reference time : substantially in the
-        // future.
-        long referenceTimeInFutureMillis =
-                utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
-        TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
-                referenceTimeInFutureMillis, validUtcTimeMillis);
-        PhoneTimeSuggestion timeSuggestion3 =
-                createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
-                .verifySystemClockWasNotSetAndResetCallTracking();
-
-        // Just to prove validUtcTimeMillis is valid.
-        long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
-        TimestampedValue<Long> utcTime4 = new TimestampedValue<>(
-                validReferenceTimeMillis, validUtcTimeMillis);
-        long expectedSystemClockMillis4 =
-                TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
-        PhoneTimeSuggestion timeSuggestion4 =
-                createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime4);
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis4, true /* expectNetworkBroadcast */);
-    }
-
-    @Test
-    public void testSuggestPhoneTime_timeDetectionToggled() {
-        Scenario scenario = SCENARIO_1;
-        final int clockIncrementMillis = 100;
-        final int systemClockUpdateThreshold = 2000;
-        mScript.pokeFakeClocks(scenario)
-                .pokeThresholds(systemClockUpdateThreshold)
-                .pokeTimeDetectionEnabled(false);
-
-        PhoneTimeSuggestion timeSuggestion1 =
-                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
-        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
-
-        // Simulate time passing.
-        mScript.simulateTimePassing(clockIncrementMillis);
-
-        // Simulate the time signal being received. It should not be used because auto time
-        // detection is off but it should be recorded.
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasNotSetAndResetCallTracking();
-
-        // Simulate more time passing.
-        mScript.simulateTimePassing(clockIncrementMillis);
-
-        long expectedSystemClockMillis1 =
-                TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
-
-        // Turn on auto time detection.
-        mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
-
-        // Turn off auto time detection.
-        mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasNotSetAndResetCallTracking();
-
-        // Receive another valid time signal.
-        // It should be on the threshold and accounting for the clock increments.
-        TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
-                mScript.peekElapsedRealtimeMillis(),
-                mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
-        PhoneTimeSuggestion timeSuggestion2 =
-                createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
-
-        // Simulate more time passing.
-        mScript.simulateTimePassing(clockIncrementMillis);
-
-        long expectedSystemClockMillis2 =
-                TimeDetectorStrategy.getTimeAt(utcTime2, mScript.peekElapsedRealtimeMillis());
-
-        // The new time, though valid, should not be set in the system clock because auto time is
-        // disabled.
-        mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
-                .verifySystemClockWasNotSetAndResetCallTracking();
-
-        // Turn on auto time detection.
-        mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis2, true /* expectNetworkBroadcast */);
-    }
-
-    @Test
-    public void testSuggestManualTime_autoTimeDisabled() {
-        Scenario scenario = SCENARIO_1;
-        mScript.pokeFakeClocks(scenario)
-                .pokeTimeDetectionEnabled(false);
-
-        ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
-        final int clockIncrement = 1000;
-        long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
-
-        mScript.simulateTimePassing(clockIncrement)
-                .simulateManualTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectSystemClockMillis, false /* expectNetworkBroadcast */);
-    }
-
-    @Test
-    public void testSuggestManualTime_retainsAutoSignal() {
-        Scenario scenario = SCENARIO_1;
-
-        // Configure the start state.
-        mScript.pokeFakeClocks(scenario)
-                .pokeTimeDetectionEnabled(true);
-
-        // Simulate a phone suggestion.
-        PhoneTimeSuggestion phoneTimeSuggestion =
-                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
-        long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
-        final int clockIncrement = 1000;
-
-        // Simulate the passage of time.
-        mScript.simulateTimePassing(clockIncrement);
-        expectedAutoClockMillis += clockIncrement;
-
-        mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedAutoClockMillis, true /* expectNetworkBroadcast */);
-
-        // Simulate the passage of time.
-        mScript.simulateTimePassing(clockIncrement);
-        expectedAutoClockMillis += clockIncrement;
-
-        // Switch to manual.
-        mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasNotSetAndResetCallTracking();
-
-        // Simulate the passage of time.
-        mScript.simulateTimePassing(clockIncrement);
-        expectedAutoClockMillis += clockIncrement;
-
-
-        // Simulate a manual suggestion 1 day different from the auto suggestion.
-        long manualTimeMillis = SCENARIO_1.getActualTimeMillis() + ONE_DAY_MILLIS;
-        long expectedManualClockMillis = manualTimeMillis;
-        ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(manualTimeMillis);
-        mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedManualClockMillis, false /* expectNetworkBroadcast */);
-
-        // Simulate the passage of time.
-        mScript.simulateTimePassing(clockIncrement);
-        expectedAutoClockMillis += clockIncrement;
-
-        // Switch back to auto.
-        mScript.simulateAutoTimeDetectionToggle();
-
-        mScript.verifySystemClockWasSetAndResetCallTracking(
-                        expectedAutoClockMillis, true /* expectNetworkBroadcast */);
-
-        // Switch back to manual - nothing should happen to the clock.
-        mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasNotSetAndResetCallTracking();
-    }
-
-    /**
-     * Manual suggestions should be ignored if auto time is enabled.
-     */
-    @Test
-    public void testSuggestManualTime_autoTimeEnabled() {
-        Scenario scenario = SCENARIO_1;
-        mScript.pokeFakeClocks(scenario)
-                .pokeTimeDetectionEnabled(true);
-
-        ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
-        final int clockIncrement = 1000;
-
-        mScript.simulateTimePassing(clockIncrement)
-                .simulateManualTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasNotSetAndResetCallTracking();
-    }
-
-    /**
-     * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
-     * like the real thing should, it also asserts preconditions.
-     */
-    private static class FakeCallback implements TimeDetectorStrategy.Callback {
-        private boolean mTimeDetectionEnabled;
-        private boolean mWakeLockAcquired;
-        private long mElapsedRealtimeMillis;
-        private long mSystemClockMillis;
-        private int mSystemClockUpdateThresholdMillis = 2000;
-
-        // Tracking operations.
-        private boolean mSystemClockWasSet;
-        private Intent mBroadcastSent;
-
-        @Override
-        public int systemClockUpdateThresholdMillis() {
-            return mSystemClockUpdateThresholdMillis;
-        }
-
-        @Override
-        public boolean isAutoTimeDetectionEnabled() {
-            return mTimeDetectionEnabled;
-        }
-
-        @Override
-        public void acquireWakeLock() {
-            if (mWakeLockAcquired) {
-                fail("Wake lock already acquired");
-            }
-            mWakeLockAcquired = true;
-        }
-
-        @Override
-        public long elapsedRealtimeMillis() {
-            assertWakeLockAcquired();
-            return mElapsedRealtimeMillis;
-        }
-
-        @Override
-        public long systemClockMillis() {
-            assertWakeLockAcquired();
-            return mSystemClockMillis;
-        }
-
-        @Override
-        public void setSystemClock(long newTimeMillis) {
-            assertWakeLockAcquired();
-            mSystemClockWasSet = true;
-            mSystemClockMillis = newTimeMillis;
-        }
-
-        @Override
-        public void releaseWakeLock() {
-            assertWakeLockAcquired();
-            mWakeLockAcquired = false;
-        }
-
-        @Override
-        public void sendStickyBroadcast(Intent intent) {
-            assertNotNull(intent);
-            mBroadcastSent = intent;
-        }
-
-        // Methods below are for managing the fake's behavior.
-
-        public void pokeSystemClockUpdateThreshold(int thresholdMillis) {
-            mSystemClockUpdateThresholdMillis = thresholdMillis;
-        }
-
-        public void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
-            mElapsedRealtimeMillis = elapsedRealtimeMillis;
-        }
-
-        public void pokeSystemClockMillis(long systemClockMillis) {
-            mSystemClockMillis = systemClockMillis;
-        }
-
-        public void pokeAutoTimeDetectionEnabled(boolean enabled) {
-            mTimeDetectionEnabled = enabled;
-        }
-
-        public long peekElapsedRealtimeMillis() {
-            return mElapsedRealtimeMillis;
-        }
-
-        public long peekSystemClockMillis() {
-            return mSystemClockMillis;
-        }
-
-        public void simulateTimePassing(int incrementMillis) {
-            mElapsedRealtimeMillis += incrementMillis;
-            mSystemClockMillis += incrementMillis;
-        }
-
-        public void simulateAutoTimeZoneDetectionToggle() {
-            mTimeDetectionEnabled = !mTimeDetectionEnabled;
-        }
-
-        public void verifySystemClockNotSet() {
-            assertFalse(mSystemClockWasSet);
-        }
-
-        public void verifySystemClockWasSet(long expectSystemClockMillis) {
-            assertTrue(mSystemClockWasSet);
-            assertEquals(expectSystemClockMillis, mSystemClockMillis);
-        }
-
-        public void verifyIntentWasBroadcast() {
-            assertTrue(mBroadcastSent != null);
-        }
-
-        public void verifyIntentWasNotBroadcast() {
-            assertNull(mBroadcastSent);
-        }
-
-        public void resetCallTracking() {
-            mSystemClockWasSet = false;
-            mBroadcastSent = null;
-        }
-
-        private void assertWakeLockAcquired() {
-            assertTrue("The operation must be performed only after acquiring the wakelock",
-                    mWakeLockAcquired);
-        }
-    }
-
-    /**
-     * A fluent helper class for tests.
-     */
-    private class Script {
-
-        private final FakeCallback mFakeCallback;
-        private final SimpleTimeDetectorStrategy mSimpleTimeDetectorStrategy;
-
-        Script() {
-            mFakeCallback = new FakeCallback();
-            mSimpleTimeDetectorStrategy = new SimpleTimeDetectorStrategy();
-            mSimpleTimeDetectorStrategy.initialize(mFakeCallback);
-
-        }
-
-        Script pokeTimeDetectionEnabled(boolean enabled) {
-            mFakeCallback.pokeAutoTimeDetectionEnabled(enabled);
-            return this;
-        }
-
-        Script pokeFakeClocks(Scenario scenario) {
-            mFakeCallback.pokeElapsedRealtimeMillis(scenario.getInitialRealTimeMillis());
-            mFakeCallback.pokeSystemClockMillis(scenario.getInitialSystemClockMillis());
-            return this;
-        }
-
-        Script pokeThresholds(int systemClockUpdateThreshold) {
-            mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
-            return this;
-        }
-
-        long peekElapsedRealtimeMillis() {
-            return mFakeCallback.peekElapsedRealtimeMillis();
-        }
-
-        long peekSystemClockMillis() {
-            return mFakeCallback.peekSystemClockMillis();
-        }
-
-        Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
-            mSimpleTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
-            return this;
-        }
-
-        Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) {
-            mSimpleTimeDetectorStrategy.suggestManualTime(timeSuggestion);
-            return this;
-        }
-
-        Script simulateAutoTimeDetectionToggle() {
-            mFakeCallback.simulateAutoTimeZoneDetectionToggle();
-            mSimpleTimeDetectorStrategy.handleAutoTimeDetectionChanged();
-            return this;
-        }
-
-        Script simulateTimePassing(int clockIncrement) {
-            mFakeCallback.simulateTimePassing(clockIncrement);
-            return this;
-        }
-
-        Script verifySystemClockWasNotSetAndResetCallTracking() {
-            mFakeCallback.verifySystemClockNotSet();
-            mFakeCallback.verifyIntentWasNotBroadcast();
-            mFakeCallback.resetCallTracking();
-            return this;
-        }
-
-        Script verifySystemClockWasSetAndResetCallTracking(
-                long expectSystemClockMillis, boolean expectNetworkBroadcast) {
-            mFakeCallback.verifySystemClockWasSet(expectSystemClockMillis);
-            if (expectNetworkBroadcast) {
-                mFakeCallback.verifyIntentWasBroadcast();
-            }
-            mFakeCallback.resetCallTracking();
-            return this;
-        }
-    }
-
-    /**
-     * A starting scenario used during tests. Describes a fictional "physical" reality.
-     */
-    private static class Scenario {
-
-        private final long mInitialDeviceSystemClockMillis;
-        private final long mInitialDeviceRealtimeMillis;
-        private final long mActualTimeMillis;
-
-        Scenario(long initialDeviceSystemClock, long elapsedRealtime, long timeMillis) {
-            mInitialDeviceSystemClockMillis = initialDeviceSystemClock;
-            mActualTimeMillis = timeMillis;
-            mInitialDeviceRealtimeMillis = elapsedRealtime;
-        }
-
-        long getInitialRealTimeMillis() {
-            return mInitialDeviceRealtimeMillis;
-        }
-
-        long getInitialSystemClockMillis() {
-            return mInitialDeviceSystemClockMillis;
-        }
-
-        long getActualTimeMillis() {
-            return mActualTimeMillis;
-        }
-
-        PhoneTimeSuggestion createPhoneTimeSuggestionForActual(int phoneId) {
-            TimestampedValue<Long> time = new TimestampedValue<>(
-                    mInitialDeviceRealtimeMillis, mActualTimeMillis);
-            return createPhoneTimeSuggestion(phoneId, time);
-        }
-
-        ManualTimeSuggestion createManualTimeSuggestionForActual() {
-            TimestampedValue<Long> time = new TimestampedValue<>(
-                    mInitialDeviceRealtimeMillis, mActualTimeMillis);
-            return new ManualTimeSuggestion(time);
-        }
-
-        static class Builder {
-
-            private long mInitialDeviceSystemClockMillis;
-            private long mInitialDeviceRealtimeMillis;
-            private long mActualTimeMillis;
-
-            Builder setInitialDeviceSystemClockUtc(int year, int monthInYear, int day,
-                    int hourOfDay, int minute, int second) {
-                mInitialDeviceSystemClockMillis = createUtcTime(year, monthInYear, day, hourOfDay,
-                        minute, second);
-                return this;
-            }
-
-            Builder setInitialDeviceRealtimeMillis(long realtimeMillis) {
-                mInitialDeviceRealtimeMillis = realtimeMillis;
-                return this;
-            }
-
-            Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay,
-                    int minute, int second) {
-                mActualTimeMillis =
-                        createUtcTime(year, monthInYear, day, hourOfDay, minute, second);
-                return this;
-            }
-
-            Scenario build() {
-                return new Scenario(mInitialDeviceSystemClockMillis, mInitialDeviceRealtimeMillis,
-                        mActualTimeMillis);
-            }
-        }
-    }
-
-    private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId,
-            TimestampedValue<Long> utcTime) {
-        return new PhoneTimeSuggestion.Builder(phoneId)
-                .setUtcTime(utcTime)
-                .build();
-    }
-
-    private ManualTimeSuggestion createManualTimeSuggestion(long timeMillis) {
-        TimestampedValue<Long> utcTime =
-                new TimestampedValue<>(mScript.peekElapsedRealtimeMillis(), timeMillis);
-        return new ManualTimeSuggestion(utcTime);
-    }
-
-    private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
-            int second) {
-        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
-        cal.clear();
-        cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
-        return cal.getTimeInMillis();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 84b495f..72a7f50 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -38,8 +38,6 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.timedetector.TimeDetectorStrategy.Callback;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -52,7 +50,6 @@
 
     private Context mMockContext;
     private StubbedTimeDetectorStrategy mStubbedTimeDetectorStrategy;
-    private Callback mMockCallback;
 
     private TimeDetectorService mTimeDetectorService;
     private HandlerThread mHandlerThread;
@@ -68,12 +65,10 @@
         mHandlerThread.start();
         mTestHandler = new TestHandler(mHandlerThread.getLooper());
 
-        mMockCallback = mock(Callback.class);
         mStubbedTimeDetectorStrategy = new StubbedTimeDetectorStrategy();
 
         mTimeDetectorService = new TimeDetectorService(
-                mMockContext, mTestHandler, mMockCallback,
-                mStubbedTimeDetectorStrategy);
+                mMockContext, mTestHandler, mStubbedTimeDetectorStrategy);
     }
 
     @After
@@ -100,13 +95,13 @@
 
     @Test
     public void testSuggestManualTime() throws Exception {
-        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+        doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
 
         ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion();
         mTimeDetectorService.suggestManualTime(manualTimeSuggestion);
         mTestHandler.assertTotalMessagesEnqueued(1);
 
-        verify(mMockContext).enforceCallingPermission(
+        verify(mMockContext).enforceCallingOrSelfPermission(
                 eq(android.Manifest.permission.SET_TIME),
                 anyString());
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
new file mode 100644
index 0000000..1aa3d8f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -0,0 +1,758 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.PhoneTimeSuggestion;
+import android.content.Intent;
+import android.icu.util.Calendar;
+import android.icu.util.GregorianCalendar;
+import android.icu.util.TimeZone;
+import android.util.TimestampedValue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeDetectorStrategyImplTest {
+
+    private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO =
+            new TimestampedValue<>(
+                    123456789L /* realtimeClockMillis */,
+                    createUtcTime(1977, 1, 1, 12, 0, 0));
+
+    private static final long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0);
+
+    private static final int ARBITRARY_PHONE_ID = 123456;
+
+    private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis();
+
+    private Script mScript;
+
+    @Before
+    public void setUp() {
+        mScript = new Script();
+    }
+
+    @Test
+    public void testSuggestPhoneTime_autoTimeEnabled() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        int phoneId = ARBITRARY_PHONE_ID;
+        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        PhoneTimeSuggestion timeSuggestion =
+                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        int clockIncrement = 1000;
+        long expectedSystemClockMillis = testTimeMillis + clockIncrement;
+
+        mScript.simulateTimePassing(clockIncrement)
+                .simulatePhoneTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
+    }
+
+    @Test
+    public void testSuggestPhoneTime_emptySuggestionIgnored() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        int phoneId = ARBITRARY_PHONE_ID;
+        PhoneTimeSuggestion timeSuggestion =
+                mScript.generatePhoneTimeSuggestion(phoneId, null);
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestPhoneSuggestion(phoneId, null);
+    }
+
+    @Test
+    public void testSuggestPhoneTime_systemClockThreshold() {
+        int systemClockUpdateThresholdMillis = 1000;
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeThresholds(systemClockUpdateThresholdMillis)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        final int clockIncrement = 100;
+        int phoneId = ARBITRARY_PHONE_ID;
+
+        // Send the first time signal. It should be used.
+        {
+            long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+            PhoneTimeSuggestion timeSuggestion1 =
+                    mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+            TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
+
+            // Increment the the device clocks to simulate the passage of time.
+            mScript.simulateTimePassing(clockIncrement);
+
+            long expectedSystemClockMillis1 =
+                    TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
+
+            mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
+                    .verifySystemClockWasSetAndResetCallTracking(
+                            expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                    .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+        }
+
+        // Now send another time signal, but one that is too similar to the last one and should be
+        // stored, but not used to set the system clock.
+        {
+            int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
+            PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
+                    phoneId, mScript.peekSystemClockMillis() + underThresholdMillis);
+            mScript.simulateTimePassing(clockIncrement)
+                    .simulatePhoneTimeSuggestion(timeSuggestion2)
+                    .verifySystemClockWasNotSetAndResetCallTracking()
+                    .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
+        }
+
+        // Now send another time signal, but one that is on the threshold and so should be used.
+        {
+            PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion(
+                    phoneId,
+                    mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
+            mScript.simulateTimePassing(clockIncrement);
+
+            long expectedSystemClockMillis3 =
+                    TimeDetectorStrategy.getTimeAt(timeSuggestion3.getUtcTime(),
+                            mScript.peekElapsedRealtimeMillis());
+
+            mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
+                    .verifySystemClockWasSetAndResetCallTracking(
+                            expectedSystemClockMillis3, true /* expectNetworkBroadcast */)
+                    .assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
+        }
+    }
+
+    @Test
+    public void testSuggestPhoneTime_multiplePhoneIdsAndBucketing() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        // There are 2 phones in this test. Phone 2 has a different idea of the current time.
+        // phone1Id < phone2Id (which is important because the strategy uses the lowest ID when
+        // multiple phone suggestions are available.
+        int phone1Id = ARBITRARY_PHONE_ID;
+        int phone2Id = ARBITRARY_PHONE_ID + 1;
+        long phone1TimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        long phone2TimeMillis = phone1TimeMillis + 60000;
+
+        final int clockIncrement = 999;
+
+        // Make a suggestion with phone2Id.
+        {
+            PhoneTimeSuggestion phone2TimeSuggestion =
+                    mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
+            mScript.simulateTimePassing(clockIncrement);
+
+            long expectedSystemClockMillis = phone2TimeMillis + clockIncrement;
+
+            mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
+                    .verifySystemClockWasSetAndResetCallTracking(
+                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .assertLatestPhoneSuggestion(phone1Id, null)
+                    .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
+        }
+
+        mScript.simulateTimePassing(clockIncrement);
+
+        // Now make a different suggestion with phone1Id.
+        {
+            PhoneTimeSuggestion phone1TimeSuggestion =
+                    mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis);
+            mScript.simulateTimePassing(clockIncrement);
+
+            long expectedSystemClockMillis = phone1TimeMillis + clockIncrement;
+
+            mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
+                    .verifySystemClockWasSetAndResetCallTracking(
+                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
+
+        }
+
+        mScript.simulateTimePassing(clockIncrement);
+
+        // Make another suggestion with phone2Id. It should be stored but not used because the
+        // phone1Id suggestion will still "win".
+        {
+            PhoneTimeSuggestion phone2TimeSuggestion =
+                    mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
+            mScript.simulateTimePassing(clockIncrement);
+
+            mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
+                    .verifySystemClockWasNotSetAndResetCallTracking()
+                    .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
+        }
+
+        // Let enough time pass that phone1Id's suggestion should now be too old.
+        mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_BUCKET_SIZE_MILLIS);
+
+        // Make another suggestion with phone2Id. It should be used because the phoneId1
+        // is in an older "bucket".
+        {
+            PhoneTimeSuggestion phone2TimeSuggestion =
+                    mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
+            mScript.simulateTimePassing(clockIncrement);
+
+            long expectedSystemClockMillis = phone2TimeMillis + clockIncrement;
+
+            mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
+                    .verifySystemClockWasSetAndResetCallTracking(
+                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
+        }
+    }
+
+    @Test
+    public void testSuggestPhoneTime_autoTimeDisabled() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(false);
+
+        int phoneId = ARBITRARY_PHONE_ID;
+        PhoneTimeSuggestion timeSuggestion =
+                mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
+        mScript.simulateTimePassing(1000)
+                .simulatePhoneTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
+    }
+
+    @Test
+    public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
+        final int systemClockUpdateThreshold = 2000;
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeThresholds(systemClockUpdateThreshold)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        int phoneId = ARBITRARY_PHONE_ID;
+
+        PhoneTimeSuggestion timeSuggestion1 =
+                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
+
+        // Initialize the strategy / device with a time set from a phone suggestion.
+        mScript.simulateTimePassing(100);
+        long expectedSystemClockMillis1 =
+                TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+        // The UTC time increment should be larger than the system clock update threshold so we
+        // know it shouldn't be ignored for other reasons.
+        long validUtcTimeMillis = utcTime1.getValue() + (2 * systemClockUpdateThreshold);
+
+        // Now supply a new signal that has an obviously bogus reference time : older than the last
+        // one.
+        long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1;
+        TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
+                referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
+        PhoneTimeSuggestion timeSuggestion2 =
+                createPhoneTimeSuggestion(phoneId, utcTime2);
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+        // Now supply a new signal that has an obviously bogus reference time : substantially in the
+        // future.
+        long referenceTimeInFutureMillis =
+                utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
+        TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
+                referenceTimeInFutureMillis, validUtcTimeMillis);
+        PhoneTimeSuggestion timeSuggestion3 =
+                createPhoneTimeSuggestion(phoneId, utcTime3);
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+        // Just to prove validUtcTimeMillis is valid.
+        long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
+        TimestampedValue<Long> utcTime4 = new TimestampedValue<>(
+                validReferenceTimeMillis, validUtcTimeMillis);
+        long expectedSystemClockMillis4 =
+                TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
+        PhoneTimeSuggestion timeSuggestion4 =
+                createPhoneTimeSuggestion(phoneId, utcTime4);
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
+    }
+
+    @Test
+    public void testSuggestPhoneTime_timeDetectionToggled() {
+        final int clockIncrementMillis = 100;
+        final int systemClockUpdateThreshold = 2000;
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeThresholds(systemClockUpdateThreshold)
+                .pokeAutoTimeDetectionEnabled(false);
+
+        int phoneId = ARBITRARY_PHONE_ID;
+        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        PhoneTimeSuggestion timeSuggestion1 =
+                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
+
+        // Simulate time passing.
+        mScript.simulateTimePassing(clockIncrementMillis);
+
+        // Simulate the time signal being received. It should not be used because auto time
+        // detection is off but it should be recorded.
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+        // Simulate more time passing.
+        mScript.simulateTimePassing(clockIncrementMillis);
+
+        long expectedSystemClockMillis1 =
+                TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
+
+        // Turn on auto time detection.
+        mScript.simulateAutoTimeDetectionToggle()
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+        // Turn off auto time detection.
+        mScript.simulateAutoTimeDetectionToggle()
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+        // Receive another valid time signal.
+        // It should be on the threshold and accounting for the clock increments.
+        PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
+                phoneId, mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
+
+        // Simulate more time passing.
+        mScript.simulateTimePassing(clockIncrementMillis);
+
+        long expectedSystemClockMillis2 = TimeDetectorStrategy.getTimeAt(
+                timeSuggestion2.getUtcTime(), mScript.peekElapsedRealtimeMillis());
+
+        // The new time, though valid, should not be set in the system clock because auto time is
+        // disabled.
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
+
+        // Turn on auto time detection.
+        mScript.simulateAutoTimeDetectionToggle()
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis2, true /* expectNetworkBroadcast */)
+                .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
+    }
+
+    @Test
+    public void testSuggestPhoneTime_maxSuggestionAge() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        int phoneId = ARBITRARY_PHONE_ID;
+        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        PhoneTimeSuggestion phoneSuggestion =
+                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        int clockIncrementMillis = 1000;
+
+        mScript.simulateTimePassing(clockIncrementMillis)
+                .simulatePhoneTimeSuggestion(phoneSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        testTimeMillis + clockIncrementMillis, true /* expectedNetworkBroadcast */)
+                .assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
+
+        // Look inside and check what the strategy considers the current best phone suggestion.
+        assertEquals(phoneSuggestion, mScript.peekBestPhoneSuggestion());
+
+        // Simulate time passing, long enough that phoneSuggestion is now too old.
+        mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_MAX_AGE_MILLIS);
+
+        // Look inside and check what the strategy considers the current best phone suggestion. It
+        // should still be the, it's just no longer used.
+        assertNull(mScript.peekBestPhoneSuggestion());
+        mScript.assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
+    }
+
+    @Test
+    public void testSuggestManualTime_autoTimeDisabled() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(false);
+
+        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(testTimeMillis);
+        final int clockIncrement = 1000;
+        long expectedSystemClockMillis = testTimeMillis + clockIncrement;
+
+        mScript.simulateTimePassing(clockIncrement)
+                .simulateManualTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+    }
+
+    @Test
+    public void testSuggestManualTime_retainsAutoSignal() {
+        // Configure the start state.
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        int phoneId = ARBITRARY_PHONE_ID;
+
+        // Simulate a phone suggestion.
+        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        PhoneTimeSuggestion phoneTimeSuggestion =
+                mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+        long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
+        final int clockIncrement = 1000;
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        // Switch to manual.
+        mScript.simulateAutoTimeDetectionToggle()
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        // Simulate a manual suggestion 1 day different from the auto suggestion.
+        long manualTimeMillis = testTimeMillis + ONE_DAY_MILLIS;
+        long expectedManualClockMillis = manualTimeMillis;
+        ManualTimeSuggestion manualTimeSuggestion =
+                mScript.generateManualTimeSuggestion(manualTimeMillis);
+        mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedManualClockMillis, false /* expectNetworkBroadcast */)
+                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        // Switch back to auto.
+        mScript.simulateAutoTimeDetectionToggle();
+
+        mScript.verifySystemClockWasSetAndResetCallTracking(
+                        expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+
+        // Switch back to manual - nothing should happen to the clock.
+        mScript.simulateAutoTimeDetectionToggle()
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+    }
+
+    /**
+     * Manual suggestions should be ignored if auto time is enabled.
+     */
+    @Test
+    public void testSuggestManualTime_autoTimeEnabled() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        ManualTimeSuggestion timeSuggestion =
+                mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
+        final int clockIncrement = 1000;
+
+        mScript.simulateTimePassing(clockIncrement)
+                .simulateManualTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+    }
+
+    /**
+     * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
+     * like the real thing should, it also asserts preconditions.
+     */
+    private static class FakeCallback implements TimeDetectorStrategy.Callback {
+        private boolean mAutoTimeDetectionEnabled;
+        private boolean mWakeLockAcquired;
+        private long mElapsedRealtimeMillis;
+        private long mSystemClockMillis;
+        private int mSystemClockUpdateThresholdMillis = 2000;
+
+        // Tracking operations.
+        private boolean mSystemClockWasSet;
+        private Intent mBroadcastSent;
+
+        @Override
+        public int systemClockUpdateThresholdMillis() {
+            return mSystemClockUpdateThresholdMillis;
+        }
+
+        @Override
+        public boolean isAutoTimeDetectionEnabled() {
+            return mAutoTimeDetectionEnabled;
+        }
+
+        @Override
+        public void acquireWakeLock() {
+            if (mWakeLockAcquired) {
+                fail("Wake lock already acquired");
+            }
+            mWakeLockAcquired = true;
+        }
+
+        @Override
+        public long elapsedRealtimeMillis() {
+            return mElapsedRealtimeMillis;
+        }
+
+        @Override
+        public long systemClockMillis() {
+            assertWakeLockAcquired();
+            return mSystemClockMillis;
+        }
+
+        @Override
+        public void setSystemClock(long newTimeMillis) {
+            assertWakeLockAcquired();
+            mSystemClockWasSet = true;
+            mSystemClockMillis = newTimeMillis;
+        }
+
+        @Override
+        public void releaseWakeLock() {
+            assertWakeLockAcquired();
+            mWakeLockAcquired = false;
+        }
+
+        @Override
+        public void sendStickyBroadcast(Intent intent) {
+            assertNotNull(intent);
+            mBroadcastSent = intent;
+        }
+
+        // Methods below are for managing the fake's behavior.
+
+        void pokeSystemClockUpdateThreshold(int thresholdMillis) {
+            mSystemClockUpdateThresholdMillis = thresholdMillis;
+        }
+
+        void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
+            mElapsedRealtimeMillis = elapsedRealtimeMillis;
+        }
+
+        void pokeSystemClockMillis(long systemClockMillis) {
+            mSystemClockMillis = systemClockMillis;
+        }
+
+        void pokeAutoTimeDetectionEnabled(boolean enabled) {
+            mAutoTimeDetectionEnabled = enabled;
+        }
+
+        long peekElapsedRealtimeMillis() {
+            return mElapsedRealtimeMillis;
+        }
+
+        long peekSystemClockMillis() {
+            return mSystemClockMillis;
+        }
+
+        void simulateTimePassing(long incrementMillis) {
+            mElapsedRealtimeMillis += incrementMillis;
+            mSystemClockMillis += incrementMillis;
+        }
+
+        void simulateAutoTimeZoneDetectionToggle() {
+            mAutoTimeDetectionEnabled = !mAutoTimeDetectionEnabled;
+        }
+
+        void verifySystemClockNotSet() {
+            assertFalse(mSystemClockWasSet);
+        }
+
+        void verifySystemClockWasSet(long expectedSystemClockMillis) {
+            assertTrue(mSystemClockWasSet);
+            assertEquals(expectedSystemClockMillis, mSystemClockMillis);
+        }
+
+        void verifyIntentWasBroadcast() {
+            assertTrue(mBroadcastSent != null);
+        }
+
+        void verifyIntentWasNotBroadcast() {
+            assertNull(mBroadcastSent);
+        }
+
+        void resetCallTracking() {
+            mSystemClockWasSet = false;
+            mBroadcastSent = null;
+        }
+
+        private void assertWakeLockAcquired() {
+            assertTrue("The operation must be performed only after acquiring the wakelock",
+                    mWakeLockAcquired);
+        }
+    }
+
+    /**
+     * A fluent helper class for tests.
+     */
+    private class Script {
+
+        private final FakeCallback mFakeCallback;
+        private final TimeDetectorStrategyImpl mTimeDetectorStrategy;
+
+        Script() {
+            mFakeCallback = new FakeCallback();
+            mTimeDetectorStrategy = new TimeDetectorStrategyImpl();
+            mTimeDetectorStrategy.initialize(mFakeCallback);
+
+        }
+
+        Script pokeAutoTimeDetectionEnabled(boolean enabled) {
+            mFakeCallback.pokeAutoTimeDetectionEnabled(enabled);
+            return this;
+        }
+
+        Script pokeFakeClocks(TimestampedValue<Long> timeInfo) {
+            mFakeCallback.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
+            mFakeCallback.pokeSystemClockMillis(timeInfo.getValue());
+            return this;
+        }
+
+        Script pokeThresholds(int systemClockUpdateThreshold) {
+            mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
+            return this;
+        }
+
+        long peekElapsedRealtimeMillis() {
+            return mFakeCallback.peekElapsedRealtimeMillis();
+        }
+
+        long peekSystemClockMillis() {
+            return mFakeCallback.peekSystemClockMillis();
+        }
+
+        Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
+            mTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
+            return this;
+        }
+
+        Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) {
+            mTimeDetectorStrategy.suggestManualTime(timeSuggestion);
+            return this;
+        }
+
+        Script simulateAutoTimeDetectionToggle() {
+            mFakeCallback.simulateAutoTimeZoneDetectionToggle();
+            mTimeDetectorStrategy.handleAutoTimeDetectionChanged();
+            return this;
+        }
+
+        Script simulateTimePassing(long clockIncrementMillis) {
+            mFakeCallback.simulateTimePassing(clockIncrementMillis);
+            return this;
+        }
+
+        Script verifySystemClockWasNotSetAndResetCallTracking() {
+            mFakeCallback.verifySystemClockNotSet();
+            mFakeCallback.verifyIntentWasNotBroadcast();
+            mFakeCallback.resetCallTracking();
+            return this;
+        }
+
+        Script verifySystemClockWasSetAndResetCallTracking(
+                long expectedSystemClockMillis, boolean expectNetworkBroadcast) {
+            mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
+            if (expectNetworkBroadcast) {
+                mFakeCallback.verifyIntentWasBroadcast();
+            }
+            mFakeCallback.resetCallTracking();
+            return this;
+        }
+
+        /**
+         * White box test info: Asserts the latest suggestion for the phone ID is as expected.
+         */
+        Script assertLatestPhoneSuggestion(int phoneId, PhoneTimeSuggestion expected) {
+            assertEquals(expected, mTimeDetectorStrategy.getLatestPhoneSuggestion(phoneId));
+            return this;
+        }
+
+        /**
+         * White box test info: Returns the phone suggestion that would be used, if any, given the
+         * current elapsed real time clock.
+         */
+        PhoneTimeSuggestion peekBestPhoneSuggestion() {
+            return mTimeDetectorStrategy.findBestPhoneSuggestionForTests();
+        }
+
+        /**
+         * Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
+         * reference time.
+         */
+        ManualTimeSuggestion generateManualTimeSuggestion(long timeMillis) {
+            TimestampedValue<Long> utcTime =
+                    new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis);
+            return new ManualTimeSuggestion(utcTime);
+        }
+
+        /**
+         * Generates a PhoneTimeSuggestion using the current elapsed realtime clock for the
+         * reference time.
+         */
+        PhoneTimeSuggestion generatePhoneTimeSuggestion(int phoneId, Long timeMillis) {
+            TimestampedValue<Long> time = null;
+            if (timeMillis != null) {
+                time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
+            }
+            return createPhoneTimeSuggestion(phoneId, time);
+        }
+    }
+
+    private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId,
+            TimestampedValue<Long> utcTime) {
+        return new PhoneTimeSuggestion.Builder(phoneId)
+                .setUtcTime(utcTime)
+                .build();
+    }
+
+    private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
+            int second) {
+        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
+        cal.clear();
+        cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
+        return cal.getTimeInMillis();
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 7c22350..fab6b7f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -52,11 +52,13 @@
 import android.media.AudioAttributes;
 import android.metrics.LogMaker;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.StatusBarNotification;
+import android.widget.RemoteViews;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -114,7 +116,9 @@
 
         when(mMockContext.getResources()).thenReturn(getContext().getResources());
         when(mMockContext.getPackageManager()).thenReturn(mPm);
-        when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.targetSdkVersion = Build.VERSION_CODES.O;
+        when(mMockContext.getApplicationInfo()).thenReturn(appInfo);
     }
 
     private StatusBarNotification getNotification(String pkg, boolean noisy, boolean defaultSound,
@@ -168,6 +172,28 @@
         return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
     }
 
+    private StatusBarNotification getStyledNotification(boolean customContent, boolean customBig,
+            boolean customHeadsUp, Notification.Style style) {
+        final Builder builder = new Builder(mMockContext)
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        if (style != null) {
+            builder.setStyle(style);
+        }
+        if (customContent) {
+            builder.setCustomContentView(mock(RemoteViews.class));
+        }
+        if (customBig) {
+            builder.setCustomBigContentView(mock(RemoteViews.class));
+        }
+        if (customHeadsUp) {
+            builder.setCustomHeadsUpContentView(mock(RemoteViews.class));
+        }
+
+        Notification n = builder.build();
+        return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
+    }
+
     //
     // Tests
     //
@@ -999,4 +1025,74 @@
 
         assertEquals(IMPORTANCE_LOW, record.getImportance());
     }
+
+    @Test
+    public void testHasUndecoratedRemoteViews_NoRemoteViews() {
+        StatusBarNotification sbn = getStyledNotification(false, false, false, null);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+    }
+
+    @Test
+    public void testHasUndecoratedRemoteViews_NoRemoteViewsWithStyle() {
+        StatusBarNotification sbn = getStyledNotification(false, false, false,
+                new Notification.BigPictureStyle());
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+    }
+
+    @Test
+    public void testHasUndecoratedRemoteViews_UndecoratedContent() {
+        StatusBarNotification sbn = getStyledNotification(true, false, false, null);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+    }
+
+
+    @Test
+    public void testHasUndecoratedRemoteViews_UndecoratedBig() {
+        StatusBarNotification sbn = getStyledNotification(false, true, false, null);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+    }
+
+
+    @Test
+    public void testHasUndecoratedRemoteViews_UndecoratedHeadsup() {
+        StatusBarNotification sbn = getStyledNotification(false, false, true, null);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+    }
+
+    @Test
+    public void testHasUndecoratedRemoteViews_DecoratedRemoteViews() {
+        StatusBarNotification sbn = getStyledNotification(true, true, true,
+                new Notification.DecoratedCustomViewStyle());
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+    }
+
+    @Test
+    public void testHasUndecoratedRemoteViews_DecoratedMediaRemoteViews() {
+        StatusBarNotification sbn = getStyledNotification(true, true, true,
+                new Notification.DecoratedMediaCustomViewStyle());
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+    }
+
+    @Test
+    public void testHasUndecoratedRemoteViews_UndecoratedWrongStyle() {
+        StatusBarNotification sbn = getStyledNotification(true, true, true,
+                new Notification.BigPictureStyle());
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index 5928641..9c17de9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -47,7 +47,7 @@
 import org.junit.runner.RunWith;
 
 /**
- * Tests for the {@link ActivityDisplay} class.
+ * Tests for the {@link DisplayContent} class.
  *
  * Build/Install/Run:
  *  atest WmTests:ActivityDisplayTests
@@ -55,12 +55,13 @@
 @SmallTest
 @Presubmit
 @RunWith(WindowTestRunner.class)
+// TODO(b/144248496): Merge to DisplayContentTests
 public class ActivityDisplayTests extends ActivityTestsBase {
 
     @Test
     public void testLastFocusedStackIsUpdatedWhenMovingStack() {
         // Create a stack at bottom.
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final ActivityStack stack =
                 new StackBuilder(mRootActivityContainer).setOnTop(!ON_TOP).build();
         final ActivityStack prevFocusedStack = display.getFocusedStack();
@@ -103,13 +104,13 @@
     }
 
     /**
-     * Test {@link ActivityDisplay#mPreferredTopFocusableStack} will be cleared when the stack is
+     * Test {@link DisplayContent#mPreferredTopFocusableStack} will be cleared when the stack is
      * removed or moved to back, and the focused stack will be according to z-order.
      */
     @Test
     public void testStackShouldNotBeFocusedAfterMovingToBackOrRemoving() {
         // Create a display which only contains 2 stacks.
-        final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+        final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         final ActivityStack stack1 = createFullscreenStackWithSimpleActivityAt(display);
         final ActivityStack stack2 = createFullscreenStackWithSimpleActivityAt(display);
 
@@ -128,13 +129,13 @@
     }
 
     /**
-     * Verifies {@link ActivityDisplay#remove} should not resume home stack on the removing display.
+     * Verifies {@link DisplayContent#remove} should not resume home stack on the removing display.
      */
     @Test
     public void testNotResumeHomeStackOnRemovingDisplay() {
         // Create a display which supports system decoration and allows reparenting stacks to
         // another display when the display is removed.
-        final ActivityDisplay display = new TestActivityDisplay.Builder(
+        final DisplayContent display = new TestDisplayContent.Builder(
                 mService, 1000, 1500).setSystemDecorations(true).build();
         doReturn(false).when(display).shouldDestroyContentOnRemove();
 
@@ -154,7 +155,7 @@
         verify(homeStack, never()).resumeTopActivityUncheckedLocked(any(), any());
     }
 
-    private ActivityStack createFullscreenStackWithSimpleActivityAt(ActivityDisplay display) {
+    private ActivityStack createFullscreenStackWithSimpleActivityAt(DisplayContent display) {
         final ActivityStack fullscreenStack = display.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP);
         final Task fullscreenTask = new TaskBuilder(mService.mStackSupervisor)
@@ -168,7 +169,7 @@
      */
     @Test
     public void testTopRunningActivity() {
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final KeyguardController keyguard = mSupervisor.getKeyguardController();
         final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
         final ActivityRecord activity = stack.getTopNonFinishingActivity();
@@ -208,7 +209,7 @@
         assertTopRunningActivity(showWhenLockedActivity, display);
     }
 
-    private static void assertTopRunningActivity(ActivityRecord top, ActivityDisplay display) {
+    private static void assertTopRunningActivity(ActivityRecord top, DisplayContent display) {
         assertEquals(top, display.topRunningActivity());
         assertEquals(top, display.topRunningActivity(true /* considerKeyguardState */));
     }
@@ -218,7 +219,7 @@
      */
     @Test
     public void testAlwaysOnTopStackLocation() {
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final ActivityStack alwaysOnTopStack = display.createStack(WINDOWING_MODE_FREEFORM,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
@@ -283,7 +284,7 @@
     }
 
     private void removeStackTests(Runnable runnable) {
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final ActivityStack stack1 = display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, ON_TOP);
         final ActivityStack stack2 = display.createStack(WINDOWING_MODE_FULLSCREEN,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 7a449b3..89723d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -635,8 +635,8 @@
         final ActivityStack stack1 = new StackBuilder(mRootActivityContainer).build();
         mStack.moveToFront("test");
         // The stack2 is needed here for moving back to simulate the
-        // {@link ActivityDisplay#mPreferredTopFocusableStack} is cleared, so
-        // {@link ActivityDisplay#getFocusedStack} will rely on the order of focusable-and-visible
+        // {@link DisplayContent#mPreferredTopFocusableStack} is cleared, so
+        // {@link DisplayContent#getFocusedStack} will rely on the order of focusable-and-visible
         // stacks. Then when mActivity is finishing, its stack will be invisible (no running
         // activities in the stack) that is the key condition to verify.
         final ActivityStack stack2 = new StackBuilder(mRootActivityContainer).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
index 0219539..530adb5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -112,7 +112,7 @@
     @Test
     public void testHandleNonResizableTaskOnSecondaryDisplay() {
         // Create an unresizable task on secondary display.
-        final ActivityDisplay newDisplay = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+        final DisplayContent newDisplay = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         final ActivityStack stack = new StackBuilder(mRootActivityContainer)
                 .setDisplay(newDisplay).build();
         final ActivityRecord unresizableActivity = stack.getTopNonFinishingActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 47f454e..8a1a10d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -54,7 +54,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 
 import android.content.ComponentName;
@@ -78,7 +77,7 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class ActivityStackTests extends ActivityTestsBase {
-    private ActivityDisplay mDefaultDisplay;
+    private DisplayContent mDefaultDisplay;
     private ActivityStack mStack;
     private Task mTask;
 
@@ -279,7 +278,7 @@
 
     @Test
     public void testMoveStackToBackIncludingParent() {
-        final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+        final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         final ActivityStack stack1 = createStackForShouldBeVisibleTest(display,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final ActivityStack stack2 = createStackForShouldBeVisibleTest(display,
@@ -815,7 +814,7 @@
 
     @SuppressWarnings("TypeParameterUnusedInFormals")
     private ActivityStack createStackForShouldBeVisibleTest(
-            ActivityDisplay display, int windowingMode, int activityType, boolean onTop) {
+            DisplayContent display, int windowingMode, int activityType, boolean onTop) {
         final ActivityStack stack;
         if (activityType == ACTIVITY_TYPE_HOME) {
             // Home stack and activity are created in ActivityTestsBase#setupActivityManagerService
@@ -998,7 +997,7 @@
     @Test
     public void testFinishCurrentActivity() {
         // Create 2 activities on a new display.
-        final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+        final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         final ActivityStack stack1 = createStackForShouldBeVisibleTest(display,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final ActivityStack stack2 = createStackForShouldBeVisibleTest(display,
@@ -1065,6 +1064,7 @@
         StackOrderChangedListener listener = new StackOrderChangedListener();
         mDefaultDisplay.registerStackOrderChangedListener(listener);
         try {
+            mStack.mReparenting = true;
             mDefaultDisplay.addStack(mStack, 0);
         } finally {
             mDefaultDisplay.unregisterStackOrderChangedListener(listener);
@@ -1156,10 +1156,10 @@
 
     private void verifyShouldSleepActivities(boolean focusedStack,
             boolean keyguardGoingAway, boolean displaySleeping, boolean expected) {
-        final ActivityDisplay display = mock(ActivityDisplay.class);
+        final DisplayContent display = mock(DisplayContent.class);
         final KeyguardController keyguardController = mSupervisor.getKeyguardController();
 
-        doReturn(display).when(mRootActivityContainer).getActivityDisplay(anyInt());
+        doReturn(display).when(mStack).getDisplay();
         doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway();
         doReturn(displaySleeping).when(display).isSleeping();
         doReturn(focusedStack).when(mStack).isFocusedStackOnDisplay();
@@ -1168,7 +1168,7 @@
     }
 
     private static class StackOrderChangedListener
-            implements ActivityDisplay.OnStackOrderChangedListener {
+            implements DisplayContent.OnStackOrderChangedListener {
         public boolean mChanged = false;
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 018c10a..7e22dfc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -47,9 +47,9 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.ActivityDisplay.POSITION_BOTTOM;
-import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -502,7 +502,7 @@
      */
     @Test
     public void testTaskModeViolation() {
-        final ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
         display.removeAllTasks();
         assertNoTasks(display);
 
@@ -517,7 +517,7 @@
         assertNoTasks(display);
     }
 
-    private void assertNoTasks(ActivityDisplay display) {
+    private void assertNoTasks(DisplayContent display) {
         for (int i = display.getStackCount() - 1; i >= 0; --i) {
             final ActivityStack stack = display.getStackAt(i);
             assertFalse(stack.hasChild());
@@ -761,8 +761,8 @@
                 false /* mockGetLaunchStack */);
 
         // Create a secondary display at bottom.
-        final TestActivityDisplay secondaryDisplay =
-                new TestActivityDisplay.Builder(mService, 1000, 1500)
+        final TestDisplayContent secondaryDisplay =
+                new TestDisplayContent.Builder(mService, 1000, 1500)
                         .setPosition(POSITION_BOTTOM).build();
         final ActivityStack stack = secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -800,8 +800,8 @@
                 false /* mockGetLaunchStack */);
 
         // Create a secondary display with an activity.
-        final TestActivityDisplay secondaryDisplay =
-                new TestActivityDisplay.Builder(mService, 1000, 1500).build();
+        final TestDisplayContent secondaryDisplay =
+                new TestDisplayContent.Builder(mService, 1000, 1500).build();
         mRootActivityContainer.addChild(secondaryDisplay, POSITION_TOP);
         final ActivityRecord singleTaskActivity = createSingleTaskActivityOn(
                 secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
@@ -853,7 +853,7 @@
                 false /* mockGetLaunchStack */);
 
         // Create a secondary display at bottom.
-        final TestActivityDisplay secondaryDisplay = addNewActivityDisplayAt(POSITION_BOTTOM);
+        final TestDisplayContent secondaryDisplay = addNewDisplayContentAt(POSITION_BOTTOM);
         secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 07b7cf4..591ed51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -126,7 +126,7 @@
         assertEquals(0, removed.size());
         added.clear();
         // Check adding a display
-        ActivityDisplay newDisp1 = new TestActivityDisplay.Builder(mService, 600, 800).build();
+        DisplayContent newDisp1 = new TestDisplayContent.Builder(mService, 600, 800).build();
         assertEquals(1, added.size());
         assertEquals(0, changed.size());
         assertEquals(0, removed.size());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 0021cc5..b72cc94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -77,9 +77,9 @@
         mRootActivityContainer = mService.mRootActivityContainer;
     }
 
-    /** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */
-    TestActivityDisplay addNewActivityDisplayAt(int position) {
-        return new TestActivityDisplay.Builder(mService, 1000, 1500).setPosition(position).build();
+    /** Creates and adds a {@link TestDisplayContent} to supervisor at the given position. */
+    TestDisplayContent addNewDisplayContentAt(int position) {
+        return new TestDisplayContent.Builder(mService, 1000, 1500).setPosition(position).build();
     }
 
     /** Sets the default minimum task size to 1 so that tests can use small task sizes */
@@ -381,7 +381,7 @@
 
     static class StackBuilder {
         private final RootActivityContainer mRootActivityContainer;
-        private ActivityDisplay mDisplay;
+        private DisplayContent mDisplay;
         private int mStackId = -1;
         private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
         private int mActivityType = ACTIVITY_TYPE_STANDARD;
@@ -408,15 +408,8 @@
             return this;
         }
 
-        // TODO(display-merge): Remove
-        StackBuilder setDisplay(ActivityDisplay display) {
-            mDisplay = display;
-            return this;
-        }
-
         StackBuilder setDisplay(DisplayContent display) {
-            // TODO(display-merge): Remove cast
-            mDisplay = (ActivityDisplay) display;
+            mDisplay = display;
             return this;
         }
 
@@ -449,7 +442,7 @@
                     if (mOnTop) {
                         // We move the task to front again in order to regain focus after activity
                         // added to the stack.
-                        // Or {@link ActivityDisplay#mPreferredTopFocusableStack} could be other
+                        // Or {@link DisplayContent#mPreferredTopFocusableStack} could be other
                         // stacks (e.g. home stack).
                         stack.moveToFront("createActivityStack");
                     } else {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 6f1e6df..0c7fad4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -181,7 +181,7 @@
 
     @Test
     public void testLandscapeSeascapeRotationByPolicy() {
-        // This instance has been spied in {@link TestActivityDisplay}.
+        // This instance has been spied in {@link TestDisplayContent}.
         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
 
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 14c09eb..ccbafd4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -768,9 +768,8 @@
 
         activity.setRequestedOrientation(newOrientation);
 
-        // TODO(display-merge): Remove cast
-        verify((ActivityDisplay) dc, never()).updateDisplayOverrideConfigurationLocked(any(),
-                eq(activity), anyBoolean(), same(null));
+        verify(dc, never()).updateDisplayOverrideConfigurationLocked(any(), eq(activity),
+                anyBoolean(), same(null));
         assertEquals(dc.getDisplayRotation().getUserRotation(), dc.getRotation());
     }
 
@@ -964,7 +963,7 @@
                 invocation -> {
                     continued[0] = true;
                     return true;
-                }).when((ActivityDisplay) dc).updateDisplayOverrideConfigurationLocked();
+                }).when(dc).updateDisplayOverrideConfigurationLocked();
         final boolean[] called = new boolean[1];
         mWm.mDisplayRotationController =
                 new IDisplayWindowRotationController.Stub() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 9e5d9da..84914bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -80,7 +80,7 @@
 
     private TestPersisterQueue mPersisterQueue;
     private File mFolder;
-    private ActivityDisplay mTestDisplay;
+    private DisplayContent mTestDisplay;
     private String mDisplayUniqueId;
     private Task mTestTask;
     private Task mTaskWithDifferentUser;
@@ -104,9 +104,9 @@
         deleteRecursively(mFolder);
 
         mDisplayUniqueId = "test:" + Integer.toString(sNextUniqueId++);
-        mTestDisplay = new TestActivityDisplay.Builder(mService, 1000, 1500)
+        mTestDisplay = new TestDisplayContent.Builder(mService, 1000, 1500)
                 .setUniqueId(mDisplayUniqueId).build();
-        when(mRootActivityContainer.getActivityDisplay(eq(mDisplayUniqueId)))
+        when(mRootActivityContainer.getDisplayContent(eq(mDisplayUniqueId)))
                 .thenReturn(mTestDisplay);
 
         ActivityStack stack = mTestDisplay.createStack(TEST_WINDOWING_MODE,
@@ -180,7 +180,7 @@
     public void testReturnsEmptyDisplayIfDisplayIsNotFound() {
         mTarget.saveTask(mTestTask);
 
-        when(mRootActivityContainer.getActivityDisplay(eq(mDisplayUniqueId))).thenReturn(null);
+        when(mRootActivityContainer.getDisplayContent(eq(mDisplayUniqueId))).thenReturn(null);
 
         mTarget.getLaunchParams(mTestTask, null, mResult);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index b1cd931..eaf4aa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -41,8 +41,12 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -98,7 +102,7 @@
             UserManager.USER_TYPE_PROFILE_MANAGED);
     private static final int INVALID_STACK_ID = 999;
 
-    private ActivityDisplay mDisplay;
+    private DisplayContent mDisplay;
     private ActivityStack mStack;
     private TestTaskPersister mTaskPersister;
     private TestRecentTasks mRecentTasks;
@@ -112,7 +116,8 @@
     @Before
     public void setUp() throws Exception {
         mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
-        mDisplay = mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY);
+        spyOn(mTaskPersister);
+        mDisplay = mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY);
 
         // Set the recent tasks we should use for testing in this class.
         mRecentTasks = new TestRecentTasks(mService, mTaskPersister);
@@ -171,6 +176,46 @@
     }
 
     @Test
+    public void testPersister() {
+        // Add some tasks, ensure the persister is woken
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(mTasks.get(1));
+        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(1)), anyBoolean());
+        reset(mTaskPersister);
+
+        // Update a task, ensure the persister is woken
+        mRecentTasks.add(mTasks.get(0));
+        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+        reset(mTaskPersister);
+
+        // Remove some tasks, ensure the persister is woken
+        mRecentTasks.remove(mTasks.get(0));
+        mRecentTasks.remove(mTasks.get(1));
+        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(1)), anyBoolean());
+        reset(mTaskPersister);
+    }
+
+    @Test
+    public void testPersisterTrimmed() {
+        mRecentTasks.setOnlyTestVisibleRange();
+
+        // Limit the global maximum number of recent tasks to a fixed size
+        mRecentTasks.setGlobalMaxNumTasks(1 /* globalMaxNumTasks */);
+
+        mRecentTasks.add(mTasks.get(0));
+        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+        reset(mTaskPersister);
+
+        // Add N+1 tasks to ensure the previous task is trimmed
+        mRecentTasks.add(mTasks.get(1));
+        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(1)), anyBoolean());
+        assertTrimmed(mTasks.get(0));
+    }
+
+    @Test
     public void testAddTasksNoMultiple_expectNoTrim() {
         // Add same non-multiple-task document tasks will remove the task (to re-add it) but not
         // trim it
@@ -608,8 +653,8 @@
         mRecentTasks.setOnlyTestVisibleRange();
         mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */);
 
-        final ActivityDisplay singleTaskDisplay =
-                addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+        final DisplayContent singleTaskDisplay =
+                addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         singleTaskDisplay.setDisplayToSingleTaskInstance();
         ActivityStack singleTaskStack = singleTaskDisplay.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -788,7 +833,7 @@
         mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
 
         final ActivityStack homeStack = mDisplay.getHomeStack();
-        final ActivityDisplay otherDisplay = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+        final DisplayContent otherDisplay = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         final ActivityStack otherDisplayStack = otherDisplay.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 2374847..945928d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -138,8 +138,7 @@
     @Test
     public void testIncludedApps_expectTargetAndVisible() {
         mWm.setRecentsAnimationController(mController);
-        // TODO(display-merge): Remove cast
-        final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack(
+        final ActivityStack homeStack = mDisplayContent.getOrCreateStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
         final ActivityRecord homeActivity =
                 new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
@@ -164,8 +163,7 @@
     @Test
     public void testWallpaperIncluded_expectTarget() throws Exception {
         mWm.setRecentsAnimationController(mController);
-        // TODO(display-merge): Remove cast
-        final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack(
+        final ActivityStack homeStack = mDisplayContent.getOrCreateStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
         final ActivityRecord homeAppWindow =
                 new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
@@ -194,8 +192,7 @@
     @Test
     public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
         mWm.setRecentsAnimationController(mController);
-        // TODO(display-merge): Remove cast
-        final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack(
+        final ActivityStack homeStack = mDisplayContent.getOrCreateStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
         final ActivityRecord homeActivity =
                 new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
@@ -226,8 +223,7 @@
     @Test
     public void testFinish_expectTargetAndWallpaperAdaptersRemoved() {
         mWm.setRecentsAnimationController(mController);
-        // TODO(display-merge): Remove cast
-        final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack(
+        final ActivityStack homeStack = mDisplayContent.getOrCreateStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
         final ActivityRecord homeActivity =
                 new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 8fbb16c..07be3e4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -88,7 +88,7 @@
 
     @Test
     public void testRecentsActivityVisiblility() {
-        ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         ActivityStack recentsStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
         ActivityRecord recentActivity = new ActivityBuilder(mService)
@@ -116,7 +116,7 @@
 
     @Test
     public void testPreloadRecentsActivity() {
-        final ActivityDisplay defaultDisplay = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent defaultDisplay = mRootActivityContainer.getDefaultDisplay();
         final ActivityStack homeStack =
                 defaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
         defaultDisplay.positionStackAtTop(homeStack, false /* includingParents */);
@@ -149,7 +149,7 @@
         mService.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
                 null /* recentsAnimationRunner */);
 
-        ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         ActivityStack recentsStack = display.getStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS);
         assertThat(recentsStack).isNotNull();
@@ -178,7 +178,7 @@
     @Test
     public void testRestartRecentsActivity() throws Exception {
         // Have a recents activity that is not attached to its process (ActivityRecord.app = null).
-        ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         ActivityStack recentsStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
         ActivityRecord recentActivity = new ActivityBuilder(mService).setComponent(
@@ -207,7 +207,7 @@
 
     @Test
     public void testSetLaunchTaskBehindOfTargetActivity() {
-        ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         display.mDisplayContent.mBoundsAnimationController = mock(BoundsAnimationController.class);
         ActivityStack homeStack = display.getHomeStack();
         // Assume the home activity support recents.
@@ -253,7 +253,7 @@
 
     @Test
     public void testCancelAnimationOnVisibleStackOrderChange() {
-        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
         display.mDisplayContent.mBoundsAnimationController = mock(BoundsAnimationController.class);
         ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -299,7 +299,7 @@
 
     @Test
     public void testKeepAnimationOnHiddenStackOrderChange() {
-        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
         ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         new ActivityBuilder(mService)
@@ -335,7 +335,7 @@
 
     @Test
     public void testMultipleUserHomeActivity_findUserHomeTask() {
-        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
         ActivityStack homeStack = display.getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
         ActivityRecord otherUserHomeActivity = new ActivityBuilder(mService)
                 .setStack(homeStack)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 5417ade..2d45a59 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -128,7 +128,7 @@
         mRootActivityContainer.moveActivityToPinnedStack(firstActivity, sourceBounds,
                 0f /*aspectRatio*/, "initialMove");
 
-        final ActivityDisplay display = mFullscreenStack.getDisplay();
+        final DisplayContent display = mFullscreenStack.getDisplay();
         ActivityStack pinnedStack = display.getPinnedStack();
         // Ensure a task has moved over.
         ensureStackPlacement(pinnedStack, firstActivity);
@@ -166,7 +166,7 @@
 
     @Test
     public void testApplySleepTokens() {
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final KeyguardController keyguard = mSupervisor.getKeyguardController();
         final ActivityStack stack = new StackBuilder(mRootActivityContainer)
                 .setCreateActivity(false)
@@ -202,7 +202,7 @@
                 false /* expectResumeTopActivity */);
     }
 
-    private void verifySleepTokenBehavior(ActivityDisplay display, KeyguardController keyguard,
+    private void verifySleepTokenBehavior(DisplayContent display, KeyguardController keyguard,
             ActivityStack stack, boolean displaySleeping, boolean displayShouldSleep,
             boolean isFocusedStack, boolean keyguardShowing, boolean expectWakeFromSleep,
             boolean expectResumeTopActivity) {
@@ -225,7 +225,7 @@
      */
     @Test
     public void testRemovingStackOnAppCrash() {
-        final ActivityDisplay defaultDisplay = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent defaultDisplay = mRootActivityContainer.getDefaultDisplay();
         final int originalStackCount = defaultDisplay.getStackCount();
         final ActivityStack stack = mRootActivityContainer.getDefaultDisplay().createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
@@ -336,7 +336,7 @@
     @Test
     public void testFindTaskToMoveToFrontWhenRecentsOnTop() {
         // Create stack/task on default display.
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final ActivityStack targetStack = new StackBuilder(mRootActivityContainer)
                 .setOnTop(false)
                 .build();
@@ -360,14 +360,14 @@
     @Test
     public void testFindTaskToMoveToFrontWhenRecentsOnOtherDisplay() {
         // Create stack/task on default display.
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final ActivityStack targetStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */);
         final Task targetTask = new TaskBuilder(mSupervisor).setStack(targetStack).build();
 
         // Create Recents on secondary display.
-        final TestActivityDisplay secondDisplay = addNewActivityDisplayAt(
-                ActivityDisplay.POSITION_TOP);
+        final TestDisplayContent secondDisplay = addNewDisplayContentAt(
+                DisplayContent.POSITION_TOP);
         final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
         final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
@@ -387,7 +387,7 @@
     @Test
     public void testResumeActivityWhenNonTopmostStackIsTopFocused() {
         // Create a stack at bottom.
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
@@ -415,8 +415,9 @@
     @Test
     public void testResumeFocusedStacksStartsHomeActivity_NoActivities() {
         mFullscreenStack.removeIfPossible();
-        mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().removeIfPossible();
-        mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY)
+        mService.mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY).getHomeStack()
+                .removeIfPossible();
+        mService.mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY)
                 .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
 
         doReturn(true).when(mRootActivityContainer).resumeHomeActivity(any(), any(), anyInt());
@@ -437,13 +438,14 @@
     @Test
     public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() {
         mFullscreenStack.removeIfPossible();
-        mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().removeIfPossible();
-        mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY)
+        mService.mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY).getHomeStack()
+                .removeIfPossible();
+        mService.mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY)
                 .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
 
         // Create an activity on secondary display.
-        final TestActivityDisplay secondDisplay = addNewActivityDisplayAt(
-                ActivityDisplay.POSITION_TOP);
+        final TestDisplayContent secondDisplay = addNewDisplayContentAt(
+                DisplayContent.POSITION_TOP);
         final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
@@ -467,7 +469,7 @@
     @Test
     public void testResumeActivityLingeringTransition() {
         // Create a stack at top.
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
@@ -487,7 +489,7 @@
     @Test
     public void testResumeActivityLingeringTransition_notExecuted() {
         // Create a stack at bottom.
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
         final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
@@ -515,8 +517,8 @@
         mockResolveSecondaryHomeActivity();
 
         // Create secondary displays.
-        final TestActivityDisplay secondDisplay =
-                new TestActivityDisplay.Builder(mService, 1000, 1500)
+        final TestDisplayContent secondDisplay =
+                new TestDisplayContent.Builder(mService, 1000, 1500)
                         .setSystemDecorations(true).build();
 
         doReturn(true).when(mRootActivityContainer)
@@ -582,8 +584,8 @@
     @Test
     public void testStartSecondaryHomeOnDisplayWithUserKeyLocked() {
         // Create secondary displays.
-        final TestActivityDisplay secondDisplay =
-                new TestActivityDisplay.Builder(mService, 1000, 1500)
+        final TestDisplayContent secondDisplay =
+                new TestDisplayContent.Builder(mService, 1000, 1500)
                         .setSystemDecorations(true).build();
 
         // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false.
@@ -608,8 +610,8 @@
     @Test
     public void testStartSecondaryHomeOnDisplayWithoutSysDecorations() {
         // Create secondary displays.
-        final TestActivityDisplay secondDisplay =
-                new TestActivityDisplay.Builder(mService, 1000, 1500)
+        final TestDisplayContent secondDisplay =
+                new TestDisplayContent.Builder(mService, 1000, 1500)
                         .setSystemDecorations(false).build();
 
         mRootActivityContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome",
@@ -831,8 +833,8 @@
     @Test
     public void testGetLaunchStackWithRealCallerId() {
         // Create a non-system owned virtual display.
-        final TestActivityDisplay secondaryDisplay =
-                new TestActivityDisplay.Builder(mService, 1000, 1500)
+        final TestDisplayContent secondaryDisplay =
+                new TestDisplayContent.Builder(mService, 1000, 1500)
                         .setType(TYPE_VIRTUAL).setOwnerUid(100).build();
 
         // Create an activity with specify the original launch pid / uid.
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index 66f4d2a..b32cb8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -57,9 +57,9 @@
     @Test
     public void testCollectTasksByLastActiveTime() {
         // Create a number of stacks with tasks (of incrementing active time)
-        final ArrayList<ActivityDisplay> displays = new ArrayList<>();
-        final ActivityDisplay display =
-                new TestActivityDisplay.Builder(mService, 1000, 2500).build();
+        final ArrayList<DisplayContent> displays = new ArrayList<>();
+        final DisplayContent display =
+                new TestDisplayContent.Builder(mService, 1000, 2500).build();
         displays.add(display);
 
         final int numStacks = 2;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 46f95ed..be0fd2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -67,7 +67,7 @@
     private Task mTask;
     private ActivityRecord mActivity;
 
-    private void setUpApp(ActivityDisplay display) {
+    private void setUpApp(DisplayContent display) {
         mStack = new StackBuilder(mRootActivityContainer).setDisplay(display).build();
         mTask = mStack.getBottomMostTask();
         mActivity = mTask.getTopNonFinishingActivity();
@@ -79,7 +79,7 @@
 
     @Test
     public void testRestartProcessIfVisible() {
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
         doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
         mActivity.mVisibleRequested = true;
         mActivity.setSavedState(null /* savedState */);
@@ -99,7 +99,7 @@
     public void testKeepBoundsWhenChangingFromFreeformToFullscreen() {
         removeGlobalMinSizeRestriction();
         // create freeform display and a freeform app
-        ActivityDisplay display = new TestActivityDisplay.Builder(mService, 2000, 1000)
+        DisplayContent display = new TestDisplayContent.Builder(mService, 2000, 1000)
                 .setCanRotate(false)
                 .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build();
         setUpApp(display);
@@ -126,7 +126,7 @@
     @Test
     public void testFixedAspectRatioBoundsWithDecor() {
         final int decorHeight = 200; // e.g. The device has cutout.
-        setUpApp(new TestActivityDisplay.Builder(mService, 600, 800)
+        setUpApp(new TestDisplayContent.Builder(mService, 600, 800)
                 .setNotch(decorHeight).build());
 
         mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
@@ -152,11 +152,11 @@
 
     @Test
     public void testFixedScreenConfigurationWhenMovingToDisplay() {
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
 
         // Make a new less-tall display with lower density
-        final ActivityDisplay newDisplay =
-                new TestActivityDisplay.Builder(mService, 1000, 2000)
+        final DisplayContent newDisplay =
+                new TestDisplayContent.Builder(mService, 1000, 2000)
                         .setDensityDpi(200).build();
 
         mActivity = new ActivityBuilder(mService)
@@ -180,7 +180,7 @@
 
     @Test
     public void testFixedScreenBoundsWhenDisplaySizeChanged() {
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
         prepareUnresizable(-1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -203,7 +203,7 @@
 
     @Test
     public void testLetterboxFullscreenBounds() {
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
 
         // Fill out required fields on default display since WM-side is mocked out
         prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
@@ -213,10 +213,10 @@
 
     @Test
     public void testMoveToDifferentOrientDisplay() {
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
 
-        final ActivityDisplay newDisplay =
-                new TestActivityDisplay.Builder(mService, 2000, 1000)
+        final DisplayContent newDisplay =
+                new TestDisplayContent.Builder(mService, 2000, 1000)
                         .setCanRotate(false).build();
 
         prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
@@ -235,7 +235,7 @@
     @Test
     public void testFixedOrientRotateCutoutDisplay() {
         // Create a display with a notch/cutout
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).setNotch(60).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).setNotch(60).build());
         prepareUnresizable(1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
 
         final Rect origBounds = new Rect(mActivity.getBounds());
@@ -259,7 +259,7 @@
 
     @Test
     public void testFixedAspOrientChangeOrient() {
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
 
         prepareUnresizable(1.4f /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
         // The display aspect ratio 2.5 > 1.4 (max of activity), so the size is fitted.
@@ -286,7 +286,7 @@
 
     @Test
     public void testFixedScreenLayoutSizeBits() {
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
         final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO
                 | Configuration.SCREENLAYOUT_SIZE_NORMAL
                 | Configuration.SCREENLAYOUT_COMPAT_NEEDED;
@@ -314,9 +314,9 @@
 
     @Test
     public void testResetNonVisibleActivity() {
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
         prepareUnresizable(1.5f, SCREEN_ORIENTATION_UNSPECIFIED);
-        final ActivityDisplay display = mStack.getDisplay();
+        final DisplayContent display = mStack.getDisplay();
         // Resize the display so the activity is in size compatibility mode.
         resizeDisplay(display, 900, 1800);
 
@@ -354,7 +354,7 @@
      */
     @Test
     public void testHandleActivitySizeCompatMode() {
-        setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2000).build());
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2000).build());
         ActivityRecord activity = mActivity;
         activity.setState(ActivityStack.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
         prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
@@ -412,7 +412,7 @@
         ensureActivityConfiguration();
     }
 
-    private void resizeDisplay(ActivityDisplay display, int width, int height) {
+    private void resizeDisplay(DisplayContent display, int width, int height) {
         final DisplayContent displayContent = display.mDisplayContent;
         displayContent.mBaseDisplayWidth = width;
         displayContent.mBaseDisplayHeight = height;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index a172b65..811f46f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -286,13 +286,12 @@
 
         // Mock root, some default display, and home stack.
         spyOn(mWmService.mRoot);
-        final ActivityDisplay display = mAtmService.mRootActivityContainer.getDefaultDisplay();
+        final DisplayContent display = mAtmService.mRootActivityContainer.getDefaultDisplay();
         // Set default display to be in fullscreen mode. Devices with PC feature may start their
         // default display in freeform mode but some of tests in WmTests have implicit assumption on
         // that the default display is in fullscreen mode.
         display.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
         spyOn(display);
-        spyOn(display.mDisplayContent);
         final ActivityStack homeStack = display.getStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
         spyOn(homeStack);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 9a91efe..5aa41eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -107,7 +107,7 @@
     // =============================
     @Test
     public void testDefaultToPrimaryDisplay() {
-        createNewActivityDisplay(WINDOWING_MODE_FREEFORM);
+        createNewDisplayContent(WINDOWING_MODE_FREEFORM);
 
         assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
                 mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
@@ -127,8 +127,8 @@
 
     @Test
     public void testUsesPreviousDisplayIdIfSet() {
-        createNewActivityDisplay(WINDOWING_MODE_FREEFORM);
-        final TestActivityDisplay display = createNewActivityDisplay(WINDOWING_MODE_FULLSCREEN);
+        createNewDisplayContent(WINDOWING_MODE_FREEFORM);
+        final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
 
         mCurrent.mPreferredDisplayId = display.mDisplayId;
 
@@ -140,9 +140,9 @@
 
     @Test
     public void testUsesSourcesDisplayIdIfSet() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
-        final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -157,9 +157,9 @@
 
     @Test
     public void testUsesOptionsDisplayIdIfSet() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
-        final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -176,9 +176,9 @@
 
     @Test
     public void testUsesTasksDisplayIdPriorToSourceIfSet() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
-        final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -193,7 +193,7 @@
 
     @Test
     public void testUsesTaskDisplayIdIfSet() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
         ActivityRecord source = createSourceActivity(freeformDisplay);
 
@@ -205,9 +205,9 @@
 
     @Test
     public void testUsesNoDisplaySourceHandoverDisplayIdIfSet() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
-        final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
 
         mCurrent.mPreferredDisplayId = fullscreenDisplay.mDisplayId;
@@ -227,7 +227,7 @@
     // =====================================
     @Test
     public void testBoundsInOptionsInfersFreeformOnFreeformDisplay() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -258,7 +258,7 @@
 
     @Test
     public void testInheritsFreeformModeFromSourceOnFullscreenDisplay() {
-        final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
         final ActivityRecord source = createSourceActivity(fullscreenDisplay);
         source.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -272,7 +272,7 @@
 
     @Test
     public void testKeepsPictureInPictureLaunchModeInOptions() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -289,7 +289,7 @@
 
     @Test
     public void testKeepsPictureInPictureLaunchModeWithBoundsInOptions() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -321,7 +321,7 @@
 
     @Test
     public void testNonEmptyLayoutInfersFreeformOnFreeformDisplay() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -338,7 +338,7 @@
 
     @Test
     public void testNonEmptyLayoutInfersFreeformWithEmptySize() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -382,7 +382,7 @@
 
     @Test
     public void testRespectsFullyResolvedCurrentParam_Fullscreen() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -397,7 +397,7 @@
 
     @Test
     public void testRespectsModeFromFullyResolvedCurrentParam_NonEmptyBounds() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -414,7 +414,7 @@
     @Test
     public void testForceMaximizesUnresizeableApp() {
         mService.mSizeCompatFreeform = false;
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -437,7 +437,7 @@
     @Test
     public void testLaunchesAppInWindowOnFreeformDisplay() {
         mService.mSizeCompatFreeform = true;
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         Rect expectedLaunchBounds = new Rect(0, 0, 200, 100);
@@ -494,7 +494,7 @@
 
     @Test
     public void testUsesFullscreenWhenRequestedOnFreeformDisplay() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -510,7 +510,7 @@
 
     @Test
     public void testUsesFreeformByDefaultForPostNApp() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -525,7 +525,7 @@
 
     @Test
     public void testUsesFreeformByDefaultForPreNResizeableAppWithoutOrientationRequest() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -559,7 +559,7 @@
     // ===============================
     @Test
     public void testKeepsBoundsWithPictureInPictureLaunchModeInOptions() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -578,7 +578,7 @@
 
     @Test
     public void testRespectsLaunchBoundsWithFreeformSourceOnFullscreenDisplay() {
-        final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+        final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
         final ActivityRecord source = createSourceActivity(fullscreenDisplay);
         source.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -595,7 +595,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_LeftGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -611,7 +611,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -627,7 +627,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopLeftGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -644,7 +644,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_RightGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -660,7 +660,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -676,7 +676,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomRightGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -693,7 +693,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsOnFreeformDisplay_CenterToDisplay() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -709,7 +709,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsOnFreeformDisplay_LeftGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -725,7 +725,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsOnFreeformDisplay_TopGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -741,7 +741,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsOnFreeformDisplay_TopLeftGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -757,7 +757,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsOnFreeformDisplay_RightGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -773,7 +773,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsOnFreeformDisplay_BottomGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -789,7 +789,7 @@
 
     @Test
     public void testNonEmptyLayoutBoundsOnFreeformDisplay_RightBottomGravity() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -805,7 +805,7 @@
 
     @Test
     public void testNonEmptyLayoutFractionBoundsOnFreeformDisplay() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -821,7 +821,7 @@
 
     @Test
     public void testRespectBoundsFromFullyResolvedCurrentParam_NonEmptyBounds() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -836,7 +836,7 @@
 
     @Test
     public void testReturnBoundsForFullscreenWindowingMode() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -851,7 +851,7 @@
 
     @Test
     public void testUsesDisplayOrientationForNoSensorOrientation() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -875,7 +875,7 @@
 
     @Test
     public void testRespectsAppRequestedOrientation_Landscape() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -892,7 +892,7 @@
 
     @Test
     public void testRespectsAppRequestedOrientation_Portrait() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -909,7 +909,7 @@
 
     @Test
     public void testDefaultSizeSmallerThanBigScreen() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -933,7 +933,7 @@
 
     @Test
     public void testDefaultFreeformSizeCenteredToDisplay() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -954,7 +954,7 @@
 
     @Test
     public void testCascadesToSourceSizeForFreeform() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -981,7 +981,7 @@
 
     @Test
     public void testAdjustBoundsToFitDisplay_TopLeftOutOfDisplay() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1003,7 +1003,7 @@
 
     @Test
     public void testAdjustBoundsToFitDisplay_BottomRightOutOfDisplay() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1025,7 +1025,7 @@
 
     @Test
     public void testAdjustBoundsToFitNewDisplay_LargerThanDisplay() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1052,7 +1052,7 @@
         overrideConfig.setLayoutDirection(new Locale("ar", "EG"));
         mRootActivityContainer.onRequestedOverrideConfigurationChanged(overrideConfig);
 
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1073,7 +1073,7 @@
 
     @Test
     public void testRespectsLayoutMinDimensions() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         // This test case requires a relatively big app bounds to ensure the default size calculated
@@ -1098,7 +1098,7 @@
 
     @Test
     public void testRotatesInPlaceInitialBoundsMismatchOrientation() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1115,7 +1115,7 @@
 
     @Test
     public void testShiftsToRightForCloseToLeftBoundsWhenConflict() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         addFreeformTaskTo(freeformDisplay, new Rect(50, 50, 100, 150));
@@ -1132,7 +1132,7 @@
 
     @Test
     public void testShiftsToLeftForCloseToRightBoundsWhenConflict() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         addFreeformTaskTo(freeformDisplay, new Rect(1720, 50, 1830, 150));
@@ -1149,7 +1149,7 @@
 
     @Test
     public void testShiftsToRightFirstForHorizontallyCenteredAndCloseToTopWhenConflict() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         addFreeformTaskTo(freeformDisplay, new Rect(0, 0, 100, 300));
@@ -1166,7 +1166,7 @@
 
     @Test
     public void testShiftsToLeftNoSpaceOnRightForHorizontallyCenteredAndCloseToTopWhenConflict() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         addFreeformTaskTo(freeformDisplay, new Rect(120, 0, 240, 300));
@@ -1183,7 +1183,7 @@
 
     @Test
     public void testShiftsToBottomRightFirstForCenteredBoundsWhenConflict() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         addFreeformTaskTo(freeformDisplay, new Rect(120, 0, 240, 100));
@@ -1200,7 +1200,7 @@
 
     @Test
     public void testShiftsToTopLeftIfNoSpaceOnBottomRightForCenteredBoundsWhenConflict() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         addFreeformTaskTo(freeformDisplay, new Rect(120, 67, 240, 100));
@@ -1230,7 +1230,7 @@
 
     @Test
     public void testAdjustsBoundsToFitInDisplayFullyResolvedBounds() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         mCurrent.mPreferredDisplayId = Display.INVALID_DISPLAY;
@@ -1248,7 +1248,7 @@
 
     @Test
     public void testAdjustsBoundsToAvoidConflictFullyResolvedBounds() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+        final TestDisplayContent freeformDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FREEFORM);
 
         addFreeformTaskTo(freeformDisplay, new Rect(0, 0, 200, 100));
@@ -1283,7 +1283,7 @@
     @Test
     public void testNoMultiDisplaySupports() {
         final boolean orgValue = mService.mSupportsMultiDisplay;
-        final TestActivityDisplay display = createNewActivityDisplay(WINDOWING_MODE_FULLSCREEN);
+        final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
         mCurrent.mPreferredDisplayId = display.mDisplayId;
 
         try {
@@ -1296,8 +1296,8 @@
         }
     }
 
-    private TestActivityDisplay createNewActivityDisplay(int windowingMode) {
-        final TestActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+    private TestDisplayContent createNewDisplayContent(int windowingMode) {
+        final TestDisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
         display.setWindowingMode(windowingMode);
         display.setBounds(DISPLAY_BOUNDS);
         display.getConfiguration().densityDpi = DENSITY_DEFAULT;
@@ -1311,13 +1311,13 @@
         return display;
     }
 
-    private ActivityRecord createSourceActivity(TestActivityDisplay display) {
+    private ActivityRecord createSourceActivity(TestDisplayContent display) {
         final ActivityStack stack = display.createStack(display.getWindowingMode(),
                 ACTIVITY_TYPE_STANDARD, true);
         return new ActivityBuilder(mService).setStack(stack).setCreateTask(true).build();
     }
 
-    private void addFreeformTaskTo(TestActivityDisplay display, Rect bounds) {
+    private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) {
         final ActivityStack stack = display.createStack(display.getWindowingMode(),
                 ACTIVITY_TYPE_STANDARD, true);
         stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 39e7885..1a18df5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -193,7 +193,7 @@
     @FlakyTest(bugId = 137879065)
     public void testFitWithinBounds() {
         final Rect parentBounds = new Rect(10, 10, 200, 200);
-        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
         ActivityStack stack = display.createStack(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
@@ -232,7 +232,7 @@
     @Test
     @FlakyTest(bugId = 137879065)
     public void testBoundsOnModeChangeFreeformToFullscreen() {
-        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
         ActivityStack stack = new StackBuilder(mRootActivityContainer).setDisplay(display)
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         Task task = stack.getBottomMostTask();
@@ -265,9 +265,9 @@
     public void testFullscreenBoundsForcedOrientation() {
         final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
         final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
-        ActivityDisplay display = new TestActivityDisplay.Builder(
+        DisplayContent display = new TestDisplayContent.Builder(
                 mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
-        assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null);
+        assertTrue(mRootActivityContainer.getDisplayContent(display.mDisplayId) != null);
         // Fix the display orientation to landscape which is the natural rotation (0) for the test
         // display.
         final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
@@ -328,7 +328,7 @@
     @Test
     public void testIgnoresForcedOrientationWhenParentHandles() {
         final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
-        ActivityDisplay display = new TestActivityDisplay.Builder(
+        DisplayContent display = new TestDisplayContent.Builder(
                 mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
 
         display.getRequestedOverrideConfiguration().orientation =
@@ -809,7 +809,7 @@
     private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
             Rect expectedConfigBounds) {
 
-        ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+        DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
         ActivityStack stack = display.createStack(windowingMode, ACTIVITY_TYPE_STANDARD,
                 true /* onTop */);
         Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
similarity index 94%
rename from services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
rename to services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 247cd87..325cea7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -35,11 +35,11 @@
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 
-class TestActivityDisplay extends ActivityDisplay {
+class TestDisplayContent extends DisplayContent {
     private final ActivityStackSupervisor mSupervisor;
 
-    private TestActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
-        super(supervisor.mService.mRootActivityContainer, display);
+    private TestDisplayContent(ActivityStackSupervisor supervisor, Display display) {
+        super(display, supervisor.mService.mRootActivityContainer);
         // Normally this comes from display-properties as exposed by WM. Without that, just
         // hard-code to FULLSCREEN for tests.
         setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -140,13 +140,13 @@
             mInfo.logicalDensityDpi = dpi;
             return this;
         }
-        TestActivityDisplay build() {
+        TestDisplayContent build() {
             final int displayId = SystemServicesTestRule.sNextDisplayId++;
             final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
                     mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
-            final TestActivityDisplay newDisplay;
+            final TestDisplayContent newDisplay;
             synchronized (mService.mGlobalLock) {
-                newDisplay = new TestActivityDisplay(mService.mStackSupervisor, display);
+                newDisplay = new TestDisplayContent(mService.mStackSupervisor, display);
                 mService.mRootActivityContainer.addChild(newDisplay, mPosition);
             }
             // disable the normal system decorations
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 4d25a83..6108db3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -61,33 +61,33 @@
         assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
 
         // Register to display 1 as a listener.
-        TestActivityDisplay testActivityDisplay1 = createTestActivityDisplayInContainer();
-        mWpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
-        assertTrue(testActivityDisplay1.containsListener(mWpc));
-        assertEquals(testActivityDisplay1.mDisplayId, mWpc.getDisplayId());
+        TestDisplayContent testDisplayContent1 = createTestDisplayContentInContainer();
+        mWpc.registerDisplayConfigurationListenerLocked(testDisplayContent1);
+        assertTrue(testDisplayContent1.containsListener(mWpc));
+        assertEquals(testDisplayContent1.mDisplayId, mWpc.getDisplayId());
 
         // Move to display 2.
-        TestActivityDisplay testActivityDisplay2 = createTestActivityDisplayInContainer();
-        mWpc.registerDisplayConfigurationListenerLocked(testActivityDisplay2);
-        assertFalse(testActivityDisplay1.containsListener(mWpc));
-        assertTrue(testActivityDisplay2.containsListener(mWpc));
-        assertEquals(testActivityDisplay2.mDisplayId, mWpc.getDisplayId());
+        TestDisplayContent testDisplayContent2 = createTestDisplayContentInContainer();
+        mWpc.registerDisplayConfigurationListenerLocked(testDisplayContent2);
+        assertFalse(testDisplayContent1.containsListener(mWpc));
+        assertTrue(testDisplayContent2.containsListener(mWpc));
+        assertEquals(testDisplayContent2.mDisplayId, mWpc.getDisplayId());
 
-        // Null ActivityDisplay will not change anything.
+        // Null DisplayContent will not change anything.
         mWpc.registerDisplayConfigurationListenerLocked(null);
-        assertTrue(testActivityDisplay2.containsListener(mWpc));
-        assertEquals(testActivityDisplay2.mDisplayId, mWpc.getDisplayId());
+        assertTrue(testDisplayContent2.containsListener(mWpc));
+        assertEquals(testDisplayContent2.mDisplayId, mWpc.getDisplayId());
 
         // Unregister listener will remove the wpc from registered displays.
         mWpc.unregisterDisplayConfigurationListenerLocked();
-        assertFalse(testActivityDisplay1.containsListener(mWpc));
-        assertFalse(testActivityDisplay2.containsListener(mWpc));
+        assertFalse(testDisplayContent1.containsListener(mWpc));
+        assertFalse(testDisplayContent2.containsListener(mWpc));
         assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
 
         // Unregistration still work even if the display was removed.
-        mWpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
-        assertEquals(testActivityDisplay1.mDisplayId, mWpc.getDisplayId());
-        mRootActivityContainer.removeChild(testActivityDisplay1);
+        mWpc.registerDisplayConfigurationListenerLocked(testDisplayContent1);
+        assertEquals(testDisplayContent1.mDisplayId, mWpc.getDisplayId());
+        mRootActivityContainer.removeChild(testDisplayContent1);
         mWpc.unregisterDisplayConfigurationListenerLocked();
         assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
     }
@@ -129,7 +129,7 @@
         orderVerifier.verifyNoMoreInteractions();
     }
 
-    private TestActivityDisplay createTestActivityDisplayInContainer() {
-        return new TestActivityDisplay.Builder(mService, 1000, 1500).build();
+    private TestDisplayContent createTestDisplayContentInContainer() {
+        return new TestDisplayContent.Builder(mService, 1000, 1500).build();
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 22ba90b..6f53428 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -340,8 +340,8 @@
 
     /** Creates a {@link DisplayContent} and adds it to the system. */
     DisplayContent createNewDisplay(DisplayInfo info) {
-        final ActivityDisplay display =
-                new TestActivityDisplay.Builder(mWm.mAtmService, info).build();
+        final DisplayContent display =
+                new TestDisplayContent.Builder(mWm.mAtmService, info).build();
         return display.mDisplayContent;
     }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 735b9a1..198b4c3 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -16,11 +16,14 @@
 
 package com.android.server.soundtrigger;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
@@ -28,6 +31,7 @@
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
@@ -605,6 +609,67 @@
         return STATUS_ERROR;
     }
 
+    int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
+        synchronized (mLock) {
+            MetricsLogger.count(mContext, "sth_set_parameter", 1);
+            if (modelId == null || mModule == null) {
+                return SoundTrigger.STATUS_ERROR;
+            }
+            ModelData modelData = mModelDataMap.get(modelId);
+            if (modelData == null) {
+                Slog.w(TAG, "SetParameter: Invalid model id:" + modelId);
+                return SoundTrigger.STATUS_BAD_VALUE;
+            }
+            if (!modelData.isModelLoaded()) {
+                Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId);
+                return SoundTrigger.STATUS_BAD_VALUE;
+            }
+
+            return mModule.setParameter(modelData.getHandle(), modelParam, value);
+        }
+    }
+
+    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam)
+            throws UnsupportedOperationException, IllegalArgumentException {
+        synchronized (mLock) {
+            MetricsLogger.count(mContext, "sth_get_parameter", 1);
+            if (mModule == null) {
+                throw new UnsupportedOperationException("SoundTriggerModule not initialized");
+            }
+
+            ModelData modelData = mModelDataMap.get(modelId);
+            if (modelData == null) {
+                throw new IllegalArgumentException("Invalid model id:" + modelId);
+            }
+            if (!modelData.isModelLoaded()) {
+                throw new UnsupportedOperationException("Given model is not loaded:" + modelId);
+            }
+
+            return mModule.getParameter(modelData.getHandle(), modelParam);
+        }
+    }
+
+    @Nullable
+    ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            MetricsLogger.count(mContext, "sth_query_parameter", 1);
+            if (mModule == null) {
+                return null;
+            }
+            ModelData modelData = mModelDataMap.get(modelId);
+            if (modelData == null) {
+                Slog.w(TAG, "queryParameter: Invalid model id:" + modelId);
+                return null;
+            }
+            if (!modelData.isModelLoaded()) {
+                Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId);
+                return null;
+            }
+
+            return mModule.queryParameter(modelData.getHandle(), modelParam);
+        }
+    }
+
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
@@ -653,15 +718,15 @@
         }
         ModelData model = getModelDataForLocked(event.soundModelHandle);
         if (model == null || !model.isGenericModel()) {
-            Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
-                    event.soundModelHandle);
+            Slog.w(TAG, "Generic recognition event: Model does not exist for handle: "
+                    + event.soundModelHandle);
             return;
         }
 
         IRecognitionStatusCallback callback = model.getCallback();
         if (callback == null) {
-            Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
-                    event.soundModelHandle);
+            Slog.w(TAG, "Generic recognition event: Null callback for model handle: "
+                    + event.soundModelHandle);
             return;
         }
 
@@ -678,8 +743,8 @@
 
         RecognitionConfig config = model.getRecognitionConfig();
         if (config == null) {
-            Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
-                    event.soundModelHandle);
+            Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: "
+                    + event.soundModelHandle);
             return;
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 1dd3972..96d2df1 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -22,7 +22,9 @@
 import static android.content.pm.PackageManager.GET_META_DATA;
 import static android.content.pm.PackageManager.GET_SERVICES;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_NO_INIT;
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
 import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
 import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
@@ -39,9 +41,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
@@ -683,6 +687,101 @@
                 return properties;
             }
         }
+
+        @Override
+        public int setParameter(ParcelUuid soundModelId,
+                @ModelParams int modelParam, int value) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return STATUS_NO_INIT;
+            if (DEBUG) {
+                Slog.d(TAG, "setParameter(): id=" + soundModelId
+                        + ", param=" + modelParam
+                        + ", value=" + value);
+            }
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    "setParameter(): id=" + soundModelId
+                            + ", param=" + modelParam
+                            + ", value=" + value));
+
+            synchronized (mLock) {
+                SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+                if (soundModel == null) {
+                    Slog.e(TAG, soundModelId + " is not loaded. Loaded models: "
+                            + mLoadedModels.toString());
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent("setParameter(): "
+                            + soundModelId + " is not loaded"));
+
+                    return STATUS_BAD_VALUE;
+                }
+
+                return mSoundTriggerHelper.setParameter(soundModel.uuid, modelParam, value);
+            }
+        }
+
+        @Override
+        public int getParameter(@NonNull ParcelUuid soundModelId,
+                @ModelParams int modelParam)
+                throws UnsupportedOperationException, IllegalArgumentException {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) {
+                throw new UnsupportedOperationException("SoundTriggerHelper not initialized");
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "getParameter(): id=" + soundModelId
+                        + ", param=" + modelParam);
+            }
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    "getParameter(): id=" + soundModelId
+                            + ", param=" + modelParam));
+
+            synchronized (mLock) {
+                SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+                if (soundModel == null) {
+                    Slog.e(TAG, soundModelId + " is not loaded");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent("getParameter(): "
+                            + soundModelId + " is not loaded"));
+
+                    throw new IllegalArgumentException("sound model is not loaded");
+                }
+
+                return mSoundTriggerHelper.getParameter(soundModel.uuid, modelParam);
+            }
+        }
+
+        @Override
+        @Nullable
+        public ModelParamRange queryParameter(@NonNull ParcelUuid soundModelId,
+                @ModelParams int modelParam) {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return null;
+            if (DEBUG) {
+                Slog.d(TAG, "queryParameter(): id=" + soundModelId
+                        + ", param=" + modelParam);
+            }
+
+            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    "queryParameter(): id=" + soundModelId
+                            + ", param=" + modelParam));
+
+            synchronized (mLock) {
+                SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+                if (soundModel == null) {
+                    Slog.e(TAG, soundModelId + " is not loaded");
+
+                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            "queryParameter(): "
+                                    + soundModelId + " is not loaded"));
+
+                    return null;
+                }
+
+                return mSoundTriggerHelper.queryParameter(soundModel.uuid, modelParam);
+            }
+        }
     }
 
     /**
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 86630b0..fbb1380 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -27,8 +27,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
@@ -42,7 +40,6 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Supplier;
 
 /** Utility class for Telephony permission enforcement. */
 public final class TelephonyPermissions {
@@ -50,9 +47,6 @@
 
     private static final boolean DBG = false;
 
-    private static final Supplier<ITelephony> TELEPHONY_SUPPLIER = () ->
-            ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
-
     /**
      * Whether to disable the new device identifier access restrictions.
      */
@@ -138,49 +132,6 @@
     public static boolean checkReadPhoneState(
             Context context, int subId, int pid, int uid, String callingPackage,
             @Nullable  String callingFeatureId, String message) {
-        return checkReadPhoneState(
-                context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, callingFeatureId,
-                message);
-    }
-
-    /**
-     * Check whether the calling packages has carrier privileges for the passing subscription.
-     * @return {@code true} if the caller has carrier privileges, {@false} otherwise.
-     */
-    public static boolean checkCarrierPrivilegeForSubId(int subId) {
-        if (SubscriptionManager.isValidSubscriptionId(subId)
-                && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, Binder.getCallingUid())
-                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Check whether the app with the given pid/uid can read phone state.
-     *
-     * <p>This method behaves in one of the following ways:
-     * <ul>
-     *   <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the
-     *       READ_PHONE_STATE runtime permission, or carrier privileges on the given subId.
-     *   <li>throw SecurityException: if the caller didn't declare any of these permissions, or, for
-     *       apps which support runtime permissions, if the caller does not currently have any of
-     *       these permissions.
-     *   <li>return false: if the caller lacks all of these permissions and doesn't support runtime
-     *       permissions. This implies that the user revoked the ability to read phone state
-     *       manually (via AppOps). In this case we can't throw as it would break app compatibility,
-     *       so we return false to indicate that the calling function should return dummy data.
-     * </ul>
-     *
-     * <p>Note: for simplicity, this method always returns false for callers using legacy
-     * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged.
-     * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+
-     * devices.
-     */
-    @VisibleForTesting
-    public static boolean checkReadPhoneState(
-            Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
-            String callingPackage, @Nullable String callingFeatureId, String message) {
         try {
             context.enforcePermission(
                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message);
@@ -195,7 +146,7 @@
                 // If we don't have the runtime permission, but do have carrier privileges, that
                 // suffices for reading phone state.
                 if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                    enforceCarrierPrivilege(telephonySupplier, subId, uid, message);
+                    enforceCarrierPrivilege(context, subId, uid, message);
                     return true;
                 }
                 throw phoneStateException;
@@ -210,6 +161,19 @@
     }
 
     /**
+     * Check whether the calling packages has carrier privileges for the passing subscription.
+     * @return {@code true} if the caller has carrier privileges, {@false} otherwise.
+     */
+    public static boolean checkCarrierPrivilegeForSubId(Context context, int subId) {
+        if (SubscriptionManager.isValidSubscriptionId(subId)
+                && getCarrierPrivilegeStatus(context, subId, Binder.getCallingUid())
+                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Check whether the app with the given pid/uid can read phone state, or has carrier
      * privileges on any active subscription.
      *
@@ -225,28 +189,6 @@
      */
     public static boolean checkReadPhoneStateOnAnyActiveSub(Context context, int pid, int uid,
             String callingPackage, @Nullable String callingFeatureId, String message) {
-        return checkReadPhoneStateOnAnyActiveSub(context, TELEPHONY_SUPPLIER, pid, uid,
-                callingPackage, callingFeatureId, message);
-    }
-
-    /**
-     * Check whether the app with the given pid/uid can read phone state, or has carrier
-     * privileges on any active subscription.
-     *
-     * <p>If the app does not have carrier privilege, this method will return {@code false} instead
-     * of throwing a SecurityException. Therefore, the callers cannot tell the difference
-     * between M+ apps which declare the runtime permission but do not have it, and pre-M apps
-     * which declare the static permission but had access revoked via AppOps. Apps in the former
-     * category expect SecurityExceptions; apps in the latter don't. So this method is suitable for
-     * use only if the behavior in both scenarios is meant to be identical.
-     *
-     * @return {@code true} if the app can read phone state or has carrier privilege;
-     *         {@code false} otherwise.
-     */
-    @VisibleForTesting
-    public static boolean checkReadPhoneStateOnAnyActiveSub(
-            Context context, Supplier<ITelephony> telephonySupplier, int pid, int uid,
-            String callingPackage, @Nullable String callingFeatureId, String message) {
         try {
             context.enforcePermission(
                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message);
@@ -260,7 +202,7 @@
             } catch (SecurityException phoneStateException) {
                 // If we don't have the runtime permission, but do have carrier privileges, that
                 // suffices for reading phone state.
-                return checkCarrierPrivilegeForAnySubId(context, telephonySupplier, uid);
+                return checkCarrierPrivilegeForAnySubId(context, uid);
             }
         }
 
@@ -375,12 +317,11 @@
         }
 
         // If the calling package has carrier privileges for specified sub, then allow access.
-        if (checkCarrierPrivilegeForSubId(subId)) return true;
+        if (checkCarrierPrivilegeForSubId(context, subId)) return true;
 
         // If the calling package has carrier privileges for any subscription
         // and allowCarrierPrivilegeOnAnySub is set true, then allow access.
-        if (allowCarrierPrivilegeOnAnySub && checkCarrierPrivilegeForAnySubId(
-                context, TELEPHONY_SUPPLIER, uid)) {
+        if (allowCarrierPrivilegeOnAnySub && checkCarrierPrivilegeForAnySubId(context, uid)) {
             return true;
         }
 
@@ -468,7 +409,7 @@
                     uid) == PackageManager.PERMISSION_GRANTED) {
                 return false;
             }
-            if (checkCarrierPrivilegeForSubId(subId)) {
+            if (checkCarrierPrivilegeForSubId(context, subId)) {
                 return false;
             }
         }
@@ -484,26 +425,12 @@
     public static boolean checkReadCallLog(
             Context context, int subId, int pid, int uid, String callingPackage,
             @Nullable String callingPackageName) {
-        return checkReadCallLog(
-                context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, callingPackageName);
-    }
-
-    /**
-     * Check whether the app with the given pid/uid can read the call log.
-     * @return {@code true} if the specified app has the read call log permission and AppOpp granted
-     *      to it, {@code false} otherwise.
-     */
-    @VisibleForTesting
-    public static boolean checkReadCallLog(
-            Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
-            String callingPackage, @Nullable String callingFeatureId) {
-
         if (context.checkPermission(Manifest.permission.READ_CALL_LOG, pid, uid)
                 != PERMISSION_GRANTED) {
             // If we don't have the runtime permission, but do have carrier privileges, that
             // suffices for being able to see the call phone numbers.
             if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                enforceCarrierPrivilege(telephonySupplier, subId, uid, "readCallLog");
+                enforceCarrierPrivilege(context, subId, uid, "readCallLog");
                 return true;
             }
             return false;
@@ -513,7 +440,7 @@
         // revoked.
         AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         return appOps.noteOp(AppOpsManager.OPSTR_READ_CALL_LOG, uid, callingPackage,
-                callingFeatureId, null) == AppOpsManager.MODE_ALLOWED;
+                callingPackageName, null) == AppOpsManager.MODE_ALLOWED;
     }
 
     /**
@@ -526,7 +453,7 @@
             Context context, int subId, String callingPackage, @Nullable String callingFeatureId,
             String message) {
         return checkReadPhoneNumber(
-                context, TELEPHONY_SUPPLIER, subId, Binder.getCallingPid(), Binder.getCallingUid(),
+                context, subId, Binder.getCallingPid(), Binder.getCallingUid(),
                 callingPackage, callingFeatureId, message);
     }
 
@@ -538,7 +465,7 @@
      */
     @VisibleForTesting
     public static boolean checkReadPhoneNumber(
-            Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
+            Context context, int subId, int pid, int uid,
             String callingPackage, @Nullable String callingFeatureId, String message) {
         // Default SMS app can always read it.
         AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
@@ -553,7 +480,7 @@
         // First, check if we can read the phone state.
         try {
             return checkReadPhoneState(
-                    context, telephonySupplier, subId, pid, uid, callingPackage, callingFeatureId,
+                    context, subId, pid, uid, callingPackage, callingFeatureId,
                     message);
         } catch (SecurityException readPhoneStateSecurityException) {
         }
@@ -595,7 +522,7 @@
         }
 
         if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next.");
-        enforceCallingOrSelfCarrierPrivilege(subId, message);
+        enforceCallingOrSelfCarrierPrivilege(context, subId, message);
     }
 
     /**
@@ -615,7 +542,7 @@
             Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
         }
 
-        enforceCallingOrSelfCarrierPrivilege(subId, message);
+        enforceCallingOrSelfCarrierPrivilege(context, subId, message);
     }
 
     /**
@@ -636,7 +563,7 @@
                     + "check carrier privilege next.");
         }
 
-        enforceCallingOrSelfCarrierPrivilege(subId, message);
+        enforceCallingOrSelfCarrierPrivilege(context, subId, message);
     }
 
     /**
@@ -644,21 +571,18 @@
      *
      * @throws SecurityException if the caller does not have the required privileges
      */
-    public static void enforceCallingOrSelfCarrierPrivilege(int subId, String message) {
+    public static void enforceCallingOrSelfCarrierPrivilege(
+            Context context, int subId, String message) {
         // NOTE: It's critical that we explicitly pass the calling UID here rather than call
         // TelephonyManager#hasCarrierPrivileges directly, as the latter only works when called from
         // the phone process. When called from another process, it will check whether that process
         // has carrier privileges instead.
-        enforceCarrierPrivilege(subId, Binder.getCallingUid(), message);
-    }
-
-    private static void enforceCarrierPrivilege(int subId, int uid, String message) {
-        enforceCarrierPrivilege(TELEPHONY_SUPPLIER, subId, uid, message);
+        enforceCarrierPrivilege(context, subId, Binder.getCallingUid(), message);
     }
 
     private static void enforceCarrierPrivilege(
-            Supplier<ITelephony> telephonySupplier, int subId, int uid, String message) {
-        if (getCarrierPrivilegeStatus(telephonySupplier, subId, uid)
+            Context context, int subId, int uid, String message) {
+        if (getCarrierPrivilegeStatus(context, subId, uid)
                 != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
             if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege.");
             throw new SecurityException(message);
@@ -666,13 +590,12 @@
     }
 
     /** Returns whether the provided uid has carrier privileges for any active subscription ID. */
-    private static boolean checkCarrierPrivilegeForAnySubId(
-            Context context, Supplier<ITelephony> telephonySupplier, int uid) {
+    private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) {
         SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false);
         for (int activeSubId : activeSubIds) {
-            if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid)
+            if (getCarrierPrivilegeStatus(context, activeSubId, uid)
                     == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                 return true;
             }
@@ -680,18 +603,15 @@
         return false;
     }
 
-    private static int getCarrierPrivilegeStatus(
-            Supplier<ITelephony> telephonySupplier, int subId, int uid) {
-        ITelephony telephony = telephonySupplier.get();
+    private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) {
+        final long identity = Binder.clearCallingIdentity();
         try {
-            if (telephony != null) {
-                return telephony.getCarrierPrivilegeStatusForUid(subId, uid);
-            }
-        } catch (RemoteException e) {
-            // Fallback below.
+            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+                Context.TELEPHONY_SERVICE);
+            return telephonyManager.createForSubscriptionId(subId).getCarrierPrivilegeStatus(uid);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
-        Rlog.e(LOG_TAG, "Phone process is down, cannot check carrier privileges");
-        return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
     }
 
     /**
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 73faf9f..3940a3b 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -116,7 +116,8 @@
             ApnSetting.TYPE_CBS,
             ApnSetting.TYPE_IA,
             ApnSetting.TYPE_EMERGENCY,
-            ApnSetting.TYPE_MCX
+            ApnSetting.TYPE_MCX,
+            ApnSetting.TYPE_XCAP,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApnType {
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index b71bd85..94085e9 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -210,6 +210,12 @@
     private int mSubscriptionType;
 
     /**
+     * Whether uicc applications are configured to enable or disable.
+     * By default it's true.
+     */
+    private boolean mAreUiccApplicationsEnabled = true;
+
+    /**
      * @hide
      */
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -220,7 +226,7 @@
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, -1,
                 false, null, false, TelephonyManager.UNKNOWN_CARRIER_ID,
                 SubscriptionManager.PROFILE_CLASS_DEFAULT,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null, true);
     }
 
     /**
@@ -234,7 +240,7 @@
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, -1,
                 isOpportunistic, groupUUID, false, carrierId, profileClass,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null, true);
     }
 
     /**
@@ -246,7 +252,8 @@
             @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
             boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
             int carrierId, int profileClass, int subType, @Nullable String groupOwner,
-            @Nullable UiccAccessRule[] carrierConfigAccessRules) {
+            @Nullable UiccAccessRule[] carrierConfigAccessRules,
+            boolean areUiccApplicationsEnabled) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -272,6 +279,7 @@
         this.mSubscriptionType = subType;
         this.mGroupOwner = groupOwner;
         this.mCarrierConfigAccessRules = carrierConfigAccessRules;
+        this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled;
     }
 
     /**
@@ -660,6 +668,15 @@
         return mIsGroupDisabled;
     }
 
+    /**
+     * Return whether uicc applications are set to be enabled or disabled.
+     * @hide
+     */
+    @SystemApi
+    public boolean areUiccApplicationsEnabled() {
+        return mAreUiccApplicationsEnabled;
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<SubscriptionInfo> CREATOR = new Parcelable.Creator<SubscriptionInfo>() {
         @Override
         public SubscriptionInfo createFromParcel(Parcel source) {
@@ -691,12 +708,13 @@
             String groupOwner = source.readString();
             UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray(
                 UiccAccessRule.CREATOR);
+            boolean areUiccApplicationsEnabled = source.readBoolean();
 
             SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
                     carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc,
                     countryIso, isEmbedded, nativeAccessRules, cardString, cardId, isOpportunistic,
                     groupUUID, isGroupDisabled, carrierid, profileClass, subType, groupOwner,
-                    carrierConfigAccessRules);
+                    carrierConfigAccessRules, areUiccApplicationsEnabled);
             info.setAssociatedPlmns(ehplmns, hplmns);
             return info;
         }
@@ -736,6 +754,7 @@
         dest.writeStringArray(mHplmns);
         dest.writeString(mGroupOwner);
         dest.writeTypedArray(mCarrierConfigAccessRules, flags);
+        dest.writeBoolean(mAreUiccApplicationsEnabled);
     }
 
     @Override
@@ -778,15 +797,16 @@
                 + " hplmns=" + Arrays.toString(mHplmns)
                 + " subscriptionType=" + mSubscriptionType
                 + " mGroupOwner=" + mGroupOwner
-                + " carrierConfigAccessRules=" + mCarrierConfigAccessRules + "}";
+                + " carrierConfigAccessRules=" + mCarrierConfigAccessRules
+                + " mAreUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}";
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
-                mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc,
-                mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mNativeAccessRules,
-                mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner);
+                mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
+                mCardId, mDisplayName, mCarrierName, mNativeAccessRules, mIsGroupDisabled,
+                mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled);
     }
 
     @Override
@@ -809,6 +829,7 @@
                 && mIsEmbedded == toCompare.mIsEmbedded
                 && mIsOpportunistic == toCompare.mIsOpportunistic
                 && mIsGroupDisabled == toCompare.mIsGroupDisabled
+                && mAreUiccApplicationsEnabled == toCompare.mAreUiccApplicationsEnabled
                 && mCarrierId == toCompare.mCarrierId
                 && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
                 && Objects.equals(mIccId, toCompare.mIccId)
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index a82ae88..740622d 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -835,6 +835,12 @@
     public static final String IMSI = "imsi";
 
     /**
+     * Whether uicc applications is set to be enabled or disabled. By default it's enabled.
+     * @hide
+     */
+    public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
+
+    /**
      * Broadcast Action: The user has changed one of the default subs related to
      * data, phone calls, or sms</p>
      *
diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
index b75d533..407ad19 100644
--- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
+++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
@@ -16,12 +16,16 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
+import android.os.TelephonyServiceManager;
 import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccManager;
 
+import com.android.internal.util.Preconditions;
+
 
 /**
  * Class for performing registration for all telephony services.
@@ -34,6 +38,23 @@
     private TelephonyFrameworkInitializer() {
     }
 
+    private static volatile TelephonyServiceManager sTelephonyServiceManager;
+
+    /**
+     * Sets an instance of {@link TelephonyServiceManager} that allows
+     * the telephony mainline module to register/obtain telephony binder services. This is called
+     * by the platform during the system initialization.
+     *
+     * @param telephonyServiceManager instance of {@link TelephonyServiceManager} that allows
+     * the telephony mainline module to register/obtain telephony binder services.
+     */
+    public static void setTelephonyServiceManager(
+            @NonNull TelephonyServiceManager telephonyServiceManager) {
+        Preconditions.checkState(sTelephonyServiceManager == null,
+                "setTelephonyServiceManager called twice!");
+        sTelephonyServiceManager = Preconditions.checkNotNull(telephonyServiceManager);
+    }
+
     /**
      * Called by {@link SystemServiceRegistry}'s static initializer and registers all telephony
      * services to {@link Context}, so that {@link Context#getSystemService} can return them.
@@ -68,4 +89,9 @@
                 context -> new EuiccCardManager(context)
         );
     }
+
+    /** @hide */
+    public static TelephonyServiceManager getTelephonyServiceManager() {
+        return sTelephonyServiceManager;
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9f5acb3..5a4a3d0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -605,6 +605,7 @@
      *
      * @hide
      */
+    @SystemApi
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_EMERGENCY_ASSISTANCE =
             "android.telephony.action.EMERGENCY_ASSISTANCE";
@@ -2441,7 +2442,7 @@
 
     /*
      * When adding a network type to the list below, make sure to add the correct icon to
-     * MobileSignalController.mapIconSets().
+     * MobileSignalController.mapIconSets() as well as NETWORK_TYPES
      * Do not add negative types.
      */
     /** Network type is unknown */
@@ -2489,8 +2490,36 @@
     /** Current network is NR(New Radio) 5G. */
     public static final int NETWORK_TYPE_NR = TelephonyProtoEnums.NETWORK_TYPE_NR; // 20.
 
-    /** Max network type number. Update as new types are added. Don't add negative types. {@hide} */
-    public static final int MAX_NETWORK_TYPE = NETWORK_TYPE_NR;
+    private static final @NetworkType int[] NETWORK_TYPES = {
+            NETWORK_TYPE_GPRS,
+            NETWORK_TYPE_EDGE,
+            NETWORK_TYPE_UMTS,
+            NETWORK_TYPE_CDMA,
+            NETWORK_TYPE_EVDO_0,
+            NETWORK_TYPE_EVDO_A,
+            NETWORK_TYPE_1xRTT,
+            NETWORK_TYPE_HSDPA,
+            NETWORK_TYPE_HSUPA,
+            NETWORK_TYPE_HSPA,
+            NETWORK_TYPE_IDEN,
+            NETWORK_TYPE_EVDO_B,
+            NETWORK_TYPE_LTE,
+            NETWORK_TYPE_EHRPD,
+            NETWORK_TYPE_HSPAP,
+            NETWORK_TYPE_GSM,
+            NETWORK_TYPE_TD_SCDMA,
+            NETWORK_TYPE_IWLAN,
+            NETWORK_TYPE_LTE_CA,
+            NETWORK_TYPE_NR
+    };
+
+    /**
+     * Return a collection of all network types
+     * @return network types
+     */
+    public static @NonNull @NetworkType int[] getAllNetworkTypes() {
+        return NETWORK_TYPES;
+    }
 
     /**
      * Return the current data network type.
@@ -5133,7 +5162,8 @@
     */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     private ITelephony getITelephony() {
-        return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
+        return ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
+                .getTelephonyServiceManager().getTelephonyServiceRegisterer().get());
     }
 
     private ITelephonyRegistry getTelephonyRegistry() {
@@ -8358,6 +8388,44 @@
     }
 
     /**
+     * Shut down all the live radios over all the slot index.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void shutdownAllRadios() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.shutdownMobileRadios();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#shutdownMobileRadios", e);
+        }
+    }
+
+    /**
+     * Check if any radio is on over all the slot indexes.
+     *
+     * @return {@code true} if any radio is on over any slot index.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isAnyRadioPoweredOn() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.needMobileRadioShutdown();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#needMobileRadioShutdown", e);
+        }
+        return false;
+    }
+
+    /**
      * Radio explicitly powered off (e.g, airplane mode).
      * @hide
      */
@@ -9611,7 +9679,8 @@
      * {@link android.telephony.ModemActivityInfo} object.
      * @hide
      */
-    public void requestModemActivityInfo(ResultReceiver result) {
+    @SystemApi
+    public void requestModemActivityInfo(@NonNull ResultReceiver result) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
@@ -11702,6 +11771,32 @@
     }
 
     /**
+     * Get the calling application status about carrier privileges for the subscription created
+     * in TelephonyManager. Used by Telephony Module for permission checking.
+     *
+     * @param uid Uid to check.
+     * @return any value of {@link #CARRIER_PRIVILEGE_STATUS_HAS_ACCESS},
+     *         {@link #CARRIER_PRIVILEGE_STATUS_NO_ACCESS},
+     *         {@link #CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED}, or
+     *         {@link #CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES}
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getCarrierPrivilegeStatus(int uid) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getCarrierPrivilegeStatusForUid(getSubId(), uid);
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "getCarrierPrivilegeStatus RemoteException", ex);
+        }
+        return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+    }
+
+    /**
      * Return whether data is enabled for certain APN type. This will tell if framework will accept
      * corresponding network requests on a subId.
      *
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 60774e7..034fc22 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ContentValues;
 import android.database.Cursor;
-import android.hardware.radio.V1_4.ApnTypes;
+import android.hardware.radio.V1_5.ApnTypes;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -110,6 +110,8 @@
     public static final int TYPE_EMERGENCY = ApnTypes.EMERGENCY;
     /** APN type for MCX (Mission Critical Service) where X can be PTT/Video/Data */
     public static final int TYPE_MCX = ApnTypes.MCX;
+    /** APN type for XCAP. */
+    public static final int TYPE_XCAP = ApnTypes.XCAP;
 
     // Possible values for authentication types.
     /** No authentication type. */
@@ -198,6 +200,7 @@
         APN_TYPE_STRING_MAP.put("ia", TYPE_IA);
         APN_TYPE_STRING_MAP.put("emergency", TYPE_EMERGENCY);
         APN_TYPE_STRING_MAP.put("mcx", TYPE_MCX);
+        APN_TYPE_STRING_MAP.put("xcap", TYPE_XCAP);
         APN_TYPE_INT_MAP = new ArrayMap<Integer, String>();
         APN_TYPE_INT_MAP.put(TYPE_DEFAULT, "default");
         APN_TYPE_INT_MAP.put(TYPE_MMS, "mms");
@@ -210,6 +213,7 @@
         APN_TYPE_INT_MAP.put(TYPE_IA, "ia");
         APN_TYPE_INT_MAP.put(TYPE_EMERGENCY, "emergency");
         APN_TYPE_INT_MAP.put(TYPE_MCX, "mcx");
+        APN_TYPE_INT_MAP.put(TYPE_XCAP, "xcap");
 
         PROTOCOL_STRING_MAP = new ArrayMap<String, Integer>();
         PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP);
@@ -1944,8 +1948,9 @@
          * {@link ApnSetting} built from this builder otherwise.
          */
         public ApnSetting build() {
-            if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI |
-                    TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX)) == 0
+            if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI
+                    | TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX
+                    | TYPE_XCAP)) == 0
                 || TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) {
                 return null;
             }
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index aad7f3e..fde2c5a 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -154,6 +154,8 @@
     public static final String APN_TYPE_EMERGENCY = "emergency";
     /** APN type for Mission Critical Services */
     public static final String APN_TYPE_MCX = "mcx";
+    /** APN type for XCAP */
+    public static final String APN_TYPE_XCAP = "xcap";
     /** Array of all APN types */
     public static final String[] APN_TYPES = {APN_TYPE_DEFAULT,
             APN_TYPE_MMS,
@@ -165,7 +167,8 @@
             APN_TYPE_CBS,
             APN_TYPE_IA,
             APN_TYPE_EMERGENCY,
-            APN_TYPE_MCX
+            APN_TYPE_MCX,
+            APN_TYPE_XCAP,
     };
 
     public static final int RIL_CARD_MAX_APPS    = 8;
diff --git a/tests/ActivityManagerPerfTests/stub-app/Android.bp b/tests/ActivityManagerPerfTests/stub-app/Android.bp
new file mode 100644
index 0000000..a3c1f5b
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/Android.bp
@@ -0,0 +1,68 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "ActivityManagerPerfTestsStubApp1",
+    static_libs: ["ActivityManagerPerfTestsUtils"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    resource_dirs: [
+        "app1/res",
+        "res",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--rename-manifest-package com.android.stubs.am1",
+        "--auto-add-overlay",
+    ],
+}
+
+android_test_helper_app {
+    name: "ActivityManagerPerfTestsStubApp2",
+    static_libs: ["ActivityManagerPerfTestsUtils"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    resource_dirs: [
+        "app2/res",
+        "res",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--rename-manifest-package com.android.stubs.am2",
+        "--auto-add-overlay",
+    ],
+}
+
+android_test_helper_app {
+    name: "ActivityManagerPerfTestsStubApp3",
+    static_libs: ["ActivityManagerPerfTestsUtils"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    resource_dirs: [
+        "app3/res",
+        "res",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    aaptflags: [
+        "--rename-manifest-package com.android.stubs.am3",
+        "--auto-add-overlay",
+    ],
+}
+
diff --git a/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml
new file mode 100644
index 0000000..a57f64c
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.stubs.am">
+
+    <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
+    <application android:label="Android TestCase" >
+        <provider
+                android:authorities="@string/authority"
+                android:name=".TestContentProvider"
+                android:exported="true" />
+        <receiver
+                android:name=".TestBroadcastReceiver"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.stubs.am.ACTION_BROADCAST_TEST" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
+        <service
+                android:name=".InitService"
+                android:exported="true" />
+        <service
+                android:name=".TestService"
+                android:exported="true" />
+        <activity
+                android:name=".TestActivity"
+                android:excludeFromRecents="true"
+                android:turnScreenOn="true"
+                android:launchMode="singleTask">
+            <intent-filter>
+                <action android:name="com.android.stubs.am.ACTION_START_TEST_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
+
diff --git a/core/java/android/service/sms/IFinancialSmsService.aidl b/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml
similarity index 61%
copy from core/java/android/service/sms/IFinancialSmsService.aidl
copy to tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml
index caabe58..667472d 100644
--- a/core/java/android/service/sms/IFinancialSmsService.aidl
+++ b/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml
@@ -1,5 +1,6 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -12,19 +13,8 @@
  * 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.service.sms;
-
-import android.os.Bundle;
-import android.os.RemoteCallback;
-
-/**
- * Service used by financial apps to read sms messages.
- *
- * @hide
- */
-oneway interface IFinancialSmsService
-{
-    void getSmsMessages(in RemoteCallback callback, in Bundle params);
-}
\ No newline at end of file
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="authority" translatable="false">com.android.stubs.am1.testapp</string>
+</resources>
diff --git a/core/java/android/service/sms/IFinancialSmsService.aidl b/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml
similarity index 61%
copy from core/java/android/service/sms/IFinancialSmsService.aidl
copy to tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml
index caabe58..0852735 100644
--- a/core/java/android/service/sms/IFinancialSmsService.aidl
+++ b/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml
@@ -1,5 +1,6 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -12,19 +13,8 @@
  * 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.service.sms;
-
-import android.os.Bundle;
-import android.os.RemoteCallback;
-
-/**
- * Service used by financial apps to read sms messages.
- *
- * @hide
- */
-oneway interface IFinancialSmsService
-{
-    void getSmsMessages(in RemoteCallback callback, in Bundle params);
-}
\ No newline at end of file
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="authority" translatable="false">com.android.stubs.am2.testapp</string>
+</resources>
diff --git a/core/java/android/service/sms/IFinancialSmsService.aidl b/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml
similarity index 61%
copy from core/java/android/service/sms/IFinancialSmsService.aidl
copy to tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml
index caabe58..6895d72 100644
--- a/core/java/android/service/sms/IFinancialSmsService.aidl
+++ b/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml
@@ -1,5 +1,6 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -12,19 +13,8 @@
  * 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.service.sms;
-
-import android.os.Bundle;
-import android.os.RemoteCallback;
-
-/**
- * Service used by financial apps to read sms messages.
- *
- * @hide
- */
-oneway interface IFinancialSmsService
-{
-    void getSmsMessages(in RemoteCallback callback, in Bundle params);
-}
\ No newline at end of file
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="authority" translatable="false">com.android.stubs.am3.testapp</string>
+</resources>
diff --git a/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml b/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml
new file mode 100644
index 0000000..f79f006
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/content"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"/>
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java
new file mode 100644
index 0000000..18fdc44
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.stubs.am;
+
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_ACQUIRE_CONTENT_PROVIDER;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_BIND_SERVICE;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_RELEASE_CONTENT_PROVIDER;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_SEND_BROADCAST;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_START_ACTIVITY;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_STOP_ACTIVITY;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_UNBIND_SERVICE;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.ICommandReceiver;
+
+public class InitService extends Service {
+    private static final String TAG = "InitService";
+    public static final boolean VERBOSE = false;
+
+    private static class Stub extends ICommandReceiver.Stub {
+        private final Context mContext;
+        private final Messenger mCallback;
+        private final Handler mHandler;
+        private final Messenger mMessenger;
+        final ArrayMap<String, MyServiceConnection> mServices =
+                new ArrayMap<String, MyServiceConnection>();
+        final ArrayMap<Uri, IContentProvider> mProviders =
+                new ArrayMap<Uri, IContentProvider>();
+
+        Stub(Context context, Messenger callback) {
+            mContext = context;
+            mCallback = callback;
+            HandlerThread thread = new HandlerThread("result handler");
+            thread.start();
+            mHandler = new H(thread.getLooper());
+            mMessenger = new Messenger(mHandler);
+        }
+
+        private class H extends Handler {
+            H(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == Constants.MSG_DEFAULT) {
+                    if (VERBOSE) {
+                        Log.i(TAG, "H: received seq=" + msg.arg1 + ", result=" + msg.arg2);
+                    }
+                    sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, msg.arg1, msg.arg2, null);
+                } else if (msg.what == Constants.MSG_UNBIND_DONE) {
+                    if (VERBOSE) {
+                        Log.i(TAG, "H: received unbind=" + msg.obj);
+                    }
+                    synchronized (InitService.sStub) {
+                        Bundle b = (Bundle) msg.obj;
+                        String pkg = b.getString(Constants.EXTRA_SOURCE_PACKAGE, "");
+                        MyServiceConnection c = mServices.remove(pkg);
+                        if (c != null) {
+                            sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, c.mSeq,
+                                    Constants.RESULT_NO_ERROR, null);
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void sendCommand(int command, int seq, String sourcePackage, String targetPackage,
+                int flags, Bundle bundle) {
+            if (VERBOSE) {
+                Log.i(TAG, "Received command=" + command + ", seq=" + seq + ", from="
+                        + sourcePackage + ", to=" + targetPackage + ", flags=" + flags);
+            }
+            switch (command) {
+                case COMMAND_BIND_SERVICE:
+                    handleBindService(seq, targetPackage, flags, bundle);
+                    break;
+                case COMMAND_UNBIND_SERVICE:
+                    handleUnbindService(seq, targetPackage);
+                    break;
+                case COMMAND_ACQUIRE_CONTENT_PROVIDER:
+                    acquireProvider(seq, bundle.getParcelable(Constants.EXTRA_URI));
+                    break;
+                case COMMAND_RELEASE_CONTENT_PROVIDER:
+                    releaseProvider(seq, bundle.getParcelable(Constants.EXTRA_URI));
+                    break;
+                case COMMAND_SEND_BROADCAST:
+                    sendBroadcast(seq, targetPackage);
+                    break;
+                case COMMAND_START_ACTIVITY:
+                    startActivity(seq, targetPackage);
+                    break;
+                case COMMAND_STOP_ACTIVITY:
+                    stopActivity(seq, targetPackage);
+                    break;
+            }
+        }
+
+        private void handleBindService(int seq, String targetPackage, int flags, Bundle bundle) {
+            Intent intent = new Intent();
+            intent.setClassName(targetPackage, "com.android.stubs.am.TestService");
+            intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger);
+            if (bundle != null) {
+                intent.putExtras(bundle);
+            }
+            synchronized (this) {
+                if (!mServices.containsKey(targetPackage)) {
+                    MyServiceConnection c = new MyServiceConnection(mCallback);
+                    c.mSeq = seq;
+                    if (!mContext.bindService(intent, c, flags)) {
+                        Log.e(TAG, "Unable to bind to service in " + targetPackage);
+                        sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                                Constants.RESULT_ERROR, null);
+                    } else {
+                        if (VERBOSE) {
+                            Log.i(TAG, "Bind to service " + intent);
+                        }
+                        mServices.put(targetPackage, c);
+                    }
+                } else {
+                    sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                            Constants.RESULT_NO_ERROR, null);
+                }
+            }
+        }
+
+        private void handleUnbindService(int seq, String target) {
+            MyServiceConnection c = null;
+            synchronized (this) {
+                c = mServices.get(target);
+            }
+            if (c != null) {
+                c.mSeq = seq;
+                mContext.unbindService(c);
+            }
+        }
+
+        private void acquireProvider(int seq, Uri uri) {
+            ContentResolver resolver = mContext.getContentResolver();
+            IContentProvider provider = resolver.acquireProvider(uri);
+            if (provider != null) {
+                synchronized (mProviders) {
+                    mProviders.put(uri, provider);
+                }
+                sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                        Constants.RESULT_NO_ERROR, null);
+            } else {
+                sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                        Constants.RESULT_ERROR, null);
+            }
+        }
+
+        private void releaseProvider(int seq, Uri uri) {
+            ContentResolver resolver = mContext.getContentResolver();
+            IContentProvider provider;
+            synchronized (mProviders) {
+                provider = mProviders.get(uri);
+            }
+            if (provider != null) {
+                resolver.releaseProvider(provider);
+                synchronized (mProviders) {
+                    mProviders.remove(uri);
+                }
+            }
+            sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                    Constants.RESULT_NO_ERROR, null);
+        }
+
+        private void sendBroadcast(final int seq, String targetPackage) {
+            Intent intent = new Intent(Constants.STUB_ACTION_BROADCAST);
+            intent.setPackage(targetPackage);
+            mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+                            Constants.RESULT_NO_ERROR, null);
+                }
+            }, null, 0, null, null);
+        }
+
+        private void startActivity(int seq, String targetPackage) {
+            Intent intent = new Intent(Constants.STUB_ACTION_ACTIVITY);
+            intent.setClassName(targetPackage, "com.android.stubs.am.TestActivity");
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            intent.putExtra(Constants.EXTRA_ARG1, seq);
+            intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger);
+            mContext.startActivity(intent);
+        }
+
+        private void stopActivity(int seq, String targetPackage) {
+            Intent intent = new Intent(Constants.STUB_ACTION_ACTIVITY);
+            intent.setClassName(targetPackage, "com.android.stubs.am.TestActivity");
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            intent.putExtra(Constants.EXTRA_REQ_FINISH_ACTIVITY, true);
+            intent.putExtra(Constants.EXTRA_ARG1, seq);
+            intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger);
+            mContext.startActivity(intent);
+        }
+    };
+
+    private static void sendResult(Messenger callback, int what, int seq, int result, Object obj) {
+        Message msg = Message.obtain();
+        msg.what = what;
+        msg.arg1 = seq;
+        msg.arg2 = result;
+        msg.obj = obj;
+        try {
+            if (VERBOSE) {
+                Log.i(TAG, "Sending result seq=" + seq + ", result=" + result);
+            }
+            callback.send(msg);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in sending result back", e);
+        }
+        msg.recycle();
+    }
+
+    private static class MyServiceConnection implements ServiceConnection {
+        private Messenger mCallback;
+        int mSeq;
+
+        MyServiceConnection(Messenger callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, mSeq,
+                    Constants.RESULT_NO_ERROR, null);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (sStub) {
+                MyServiceConnection c = sStub.mServices.remove(name.getPackageName());
+                if (c != null) {
+                    sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, c.mSeq,
+                            Constants.RESULT_NO_ERROR, null);
+                }
+            }
+        }
+    }
+
+    private static Stub sStub = null;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new Binder();
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Messenger callback = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+        if (sStub == null) {
+            sStub = new Stub(getApplicationContext(), callback);
+        }
+
+        Bundle extras = new Bundle();
+        extras.putString(Constants.EXTRA_SOURCE_PACKAGE, getPackageName());
+        extras.putBinder(Constants.EXTRA_RECEIVER_CALLBACK, sStub);
+        sendResult(callback, Constants.REPLY_PACKAGE_START_RESULT,
+                intent.getIntExtra(Constants.EXTRA_SEQ, -1), 0, extras);
+        return START_NOT_STICKY;
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java
new file mode 100644
index 0000000..f7ea356
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.stubs.am;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+
+public class TestActivity extends Activity {
+    private static final String TAG = "TestActivity";
+    private static final boolean VERBOSE = InitService.VERBOSE;
+
+    private Messenger mResultTo;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onCreate()");
+        }
+        setContentView(R.layout.activity_content);
+        mResultTo = getIntent().getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+    }
+
+    @Override
+    public void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        setIntent(intent);
+        mResultTo = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+        if (intent.getBooleanExtra(Constants.EXTRA_REQ_FINISH_ACTIVITY, false)) {
+            if (VERBOSE) {
+                Log.i(TAG, getPackageName() + " finishing activity");
+            }
+            finish();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onResume()");
+        }
+        sendResult(Constants.RESULT_NO_ERROR);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onDestroy()");
+        }
+        sendResult(Constants.RESULT_NO_ERROR);
+    }
+
+    private void sendResult(int result) {
+        Message msg = Message.obtain();
+        msg.arg1 = getIntent().getIntExtra(Constants.EXTRA_ARG1, -1);
+        msg.arg2 = result;
+        try {
+            mResultTo.send(msg);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in sending result back", e);
+        }
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java
new file mode 100644
index 0000000..36c7a0a
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.stubs.am;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class TestBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = "TestBroadcastReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, context.getPackageName() + " received broadcast: " + intent);
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java
new file mode 100644
index 0000000..4fdbf1f
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.stubs.am;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+public class TestContentProvider extends ContentProvider {
+    private static final String TAG = "TestContentProvider";
+    private static final boolean VERBOSE = InitService.VERBOSE;
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public boolean onCreate() {
+        if (VERBOSE) {
+            Log.i(TAG, getContext().getPackageName() + " onCreate()");
+        }
+        return false;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java
new file mode 100644
index 0000000..ba220e0
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.stubs.am;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+
+public class TestService extends Service {
+    private static final String TAG = "TestService";
+    private static final boolean VERBOSE = InitService.VERBOSE;
+
+    private Binder mStub = new Binder();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onBind()");
+        }
+        return mStub;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onStartCommand()");
+        }
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        if (VERBOSE) {
+            Log.i(TAG, getPackageName() + " onUnbind()");
+        }
+        Messenger messenger = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+        Message msg = Message.obtain();
+        msg.what = Constants.MSG_UNBIND_DONE;
+        Bundle b = new Bundle();
+        b.putString(Constants.EXTRA_SOURCE_PACKAGE, getPackageName());
+        msg.obj = b;
+        try {
+            messenger.send(msg);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in sending result back", e);
+        }
+        return false;
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
index 76c40b2..475bb82 100644
--- a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
+++ b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
@@ -17,6 +17,9 @@
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="test-file-name" value="ActivityManagerPerfTests.apk"/>
         <option name="test-file-name" value="ActivityManagerPerfTestsTestApp.apk"/>
+        <option name="test-file-name" value="ActivityManagerPerfTestsStubApp1.apk"/>
+        <option name="test-file-name" value="ActivityManagerPerfTestsStubApp2.apk"/>
+        <option name="test-file-name" value="ActivityManagerPerfTestsStubApp3.apk"/>
         <option name="cleanup-apks" value="true"/>
     </target_preparer>
 
@@ -26,4 +29,4 @@
         <option name="package" value="com.android.frameworks.perftests.amtests"/>
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java
new file mode 100644
index 0000000..1d3ff06
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.perftests.am.tests;
+
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkLine;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
+import com.android.frameworks.perftests.am.util.AtraceUtils;
+import com.android.frameworks.perftests.am.util.TargetPackageUtils;
+import com.android.frameworks.perftests.am.util.Utils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This benchmark test basically manipulates 3 test packages, let them bind to
+ * each other, send broadcast to each other, etc. All of these actions essentially
+ * triggers OomAdjuster to update the oom_adj scores and proc state of them.
+ * In the meanwhile it'll also monitor the atrace output, extract duration between
+ * the start and exit entries of the updateOomAdjLocked, include each of them
+ * into the stats; when there are enough samples in the stats, the test will
+ * stop and output the mean/stddev time spent on the updateOomAdjLocked.
+ */
+@RunWith(JUnit4.class)
+@LargeTest
+public final class OomAdjPerfTest {
+    private static final String TAG = "OomAdjPerfTest";
+    private static final boolean VERBOSE = true;
+
+    private static final String STUB_PACKAGE1_NAME = "com.android.stubs.am1";
+    private static final String STUB_PACKAGE2_NAME = "com.android.stubs.am2";
+    private static final String STUB_PACKAGE3_NAME = "com.android.stubs.am3";
+
+    private static final Uri STUB_PACKAGE1_URI = new Uri.Builder().scheme(
+            ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am1.testapp").build();
+    private static final Uri STUB_PACKAGE2_URI = new Uri.Builder().scheme(
+            ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am2.testapp").build();
+    private static final Uri STUB_PACKAGE3_URI = new Uri.Builder().scheme(
+            ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am3.testapp").build();
+    private static final long NANOS_PER_MICROSECOND = 1000L;
+
+    private static final String ATRACE_CATEGORY = "am";
+    private static final String ATRACE_OOMADJ_PREFIX = "updateOomAdj_";
+
+    @Rule
+    public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
+    private TraceMarkParser mTraceMarkParser = new TraceMarkParser(this::shouldFilterTraceLine);
+    private final ArrayList<Long> mDurations = new ArrayList<Long>();
+    private Context mContext;
+    private HandlerThread mHandlerThread;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mHandlerThread = new HandlerThread("command receiver");
+        mHandlerThread.start();
+        TargetPackageUtils.initCommandResultReceiver(mHandlerThread.getLooper());
+
+        Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE1_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE2_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE3_NAME);
+        TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE1_NAME);
+        TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE2_NAME);
+        TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE3_NAME);
+    }
+
+    @After
+    public void tearDown() {
+        TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE1_NAME);
+        TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE2_NAME);
+        TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE3_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE1_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE2_NAME);
+        Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE3_NAME);
+        mHandlerThread.quitSafely();
+    }
+
+    @Test
+    public void testOomAdj() {
+        final AtraceUtils atraceUtils = AtraceUtils.getInstance(
+                InstrumentationRegistry.getInstrumentation());
+        final ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
+        atraceUtils.startTrace(ATRACE_CATEGORY);
+        while (state.keepRunning(mDurations)) {
+            runCUJWithOomComputationOnce();
+
+            // Now kick off the trace dump
+            mDurations.clear();
+            atraceUtils.performDump(mTraceMarkParser, this::handleTraceMarkSlices);
+        }
+        atraceUtils.stopTrace();
+    }
+
+    private boolean shouldFilterTraceLine(final TraceMarkLine line) {
+        return line.name.startsWith(ATRACE_OOMADJ_PREFIX);
+    }
+
+    private void handleTraceMarkSlices(String key, List<TraceMarkSlice> slices) {
+        for (TraceMarkSlice slice: slices) {
+            mDurations.add(slice.getDurationInMicroseconds() * NANOS_PER_MICROSECOND);
+        }
+    }
+
+    /**
+     * This tries to mimic a user journey, involes multiple activity/service starts/stop,
+     * the time spent on oom adj computation would be different between all these samples,
+     * but with enough samples, we'll be able to know the overall distribution of the time
+     * spent on it.
+     */
+    private void runCUJWithOomComputationOnce() {
+        // Start activity from package1
+        TargetPackageUtils.startActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        // Start activity from package2
+        TargetPackageUtils.startActivity(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+        // Start activity from package3
+        TargetPackageUtils.startActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+        // Stop activity in package1
+        TargetPackageUtils.stopActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        // Stop activity in package2
+        TargetPackageUtils.stopActivity(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+        // Stop activity in package3
+        TargetPackageUtils.stopActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+        // Bind from package1 to package2
+        TargetPackageUtils.bindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE);
+        // Acquire content provider from package 1 to package3
+        TargetPackageUtils.acquireProvider(STUB_PACKAGE1_NAME, STUB_PACKAGE3_NAME,
+                STUB_PACKAGE3_URI);
+        // Start activity from package1
+        TargetPackageUtils.startActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        // Bind from package2 to package3
+        TargetPackageUtils.bindService(STUB_PACKAGE2_NAME, STUB_PACKAGE3_NAME,
+                Context.BIND_AUTO_CREATE);
+        // Unbind from package 1 to package 2
+        TargetPackageUtils.unbindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, 0);
+        // Stop activity in package1
+        TargetPackageUtils.stopActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+
+        // Send broadcast to all of them
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+        // Bind from package1 to package2 again
+        TargetPackageUtils.bindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE);
+        // Create a cycle: package3 to package1
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME,
+                Context.BIND_AUTO_CREATE);
+
+        // Send broadcast to all of them again
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+        TargetPackageUtils.sendBroadcast(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+        // Start activity in package3
+        TargetPackageUtils.startActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+        // Break the cycle: unbind from package3 to package1
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, 0);
+
+        // Bind from package3 to package1 with waive priority
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
+        // Release provider connection
+        TargetPackageUtils.releaseProvider(STUB_PACKAGE1_NAME, STUB_PACKAGE3_NAME,
+                STUB_PACKAGE3_URI);
+        // Unbind from package1 to package2
+        TargetPackageUtils.unbindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, 0);
+        // Unbind from package2 to packagae3
+        TargetPackageUtils.unbindService(STUB_PACKAGE2_NAME, STUB_PACKAGE3_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_ABOVE_CLIENT
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_ALLOW_OOM_MANAGEMENT
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_IMPORTANT
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_NOT_FOREGROUND
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Bind from package3 to package2 with BIND_NOT_PERCEPTIBLE
+        TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+                Context.BIND_AUTO_CREATE | Context.BIND_NOT_PERCEPTIBLE);
+        // Unbind from package3 to packagae2
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+        // Stop activity in package3
+        TargetPackageUtils.stopActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+        // Unbind from package3 to package1
+        TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, 0);
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java
new file mode 100644
index 0000000..fcccfce
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.perftests.am.util;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+// Simplified version of AtraceLogger.
+public class AtraceUtils {
+    private static final String TAG = "AtraceUtils";
+    private static final boolean VERBOSE = true;
+
+    private static final String ATRACE_START = "atrace --async_start -b %d -c %s";
+    private static final String ATRACE_DUMP = "atrace --async_dump";
+    private static final String ATRACE_STOP = "atrace --async_stop";
+    private static final int DEFAULT_ATRACE_BUF_SIZE = 1024;
+
+    private UiAutomation mAutomation;
+    private static AtraceUtils sUtils = null;
+    private boolean mStarted = false;
+
+    private AtraceUtils(Instrumentation instrumentation) {
+        mAutomation = instrumentation.getUiAutomation();
+    }
+
+    public static AtraceUtils getInstance(Instrumentation instrumentation) {
+        if (sUtils == null) {
+            sUtils = new AtraceUtils(instrumentation);
+        }
+        return sUtils;
+    }
+
+    /**
+     * @param categories The list of the categories to trace, separated with space.
+     */
+    public void startTrace(String categories) {
+        synchronized (this) {
+            if (mStarted) {
+                throw new IllegalStateException("atrace already started");
+            }
+            Utils.runShellCommand(String.format(
+                    ATRACE_START, DEFAULT_ATRACE_BUF_SIZE, categories));
+            mStarted = true;
+        }
+    }
+
+    public void stopTrace() {
+        synchronized (this) {
+            mStarted = false;
+            Utils.runShellCommand(ATRACE_STOP);
+        }
+    }
+
+    /**
+     * @param parser The function that can accept the buffer of atrace dump and parse it.
+     * @param handler The parse result handler
+     */
+    public void performDump(TraceMarkParser parser,
+            BiConsumer<String, List<TraceMarkSlice>> handler) {
+        parser.reset();
+        try {
+            if (VERBOSE) {
+                Log.i(TAG, "Collecting atrace dump...");
+            }
+            writeDataToBuf(mAutomation.executeShellCommand(ATRACE_DUMP), parser);
+        } catch (IOException e) {
+            Log.e(TAG, "Error in reading dump", e);
+        }
+        parser.forAllSlices(handler);
+    }
+
+    // The given file descriptor here will be closed by this function
+    private void writeDataToBuf(ParcelFileDescriptor pfDescriptor,
+            TraceMarkParser parser) throws IOException {
+        InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor);
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                parser.visit(line);
+            }
+        }
+    }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
index 046dd6b..d7f4d9d 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
@@ -22,12 +22,18 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.ResultReceiver;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
 import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
 
 import org.junit.Assert;
 
@@ -36,6 +42,7 @@
 
 public class TargetPackageUtils {
     private static final String TAG = TargetPackageUtils.class.getSimpleName();
+    public static final boolean VERBOSE = true;
 
     public static final String PACKAGE_NAME = "com.android.frameworks.perftests.amteststestapp";
     public static final String ACTIVITY_NAME = PACKAGE_NAME + ".TestActivity";
@@ -48,6 +55,12 @@
     // Cache for test app's uid, so we only have to query it once.
     private static int sTestAppUid = -1;
 
+    private static final ArrayMap<String, ICommandReceiver> sStubPackages =
+            new ArrayMap<String, ICommandReceiver>();
+    private static final ArrayMap<Integer, CountDownLatch> sCommandLatches =
+            new ArrayMap<Integer, CountDownLatch>();
+    private static int sSeqNum = 0;
+
     /**
      * Kills the test package synchronously.
      */
@@ -145,5 +158,160 @@
         }
     }
 
+    private static boolean isUidRunning(int uid) {
+        return !Utils.runShellCommand(String.format("cmd activity get-uid-state %d", uid))
+                .contains("(NONEXISTENT)");
+    }
+
+    public static void startStubPackage(Context context, String pkgName) {
+        stopStubPackage(context, pkgName);
+        try {
+            Pair<Integer, CountDownLatch> pair = obtainLatch();
+            Intent intent = new Intent();
+            intent.setComponent(new ComponentName(pkgName, Constants.STUB_INIT_SERVICE_NAME));
+            intent.putExtra(Constants.EXTRA_SOURCE_PACKAGE, context.getPackageName());
+            intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, sMessenger);
+            intent.putExtra(Constants.EXTRA_SEQ, pair.first);
+            context.startService(intent);
+            Assert.assertTrue("Timeout when waiting for starting package " +  pkgName,
+                    pair.second.await(AWAIT_SERVICE_CONNECT_MS, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void stopStubPackage(Context context, String pkgName) {
+        final PackageManager pm = context.getPackageManager();
+        try {
+            final int uid = pm.getPackageUid(pkgName, 0);
+            if (isUidRunning(uid)) {
+                ActivityManager am = context.getSystemService(ActivityManager.class);
+                am.forceStopPackage(pkgName);
+                while (isUidRunning(uid)) {
+                    SystemClock.sleep(WAIT_TIME_MS);
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void initCommandResultReceiver(Looper looper) {
+        if (sMessenger == null) {
+            sMessenger = new Messenger(new H(looper));
+        }
+    }
+
+    private static void onPackageStartResult(int seq, Bundle bundle) {
+        ICommandReceiver receiver = ICommandReceiver.Stub.asInterface(
+                bundle.getBinder(Constants.EXTRA_RECEIVER_CALLBACK));
+        String sourcePkg = bundle.getString(Constants.EXTRA_SOURCE_PACKAGE);
+        sStubPackages.put(sourcePkg, receiver);
+        releaseLatch(seq);
+    }
+
+    private static void onCommandResult(int seq, int result) {
+        Assert.assertTrue("Error in command seq " + seq, result == Constants.RESULT_NO_ERROR);
+        releaseLatch(seq);
+    }
+
+    private static Messenger sMessenger = null;
+    private static class H extends Handler {
+        H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case Constants.REPLY_PACKAGE_START_RESULT:
+                    onPackageStartResult(msg.arg1 /* seq */, (Bundle) msg.obj);
+                    break;
+                case Constants.REPLY_COMMAND_RESULT:
+                    onCommandResult(msg.arg1, msg.arg2);
+                    break;
+            }
+        }
+    }
+
+    private static Pair<Integer, CountDownLatch> obtainLatch() {
+        CountDownLatch latch = new CountDownLatch(1);
+        int seq;
+        synchronized (sCommandLatches) {
+            seq = sSeqNum++;
+            sCommandLatches.put(seq, latch);
+        }
+        return new Pair<>(seq, latch);
+    }
+
+    private static void releaseLatch(int seq) {
+        synchronized (sCommandLatches) {
+            CountDownLatch latch = sCommandLatches.get(seq);
+            if (latch != null) {
+                latch.countDown();
+                sCommandLatches.remove(seq);
+            }
+        }
+    }
+
+    public static void sendCommand(int command, String sourcePackage, String targetPackage,
+            int flags, Bundle bundle, boolean waitForResult) {
+        ICommandReceiver receiver = sStubPackages.get(sourcePackage);
+        Assert.assertTrue("Package hasn't been started: " + sourcePackage, receiver != null);
+        try {
+            Pair<Integer, CountDownLatch> pair = null;
+            if (waitForResult) {
+                pair = obtainLatch();
+            }
+            if (VERBOSE) {
+                Log.i(TAG, "Sending command=" + command + ", seq=" + pair.first + ", from="
+                        + sourcePackage + ", to=" + targetPackage + ", flags=" + flags);
+            }
+            receiver.sendCommand(command, pair.first, sourcePackage, targetPackage, flags, bundle);
+            if (waitForResult) {
+                Assert.assertTrue("Timeout when waiting for command " + command + " from "
+                        + sourcePackage + " to " + targetPackage,
+                        pair.second.await(AWAIT_SERVICE_CONNECT_MS, TimeUnit.MILLISECONDS));
+            }
+        } catch (RemoteException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void bindService(String sourcePackage, String targetPackage, int flags) {
+        sendCommand(Constants.COMMAND_BIND_SERVICE, sourcePackage, targetPackage, flags, null,
+                true);
+    }
+
+    public static void unbindService(String sourcePackage, String targetPackage, int flags) {
+        sendCommand(Constants.COMMAND_UNBIND_SERVICE, sourcePackage, targetPackage, flags, null,
+                true);
+    }
+
+    public static void acquireProvider(String sourcePackage, String targetPackage, Uri uri) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(Constants.EXTRA_URI, uri);
+        sendCommand(Constants.COMMAND_ACQUIRE_CONTENT_PROVIDER, sourcePackage, targetPackage, 0,
+                bundle, true);
+    }
+
+    public static void releaseProvider(String sourcePackage, String targetPackage, Uri uri) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(Constants.EXTRA_URI, uri);
+        sendCommand(Constants.COMMAND_RELEASE_CONTENT_PROVIDER, sourcePackage, targetPackage, 0,
+                bundle, true);
+    }
+
+    public static void sendBroadcast(String sourcePackage, String targetPackage) {
+        sendCommand(Constants.COMMAND_SEND_BROADCAST, sourcePackage, targetPackage, 0, null, true);
+    }
+
+    public static void startActivity(String sourcePackage, String targetPackage) {
+        sendCommand(Constants.COMMAND_START_ACTIVITY, sourcePackage, targetPackage, 0, null, true);
+    }
+
+    public static void stopActivity(String sourcePackage, String targetPackage) {
+        sendCommand(Constants.COMMAND_STOP_ACTIVITY, sourcePackage, targetPackage, 0, null, true);
+    }
 }
 
diff --git a/tests/ActivityManagerPerfTests/utils/Android.bp b/tests/ActivityManagerPerfTests/utils/Android.bp
index 300b7ea..766c3ac 100644
--- a/tests/ActivityManagerPerfTests/utils/Android.bp
+++ b/tests/ActivityManagerPerfTests/utils/Android.bp
@@ -18,6 +18,7 @@
     srcs: [
         "src/**/*.java",
         "src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl",
+        "src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl",
     ],
     static_libs: [
         "androidx.test.rules",
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
index 9b076c5..8e58665 100644
--- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
@@ -30,4 +30,33 @@
 
     public static final String EXTRA_RECEIVER_CALLBACK = "receiver_callback_binder";
     public static final String EXTRA_LOOPER_IDLE_CALLBACK = "looper_idle_callback_binder";
+    public static final String EXTRA_SOURCE_PACKAGE = "source_package";
+    public static final String EXTRA_URI = "uri";
+    public static final String EXTRA_REQ_FINISH_ACTIVITY = "req_finish_activity";
+    public static final String EXTRA_SEQ = "seq";
+    public static final String EXTRA_ARG1 = "arg1";
+    public static final String EXTRA_ARG2 = "arg2";
+
+    public static final int RESULT_NO_ERROR = 0;
+    public static final int RESULT_ERROR = 1;
+    public static final String STUB_INIT_SERVICE_NAME = "com.android.stubs.am.InitService";
+
+    public static final int COMMAND_BIND_SERVICE = 1;
+    public static final int COMMAND_UNBIND_SERVICE = 2;
+    public static final int COMMAND_ACQUIRE_CONTENT_PROVIDER = 3;
+    public static final int COMMAND_RELEASE_CONTENT_PROVIDER = 4;
+    public static final int COMMAND_SEND_BROADCAST = 5;
+    public static final int COMMAND_START_ACTIVITY = 6;
+    public static final int COMMAND_STOP_ACTIVITY = 7;
+
+    public static final int MSG_DEFAULT = 0;
+    public static final int MSG_UNBIND_DONE = 1;
+
+    public static final int REPLY_PACKAGE_START_RESULT = 0;
+    public static final int REPLY_COMMAND_RESULT = 1;
+
+    public static final String STUB_ACTION_ACTIVITY =
+            "com.android.stubs.am.ACTION_START_TEST_ACTIVITY";
+    public static final String STUB_ACTION_BROADCAST =
+            "com.android.stubs.am.ACTION_BROADCAST_TEST";
 }
diff --git a/core/java/android/service/sms/IFinancialSmsService.aidl b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl
similarity index 65%
rename from core/java/android/service/sms/IFinancialSmsService.aidl
rename to tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl
index caabe58..59ea761 100644
--- a/core/java/android/service/sms/IFinancialSmsService.aidl
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,17 +14,11 @@
  * limitations under the License.
  */
 
-package android.service.sms;
+package com.android.frameworks.perftests.am.util;
 
 import android.os.Bundle;
-import android.os.RemoteCallback;
 
-/**
- * Service used by financial apps to read sms messages.
- *
- * @hide
- */
-oneway interface IFinancialSmsService
-{
-    void getSmsMessages(in RemoteCallback callback, in Bundle params);
-}
\ No newline at end of file
+interface ICommandReceiver {
+    oneway void sendCommand(int command, int seq, String sourcePackage, String targetPackage,
+            int flags, in Bundle bundle);
+}
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 9bb9983..c8d1ce1 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -19,14 +19,14 @@
     test_suites: ["general-tests"],
     target_required: [
         "block_device_writer_module",
-        "ApkVerityTestApp",
-        "ApkVerityTestAppSplit",
     ],
     data: [
         ":ApkVerityTestCertDer",
+        ":ApkVerityTestApp",
         ":ApkVerityTestAppFsvSig",
         ":ApkVerityTestAppDm",
         ":ApkVerityTestAppDmFsvSig",
+        ":ApkVerityTestAppSplit",
         ":ApkVerityTestAppSplitFsvSig",
         ":ApkVerityTestAppSplitDm",
         ":ApkVerityTestAppSplitDmFsvSig",
diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp
index d95af34..1f47b03 100644
--- a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp
+++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp
@@ -17,4 +17,5 @@
     defaults: ["cts_defaults"],
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+    test_suites: ["device-tests"],
 }
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index ef8face..b4cafe4 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -328,7 +328,8 @@
         long differentVersionCode = 2L;
         TestObserver observer = new TestObserver(OBSERVER_NAME_1) {
                 @Override
-                public int onHealthCheckFailed(VersionedPackage versionedPackage) {
+                public int onHealthCheckFailed(VersionedPackage versionedPackage,
+                        int failureReason) {
                     if (versionedPackage.getVersionCode() == VERSION_CODE) {
                         // Only rollback for specific versionCode
                         return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
@@ -1012,7 +1013,7 @@
             mImpact = impact;
         }
 
-        public int onHealthCheckFailed(VersionedPackage versionedPackage) {
+        public int onHealthCheckFailed(VersionedPackage versionedPackage, int failureReason) {
             mHealthCheckFailedPackages.add(versionedPackage.getPackageName());
             return mImpact;
         }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index b52880e..1678d5a 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -108,7 +108,9 @@
 
     String getCountryCode();
 
-    boolean isDualBandSupported();
+    boolean is5GHzBandSupported();
+
+    boolean is6GHzBandSupported();
 
     boolean needs5GHzToAnyApBandConversion();
 
@@ -186,7 +188,7 @@
 
     byte[] retrieveSoftApBackupData();
 
-    void restoreSoftApBackupData(in byte[] data);
+    SoftApConfiguration restoreSoftApBackupData(in byte[] data);
 
     void restoreSupplicantBackupData(in byte[] supplicantData, in byte[] ipConfigData);
 
diff --git a/wifi/java/android/net/wifi/WifiCondManager.java b/wifi/java/android/net/wifi/WifiCondManager.java
new file mode 100644
index 0000000..9ae7e3a
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiCondManager.java
@@ -0,0 +1,983 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.net.wifi.wificond.ChannelSettings;
+import android.net.wifi.wificond.HiddenNetwork;
+import android.net.wifi.wificond.NativeScanResult;
+import android.net.wifi.wificond.NativeWifiClient;
+import android.net.wifi.wificond.PnoSettings;
+import android.net.wifi.wificond.SingleScanSettings;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class provides methods for WifiNative to send control commands to wificond.
+ * NOTE: This class should only be used from WifiNative.
+ * @hide
+ */
+public class WifiCondManager implements IBinder.DeathRecipient {
+    private static final String TAG = "WifiCondManager";
+    private boolean mVerboseLoggingEnabled = false;
+
+    /**
+     * The {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}
+     * timeout, in milliseconds, after which
+     * {@link SendMgmtFrameCallback#onFailure(int)} will be called with reason
+     * {@link #SEND_MGMT_FRAME_ERROR_TIMEOUT}.
+     */
+    public static final int SEND_MGMT_FRAME_TIMEOUT_MS = 1000;
+
+    private static final String TIMEOUT_ALARM_TAG = TAG + " Send Management Frame Timeout";
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SCAN_TYPE_"},
+            value = {SCAN_TYPE_SINGLE_SCAN,
+                    SCAN_TYPE_PNO_SCAN})
+    public @interface ScanResultType {}
+
+    /** Get scan results for a single scan */
+    public static final int SCAN_TYPE_SINGLE_SCAN = 0;
+
+    /** Get scan results for Pno Scan */
+    public static final int SCAN_TYPE_PNO_SCAN = 1;
+
+    private AlarmManager mAlarmManager;
+    private Handler mEventHandler;
+
+    // Cached wificond binder handlers.
+    private IWificond mWificond;
+    private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
+    private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
+    private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>();
+    private HashMap<String, IScanEvent> mScanEventHandlers = new HashMap<>();
+    private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>();
+    private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>();
+    private Runnable mDeathEventHandler;
+    /**
+     * Ensures that no more than one sendMgmtFrame operation runs concurrently.
+     */
+    private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false);
+
+    /**
+     * Interface for a callback to be used to handle scan results.
+     */
+    public interface ScanEventCallback {
+        /**
+         * Called when scan results are available.
+         */
+        void onScanResultReady();
+
+        /**
+         * Called when a scan has failed.
+         */
+        void onScanFailed();
+    }
+
+    /**
+     * Interface for a callback to provide information about PNO scan request.
+     */
+    public interface PnoScanRequestCallback {
+        /**
+         * Called when the PNO scan is requested.
+         */
+        void onPnoRequestSucceeded();
+
+        /**
+         * Called when a PNO scan request fails.
+         */
+        void onPnoRequestFailed();
+    }
+
+    private class ScanEventHandler extends IScanEvent.Stub {
+        private ScanEventCallback mCallback;
+
+        ScanEventHandler(@NonNull ScanEventCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void OnScanResultReady() {
+            Log.d(TAG, "Scan result ready event");
+            mCallback.onScanResultReady();
+        }
+
+        @Override
+        public void OnScanFailed() {
+            Log.d(TAG, "Scan failed event");
+            mCallback.onScanFailed();
+        }
+    }
+
+    /**
+     * Result of a signal poll.
+     */
+    public static class SignalPollResult {
+        // RSSI value in dBM.
+        public int currentRssi;
+        //Transmission bit rate in Mbps.
+        public int txBitrate;
+        // Association frequency in MHz.
+        public int associationFrequency;
+        //Last received packet bit rate in Mbps.
+        public int rxBitrate;
+    }
+
+    /**
+     * WiFi interface transimission counters.
+     */
+    public static class TxPacketCounters {
+        // Number of successfully transmitted packets.
+        public int txSucceeded;
+        // Number of tramsmission failures.
+        public int txFailed;
+    }
+
+    /**
+     * Callbacks for SoftAp interface.
+     */
+    public interface SoftApListener {
+        /**
+         * Invoked when there is some fatal failure in the lower layers.
+         */
+        void onFailure();
+
+        /**
+         * Invoked when the associated stations changes.
+         */
+        void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected);
+
+        /**
+         * Invoked when the channel switch event happens.
+         */
+        void onSoftApChannelSwitched(int frequency, int bandwidth);
+    }
+
+    /**
+     * Callback to notify the results of a
+     * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()} call.
+     * Note: no callbacks will be triggered if the iface dies while sending a frame.
+     */
+    public interface SendMgmtFrameCallback {
+        /**
+         * Called when the management frame was successfully sent and ACKed by the recipient.
+         * @param elapsedTimeMs The elapsed time between when the management frame was sent and when
+         *                      the ACK was processed, in milliseconds, as measured by wificond.
+         *                      This includes the time that the send frame spent queuing before it
+         *                      was sent, any firmware retries, and the time the received ACK spent
+         *                      queuing before it was processed.
+         */
+        void onAck(int elapsedTimeMs);
+
+        /**
+         * Called when the send failed.
+         * @param reason The error code for the failure.
+         */
+        void onFailure(@SendMgmtFrameError int reason);
+    }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SEND_MGMT_FRAME_ERROR_"},
+            value = {SEND_MGMT_FRAME_ERROR_UNKNOWN,
+                    SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED,
+                    SEND_MGMT_FRAME_ERROR_NO_ACK,
+                    SEND_MGMT_FRAME_ERROR_TIMEOUT,
+                    SEND_MGMT_FRAME_ERROR_ALREADY_STARTED})
+    public @interface SendMgmtFrameError {}
+
+    // Send management frame error codes
+
+    /**
+     * Unknown error occurred during call to
+     * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1;
+
+    /**
+     * Specifying the MCS rate in
+     * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()} is not
+     * supported by this device.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED = 2;
+
+    /**
+     * Driver reported that no ACK was received for the frame transmitted using
+     * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_NO_ACK = 3;
+
+    /**
+     * Error code for when the driver fails to report on the status of the frame sent by
+     * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}
+     * after {@link #SEND_MGMT_FRAME_TIMEOUT_MS} milliseconds.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_TIMEOUT = 4;
+
+    /**
+     * An existing call to
+     * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}
+     * is in progress. Another frame cannot be sent until the first call completes.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5;
+
+
+    public WifiCondManager(Context context) {
+        mAlarmManager = (AlarmManager) context.getSystemService(AlarmManager.class);
+        mEventHandler = new Handler(context.getMainLooper());
+    }
+
+    @VisibleForTesting
+    public WifiCondManager(Context context, IWificond wificond) {
+        this(context);
+        mWificond = wificond;
+    }
+
+    private class PnoScanEventHandler extends IPnoScanEvent.Stub {
+        private ScanEventCallback mCallback;
+
+        PnoScanEventHandler(@NonNull ScanEventCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void OnPnoNetworkFound() {
+            Log.d(TAG, "Pno scan result event");
+            mCallback.onScanResultReady();
+        }
+
+        @Override
+        public void OnPnoScanFailed() {
+            Log.d(TAG, "Pno Scan failed event");
+            mCallback.onScanFailed();
+        }
+    }
+
+    /**
+     * Listener for AP Interface events.
+     */
+    private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub {
+        private SoftApListener mSoftApListener;
+
+        ApInterfaceEventCallback(SoftApListener listener) {
+            mSoftApListener = listener;
+        }
+
+        @Override
+        public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "onConnectedClientsChanged called with "
+                        + client.macAddress + " isConnected: " + isConnected);
+            }
+
+            mSoftApListener.onConnectedClientsChanged(client, isConnected);
+        }
+
+        @Override
+        public void onSoftApChannelSwitched(int frequency, int bandwidth) {
+            mSoftApListener.onSoftApChannelSwitched(frequency, bandwidth);
+        }
+    }
+
+    /**
+     * Callback triggered by wificond.
+     */
+    private class SendMgmtFrameEvent extends ISendMgmtFrameEvent.Stub {
+        private SendMgmtFrameCallback mCallback;
+        private AlarmManager.OnAlarmListener mTimeoutCallback;
+        /**
+         * ensures that mCallback is only called once
+         */
+        private boolean mWasCalled;
+
+        private void runIfFirstCall(Runnable r) {
+            if (mWasCalled) return;
+            mWasCalled = true;
+
+            mSendMgmtFrameInProgress.set(false);
+            r.run();
+        }
+
+        SendMgmtFrameEvent(@NonNull SendMgmtFrameCallback callback) {
+            mCallback = callback;
+            // called in main thread
+            mTimeoutCallback = () -> runIfFirstCall(() -> {
+                if (mVerboseLoggingEnabled) {
+                    Log.e(TAG, "Timed out waiting for ACK");
+                }
+                mCallback.onFailure(SEND_MGMT_FRAME_ERROR_TIMEOUT);
+            });
+            mWasCalled = false;
+
+            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime() + SEND_MGMT_FRAME_TIMEOUT_MS,
+                    TIMEOUT_ALARM_TAG, mTimeoutCallback, mEventHandler);
+        }
+
+        // called in binder thread
+        @Override
+        public void OnAck(int elapsedTimeMs) {
+            // post to main thread
+            mEventHandler.post(() -> runIfFirstCall(() -> {
+                mAlarmManager.cancel(mTimeoutCallback);
+                mCallback.onAck(elapsedTimeMs);
+            }));
+        }
+
+        // called in binder thread
+        @Override
+        public void OnFailure(int reason) {
+            // post to main thread
+            mEventHandler.post(() -> runIfFirstCall(() -> {
+                mAlarmManager.cancel(mTimeoutCallback);
+                mCallback.onFailure(reason);
+            }));
+        }
+    }
+
+    /**
+     * Called by the binder subsystem upon remote object death.
+     * Invoke all the register death handlers and clear state.
+     */
+    @Override
+    public void binderDied() {
+        mEventHandler.post(() -> {
+            Log.e(TAG, "Wificond died!");
+            clearState();
+            // Invalidate the global wificond handle on death. Will be refreshed
+            // on the next setup call.
+            mWificond = null;
+            if (mDeathEventHandler != null) {
+                mDeathEventHandler.run();
+            }
+        });
+    }
+
+    /** Enable or disable verbose logging of WificondControl.
+     *  @param enable True to enable verbose logging. False to disable verbose logging.
+     */
+    public void enableVerboseLogging(boolean enable) {
+        mVerboseLoggingEnabled = enable;
+    }
+
+    /**
+     * Initializes wificond & registers a death notification for wificond.
+     * This method clears any existing state in wificond daemon.
+     *
+     * @return Returns true on success.
+     */
+    public boolean initialize(@NonNull Runnable deathEventHandler) {
+        if (mDeathEventHandler != null) {
+            Log.e(TAG, "Death handler already present");
+        }
+        mDeathEventHandler = deathEventHandler;
+        tearDownInterfaces();
+        return true;
+    }
+
+    /**
+     * Helper method to retrieve the global wificond handle and register for
+     * death notifications.
+     */
+    private boolean retrieveWificondAndRegisterForDeath() {
+        if (mWificond != null) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Wificond handle already retrieved");
+            }
+            // We already have a wificond handle.
+            return true;
+        }
+        IBinder binder = ServiceManager.getService(Context.WIFI_COND_SERVICE);
+        mWificond = IWificond.Stub.asInterface(binder);
+        if (mWificond == null) {
+            Log.e(TAG, "Failed to get reference to wificond");
+            return false;
+        }
+        try {
+            mWificond.asBinder().linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register death notification for wificond");
+            // The remote has already died.
+            return false;
+        }
+        return true;
+    }
+
+    /**
+    * Setup interface for client mode via wificond.
+    * @return true on success.
+    */
+    public boolean setupInterfaceForClientMode(@NonNull String ifaceName,
+            @NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) {
+        Log.d(TAG, "Setting up interface for client mode");
+        if (!retrieveWificondAndRegisterForDeath()) {
+            return false;
+        }
+
+        IClientInterface clientInterface = null;
+        try {
+            clientInterface = mWificond.createClientInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to get IClientInterface due to remote exception");
+            return false;
+        }
+
+        if (clientInterface == null) {
+            Log.e(TAG, "Could not get IClientInterface instance from wificond");
+            return false;
+        }
+        Binder.allowBlocking(clientInterface.asBinder());
+
+        // Refresh Handlers
+        mClientInterfaces.put(ifaceName, clientInterface);
+        try {
+            IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();
+            if (wificondScanner == null) {
+                Log.e(TAG, "Failed to get WificondScannerImpl");
+                return false;
+            }
+            mWificondScanners.put(ifaceName, wificondScanner);
+            Binder.allowBlocking(wificondScanner.asBinder());
+            ScanEventHandler scanEventHandler = new ScanEventHandler(scanCallback);
+            mScanEventHandlers.put(ifaceName,  scanEventHandler);
+            wificondScanner.subscribeScanEvents(scanEventHandler);
+            PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(pnoScanCallback);
+            mPnoScanEventHandlers.put(ifaceName,  pnoScanEventHandler);
+            wificondScanner.subscribePnoScanEvents(pnoScanEventHandler);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
+        }
+
+        return true;
+    }
+
+    /**
+     * Teardown a specific STA interface configured in wificond.
+     *
+     * @return Returns true on success.
+     */
+    public boolean tearDownClientInterface(@NonNull String ifaceName) {
+        if (getClientInterface(ifaceName) == null) {
+            Log.e(TAG, "No valid wificond client interface handler");
+            return false;
+        }
+        try {
+            IWifiScannerImpl scannerImpl = mWificondScanners.get(ifaceName);
+            if (scannerImpl != null) {
+                scannerImpl.unsubscribeScanEvents();
+                scannerImpl.unsubscribePnoScanEvents();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unsubscribe wificond scanner due to remote exception");
+            return false;
+        }
+
+        if (mWificond == null) {
+            Log.e(TAG, "Reference to wifiCond is null");
+            return false;
+        }
+
+        boolean success;
+        try {
+            success = mWificond.tearDownClientInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to teardown client interface due to remote exception");
+            return false;
+        }
+        if (!success) {
+            Log.e(TAG, "Failed to teardown client interface");
+            return false;
+        }
+
+        mClientInterfaces.remove(ifaceName);
+        mWificondScanners.remove(ifaceName);
+        mScanEventHandlers.remove(ifaceName);
+        mPnoScanEventHandlers.remove(ifaceName);
+        return true;
+    }
+
+    /**
+    * Setup interface for softAp mode via wificond.
+    * @return true on success.
+    */
+    public boolean setupInterfaceForSoftApMode(@NonNull String ifaceName) {
+        Log.d(TAG, "Setting up interface for soft ap mode");
+        if (!retrieveWificondAndRegisterForDeath()) {
+            return false;
+        }
+
+        IApInterface apInterface = null;
+        try {
+            apInterface = mWificond.createApInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to get IApInterface due to remote exception");
+            return false;
+        }
+
+        if (apInterface == null) {
+            Log.e(TAG, "Could not get IApInterface instance from wificond");
+            return false;
+        }
+        Binder.allowBlocking(apInterface.asBinder());
+
+        // Refresh Handlers
+        mApInterfaces.put(ifaceName, apInterface);
+        return true;
+    }
+
+    /**
+     * Teardown a specific AP interface configured in wificond.
+     *
+     * @return Returns true on success.
+     */
+    public boolean tearDownSoftApInterface(@NonNull String ifaceName) {
+        if (getApInterface(ifaceName) == null) {
+            Log.e(TAG, "No valid wificond ap interface handler");
+            return false;
+        }
+
+        if (mWificond == null) {
+            Log.e(TAG, "Reference to wifiCond is null");
+            return false;
+        }
+
+        boolean success;
+        try {
+            success = mWificond.tearDownApInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to teardown AP interface due to remote exception");
+            return false;
+        }
+        if (!success) {
+            Log.e(TAG, "Failed to teardown AP interface");
+            return false;
+        }
+        mApInterfaces.remove(ifaceName);
+        mApInterfaceListeners.remove(ifaceName);
+        return true;
+    }
+
+    /**
+    * Teardown all interfaces configured in wificond.
+    * @return Returns true on success.
+    */
+    public boolean tearDownInterfaces() {
+        Log.d(TAG, "tearing down interfaces in wificond");
+        // Explicitly refresh the wificodn handler because |tearDownInterfaces()|
+        // could be used to cleanup before we setup any interfaces.
+        if (!retrieveWificondAndRegisterForDeath()) {
+            return false;
+        }
+
+        try {
+            for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
+                entry.getValue().unsubscribeScanEvents();
+                entry.getValue().unsubscribePnoScanEvents();
+            }
+            mWificond.tearDownInterfaces();
+            clearState();
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to tear down interfaces due to remote exception");
+        }
+
+        return false;
+    }
+
+    /** Helper function to look up the interface handle using name */
+    private IClientInterface getClientInterface(@NonNull String ifaceName) {
+        return mClientInterfaces.get(ifaceName);
+    }
+
+    /**
+     * Request signal polling to wificond.
+     * @param ifaceName Name of the interface.
+     * Returns an SignalPollResult object.
+     * Returns null on failure.
+     */
+    public SignalPollResult signalPoll(@NonNull String ifaceName) {
+        IClientInterface iface = getClientInterface(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "No valid wificond client interface handler");
+            return null;
+        }
+
+        int[] resultArray;
+        try {
+            resultArray = iface.signalPoll();
+            if (resultArray == null || resultArray.length != 4) {
+                Log.e(TAG, "Invalid signal poll result from wificond");
+                return null;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to do signal polling due to remote exception");
+            return null;
+        }
+        SignalPollResult pollResult = new SignalPollResult();
+        pollResult.currentRssi = resultArray[0];
+        pollResult.txBitrate = resultArray[1];
+        pollResult.associationFrequency = resultArray[2];
+        pollResult.rxBitrate = resultArray[3];
+        return pollResult;
+    }
+
+    /**
+     * Fetch TX packet counters on current connection from wificond.
+     * @param ifaceName Name of the interface.
+     * Returns an TxPacketCounters object.
+     * Returns null on failure.
+     */
+    public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) {
+        IClientInterface iface = getClientInterface(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "No valid wificond client interface handler");
+            return null;
+        }
+
+        int[] resultArray;
+        try {
+            resultArray = iface.getPacketCounters();
+            if (resultArray == null || resultArray.length != 2) {
+                Log.e(TAG, "Invalid signal poll result from wificond");
+                return null;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to do signal polling due to remote exception");
+            return null;
+        }
+        TxPacketCounters counters = new TxPacketCounters();
+        counters.txSucceeded = resultArray[0];
+        counters.txFailed = resultArray[1];
+        return counters;
+    }
+
+    /** Helper function to look up the scanner impl handle using name */
+    private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) {
+        return mWificondScanners.get(ifaceName);
+    }
+
+    /**
+    * Fetch the latest scan result from kernel via wificond.
+    * @param ifaceName Name of the interface.
+    * @return Returns an array of native scan results or an empty array on failure.
+    */
+    @NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName,
+            @ScanResultType int scanType) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return new ArrayList<>();
+        }
+        List<NativeScanResult> results = null;
+        try {
+            if (scanType == SCAN_TYPE_SINGLE_SCAN) {
+                results = Arrays.asList(scannerImpl.getScanResults());
+            } else {
+                results = Arrays.asList(scannerImpl.getPnoScanResults());
+            }
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to create ScanDetail ArrayList");
+        }
+        if (results == null) {
+            results = new ArrayList<>();
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "get " + results.size() + " scan results from wificond");
+        }
+
+        return results;
+    }
+
+    /**
+     * Return scan type for the parcelable {@link SingleScanSettings}
+     */
+    private static int getScanType(@WifiScanner.ScanType int scanType) {
+        switch (scanType) {
+            case WifiScanner.SCAN_TYPE_LOW_LATENCY:
+                return IWifiScannerImpl.SCAN_TYPE_LOW_SPAN;
+            case WifiScanner.SCAN_TYPE_LOW_POWER:
+                return IWifiScannerImpl.SCAN_TYPE_LOW_POWER;
+            case WifiScanner.SCAN_TYPE_HIGH_ACCURACY:
+                return IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+            default:
+                throw new IllegalArgumentException("Invalid scan type " + scanType);
+        }
+    }
+
+    /**
+     * Start a scan using wificond for the given parameters.
+     * @param ifaceName Name of the interface.
+     * @param scanType Type of scan to perform.
+     * @param freqs list of frequencies to scan for, if null scan all supported channels.
+     * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
+     * @return Returns true on success.
+     */
+    public boolean scan(@NonNull String ifaceName, @WifiScanner.ScanType int scanType,
+            Set<Integer> freqs, List<byte[]> hiddenNetworkSSIDs) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return false;
+        }
+        SingleScanSettings settings = new SingleScanSettings();
+        try {
+            settings.scanType = getScanType(scanType);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Invalid scan type ", e);
+            return false;
+        }
+        settings.channelSettings  = new ArrayList<>();
+        settings.hiddenNetworks  = new ArrayList<>();
+
+        if (freqs != null) {
+            for (Integer freq : freqs) {
+                ChannelSettings channel = new ChannelSettings();
+                channel.frequency = freq;
+                settings.channelSettings.add(channel);
+            }
+        }
+        if (hiddenNetworkSSIDs != null) {
+            for (byte[] ssid : hiddenNetworkSSIDs) {
+                HiddenNetwork network = new HiddenNetwork();
+                network.ssid = ssid;
+
+                // settings.hiddenNetworks is expected to be very small, so this shouldn't cause
+                // any performance issues.
+                if (!settings.hiddenNetworks.contains(network)) {
+                    settings.hiddenNetworks.add(network);
+                }
+            }
+        }
+
+        try {
+            return scannerImpl.scan(settings);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+     * Start PNO scan.
+     * @param ifaceName Name of the interface.
+     * @param pnoSettings Pno scan configuration.
+     * @return true on success.
+     */
+    public boolean startPnoScan(@NonNull String ifaceName, PnoSettings pnoSettings,
+            PnoScanRequestCallback callback) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return false;
+        }
+
+        try {
+            boolean success = scannerImpl.startPnoScan(pnoSettings);
+            if (success) {
+                callback.onPnoRequestSucceeded();
+            } else {
+                callback.onPnoRequestFailed();
+            }
+            return success;
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to start pno scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+     * Stop PNO scan.
+     * @param ifaceName Name of the interface.
+     * @return true on success.
+     */
+    public boolean stopPnoScan(@NonNull String ifaceName) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return false;
+        }
+        try {
+            return scannerImpl.stopPnoScan();
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to stop pno scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+     * Abort ongoing single scan.
+     * @param ifaceName Name of the interface.
+     */
+    public void abortScan(@NonNull String ifaceName) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return;
+        }
+        try {
+            scannerImpl.abortScan();
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request abortScan due to remote exception");
+        }
+    }
+
+    /**
+     * Query the list of valid frequencies for the provided band.
+     * The result depends on the on the country code that has been set.
+     *
+     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+     * The following bands are supported {@link @WifiScanner.WifiBandBasic}:
+     * WifiScanner.WIFI_BAND_24_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
+     * WifiScanner.WIFI_BAND_6_GHZ
+     * @return frequencies vector of valid frequencies (MHz), or null for error.
+     * @throws IllegalArgumentException if band is not recognized.
+     */
+    public int [] getChannelsForBand(@WifiScanner.WifiBandBasic int band) {
+        if (mWificond == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return null;
+        }
+        try {
+            switch (band) {
+                case WifiScanner.WIFI_BAND_24_GHZ:
+                    return mWificond.getAvailable2gChannels();
+                case WifiScanner.WIFI_BAND_5_GHZ:
+                    return mWificond.getAvailable5gNonDFSChannels();
+                case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
+                    return mWificond.getAvailableDFSChannels();
+                case WifiScanner.WIFI_BAND_6_GHZ:
+                    return mWificond.getAvailable6gChannels();
+                default:
+                    throw new IllegalArgumentException("unsupported band " + band);
+            }
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+        }
+        return null;
+    }
+
+    /** Helper function to look up the interface handle using name */
+    private IApInterface getApInterface(@NonNull String ifaceName) {
+        return mApInterfaces.get(ifaceName);
+    }
+
+    /**
+     * Register the provided listener for SoftAp events.
+     *
+     * @param ifaceName Name of the interface.
+     * @param listener Callback for AP events.
+     * @return true on success, false otherwise.
+     */
+    public boolean registerApListener(@NonNull String ifaceName, SoftApListener listener) {
+        IApInterface iface = getApInterface(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "No valid ap interface handler");
+            return false;
+        }
+        try {
+            IApInterfaceEventCallback  callback = new ApInterfaceEventCallback(listener);
+            mApInterfaceListeners.put(ifaceName, callback);
+            boolean success = iface.registerCallback(callback);
+            if (!success) {
+                Log.e(TAG, "Failed to register ap callback.");
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in registering AP callback: " + e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * See {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int)}
+     */
+    public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame,
+            @NonNull SendMgmtFrameCallback callback, int mcs) {
+
+        if (callback == null) {
+            Log.e(TAG, "callback cannot be null!");
+            return;
+        }
+
+        if (frame == null) {
+            Log.e(TAG, "frame cannot be null!");
+            callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
+            return;
+        }
+
+        // TODO (b/112029045) validate mcs
+        IClientInterface clientInterface = getClientInterface(ifaceName);
+        if (clientInterface == null) {
+            Log.e(TAG, "No valid wificond client interface handler");
+            callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
+            return;
+        }
+
+        if (!mSendMgmtFrameInProgress.compareAndSet(false, true)) {
+            Log.e(TAG, "An existing management frame transmission is in progress!");
+            callback.onFailure(SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
+            return;
+        }
+
+        SendMgmtFrameEvent sendMgmtFrameEvent = new SendMgmtFrameEvent(callback);
+        try {
+            clientInterface.SendMgmtFrame(frame, sendMgmtFrameEvent, mcs);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while starting link probe: " + e);
+            // Call sendMgmtFrameEvent.OnFailure() instead of callback.onFailure() so that
+            // sendMgmtFrameEvent can clean up internal state, such as cancelling the timer.
+            sendMgmtFrameEvent.OnFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        }
+    }
+
+    /**
+     * Clear all internal handles.
+     */
+    private void clearState() {
+        // Refresh handlers
+        mClientInterfaces.clear();
+        mWificondScanners.clear();
+        mPnoScanEventHandlers.clear();
+        mScanEventHandlers.clear();
+        mApInterfaces.clear();
+        mApInterfaceListeners.clear();
+        mSendMgmtFrameInProgress.set(false);
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 50d62a0..9691bda 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -51,16 +51,16 @@
 import android.os.WorkSource;
 import android.os.connectivity.WifiActivityEnergyInfo;
 import android.text.TextUtils;
+import android.util.CloseGuard;
 import android.util.Log;
 import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
-import dalvik.system.CloseGuard;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import java.net.InetAddress;
 import java.util.ArrayList;
@@ -2156,8 +2156,6 @@
     /** @hide */
     public static final long WIFI_FEATURE_INFRA            = 0x0001L;  // Basic infrastructure mode
     /** @hide */
-    public static final long WIFI_FEATURE_INFRA_5G         = 0x0002L;  // Support for 5 GHz Band
-    /** @hide */
     public static final long WIFI_FEATURE_PASSPOINT        = 0x0004L;  // Support for GAS/ANQP
     /** @hide */
     public static final long WIFI_FEATURE_P2P              = 0x0008L;  // Wifi-Direct
@@ -2227,8 +2225,6 @@
     public static final long WIFI_FEATURE_MBO              = 0x800000000L; // MBO Support
     /** @hide */
     public static final long WIFI_FEATURE_OCE              = 0x1000000000L; // OCE Support
-    /** @hide */
-    public static final long WIFI_FEATURE_INFRA_6G         = 0x2000000000L; // Support 6 GHz band
 
     private long getSupportedFeatures() {
         try {
@@ -2242,22 +2238,7 @@
         return (getSupportedFeatures() & feature) == feature;
     }
 
-    /**
-     * @return true if this adapter supports 5 GHz band
-     */
-    public boolean is5GHzBandSupported() {
-        return isFeatureSupported(WIFI_FEATURE_INFRA_5G);
-    }
-
-    /**
-     * @return true if the device supports operating in the 6 GHz band and Wi-Fi is enabled,
-     *         false otherwise.
-     */
-    public boolean is6GHzBandSupported() {
-        return isFeatureSupported(WIFI_FEATURE_INFRA_6G);
-    }
-
-    /**
+   /**
      * @return true if this adapter supports Passpoint
      * @hide
      */
@@ -2379,6 +2360,30 @@
     }
 
     /**
+     * Check if the chipset supports 5GHz band.
+     * @return {@code true} if supported, {@code false} otherwise.
+     */
+    public boolean is5GHzBandSupported() {
+        try {
+            return mService.is5GHzBandSupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Check if the chipset supports 6GHz band.
+     * @return {@code true} if supported, {@code false} otherwise.
+     */
+    public boolean is6GHzBandSupported() {
+        try {
+            return mService.is6GHzBandSupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Interface for Wi-Fi activity energy info listener. Should be implemented by applications and
      * set when calling {@link WifiManager#getWifiActivityEnergyInfoAsync}.
      *
@@ -2592,21 +2597,6 @@
     }
 
     /**
-     * Check if the chipset supports dual frequency band (2.4 GHz and 5 GHz).
-     * No permissions are required to call this method.
-     * @return {@code true} if supported, {@code false} otherwise.
-     * @hide
-     */
-    @SystemApi
-    public boolean isDualBandSupported() {
-        try {
-            return mService.isDualBandSupported();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Check if the device is dual mode capable i.e. supports concurrent STA + Soft AP.
      *
      * If the device is dual mode capable, it may require conversion of the user's Soft AP band
@@ -3574,7 +3564,7 @@
      */
     public class LocalOnlyHotspotReservation implements AutoCloseable {
 
-        private final CloseGuard mCloseGuard = CloseGuard.get();
+        private final CloseGuard mCloseGuard = new CloseGuard();
         private final WifiConfiguration mConfig;
         private boolean mClosed = false;
 
@@ -3601,6 +3591,8 @@
                 }
             } catch (Exception e) {
                 Log.e(TAG, "Failed to stop Local Only Hotspot.");
+            } finally {
+                Reference.reachabilityFence(this);
             }
         }
 
@@ -3725,7 +3717,7 @@
      * @hide
      */
     public class LocalOnlyHotspotSubscription implements AutoCloseable {
-        private final CloseGuard mCloseGuard = CloseGuard.get();
+        private final CloseGuard mCloseGuard = new CloseGuard();
 
         /** @hide */
         @VisibleForTesting
@@ -3740,6 +3732,8 @@
                 mCloseGuard.close();
             } catch (Exception e) {
                 Log.e(TAG, "Failed to unregister LocalOnlyHotspotObserver.");
+            } finally {
+                Reference.reachabilityFence(this);
             }
         }
 
@@ -4686,10 +4680,13 @@
     }
 
     /**
-     * Retrieve the soft ap config data to be backed to save current config data.
+     * Returns a byte stream representing the data that needs to be backed up to save the
+     * current soft ap config data.
+     *
+     * This soft ap config can be restored by calling {@link #restoreSoftApBackupData(byte[])}
      * @hide
      */
-    @Nullable
+    @NonNull
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public byte[] retrieveSoftApBackupData() {
@@ -4701,15 +4698,17 @@
     }
 
     /**
-     * Restore soft ap config from the backed up data.
+     * Returns soft ap config from the backed up data.
+     * @param data byte stream in the same format produced by {@link #retrieveSoftApBackupData()}
+     *
      * @hide
      */
     @Nullable
     @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
-    public void restoreSoftApBackupData(@NonNull byte[] data) {
+    public SoftApConfiguration restoreSoftApBackupData(@NonNull byte[] data) {
         try {
-            mService.restoreSoftApBackupData(data);
+            return mService.restoreSoftApBackupData(data);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index d97f6fb..4d92ae1 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -20,12 +20,12 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.NetworkSpecifier;
+import android.util.CloseGuard;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import dalvik.system.CloseGuard;
-
+import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 
 /**
@@ -58,7 +58,7 @@
     /** @hide */
     protected boolean mTerminated = false;
 
-    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private final CloseGuard mCloseGuard = new CloseGuard();
 
     /**
      * Return the maximum permitted retry count when sending messages using
@@ -108,6 +108,7 @@
         mTerminated = true;
         mMgr.clear();
         mCloseGuard.close();
+        Reference.reachabilityFence(this);
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 3c97813..fe0872c 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -23,12 +23,12 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.CloseGuard;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import dalvik.system.CloseGuard;
-
+import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 
 /**
@@ -45,7 +45,7 @@
     private final int mClientId;
 
     private boolean mTerminated = true;
-    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private final CloseGuard mCloseGuard = new CloseGuard();
 
     /** @hide */
     public WifiAwareSession(WifiAwareManager manager, Binder binder, int clientId) {
@@ -80,6 +80,7 @@
         mTerminated = true;
         mMgr.clear();
         mCloseGuard.close();
+        Reference.reachabilityFence(this);
     }
 
     /** @hide */
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 1c20679..6120e4e 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -44,15 +44,15 @@
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.text.TextUtils;
+import android.util.CloseGuard;
 import android.util.Log;
 
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 
-import dalvik.system.CloseGuard;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -869,7 +869,7 @@
         private final Object mListenerMapLock = new Object();
         private int mListenerKey = 0;
 
-        private final CloseGuard mCloseGuard = CloseGuard.get();
+        private final CloseGuard mCloseGuard = new CloseGuard();
 
         /**
          * Close the current P2P connection and indicate to the P2P service that connections
@@ -888,6 +888,7 @@
 
             mAsyncChannel.disconnect();
             mCloseGuard.close();
+            Reference.reachabilityFence(this);
         }
 
         /** @hide */
diff --git a/wifi/java/android/net/wifi/wificond/ChannelSettings.java b/wifi/java/android/net/wifi/wificond/ChannelSettings.java
new file mode 100644
index 0000000..c2d65b5
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/ChannelSettings.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * ChannelSettings for wificond
+ *
+ * @hide
+ */
+public class ChannelSettings implements Parcelable {
+    private static final String TAG = "ChannelSettings";
+
+    public int frequency;
+
+    /** public constructor */
+    public ChannelSettings() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof ChannelSettings)) {
+            return false;
+        }
+        ChannelSettings channel = (ChannelSettings) rhs;
+        if (channel == null) {
+            return false;
+        }
+        return frequency == channel.frequency;
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(frequency);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     **/
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(frequency);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<ChannelSettings> CREATOR =
+            new Parcelable.Creator<ChannelSettings>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public ChannelSettings createFromParcel(Parcel in) {
+            ChannelSettings result = new ChannelSettings();
+            result.frequency = in.readInt();
+            if (in.dataAvail() != 0) {
+                Log.e(TAG, "Found trailing data after parcel parsing.");
+            }
+
+            return result;
+        }
+
+        @Override
+        public ChannelSettings[] newArray(int size) {
+            return new ChannelSettings[size];
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/wificond/HiddenNetwork.java b/wifi/java/android/net/wifi/wificond/HiddenNetwork.java
new file mode 100644
index 0000000..38dacea
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/HiddenNetwork.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * HiddenNetwork for wificond
+ *
+ * @hide
+ */
+public class HiddenNetwork implements Parcelable {
+    private static final String TAG = "HiddenNetwork";
+
+    public byte[] ssid;
+
+    /** public constructor */
+    public HiddenNetwork() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof HiddenNetwork)) {
+            return false;
+        }
+        HiddenNetwork network = (HiddenNetwork) rhs;
+        return Arrays.equals(ssid, network.ssid);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(ssid);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeByteArray(ssid);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<HiddenNetwork> CREATOR =
+            new Parcelable.Creator<HiddenNetwork>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public HiddenNetwork createFromParcel(Parcel in) {
+            HiddenNetwork result = new HiddenNetwork();
+            result.ssid = in.createByteArray();
+            return result;
+        }
+
+        @Override
+        public HiddenNetwork[] newArray(int size) {
+            return new HiddenNetwork[size];
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
new file mode 100644
index 0000000..ff8e935
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * ScanResult from wificond
+ *
+ * @hide
+ */
+public class NativeScanResult implements Parcelable {
+    private static final int CAPABILITY_SIZE = 16;
+
+    public byte[] ssid;
+    public byte[] bssid;
+    public byte[] infoElement;
+    public int frequency;
+    public int signalMbm;
+    public long tsf;
+    public BitSet capability;
+    public boolean associated;
+    public List<RadioChainInfo> radioChainInfos;
+
+    /** public constructor */
+    public NativeScanResult() { }
+
+    /** copy constructor */
+    public NativeScanResult(NativeScanResult source) {
+        ssid = source.ssid.clone();
+        bssid = source.bssid.clone();
+        infoElement = source.infoElement.clone();
+        frequency = source.frequency;
+        signalMbm = source.signalMbm;
+        tsf = source.tsf;
+        capability = (BitSet) source.capability.clone();
+        associated = source.associated;
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeByteArray(ssid);
+        out.writeByteArray(bssid);
+        out.writeByteArray(infoElement);
+        out.writeInt(frequency);
+        out.writeInt(signalMbm);
+        out.writeLong(tsf);
+        int capabilityInt = 0;
+        for (int i = 0; i < CAPABILITY_SIZE; i++) {
+            if (capability.get(i)) {
+                capabilityInt |= 1 << i;
+            }
+        }
+        out.writeInt(capabilityInt);
+        out.writeInt(associated ? 1 : 0);
+        out.writeTypedList(radioChainInfos);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<NativeScanResult> CREATOR =
+            new Parcelable.Creator<NativeScanResult>() {
+        @Override
+        public NativeScanResult createFromParcel(Parcel in) {
+            NativeScanResult result = new NativeScanResult();
+            result.ssid = in.createByteArray();
+            result.bssid = in.createByteArray();
+            result.infoElement = in.createByteArray();
+            result.frequency = in.readInt();
+            result.signalMbm = in.readInt();
+            result.tsf = in.readLong();
+            int capabilityInt = in.readInt();
+            result.capability = new BitSet(CAPABILITY_SIZE);
+            for (int i = 0; i < CAPABILITY_SIZE; i++) {
+                if ((capabilityInt & (1 << i)) != 0) {
+                    result.capability.set(i);
+                }
+            }
+            result.associated = (in.readInt() != 0);
+            result.radioChainInfos = new ArrayList<>();
+            in.readTypedList(result.radioChainInfos, RadioChainInfo.CREATOR);
+            return result;
+        }
+
+        @Override
+        public NativeScanResult[] newArray(int size) {
+            return new NativeScanResult[size];
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
new file mode 100644
index 0000000..4994ebd
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * NativeWifiClient for wificond
+ *
+ * @hide
+ */
+public class NativeWifiClient implements Parcelable {
+    public byte[] macAddress;
+
+    /** public constructor */
+    public NativeWifiClient() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof NativeWifiClient)) {
+            return false;
+        }
+        NativeWifiClient other = (NativeWifiClient) rhs;
+        return Arrays.equals(macAddress, other.macAddress);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(macAddress);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flag| is ignored.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeByteArray(macAddress);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<NativeWifiClient> CREATOR =
+            new Parcelable.Creator<NativeWifiClient>() {
+                @Override
+                public NativeWifiClient createFromParcel(Parcel in) {
+                    NativeWifiClient result = new NativeWifiClient();
+                    result.macAddress = in.createByteArray();
+                    return result;
+                }
+
+                @Override
+                public NativeWifiClient[] newArray(int size) {
+                    return new NativeWifiClient[size];
+                }
+            };
+}
diff --git a/wifi/java/android/net/wifi/wificond/PnoNetwork.java b/wifi/java/android/net/wifi/wificond/PnoNetwork.java
new file mode 100644
index 0000000..f923fd3
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/PnoNetwork.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * PnoNetwork for wificond
+ *
+ * @hide
+ */
+public class PnoNetwork implements Parcelable {
+
+    public boolean isHidden;
+    public byte[] ssid;
+    public int[] frequencies;
+
+    /** public constructor */
+    public PnoNetwork() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof PnoNetwork)) {
+            return false;
+        }
+        PnoNetwork network = (PnoNetwork) rhs;
+        return Arrays.equals(ssid, network.ssid)
+                && Arrays.equals(frequencies, network.frequencies)
+                && isHidden == network.isHidden;
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                isHidden,
+                Arrays.hashCode(ssid),
+                Arrays.hashCode(frequencies));
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flag| is ignored.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(isHidden ? 1 : 0);
+        out.writeByteArray(ssid);
+        out.writeIntArray(frequencies);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<PnoNetwork> CREATOR =
+            new Parcelable.Creator<PnoNetwork>() {
+        @Override
+        public PnoNetwork createFromParcel(Parcel in) {
+            PnoNetwork result = new PnoNetwork();
+            result.isHidden = in.readInt() != 0 ? true : false;
+            result.ssid = in.createByteArray();
+            result.frequencies = in.createIntArray();
+            return result;
+        }
+
+        @Override
+        public PnoNetwork[] newArray(int size) {
+            return new PnoNetwork[size];
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/wificond/PnoSettings.java b/wifi/java/android/net/wifi/wificond/PnoSettings.java
new file mode 100644
index 0000000..96cf24f
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/PnoSettings.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * PnoSettings for wificond
+ *
+ * @hide
+ */
+public class PnoSettings implements Parcelable {
+    public int intervalMs;
+    public int min2gRssi;
+    public int min5gRssi;
+    public int min6gRssi;
+    public ArrayList<PnoNetwork> pnoNetworks;
+
+    /** public constructor */
+    public PnoSettings() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof PnoSettings)) {
+            return false;
+        }
+        PnoSettings settings = (PnoSettings) rhs;
+        if (settings == null) {
+            return false;
+        }
+        return intervalMs == settings.intervalMs
+                && min2gRssi == settings.min2gRssi
+                && min5gRssi == settings.min5gRssi
+                && min6gRssi == settings.min6gRssi
+                && pnoNetworks.equals(settings.pnoNetworks);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(intervalMs, min2gRssi, min5gRssi, min6gRssi, pnoNetworks);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flag| is ignored.
+     **/
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(intervalMs);
+        out.writeInt(min2gRssi);
+        out.writeInt(min5gRssi);
+        out.writeInt(min6gRssi);
+        out.writeTypedList(pnoNetworks);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<PnoSettings> CREATOR =
+            new Parcelable.Creator<PnoSettings>() {
+        @Override
+        public PnoSettings createFromParcel(Parcel in) {
+            PnoSettings result = new PnoSettings();
+            result.intervalMs = in.readInt();
+            result.min2gRssi = in.readInt();
+            result.min5gRssi = in.readInt();
+            result.min6gRssi = in.readInt();
+
+            result.pnoNetworks = new ArrayList<PnoNetwork>();
+            in.readTypedList(result.pnoNetworks, PnoNetwork.CREATOR);
+
+            return result;
+        }
+
+        @Override
+        public PnoSettings[] newArray(int size) {
+            return new PnoSettings[size];
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/wificond/RadioChainInfo.java b/wifi/java/android/net/wifi/wificond/RadioChainInfo.java
new file mode 100644
index 0000000..2b03450
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/RadioChainInfo.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * RadioChainInfo for wificond
+ *
+ * @hide
+ */
+public class RadioChainInfo implements Parcelable {
+    private static final String TAG = "RadioChainInfo";
+
+    public int chainId;
+    public int level;
+
+
+    /** public constructor */
+    public RadioChainInfo() { }
+
+    public RadioChainInfo(int chainId, int level) {
+        this.chainId = chainId;
+        this.level = level;
+    }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof RadioChainInfo)) {
+            return false;
+        }
+        RadioChainInfo chainInfo = (RadioChainInfo) rhs;
+        if (chainInfo == null) {
+            return false;
+        }
+        return chainId == chainInfo.chainId && level == chainInfo.level;
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(chainId, level);
+    }
+
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(chainId);
+        out.writeInt(level);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<RadioChainInfo> CREATOR =
+            new Parcelable.Creator<RadioChainInfo>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public RadioChainInfo createFromParcel(Parcel in) {
+            RadioChainInfo result = new RadioChainInfo();
+            result.chainId = in.readInt();
+            result.level = in.readInt();
+            return result;
+        }
+
+        @Override
+        public RadioChainInfo[] newArray(int size) {
+            return new RadioChainInfo[size];
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/wificond/SingleScanSettings.java b/wifi/java/android/net/wifi/wificond/SingleScanSettings.java
new file mode 100644
index 0000000..8065c01
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/SingleScanSettings.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.net.wifi.IWifiScannerImpl;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * SingleScanSettings for wificond
+ *
+ * @hide
+ */
+public class SingleScanSettings implements Parcelable {
+    private static final String TAG = "SingleScanSettings";
+
+    public int scanType;
+    public ArrayList<ChannelSettings> channelSettings;
+    public ArrayList<HiddenNetwork> hiddenNetworks;
+
+    /** public constructor */
+    public SingleScanSettings() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof SingleScanSettings)) {
+            return false;
+        }
+        SingleScanSettings settings = (SingleScanSettings) rhs;
+        if (settings == null) {
+            return false;
+        }
+        return scanType == settings.scanType
+                && channelSettings.equals(settings.channelSettings)
+                && hiddenNetworks.equals(settings.hiddenNetworks);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(scanType, channelSettings, hiddenNetworks);
+    }
+
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private static boolean isValidScanType(int scanType) {
+        return scanType == IWifiScannerImpl.SCAN_TYPE_LOW_SPAN
+                || scanType == IWifiScannerImpl.SCAN_TYPE_LOW_POWER
+                || scanType == IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        if (!isValidScanType(scanType)) {
+            Log.wtf(TAG, "Invalid scan type " + scanType);
+        }
+        out.writeInt(scanType);
+        out.writeTypedList(channelSettings);
+        out.writeTypedList(hiddenNetworks);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<SingleScanSettings> CREATOR =
+            new Parcelable.Creator<SingleScanSettings>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public SingleScanSettings createFromParcel(Parcel in) {
+            SingleScanSettings result = new SingleScanSettings();
+            result.scanType = in.readInt();
+            if (!isValidScanType(result.scanType)) {
+                Log.wtf(TAG, "Invalid scan type " + result.scanType);
+            }
+            result.channelSettings = new ArrayList<ChannelSettings>();
+            in.readTypedList(result.channelSettings, ChannelSettings.CREATOR);
+            result.hiddenNetworks = new ArrayList<HiddenNetwork>();
+            in.readTypedList(result.hiddenNetworks, HiddenNetwork.CREATOR);
+            if (in.dataAvail() != 0) {
+                Log.e(TAG, "Found trailing data after parcel parsing.");
+            }
+            return result;
+        }
+
+        @Override
+        public SingleScanSettings[] newArray(int size) {
+            return new SingleScanSettings[size];
+        }
+    };
+}
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index 524a53c..d58083c 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -28,7 +28,6 @@
 import android.net.wifi.IOnWifiActivityEnergyInfoListener;
 import android.net.wifi.IOnWifiUsabilityStatsListener;
 import android.net.wifi.IScanResultsCallback;
-import android.net.wifi.IScanResultsListener;
 import android.net.wifi.ISoftApCallback;
 import android.net.wifi.ISuggestionConnectionStatusListener;
 import android.net.wifi.ITrafficStateCallback;
@@ -227,12 +226,23 @@
         throw new UnsupportedOperationException();
     }
 
-    @Override
+    /** @deprecated use {@link #is5GHzBandSupported} instead */
+    @Deprecated
     public boolean isDualBandSupported() {
         throw new UnsupportedOperationException();
     }
 
     @Override
+    public boolean is5GHzBandSupported() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean is6GHzBandSupported() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public boolean needs5GHzToAnyApBandConversion() {
         throw new UnsupportedOperationException();
     }
@@ -414,7 +424,7 @@
     }
 
     @Override
-    public void restoreSoftApBackupData(byte[] data) {
+    public SoftApConfiguration restoreSoftApBackupData(byte[] data) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/wifi/tests/src/android/net/wifi/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/WifiCondManagerTest.java
new file mode 100644
index 0000000..48a9afa
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiCondManagerTest.java
@@ -0,0 +1,1281 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.net.wifi.util.HexEncoding;
+import android.net.wifi.wificond.ChannelSettings;
+import android.net.wifi.wificond.HiddenNetwork;
+import android.net.wifi.wificond.NativeWifiClient;
+import android.net.wifi.wificond.PnoNetwork;
+import android.net.wifi.wificond.PnoSettings;
+import android.net.wifi.wificond.SingleScanSettings;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.AdditionalMatchers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiCondManager}.
+ */
+@SmallTest
+public class WifiCondManagerTest {
+    @Mock
+    private IWificond mWificond;
+    @Mock
+    private IBinder mWifiCondBinder;
+    @Mock
+    private IClientInterface mClientInterface;
+    @Mock
+    private IWifiScannerImpl mWifiScannerImpl;
+    @Mock
+    private IApInterface mApInterface;
+    @Mock
+    private WifiCondManager.SoftApListener mSoftApListener;
+    @Mock
+    private WifiCondManager.SendMgmtFrameCallback mSendMgmtFrameCallback;
+    @Mock
+    private WifiCondManager.ScanEventCallback mNormalScanCallback;
+    @Mock
+    private WifiCondManager.ScanEventCallback mPnoScanCallback;
+    @Mock
+    private WifiCondManager.PnoScanRequestCallback mPnoScanRequestCallback;
+    @Mock
+    private Context mContext;
+    private TestLooper mLooper;
+    private TestAlarmManager mTestAlarmManager;
+    private AlarmManager mAlarmManager;
+    private WifiCondManager mWificondControl;
+    private static final String TEST_INTERFACE_NAME = "test_wlan_if";
+    private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
+    private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
+    private static final byte[] TEST_SSID =
+            new byte[]{'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_PSK =
+            new byte[]{'T', 'e', 's', 't'};
+
+    private static final Set<Integer> SCAN_FREQ_SET =
+            new HashSet<Integer>() {{
+                add(2410);
+                add(2450);
+                add(5050);
+                add(5200);
+            }};
+    private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
+    private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
+    private static final int[] TEST_FREQUENCIES_1 = {};
+    private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+
+    private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
+            new ArrayList<byte[]>() {{
+                add(LocalNativeUtil.byteArrayFromArrayList(
+                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
+                add(LocalNativeUtil.byteArrayFromArrayList(
+                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
+            }};
+
+    private static final PnoSettings TEST_PNO_SETTINGS =
+            new PnoSettings() {{
+                intervalMs = 6000;
+                pnoNetworks = new ArrayList<>();
+                PnoNetwork network = new PnoNetwork();
+                network.ssid = LocalNativeUtil.byteArrayFromArrayList(
+                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1));
+                network.isHidden = true;
+                network.frequencies = TEST_FREQUENCIES_1;
+                pnoNetworks.add(network);
+                network.ssid = LocalNativeUtil.byteArrayFromArrayList(
+                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2));
+                network.isHidden = false;
+                network.frequencies = TEST_FREQUENCIES_2;
+                pnoNetworks.add(network);
+            }};
+
+    private static final int TEST_MCS_RATE = 5;
+    private static final int TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS = 100;
+    private static final byte[] TEST_PROBE_FRAME = {
+            0x40, 0x00, 0x3c, 0x00, (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b,
+            0x33, 0x72, (byte) 0xf4, (byte) 0xf5, (byte) 0xe8, 0x51, (byte) 0x9e, 0x09,
+            (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b, 0x33, 0x72, (byte) 0xb0, 0x66,
+            0x00, 0x00
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        // Setup mocks for successful WificondControl operation. Failure case mocks should be
+        // created in specific tests
+        MockitoAnnotations.initMocks(this);
+
+        mTestAlarmManager = new TestAlarmManager();
+        mAlarmManager = mTestAlarmManager.getAlarmManager();
+        when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn(Context.ALARM_SERVICE);
+        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
+        mLooper = new TestLooper();
+        when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+
+        when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
+        when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+        when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
+        when(mWificond.createApInterface(any())).thenReturn(mApInterface);
+        when(mWificond.tearDownClientInterface(any())).thenReturn(true);
+        when(mWificond.tearDownApInterface(any())).thenReturn(true);
+        when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+        when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
+        mWificondControl = new WifiCondManager(mContext, mWificond);
+        assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+                mNormalScanCallback, mPnoScanCallback));
+    }
+
+    /**
+     * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testSetupInterfaceForClientMode() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+        verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls subscribeScanEvents().
+     */
+    @Test
+    public void testSetupInterfaceForClientModeCallsScanEventSubscripiton() throws Exception {
+        verify(mWifiScannerImpl).subscribeScanEvents(any(IScanEvent.class));
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownClientInterface() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+        assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+        verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+        verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownClientInterfaceOnInvalidIface() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+        assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME1));
+        verify(mWifiScannerImpl, never()).unsubscribeScanEvents();
+        verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+        verify(mWificond, never()).tearDownClientInterface(any());
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownClientInterfaceFailDueToExceptionScannerUnsubscribe() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+        doThrow(new RemoteException()).when(mWifiScannerImpl).unsubscribeScanEvents();
+
+        assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+        verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+        verify(mWificond, never()).tearDownClientInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownClientInterfaceErrorWhenWificondFailed() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+        assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+        verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+        verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that the client handles are cleared after teardown.
+     */
+    @Test
+    public void testTeardownClientInterfaceClearsHandles() throws Exception {
+        testTeardownClientInterface();
+
+        assertNull(mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+        verify(mClientInterface, never()).signalPoll();
+
+        assertFalse(mWificondControl.scan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mWifiScannerImpl, never()).scan(any());
+    }
+
+    /**
+     * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) calls wificond.
+     */
+    @Test
+    public void testSetupInterfaceForSoftApMode() throws Exception {
+        when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(mApInterface);
+
+        assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+        verify(mWificond).createApInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that setupInterfaceForSoftAp() returns null when wificond is not started.
+     */
+    @Test
+    public void testSetupInterfaceForSoftApModeErrorWhenWificondIsNotStarted() throws Exception {
+        // Invoke wificond death handler to clear the handle.
+        mWificondControl.binderDied();
+        mLooper.dispatchAll();
+
+        assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+    }
+
+    /**
+     * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) returns null when wificond
+     * failed to setup AP interface.
+     */
+    @Test
+    public void testSetupInterfaceForSoftApModeErrorWhenWificondFailedToSetupInterface()
+            throws Exception {
+        when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(null);
+
+        assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownSoftApInterface() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+        assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+        verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that tearDownSoftapInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownSoftApInterfaceOnInvalidIface() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+        assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+        verify(mWificond, never()).tearDownApInterface(any());
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownSoftApInterfaceErrorWhenWificondFailed() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+        assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+        verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that the SoftAp handles are cleared after teardown.
+     */
+    @Test
+    public void testTeardownSoftApInterfaceClearsHandles() throws Exception {
+        testTeardownSoftApInterface();
+
+        assertFalse(mWificondControl.registerApListener(
+                TEST_INTERFACE_NAME, mSoftApListener));
+        verify(mApInterface, never()).registerCallback(any());
+    }
+
+    /**
+     * Verifies that we can setup concurrent interfaces.
+     */
+    @Test
+    public void testSetupMultipleInterfaces() throws Exception {
+        when(mWificond.createApInterface(TEST_INTERFACE_NAME1)).thenReturn(mApInterface);
+
+        assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME1));
+
+        verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+        verify(mWificond).createApInterface(TEST_INTERFACE_NAME1);
+    }
+
+    /**
+     * Verifies that we can setup concurrent interfaces.
+     */
+    @Test
+    public void testTeardownMultipleInterfaces() throws Exception {
+        testSetupMultipleInterfaces();
+        assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+
+        verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+        verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME1);
+    }
+
+    /**
+     * Verifies that tearDownInterfaces() calls wificond.
+     */
+    @Test
+    public void testTearDownInterfaces() throws Exception {
+        assertTrue(mWificondControl.tearDownInterfaces());
+        verify(mWificond).tearDownInterfaces();
+    }
+
+    /**
+     * Verifies that tearDownInterfaces() calls unsubscribeScanEvents() when there was
+     * a configured client interface.
+     */
+    @Test
+    public void testTearDownInterfacesRemovesScanEventSubscription() throws Exception {
+        assertTrue(mWificondControl.tearDownInterfaces());
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+    }
+
+
+    /**
+     * Verifies that tearDownInterfaces() returns false when wificond is not started.
+     */
+    @Test
+    public void testTearDownInterfacesErrorWhenWificondIsNotStarterd() throws Exception {
+        // Invoke wificond death handler to clear the handle.
+        mWificondControl.binderDied();
+        mLooper.dispatchAll();
+        assertFalse(mWificondControl.tearDownInterfaces());
+    }
+
+    /**
+     * Verifies that signalPoll() calls wificond.
+     */
+    @Test
+    public void testSignalPoll() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, mNormalScanCallback,
+                mPnoScanCallback);
+        mWificondControl.signalPoll(TEST_INTERFACE_NAME);
+        verify(mClientInterface).signalPoll();
+    }
+
+    /**
+     * Verifies that signalPoll() returns null when there is no configured client interface.
+     */
+    @Test
+    public void testSignalPollErrorWhenNoClientInterfaceConfigured() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        // Configure client interface.
+        assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+                mNormalScanCallback, mPnoScanCallback));
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // Signal poll should fail.
+        assertEquals(null, mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+    }
+
+    /**
+     * Verifies that getTxPacketCounters() calls wificond.
+     */
+    @Test
+    public void testGetTxPacketCounters() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, mNormalScanCallback,
+                mPnoScanCallback);
+        mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME);
+        verify(mClientInterface).getPacketCounters();
+    }
+
+    /**
+     * Verifies that getTxPacketCounters() returns null when there is no configured client
+     * interface.
+     */
+    @Test
+    public void testGetTxPacketCountersErrorWhenNoClientInterfaceConfigured() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        // Configure client interface.
+        assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+                mNormalScanCallback, mPnoScanCallback));
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // Signal poll should fail.
+        assertEquals(null, mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME));
+    }
+
+    /**
+     * Verifies that getScanResults() returns null when there is no configured client
+     * interface.
+     */
+    @Test
+    public void testGetScanResultsErrorWhenNoClientInterfaceConfigured() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        // Configure client interface.
+        assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+                mNormalScanCallback, mPnoScanCallback));
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // getScanResults should fail.
+        assertEquals(0,
+                mWificondControl.getScanResults(TEST_INTERFACE_NAME,
+                        WifiCondManager.SCAN_TYPE_SINGLE_SCAN).size());
+    }
+
+    /**
+     * Verifies that Scan() can convert input parameters to SingleScanSettings correctly.
+     */
+    @Test
+    public void testScan() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.scan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+    }
+
+    /**
+     * Verifies that Scan() removes duplicates hiddenSsids passed in from input.
+     */
+    @Test
+    public void testScanWithDuplicateHiddenSsids() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        // Create a list of hiddenSsid that has a duplicate element
+        List<byte[]> hiddenSsidWithDup = new ArrayList<>(SCAN_HIDDEN_NETWORK_SSID_LIST);
+        hiddenSsidWithDup.add(SCAN_HIDDEN_NETWORK_SSID_LIST.get(0));
+        assertEquals(hiddenSsidWithDup.get(0),
+                hiddenSsidWithDup.get(hiddenSsidWithDup.size() - 1));
+        // Pass the List with duplicate elements into scan()
+        assertTrue(mWificondControl.scan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, hiddenSsidWithDup));
+        // But the argument passed down should have the duplicate removed.
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+    }
+
+    /**
+     * Verifies that Scan() can handle null input parameters correctly.
+     */
+    @Test
+    public void testScanNullParameters() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.scan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, null, null));
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY, null, null)));
+    }
+
+    /**
+     * Verifies that Scan() can handle wificond scan failure.
+     */
+    @Test
+    public void testScanFailure() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(false);
+        assertFalse(mWificondControl.scan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mWifiScannerImpl).scan(any(SingleScanSettings.class));
+    }
+
+    /**
+     * Verifies that Scan() can handle invalid type.
+     */
+    @Test
+    public void testScanFailureDueToInvalidType() throws Exception {
+        assertFalse(mWificondControl.scan(
+                TEST_INTERFACE_NAME, 100,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mWifiScannerImpl, never()).scan(any(SingleScanSettings.class));
+    }
+
+    /**
+     * Verifies that startPnoScan() can convert input parameters to PnoSettings correctly.
+     */
+    @Test
+    public void testStartPnoScan() throws Exception {
+        when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS,
+                mPnoScanRequestCallback));
+        verify(mWifiScannerImpl).startPnoScan(argThat(new PnoScanMatcher(TEST_PNO_SETTINGS)));
+        verify(mPnoScanRequestCallback).onPnoRequestSucceeded();
+    }
+
+    /**
+     * Verifies that stopPnoScan() calls underlying wificond.
+     */
+    @Test
+    public void testStopPnoScan() throws Exception {
+        when(mWifiScannerImpl.stopPnoScan()).thenReturn(true);
+        assertTrue(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).stopPnoScan();
+    }
+
+    /**
+     * Verifies that stopPnoScan() can handle wificond failure.
+     */
+    @Test
+    public void testStopPnoScanFailure() throws Exception {
+
+        when(mWifiScannerImpl.stopPnoScan()).thenReturn(false);
+        assertFalse(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).stopPnoScan();
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+     * result event.
+     */
+    @Test
+    public void testScanResultEvent() throws Exception {
+        ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+        verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+        IScanEvent scanEvent = messageCaptor.getValue();
+        assertNotNull(scanEvent);
+        scanEvent.OnScanResultReady();
+
+        verify(mNormalScanCallback).onScanResultReady();
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+     * failed event.
+     */
+    @Test
+    public void testScanFailedEvent() throws Exception {
+        ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+        verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+        IScanEvent scanEvent = messageCaptor.getValue();
+        assertNotNull(scanEvent);
+        scanEvent.OnScanFailed();
+
+        verify(mNormalScanCallback).onScanFailed();
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon pno scan
+     * result event.
+     */
+    @Test
+    public void testPnoScanResultEvent() throws Exception {
+        ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+        verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+        IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+        assertNotNull(pnoScanEvent);
+        pnoScanEvent.OnPnoNetworkFound();
+        verify(mPnoScanCallback).onScanResultReady();
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMetrics pno scan count methods upon pno event.
+     */
+    @Test
+    public void testPnoScanEventsForMetrics() throws Exception {
+        ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+        verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+        IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+        assertNotNull(pnoScanEvent);
+
+        pnoScanEvent.OnPnoNetworkFound();
+        verify(mPnoScanCallback).onScanResultReady();
+
+        pnoScanEvent.OnPnoScanFailed();
+        verify(mPnoScanCallback).onScanFailed();
+    }
+
+    /**
+     * Verifies that startPnoScan() can invoke WifiMetrics pno scan count methods correctly.
+     */
+    @Test
+    public void testStartPnoScanForMetrics() throws Exception {
+        when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(false);
+
+        assertFalse(mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS,
+                mPnoScanRequestCallback));
+        verify(mPnoScanRequestCallback).onPnoRequestFailed();
+    }
+
+    /**
+     * Verifies that abortScan() calls underlying wificond.
+     */
+    @Test
+    public void testAbortScan() throws Exception {
+        mWificondControl.abortScan(TEST_INTERFACE_NAME);
+        verify(mWifiScannerImpl).abortScan();
+    }
+
+    /**
+     * Ensures that the Ap interface callbacks are forwarded to the
+     * SoftApListener used for starting soft AP.
+     */
+    @Test
+    public void testSoftApListenerInvocation() throws Exception {
+        testSetupInterfaceForSoftApMode();
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+
+        when(mApInterface.registerCallback(any())).thenReturn(true);
+
+        final ArgumentCaptor<IApInterfaceEventCallback> apInterfaceCallbackCaptor =
+                ArgumentCaptor.forClass(IApInterfaceEventCallback.class);
+
+        assertTrue(mWificondControl.registerApListener(
+                TEST_INTERFACE_NAME, mSoftApListener));
+        verify(mApInterface).registerCallback(apInterfaceCallbackCaptor.capture());
+
+        final NativeWifiClient testClient = new NativeWifiClient();
+        apInterfaceCallbackCaptor.getValue().onConnectedClientsChanged(testClient, true);
+        verify(mSoftApListener).onConnectedClientsChanged(eq(testClient), eq(true));
+
+        int channelFrequency = 2437;
+        int channelBandwidth = IApInterfaceEventCallback.BANDWIDTH_20;
+        apInterfaceCallbackCaptor.getValue().onSoftApChannelSwitched(channelFrequency,
+                channelBandwidth);
+        verify(mSoftApListener).onSoftApChannelSwitched(eq(channelFrequency), eq(channelBandwidth));
+    }
+
+    /**
+     * Verifies registration and invocation of wificond death handler.
+     */
+    @Test
+    public void testRegisterDeathHandler() throws Exception {
+        Runnable deathHandler = mock(Runnable.class);
+        assertTrue(mWificondControl.initialize(deathHandler));
+        verify(mWificond).tearDownInterfaces();
+        mWificondControl.binderDied();
+        mLooper.dispatchAll();
+        verify(deathHandler).run();
+    }
+
+    /**
+     * Verifies handling of wificond death and ensures that all internal state is cleared and
+     * handlers are invoked.
+     */
+    @Test
+    public void testDeathHandling() throws Exception {
+        Runnable deathHandler = mock(Runnable.class);
+        assertTrue(mWificondControl.initialize(deathHandler));
+
+        testSetupInterfaceForClientMode();
+
+        mWificondControl.binderDied();
+        mLooper.dispatchAll();
+        verify(deathHandler).run();
+
+        // The handles should be cleared after death.
+        assertNull(mWificondControl.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ));
+        verify(mWificond, never()).getAvailable5gNonDFSChannels();
+    }
+
+    /**
+     * sendMgmtFrame() should fail if a null callback is passed in.
+     */
+    @Test
+    public void testSendMgmtFrameNullCallback() throws Exception {
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, null, TEST_MCS_RATE);
+
+        verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+    }
+
+    /**
+     * sendMgmtFrame() should fail if a null frame is passed in.
+     */
+    @Test
+    public void testSendMgmtFrameNullFrame() throws Exception {
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, null,
+                mSendMgmtFrameCallback, TEST_MCS_RATE);
+
+        verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+        verify(mSendMgmtFrameCallback).onFailure(anyInt());
+    }
+
+    /**
+     * sendMgmtFrame() should fail if an interface name that does not exist is passed in.
+     */
+    @Test
+    public void testSendMgmtFrameInvalidInterfaceName() throws Exception {
+        mWificondControl.sendMgmtFrame(TEST_INVALID_INTERFACE_NAME, TEST_PROBE_FRAME,
+                mSendMgmtFrameCallback, TEST_MCS_RATE);
+
+        verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+        verify(mSendMgmtFrameCallback).onFailure(anyInt());
+    }
+
+    /**
+     * sendMgmtFrame() should fail if it is called a second time before the first call completed.
+     */
+    @Test
+    public void testSendMgmtFrameCalledTwiceBeforeFinished() throws Exception {
+        WifiCondManager.SendMgmtFrameCallback cb1 = mock(
+                WifiCondManager.SendMgmtFrameCallback.class);
+        WifiCondManager.SendMgmtFrameCallback cb2 = mock(
+                WifiCondManager.SendMgmtFrameCallback.class);
+
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb1, TEST_MCS_RATE);
+        verify(cb1, never()).onFailure(anyInt());
+        verify(mClientInterface, times(1))
+                .SendMgmtFrame(AdditionalMatchers.aryEq(TEST_PROBE_FRAME),
+                        any(), eq(TEST_MCS_RATE));
+
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb2, TEST_MCS_RATE);
+        verify(cb2).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
+        // verify SendMgmtFrame() still was only called once i.e. not called again
+        verify(mClientInterface, times(1))
+                .SendMgmtFrame(any(), any(), anyInt());
+    }
+
+    /**
+     * Tests that when a RemoteException is triggered on AIDL call, onFailure() is called only once.
+     */
+    @Test
+    public void testSendMgmtFrameThrowsException() throws Exception {
+        WifiCondManager.SendMgmtFrameCallback cb = mock(
+                WifiCondManager.SendMgmtFrameCallback.class);
+
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+
+        doThrow(new RemoteException()).when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME,
+                cb, TEST_MCS_RATE);
+        mLooper.dispatchAll();
+
+        verify(cb).onFailure(anyInt());
+        verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+        sendMgmtFrameEventCaptor.getValue().OnFailure(
+                WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        mLooper.dispatchAll();
+
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(cb);
+    }
+
+    /**
+     * Tests that the onAck() callback is triggered correctly.
+     */
+    @Test
+    public void testSendMgmtFrameSuccess() throws Exception {
+        WifiCondManager.SendMgmtFrameCallback cb = mock(
+                WifiCondManager.SendMgmtFrameCallback.class);
+
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb, TEST_MCS_RATE);
+
+        sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+        mLooper.dispatchAll();
+        verify(cb).onAck(eq(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS));
+        verify(cb, never()).onFailure(anyInt());
+        verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+        // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+        // triggered again
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        mLooper.dispatchAll();
+        verify(cb, times(1)).onAck(anyInt());
+        verify(cb, never()).onFailure(anyInt());
+    }
+
+    /**
+     * Tests that the onFailure() callback is triggered correctly.
+     */
+    @Test
+    public void testSendMgmtFrameFailure() throws Exception {
+        WifiCondManager.SendMgmtFrameCallback cb = mock(
+                WifiCondManager.SendMgmtFrameCallback.class);
+
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb, TEST_MCS_RATE);
+
+        sendMgmtFrameEventCaptor.getValue().OnFailure(
+                WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        mLooper.dispatchAll();
+        verify(cb, never()).onAck(anyInt());
+        verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+        // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+        // triggered again
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        mLooper.dispatchAll();
+        verify(cb, never()).onAck(anyInt());
+        verify(cb, times(1)).onFailure(anyInt());
+    }
+
+    /**
+     * Tests that the onTimeout() callback is triggered correctly.
+     */
+    @Test
+    public void testSendMgmtFrameTimeout() throws Exception {
+        WifiCondManager.SendMgmtFrameCallback cb = mock(
+                WifiCondManager.SendMgmtFrameCallback.class);
+
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb, TEST_MCS_RATE);
+
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        mLooper.dispatchAll();
+        verify(cb, never()).onAck(anyInt());
+        verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+
+        // verify that even if onAck() callback is triggered after timeout,
+        // SendMgmtFrameCallback is not triggered again
+        sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+        mLooper.dispatchAll();
+        verify(cb, never()).onAck(anyInt());
+        verify(cb, times(1)).onFailure(anyInt());
+    }
+
+    /**
+     * Tests every possible test outcome followed by every other test outcome to ensure that the
+     * internal state is reset correctly between calls.
+     * i.e. (success, success), (success, failure), (success, timeout),
+     * (failure, failure), (failure, success), (failure, timeout),
+     * (timeout, timeout), (timeout, success), (timeout, failure)
+     *
+     * Also tests that internal state is reset correctly after a transient AIDL RemoteException.
+     */
+    @Test
+    public void testSendMgmtFrameMixed() throws Exception {
+        testSendMgmtFrameThrowsException();
+        testSendMgmtFrameSuccess();
+        testSendMgmtFrameSuccess();
+        testSendMgmtFrameFailure();
+        testSendMgmtFrameFailure();
+        testSendMgmtFrameTimeout();
+        testSendMgmtFrameTimeout();
+        testSendMgmtFrameSuccess();
+        testSendMgmtFrameTimeout();
+        testSendMgmtFrameFailure();
+        testSendMgmtFrameSuccess();
+    }
+
+    /**
+     * Tests that OnAck() does not perform any non-thread-safe operations on the binder thread.
+     *
+     * The sequence of instructions are:
+     * 1. post onAlarm() onto main thread
+     * 2. OnAck()
+     * 3. mLooper.dispatchAll()
+     *
+     * The actual order of execution is:
+     * 1. binder thread portion of OnAck()
+     * 2. onAlarm() (which purely executes on the main thread)
+     * 3. main thread portion of OnAck()
+     *
+     * If the binder thread portion of OnAck() is not thread-safe, it can possibly mess up
+     * onAlarm(). Tests that this does not occur.
+     */
+    @Test
+    public void testSendMgmtFrameTimeoutAckThreadSafe() throws Exception {
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME,
+                mSendMgmtFrameCallback, TEST_MCS_RATE);
+
+        // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+        // triggering onAlarm() ourselves during the test, manually post onto handler
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        // OnAck posts to the handler
+        sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+        mLooper.dispatchAll();
+        verify(mSendMgmtFrameCallback, never()).onAck(anyInt());
+        verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+    }
+
+    /**
+     * See {@link #testSendMgmtFrameTimeoutAckThreadSafe()}. This test replaces OnAck() with
+     * OnFailure().
+     */
+    @Test
+    public void testSendMgmtFrameTimeoutFailureThreadSafe() throws Exception {
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME,
+                mSendMgmtFrameCallback, TEST_MCS_RATE);
+
+        // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+        // triggering onAlarm() ourselves during the test, manually post onto handler
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        // OnFailure posts to the handler
+        sendMgmtFrameEventCaptor.getValue().OnFailure(
+                WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        mLooper.dispatchAll();
+        verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+    }
+
+    // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
+    // matches the provided frequency set and ssid set.
+    private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {
+        int mExpectedScanType;
+        private final Set<Integer> mExpectedFreqs;
+        private final List<byte[]> mExpectedSsids;
+
+        ScanMatcher(int expectedScanType, Set<Integer> expectedFreqs, List<byte[]> expectedSsids) {
+            this.mExpectedScanType = expectedScanType;
+            this.mExpectedFreqs = expectedFreqs;
+            this.mExpectedSsids = expectedSsids;
+        }
+
+        @Override
+        public boolean matches(SingleScanSettings settings) {
+            if (settings.scanType != mExpectedScanType) {
+                return false;
+            }
+            ArrayList<ChannelSettings> channelSettings = settings.channelSettings;
+            ArrayList<HiddenNetwork> hiddenNetworks = settings.hiddenNetworks;
+            if (mExpectedFreqs != null) {
+                Set<Integer> freqSet = new HashSet<Integer>();
+                for (ChannelSettings channel : channelSettings) {
+                    freqSet.add(channel.frequency);
+                }
+                if (!mExpectedFreqs.equals(freqSet)) {
+                    return false;
+                }
+            } else {
+                if (channelSettings != null && channelSettings.size() > 0) {
+                    return false;
+                }
+            }
+
+            if (mExpectedSsids != null) {
+                List<byte[]> ssidSet = new ArrayList<>();
+                for (HiddenNetwork network : hiddenNetworks) {
+                    ssidSet.add(network.ssid);
+                }
+                if (!mExpectedSsids.equals(ssidSet)) {
+                    return false;
+                }
+
+            } else {
+                if (hiddenNetworks != null && hiddenNetworks.size() > 0) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "ScanMatcher{mExpectedFreqs=" + mExpectedFreqs
+                    + ", mExpectedSsids=" + mExpectedSsids + '}';
+        }
+    }
+
+    // Create a ArgumentMatcher which captures a PnoSettings parameter and checks if it
+    // matches the WifiNative.PnoSettings;
+    private class PnoScanMatcher implements ArgumentMatcher<PnoSettings> {
+        private final PnoSettings mExpectedPnoSettings;
+
+        PnoScanMatcher(PnoSettings expectedPnoSettings) {
+            this.mExpectedPnoSettings = expectedPnoSettings;
+        }
+
+        @Override
+        public boolean matches(PnoSettings settings) {
+            if (mExpectedPnoSettings == null) {
+                return false;
+            }
+            if (settings.intervalMs != mExpectedPnoSettings.intervalMs
+                    || settings.min2gRssi != mExpectedPnoSettings.min2gRssi
+                    || settings.min5gRssi != mExpectedPnoSettings.min5gRssi
+                    || settings.min6gRssi != mExpectedPnoSettings.min6gRssi) {
+                return false;
+            }
+            if (settings.pnoNetworks == null || mExpectedPnoSettings.pnoNetworks == null) {
+                return false;
+            }
+            if (settings.pnoNetworks.size() != mExpectedPnoSettings.pnoNetworks.size()) {
+                return false;
+            }
+
+            for (int i = 0; i < settings.pnoNetworks.size(); i++) {
+                if (!Arrays.equals(settings.pnoNetworks.get(i).ssid,
+                        mExpectedPnoSettings.pnoNetworks.get(i).ssid)) {
+                    return false;
+                }
+                if (settings.pnoNetworks.get(i).isHidden != mExpectedPnoSettings.pnoNetworks.get(
+                        i).isHidden) {
+                    return false;
+                }
+                if (!Arrays.equals(settings.pnoNetworks.get(i).frequencies,
+                        mExpectedPnoSettings.pnoNetworks.get(i).frequencies)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "PnoScanMatcher{" + "mExpectedPnoSettings=" + mExpectedPnoSettings + '}';
+        }
+    }
+
+    private static class LocalNativeUtil {
+        private static final int SSID_BYTES_MAX_LEN = 32;
+
+        /**
+         * Converts an ArrayList<Byte> of UTF_8 byte values to string.
+         * The string will either be:
+         * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non
+         * null),
+         * or
+         * b) Hex string with no delimiters.
+         *
+         * @param bytes List of bytes for ssid.
+         * @throws IllegalArgumentException for null bytes.
+         */
+        public static String bytesToHexOrQuotedString(ArrayList<Byte> bytes) {
+            if (bytes == null) {
+                throw new IllegalArgumentException("null ssid bytes");
+            }
+            byte[] byteArray = byteArrayFromArrayList(bytes);
+            // Check for 0's in the byte stream in which case we cannot convert this into a string.
+            if (!bytes.contains(Byte.valueOf((byte) 0))) {
+                CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
+                try {
+                    CharBuffer decoded = decoder.decode(ByteBuffer.wrap(byteArray));
+                    return "\"" + decoded.toString() + "\"";
+                } catch (CharacterCodingException cce) {
+                }
+            }
+            return hexStringFromByteArray(byteArray);
+        }
+
+        /**
+         * Converts an ssid string to an arraylist of UTF_8 byte values.
+         * These forms are acceptable:
+         * a) UTF-8 String encapsulated in quotes, or
+         * b) Hex string with no delimiters.
+         *
+         * @param ssidStr String to be converted.
+         * @throws IllegalArgumentException for null string.
+         */
+        public static ArrayList<Byte> decodeSsid(String ssidStr) {
+            ArrayList<Byte> ssidBytes = hexOrQuotedStringToBytes(ssidStr);
+            if (ssidBytes.size() > SSID_BYTES_MAX_LEN) {
+                throw new IllegalArgumentException(
+                        "ssid bytes size out of range: " + ssidBytes.size());
+            }
+            return ssidBytes;
+        }
+
+        /**
+         * Convert from an array list of Byte to an array of primitive bytes.
+         */
+        public static byte[] byteArrayFromArrayList(ArrayList<Byte> bytes) {
+            byte[] byteArray = new byte[bytes.size()];
+            int i = 0;
+            for (Byte b : bytes) {
+                byteArray[i++] = b;
+            }
+            return byteArray;
+        }
+
+        /**
+         * Converts a byte array to hex string.
+         *
+         * @param bytes List of bytes for ssid.
+         * @throws IllegalArgumentException for null bytes.
+         */
+        public static String hexStringFromByteArray(byte[] bytes) {
+            if (bytes == null) {
+                throw new IllegalArgumentException("null hex bytes");
+            }
+            return new String(HexEncoding.encode(bytes)).toLowerCase();
+        }
+
+        /**
+         * Converts an string to an arraylist of UTF_8 byte values.
+         * These forms are acceptable:
+         * a) UTF-8 String encapsulated in quotes, or
+         * b) Hex string with no delimiters.
+         *
+         * @param str String to be converted.
+         * @throws IllegalArgumentException for null string.
+         */
+        public static ArrayList<Byte> hexOrQuotedStringToBytes(String str) {
+            if (str == null) {
+                throw new IllegalArgumentException("null string");
+            }
+            int length = str.length();
+            if ((length > 1) && (str.charAt(0) == '"') && (str.charAt(length - 1) == '"')) {
+                str = str.substring(1, str.length() - 1);
+                return stringToByteArrayList(str);
+            } else {
+                return byteArrayToArrayList(hexStringToByteArray(str));
+            }
+        }
+
+        /**
+         * Convert the string to byte array list.
+         *
+         * @return the UTF_8 char byte values of str, as an ArrayList.
+         * @throws IllegalArgumentException if a null or unencodable string is sent.
+         */
+        public static ArrayList<Byte> stringToByteArrayList(String str) {
+            if (str == null) {
+                throw new IllegalArgumentException("null string");
+            }
+            // Ensure that the provided string is UTF_8 encoded.
+            CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+            try {
+                ByteBuffer encoded = encoder.encode(CharBuffer.wrap(str));
+                byte[] byteArray = new byte[encoded.remaining()];
+                encoded.get(byteArray);
+                return byteArrayToArrayList(byteArray);
+            } catch (CharacterCodingException cce) {
+                throw new IllegalArgumentException("cannot be utf-8 encoded", cce);
+            }
+        }
+
+        /**
+         * Convert from an array of primitive bytes to an array list of Byte.
+         */
+        public static ArrayList<Byte> byteArrayToArrayList(byte[] bytes) {
+            ArrayList<Byte> byteList = new ArrayList<>();
+            for (Byte b : bytes) {
+                byteList.add(b);
+            }
+            return byteList;
+        }
+
+        /**
+         * Converts a hex string to byte array.
+         *
+         * @param hexStr String to be converted.
+         * @throws IllegalArgumentException for null string.
+         */
+        public static byte[] hexStringToByteArray(String hexStr) {
+            if (hexStr == null) {
+                throw new IllegalArgumentException("null hex string");
+            }
+            return HexEncoding.decode(hexStr.toCharArray(), false);
+        }
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index f92d38c..62ff9f6 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -1696,8 +1696,6 @@
         assertTrue(mWifiManager.isPasspointSupported());
         assertTrue(mWifiManager.isP2pSupported());
         assertFalse(mWifiManager.isPortableHotspotSupported());
-        assertFalse(mWifiManager.is5GHzBandSupported());
-        assertFalse(mWifiManager.is6GHzBandSupported());
         assertFalse(mWifiManager.isDeviceToDeviceRttSupported());
         assertFalse(mWifiManager.isDeviceToApRttSupported());
         assertFalse(mWifiManager.isPreferredNetworkOffloadSupported());
@@ -1782,13 +1780,23 @@
     }
 
     /**
-     * Test behavior of {@link WifiManager#isDualBandSupported()}
+     * Test behavior of {@link WifiManager#is5GHzBandSupported()}
      */
     @Test
-    public void testIsDualBandSupported() throws Exception {
-        when(mWifiService.isDualBandSupported()).thenReturn(true);
-        assertTrue(mWifiManager.isDualBandSupported());
-        verify(mWifiService).isDualBandSupported();
+    public void testIs5GHzBandSupported() throws Exception {
+        when(mWifiService.is5GHzBandSupported()).thenReturn(true);
+        assertTrue(mWifiManager.is5GHzBandSupported());
+        verify(mWifiService).is5GHzBandSupported();
+    }
+
+    /**
+     * Test behavior of {@link WifiManager#is6GHzBandSupported()}
+     */
+    @Test
+    public void testIs6GHzBandSupported() throws Exception {
+        when(mWifiService.is6GHzBandSupported()).thenReturn(true);
+        assertTrue(mWifiManager.is6GHzBandSupported());
+        verify(mWifiService).is6GHzBandSupported();
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
new file mode 100644
index 0000000..06f12f7
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.NativeScanResult}.
+ */
+@SmallTest
+public class NativeScanResultTest {
+
+    private static final byte[] TEST_SSID =
+            new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_BSSID =
+            new byte[] {(byte) 0x12, (byte) 0xef, (byte) 0xa1,
+                        (byte) 0x2c, (byte) 0x97, (byte) 0x8b};
+    private static final byte[] TEST_INFO_ELEMENT =
+            new byte[] {(byte) 0x01, (byte) 0x03, (byte) 0x12, (byte) 0xbe, (byte) 0xff};
+    private static final int TEST_FREQUENCY = 2456;
+    private static final int TEST_SIGNAL_MBM = -45;
+    private static final long TEST_TSF = 34455441;
+    private static final BitSet TEST_CAPABILITY = new BitSet(16) {{ set(2); set(5); }};
+    private static final boolean TEST_ASSOCIATED = true;
+    private static final int[] RADIO_CHAIN_IDS = { 0, 1 };
+    private static final int[] RADIO_CHAIN_LEVELS = { -56, -65 };
+
+    /**
+     *  NativeScanResult object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() {
+        NativeScanResult scanResult = new NativeScanResult();
+        scanResult.ssid = TEST_SSID;
+        scanResult.bssid = TEST_BSSID;
+        scanResult.infoElement = TEST_INFO_ELEMENT;
+        scanResult.frequency = TEST_FREQUENCY;
+        scanResult.signalMbm = TEST_SIGNAL_MBM;
+        scanResult.tsf = TEST_TSF;
+        scanResult.capability = TEST_CAPABILITY;
+        scanResult.associated = TEST_ASSOCIATED;
+        scanResult.radioChainInfos = new ArrayList<>(Arrays.asList(
+                new RadioChainInfo(RADIO_CHAIN_IDS[0], RADIO_CHAIN_LEVELS[0]),
+                new RadioChainInfo(RADIO_CHAIN_IDS[1], RADIO_CHAIN_LEVELS[1])));
+        Parcel parcel = Parcel.obtain();
+        scanResult.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        NativeScanResult scanResultDeserialized = NativeScanResult.CREATOR.createFromParcel(parcel);
+
+        assertArrayEquals(scanResult.ssid, scanResultDeserialized.ssid);
+        assertArrayEquals(scanResult.bssid, scanResultDeserialized.bssid);
+        assertArrayEquals(scanResult.infoElement, scanResultDeserialized.infoElement);
+        assertEquals(scanResult.frequency, scanResultDeserialized.frequency);
+        assertEquals(scanResult.signalMbm, scanResultDeserialized.signalMbm);
+        assertEquals(scanResult.tsf, scanResultDeserialized.tsf);
+        assertEquals(scanResult.capability, scanResultDeserialized.capability);
+        assertEquals(scanResult.associated, scanResultDeserialized.associated);
+        assertTrue(scanResult.radioChainInfos.containsAll(scanResultDeserialized.radioChainInfos));
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/wificond/PnoSettingsTest.java b/wifi/tests/src/android/net/wifi/wificond/PnoSettingsTest.java
new file mode 100644
index 0000000..775acc7
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/PnoSettingsTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.PnoSettingsResult}.
+ */
+@SmallTest
+public class PnoSettingsTest {
+
+    private static final byte[] TEST_SSID_1 =
+            new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_SSID_2 =
+            new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+    private static final int[] TEST_FREQUENCIES_1 = {};
+    private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+    private static final int TEST_INTERVAL_MS = 30000;
+    private static final int TEST_MIN_2G_RSSI = -60;
+    private static final int TEST_MIN_5G_RSSI = -65;
+    private static final int TEST_VALUE = 42;
+
+    private PnoNetwork mPnoNetwork1;
+    private PnoNetwork mPnoNetwork2;
+
+    @Before
+    public void setUp() {
+        mPnoNetwork1 = new PnoNetwork();
+        mPnoNetwork1.ssid = TEST_SSID_1;
+        mPnoNetwork1.isHidden = true;
+        mPnoNetwork1.frequencies = TEST_FREQUENCIES_1;
+
+        mPnoNetwork2 = new PnoNetwork();
+        mPnoNetwork2.ssid = TEST_SSID_2;
+        mPnoNetwork2.isHidden = false;
+        mPnoNetwork2.frequencies = TEST_FREQUENCIES_2;
+    }
+
+    /**
+     *  PnoSettings object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() {
+        PnoSettings pnoSettings = new PnoSettings();
+        pnoSettings.intervalMs = TEST_INTERVAL_MS;
+        pnoSettings.min2gRssi = TEST_MIN_2G_RSSI;
+        pnoSettings.min5gRssi = TEST_MIN_5G_RSSI;
+        pnoSettings.pnoNetworks = new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2));
+
+        Parcel parcel = Parcel.obtain();
+        pnoSettings.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        PnoSettings pnoSettingsDeserialized = PnoSettings.CREATOR.createFromParcel(parcel);
+
+        assertEquals(pnoSettings, pnoSettingsDeserialized);
+        assertEquals(pnoSettings.hashCode(), pnoSettingsDeserialized.hashCode());
+    }
+
+    /**
+     * Tests usage of {@link PnoSettings} as a HashMap key type.
+     */
+    @Test
+    public void testAsHashMapKey() {
+        PnoSettings pnoSettings1 = new PnoSettings();
+        pnoSettings1.intervalMs = TEST_INTERVAL_MS;
+        pnoSettings1.min2gRssi = TEST_MIN_2G_RSSI;
+        pnoSettings1.min5gRssi = TEST_MIN_5G_RSSI;
+        pnoSettings1.pnoNetworks = new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2));
+
+        PnoSettings pnoSettings2 = new PnoSettings();
+        pnoSettings2.intervalMs = TEST_INTERVAL_MS;
+        pnoSettings2.min2gRssi = TEST_MIN_2G_RSSI;
+        pnoSettings2.min5gRssi = TEST_MIN_5G_RSSI;
+        pnoSettings2.pnoNetworks = new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2));
+
+        assertEquals(pnoSettings1, pnoSettings2);
+        assertEquals(pnoSettings1.hashCode(), pnoSettings2.hashCode());
+
+        HashMap<PnoSettings, Integer> map = new HashMap<>();
+        map.put(pnoSettings1, TEST_VALUE);
+
+        assertEquals(TEST_VALUE, map.get(pnoSettings2).intValue());
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java b/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java
new file mode 100644
index 0000000..ef59839
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.wifi.IWifiScannerImpl;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.SingleScanSettingsResult}.
+ */
+@SmallTest
+public class SingleScanSettingsTest {
+
+    private static final byte[] TEST_SSID_1 =
+            new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_SSID_2 =
+            new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+    private static final int TEST_FREQUENCY_1 = 2456;
+    private static final int TEST_FREQUENCY_2 = 5215;
+    private static final int TEST_VALUE = 42;
+
+    private ChannelSettings mChannelSettings1;
+    private ChannelSettings mChannelSettings2;
+    private HiddenNetwork mHiddenNetwork1;
+    private HiddenNetwork mHiddenNetwork2;
+
+    @Before
+    public void setUp() {
+        mChannelSettings1 = new ChannelSettings();
+        mChannelSettings1.frequency = TEST_FREQUENCY_1;
+        mChannelSettings2 = new ChannelSettings();
+        mChannelSettings2.frequency = TEST_FREQUENCY_2;
+
+        mHiddenNetwork1 = new HiddenNetwork();
+        mHiddenNetwork1.ssid = TEST_SSID_1;
+        mHiddenNetwork2 = new HiddenNetwork();
+        mHiddenNetwork2.ssid = TEST_SSID_2;
+    }
+
+    /**
+     *  SingleScanSettings object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() {
+        SingleScanSettings scanSettings = new SingleScanSettings();
+        scanSettings.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+
+        scanSettings.channelSettings =
+                new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+        scanSettings.hiddenNetworks =
+                new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+        Parcel parcel = Parcel.obtain();
+        scanSettings.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        SingleScanSettings scanSettingsDeserialized =
+                SingleScanSettings.CREATOR.createFromParcel(parcel);
+
+        assertEquals(scanSettings, scanSettingsDeserialized);
+        assertEquals(scanSettings.hashCode(), scanSettingsDeserialized.hashCode());
+    }
+
+    /**
+     * Tests usage of {@link SingleScanSettings} as a HashMap key type.
+     */
+    @Test
+    public void testAsHashMapKey() {
+        SingleScanSettings scanSettings1 = new SingleScanSettings();
+        scanSettings1.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+        scanSettings1.channelSettings =
+                new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+        scanSettings1.hiddenNetworks =
+                new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+        SingleScanSettings scanSettings2 = new SingleScanSettings();
+        scanSettings2.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+        scanSettings2.channelSettings =
+                new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+        scanSettings2.hiddenNetworks =
+                new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+        assertEquals(scanSettings1, scanSettings2);
+        assertEquals(scanSettings1.hashCode(), scanSettings2.hashCode());
+
+        HashMap<SingleScanSettings, Integer> map = new HashMap<>();
+        map.put(scanSettings1, TEST_VALUE);
+
+        assertEquals(TEST_VALUE, map.get(scanSettings2).intValue());
+    }
+}