Merge "Add __INTRODUCED_IN annotations to <amidi/midi.h>."
diff --git a/Android.bp b/Android.bp
index e9fb93e..4d2e5e1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1251,6 +1251,9 @@
     local_sourcepaths: frameworks_base_subdirs,
     installable: false,
     metalava_enabled: true,
+    metalava_annotations_enabled: true,
+    metalava_previous_api: ":public-api-for-metalava-annotations",
+    metalava_merge_annotations_dir: "tools/metalava/manual",
 }
 
 droiddoc {
diff --git a/api/system-current.txt b/api/system-current.txt
index 143b46d4..43f10fd 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1202,6 +1202,9 @@
   }
 
   public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+    field public int backgroundRequestDetailResourceId;
+    field public int backgroundRequestResourceId;
+    field public int requestDetailResourceId;
     field public int requestRes;
   }
 
@@ -1209,6 +1212,7 @@
     field public static final int FLAG_REMOVED = 2; // 0x2
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
+    field public java.lang.String backgroundPermission;
     field public int requestRes;
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 6e3c768..930ccd0 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -276,6 +276,7 @@
   public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
     field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
+    field public java.lang.String backgroundPermission;
   }
 
   public final class ShortcutInfo implements android.os.Parcelable {
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk
index 453fb72..6943dab 100644
--- a/cmds/bootanimation/Android.mk
+++ b/cmds/bootanimation/Android.mk
@@ -27,7 +27,9 @@
 
 LOCAL_SHARED_LIBRARIES += \
     libandroidthings \
+    libandroidthings_protos \
     libchrome \
+    libprotobuf-cpp-lite \
 
 LOCAL_STATIC_LIBRARIES += \
     libjsoncpp
diff --git a/cmds/bootanimation/iot/Android.mk b/cmds/bootanimation/iot/Android.mk
index 8b475d3..3d288e4 100644
--- a/cmds/bootanimation/iot/Android.mk
+++ b/cmds/bootanimation/iot/Android.mk
@@ -25,9 +25,11 @@
 
 LOCAL_SHARED_LIBRARIES := \
     libandroidthings \
+    libandroidthings_protos \
     libbase \
     libchrome \
     liblog \
+    libprotobuf-cpp-lite \
 
 LOCAL_STATIC_LIBRARIES += \
     libjsoncpp
diff --git a/cmds/bootanimation/iot/BootParameters.cpp b/cmds/bootanimation/iot/BootParameters.cpp
index 2cf1c19..30a9b28 100644
--- a/cmds/bootanimation/iot/BootParameters.cpp
+++ b/cmds/bootanimation/iot/BootParameters.cpp
@@ -22,10 +22,13 @@
 #include <fcntl.h>
 
 #include <android-base/file.h>
+#include <json/json.h>
 #include <utils/Log.h>
 
 using android::base::ReadFileToString;
 using android::base::RemoveFileIfExists;
+using android::base::WriteStringToFile;
+using Json::ArrayIndex;
 using Json::Reader;
 using Json::Value;
 
@@ -33,23 +36,34 @@
 
 namespace {
 
-// Keys for supporting a silent boot and user-defined BootAction parameters.
-constexpr const char *kKeySilentBoot = "silent_boot";
-constexpr const char* kKeyParams = "params";
+// Keys for deprecated parameters. Devices that OTA from N to O and that used
+// the hidden BootParameters API will store these in the JSON blob. To support
+// the transition from N to O, these keys are mapped to the new parameters.
+constexpr const char *kKeyLegacyVolume = "volume";
+constexpr const char *kKeyLegacyAnimationsDisabled = "boot_animation_disabled";
+constexpr const char *kKeyLegacyParamNames = "param_names";
+constexpr const char *kKeyLegacyParamValues = "param_values";
 
-constexpr const char* kNextBootFile = "/data/misc/bootanimation/next_boot.json";
-constexpr const char* kLastBootFile = "/data/misc/bootanimation/last_boot.json";
+constexpr const char *kNextBootFile = "/data/misc/bootanimation/next_boot.proto";
+constexpr const char *kLastBootFile = "/data/misc/bootanimation/last_boot.proto";
 
-void swapBootConfigs() {
-    // rename() will fail if next_boot.json doesn't exist, so delete
-    // last_boot.json manually first.
+constexpr const char *kLegacyNextBootFile = "/data/misc/bootanimation/next_boot.json";
+constexpr const char *kLegacyLastBootFile = "/data/misc/bootanimation/last_boot.json";
+
+void removeLegacyFiles() {
     std::string err;
-    if (!RemoveFileIfExists(kLastBootFile, &err))
-        ALOGE("Unable to delete last boot file: %s", err.c_str());
+    if (!RemoveFileIfExists(kLegacyLastBootFile, &err)) {
+        ALOGW("Unable to delete %s: %s", kLegacyLastBootFile, err.c_str());
+    }
 
-    if (rename(kNextBootFile, kLastBootFile) && errno != ENOENT)
-        ALOGE("Unable to swap boot files: %s", strerror(errno));
+    err.clear();
+    if (!RemoveFileIfExists(kLegacyNextBootFile, &err)) {
+        ALOGW("Unable to delete %s: %s", kLegacyNextBootFile, err.c_str());
+    }
+}
 
+void createNextBootFile() {
+    errno = 0;
     int fd = open(kNextBootFile, O_CREAT, DEFFILEMODE);
     if (fd == -1) {
         ALOGE("Unable to create next boot file: %s", strerror(errno));
@@ -64,54 +78,120 @@
 
 }  // namespace
 
+// Renames the 'next' boot file to the 'last' file and reads its contents.
+bool BootParameters::swapAndLoadBootConfigContents(const char *lastBootFile,
+                                                   const char *nextBootFile,
+                                                   std::string *contents) {
+    if (!ReadFileToString(nextBootFile, contents)) {
+        RemoveFileIfExists(lastBootFile);
+        return false;
+    }
+
+    errno = 0;
+    if (rename(nextBootFile, lastBootFile) && errno != ENOENT)
+        ALOGE("Unable to swap boot files: %s", strerror(errno));
+
+    return true;
+}
+
 BootParameters::BootParameters() {
-    swapBootConfigs();
     loadParameters();
 }
 
-void BootParameters::loadParameters() {
-    std::string contents;
-    if (!ReadFileToString(kLastBootFile, &contents)) {
-        if (errno != ENOENT)
-            ALOGE("Unable to read from %s: %s", kLastBootFile, strerror(errno));
+// Saves the boot parameters state to disk so the framework can read it.
+void BootParameters::storeParameters() {
+    errno = 0;
+    if (!WriteStringToFile(mProto.SerializeAsString(), kLastBootFile)) {
+        ALOGE("Failed to write boot parameters to %s: %s", kLastBootFile, strerror(errno));
+    }
 
+    // WriteStringToFile sets the file permissions to 0666, but these are not
+    // honored by the system.
+    errno = 0;
+    if (chmod(kLastBootFile, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) {
+        ALOGE("Failed to set permissions for %s: %s", kLastBootFile, strerror(errno));
+    }
+}
+
+// Load the boot parameters from disk, try the old location and format if the
+// file does not exist. Note:
+// - Parse errors result in defaults being used (a normal boot).
+// - Legacy boot parameters default to a silent boot.
+void BootParameters::loadParameters() {
+    // Precedence is given to the new file format (.proto).
+    std::string contents;
+    if (swapAndLoadBootConfigContents(kLastBootFile, kNextBootFile, &contents)) {
+        parseBootParameters(contents);
+    } else if (swapAndLoadBootConfigContents(kLegacyLastBootFile, kLegacyNextBootFile, &contents)) {
+        parseLegacyBootParameters(contents);
+        storeParameters();
+        removeLegacyFiles();
+    }
+
+    createNextBootFile();
+}
+
+void BootParameters::parseBootParameters(const std::string &contents) {
+    if (!mProto.ParseFromString(contents)) {
+        ALOGW("Failed to parse parameters from %s", kLastBootFile);
         return;
     }
 
-    loadParameters(contents);
+    loadStateFromProto();
 }
 
-// If the boot parameters -
-// - File is missing, we assume a normal, non-silent boot.
-// - Are well-formed, initially assume a normal, non-silent boot and parse.
-void BootParameters::loadParameters(const std::string& raw_json) {
-  if (!Reader().parse(raw_json, mJson)) {
-    return;
-  }
-
-  parseBootParameters();
-}
-
-void BootParameters::parseBootParameters() {
-    // A missing key returns a safe, missing value.
-    // Ignore invalid or missing JSON parameters.
-    Value &jsonValue = mJson[kKeySilentBoot];
-    if (jsonValue.isBool()) {
-        mIsSilentBoot = jsonValue.asBool();
+// Parses the JSON in the proto.
+void BootParameters::parseLegacyBootParameters(const std::string &contents) {
+    Value json;
+    if (!Reader().parse(contents, json)) {
+        ALOGW("Failed to parse parameters from %s", kLegacyLastBootFile);
+        return;
     }
 
-    jsonValue = mJson[kKeyParams];
-    if (jsonValue.isObject()) {
-        // getMemberNames returns a copy of the keys which must be stored.
-        mKeys = jsonValue.getMemberNames();
-        for (auto &key : mKeys) {
-            Value &value = jsonValue[key];
-            if (value.isString()) {
-                mParameters.push_back(
-                    {.key = key.c_str(), .value = value.asCString()});
+    int volume = 0;
+    bool bootAnimationDisabled = true;
+
+    Value &jsonValue = json[kKeyLegacyVolume];
+    if (jsonValue.isIntegral()) {
+        volume = jsonValue.asInt();
+    }
+
+    jsonValue = json[kKeyLegacyAnimationsDisabled];
+    if (jsonValue.isIntegral()) {
+        bootAnimationDisabled = jsonValue.asInt() == 1;
+    }
+
+    // Assume a silent boot unless all of the following are true -
+    // 1. The volume is neither 0 nor -1000 (the legacy default value).
+    // 2. The boot animations are explicitly enabled.
+    // Note: brightness was never used.
+    mProto.set_silent_boot((volume == 0) || (volume == -1000) || bootAnimationDisabled);
+
+    Value &keys = json[kKeyLegacyParamNames];
+    Value &values = json[kKeyLegacyParamValues];
+    if (keys.isArray() && values.isArray() && (keys.size() == values.size())) {
+        for (ArrayIndex i = 0; i < keys.size(); ++i) {
+            auto &key = keys[i];
+            auto &value = values[i];
+            if (key.isString() && value.isString()) {
+                auto userParameter = mProto.add_user_parameter();
+                userParameter->set_key(key.asString());
+                userParameter->set_value(value.asString());
             }
         }
     }
+
+    loadStateFromProto();
+}
+
+void BootParameters::loadStateFromProto() {
+    // A missing key returns a safe, default value.
+    // Ignore invalid or missing parameters.
+    mIsSilentBoot = mProto.silent_boot();
+
+    for (const auto &param : mProto.user_parameter()) {
+        mParameters.push_back({.key = param.key().c_str(), .value = param.value().c_str()});
+    }
 }
 
 }  // namespace android
diff --git a/cmds/bootanimation/iot/BootParameters.h b/cmds/bootanimation/iot/BootParameters.h
index 49c34dd..cbd1ca6 100644
--- a/cmds/bootanimation/iot/BootParameters.h
+++ b/cmds/bootanimation/iot/BootParameters.h
@@ -22,7 +22,7 @@
 #include <vector>
 
 #include <boot_action/boot_action.h>  // libandroidthings native API.
-#include <json/json.h>
+#include <boot_parameters.pb.h>
 
 namespace android {
 
@@ -39,22 +39,33 @@
     // Returns the additional boot parameters that were set on reboot.
     const std::vector<ABootActionParameter>& getParameters() const { return mParameters; }
 
-    // Exposed for testing. Updates the parameters with new JSON values.
-    void loadParameters(const std::string& raw_json);
-private:
+    // Exposed for testing. Sets the parameters to the serialized proto.
+    void parseBootParameters(const std::string &contents);
+
+    // For devices that OTA from N to O.
+    // Exposed for testing. Sets the parameters to the raw JSON.
+    void parseLegacyBootParameters(const std::string &contents);
+
+    // Exposed for testing. Loads the contents from |nextBootFile| and replaces
+    // |lastBootFile| with |nextBootFile|.
+    static bool swapAndLoadBootConfigContents(const char *lastBootFile, const char *nextBootFile,
+                                              std::string *contents);
+
+  private:
     void loadParameters();
 
-    void parseBootParameters();
+    // Replaces the legacy JSON blob with the updated version, allowing the
+    // framework to read it.
+    void storeParameters();
+
+    void loadStateFromProto();
 
     bool mIsSilentBoot = false;
 
     std::vector<ABootActionParameter> mParameters;
 
-    // Store parsed JSON because mParameters makes a shallow copy.
-    Json::Value mJson;
-
-    // Store parameter keys because mParameters makes a shallow copy.
-    Json::Value::Members mKeys;
+    // Store the proto because mParameters makes a shallow copy.
+    android::things::proto::BootParameters mProto;
 };
 
 }  // namespace android
diff --git a/cmds/bootanimation/iot/BootParameters_test.cpp b/cmds/bootanimation/iot/BootParameters_test.cpp
index 27d7d6f..d55bce6 100644
--- a/cmds/bootanimation/iot/BootParameters_test.cpp
+++ b/cmds/bootanimation/iot/BootParameters_test.cpp
@@ -16,6 +16,13 @@
 
 #include "BootParameters.h"
 
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <boot_parameters.pb.h>
 #include <gtest/gtest.h>
 
 namespace android {
@@ -23,50 +30,51 @@
 namespace {
 
 TEST(BootParametersTest, TestNoBootParametersIsNotSilent) {
-    BootParameters boot_parameters = BootParameters();
-    boot_parameters.loadParameters("");
+    android::things::proto::BootParameters proto;
 
-    ASSERT_FALSE(boot_parameters.isSilentBoot());
-    ASSERT_EQ(0u, boot_parameters.getParameters().size());
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseBootParameters(proto.SerializeAsString());
+
+    ASSERT_FALSE(bootParameters.isSilentBoot());
+    ASSERT_EQ(0u, bootParameters.getParameters().size());
 }
 
 TEST(BootParametersTest, TestParseIsSilent) {
-    BootParameters boot_parameters = BootParameters();
-    boot_parameters.loadParameters(R"(
-    {
-      "silent_boot":true,
-      "params":{}
-    }
-    )");
+    android::things::proto::BootParameters proto;
+    proto.set_silent_boot(true);
 
-    ASSERT_TRUE(boot_parameters.isSilentBoot());
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseBootParameters(proto.SerializeAsString());
+
+    ASSERT_TRUE(bootParameters.isSilentBoot());
 }
 
 TEST(BootParametersTest, TestParseIsNotSilent) {
-    BootParameters boot_parameters = BootParameters();
-    boot_parameters.loadParameters(R"(
-    {
-      "silent_boot":false,
-      "params":{}
-    }
-    )");
+    android::things::proto::BootParameters proto;
+    proto.set_silent_boot(false);
 
-    ASSERT_FALSE(boot_parameters.isSilentBoot());
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseBootParameters(proto.SerializeAsString());
+
+    ASSERT_FALSE(bootParameters.isSilentBoot());
 }
 
 TEST(BootParametersTest, TestParseBootParameters) {
-    BootParameters boot_parameters = BootParameters();
-    boot_parameters.loadParameters(R"(
-    {
-      "silent_boot":false,
-      "params":{
-        "key1":"value1",
-        "key2":"value2"
-      }
-    }
-    )");
+    android::things::proto::BootParameters proto;
+    proto.set_silent_boot(false);
 
-    auto &parameters = boot_parameters.getParameters();
+    auto userParameter = proto.add_user_parameter();
+    userParameter->set_key("key1");
+    userParameter->set_value("value1");
+
+    userParameter = proto.add_user_parameter();
+    userParameter->set_key("key2");
+    userParameter->set_value("value2");
+
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseBootParameters(proto.SerializeAsString());
+
+    auto &parameters = bootParameters.getParameters();
     ASSERT_EQ(2u, parameters.size());
     ASSERT_STREQ(parameters[0].key, "key1");
     ASSERT_STREQ(parameters[0].value, "value1");
@@ -74,35 +82,182 @@
     ASSERT_STREQ(parameters[1].value, "value2");
 }
 
-TEST(BootParametersTest, TestParseMissingParametersIsNotSilent) {
-    BootParameters boot_parameters = BootParameters();
-    boot_parameters.loadParameters(R"(
+TEST(BootParametersTest, TestParseLegacyDisableBootAnimationIsSilent) {
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseLegacyBootParameters(R"(
     {
-      "params":{}
+      "brightness":200,
+      "volume":100,
+      "boot_animation_disabled":1,
+      "param_names":[],
+      "param_values":[]
     }
     )");
 
-    ASSERT_FALSE(boot_parameters.isSilentBoot());
+    ASSERT_TRUE(bootParameters.isSilentBoot());
 }
 
-TEST(BootParametersTest, TestParseMalformedParametersAreSkipped) {
-    BootParameters boot_parameters = BootParameters();
-    boot_parameters.loadParameters(R"(
+TEST(BootParametersTest, TestParseLegacyZeroVolumeIsSilent) {
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseLegacyBootParameters(R"(
     {
-      "silent_boot":false,
-      "params":{
-        "key1":123,
-        "key2":"value2"
-      }
+      "brightness":200,
+      "volume":0,
+      "boot_animation_disabled":0,
+      "param_names":[],
+      "param_values":[]
     }
     )");
 
-    auto &parameters = boot_parameters.getParameters();
+    ASSERT_TRUE(bootParameters.isSilentBoot());
+}
+
+TEST(BootParametersTest, TestParseLegacyDefaultVolumeIsSilent) {
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseLegacyBootParameters(R"(
+    {
+      "brightness":200,
+      "volume":-1000,
+      "boot_animation_disabled":0,
+      "param_names":[],
+      "param_values":[]
+    }
+    )");
+
+    ASSERT_TRUE(bootParameters.isSilentBoot());
+}
+
+TEST(BootParametersTest, TestParseLegacyNotSilent) {
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseLegacyBootParameters(R"(
+    {
+      "brightness":200,
+      "volume":500,
+      "boot_animation_disabled":0,
+      "param_names":[],
+      "param_values":[]
+    }
+    )");
+
+    ASSERT_FALSE(bootParameters.isSilentBoot());
+}
+
+TEST(BootParametersTest, TestParseLegacyParameters) {
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseLegacyBootParameters(R"(
+    {
+      "brightness":200,
+      "volume":100,
+      "boot_animation_disabled":1,
+      "param_names":["key1", "key2"],
+      "param_values":["value1", "value2"]
+    }
+    )");
+
+    auto parameters = bootParameters.getParameters();
+    ASSERT_EQ(2u, parameters.size());
+    ASSERT_STREQ(parameters[0].key, "key1");
+    ASSERT_STREQ(parameters[0].value, "value1");
+    ASSERT_STREQ(parameters[1].key, "key2");
+    ASSERT_STREQ(parameters[1].value, "value2");
+}
+
+TEST(BootParametersTest, TestParseLegacyZeroParameters) {
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseLegacyBootParameters(R"(
+    {
+      "brightness":200,
+      "volume":100,
+      "boot_animation_disabled":1,
+      "param_names":[],
+      "param_values":[]
+    }
+    )");
+
+    ASSERT_EQ(0u, bootParameters.getParameters().size());
+}
+
+TEST(BootParametersTest, TestMalformedLegacyParametersAreSkipped) {
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseLegacyBootParameters(R"(
+    {
+      "brightness":500,
+      "volume":500,
+      "boot_animation_disabled":0,
+      "param_names":["key1", "key2"],
+      "param_values":[1, "value2"]
+    }
+    )");
+
+    auto parameters = bootParameters.getParameters();
     ASSERT_EQ(1u, parameters.size());
     ASSERT_STREQ(parameters[0].key, "key2");
     ASSERT_STREQ(parameters[0].value, "value2");
 }
 
+TEST(BootParametersTest, TestLegacyUnequalParameterSizesAreSkipped) {
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseLegacyBootParameters(R"(
+    {
+      "brightness":500,
+      "volume":500,
+      "boot_animation_disabled":0,
+      "param_names":["key1", "key2"],
+      "param_values":["value1"]
+    }
+    )");
+
+    ASSERT_EQ(0u, bootParameters.getParameters().size());
+}
+
+TEST(BootParametersTest, TestMissingLegacyBootParametersIsSilent) {
+    BootParameters bootParameters = BootParameters();
+    bootParameters.parseLegacyBootParameters(R"(
+    {
+      "brightness":500
+    }
+    )");
+
+    EXPECT_TRUE(bootParameters.isSilentBoot());
+    ASSERT_EQ(0u, bootParameters.getParameters().size());
+}
+
+TEST(BootParametersTest, TestLastFileIsRemovedOnError) {
+    TemporaryFile lastFile;
+    TemporaryDir tempDir;
+    std::string nonExistentFilePath(std::string(tempDir.path) + "/nonexistent");
+    std::string contents;
+
+    BootParameters::swapAndLoadBootConfigContents(lastFile.path, nonExistentFilePath.c_str(),
+                                                  &contents);
+
+    struct stat buf;
+    ASSERT_EQ(-1, lstat(lastFile.path, &buf));
+    ASSERT_TRUE(contents.empty());
+}
+
+TEST(BootParametersTest, TestNextFileIsRemovedLastFileExistsOnSuccess) {
+    TemporaryFile lastFile;
+    TemporaryFile nextFile;
+
+    base::WriteStringToFile("foo", nextFile.path);
+
+    std::string contents;
+    // Expected side effects:
+    // - |next_file| is moved to |last_file|
+    // - |contents| is the contents of |next_file| before being moved.
+    BootParameters::swapAndLoadBootConfigContents(lastFile.path, nextFile.path, &contents);
+
+    struct stat buf;
+    ASSERT_EQ(0, lstat(lastFile.path, &buf));
+    ASSERT_EQ(-1, lstat(nextFile.path, &buf));
+    ASSERT_EQ(contents, "foo");
+
+    contents.clear();
+    ASSERT_TRUE(base::ReadFileToString(lastFile.path, &contents));
+    ASSERT_EQ(contents, "foo");
+}
+
 }  // namespace
 
 }  // namespace android
diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
index 7c1a555..376b13c 100644
--- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java
+++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
@@ -46,6 +46,7 @@
     private static final String COMMAND_SET_PROFILE_OWNER = "set-profile-owner";
     private static final String COMMAND_REMOVE_ACTIVE_ADMIN = "remove-active-admin";
     private static final String COMMAND_CLEAR_FREEZE_PERIOD_RECORD = "clear-freeze-period-record";
+    private static final String COMMAND_FORCE_NETWORK_LOGS = "force-network-logs";
     private static final String COMMAND_FORCE_SECURITY_LOGS = "force-security-logs";
 
     private IDevicePolicyManager mDevicePolicyManager;
@@ -84,6 +85,9 @@
                 "feature development to prevent triggering restriction on setting freeze " +
                 "periods.\n" +
                 "\n" +
+                "dpm " + COMMAND_FORCE_NETWORK_LOGS + ": makes all network logs available to " +
+                "the DPC and triggers DeviceAdminReceiver.onNetworkLogsAvailable() if needed.\n" +
+                "\n" +
                 "dpm " + COMMAND_FORCE_SECURITY_LOGS + ": makes all security logs available to " +
                 "the DPC and triggers DeviceAdminReceiver.onSecurityLogsAvailable() if needed.");
     }
@@ -114,6 +118,9 @@
             case COMMAND_CLEAR_FREEZE_PERIOD_RECORD:
                 runClearFreezePeriodRecord();
                 break;
+            case COMMAND_FORCE_NETWORK_LOGS:
+                runForceNetworkLogs();
+                break;
             case COMMAND_FORCE_SECURITY_LOGS:
                 runForceSecurityLogs();
                 break;
@@ -122,6 +129,18 @@
         }
     }
 
+    private void runForceNetworkLogs() throws RemoteException, InterruptedException {
+        while (true) {
+            final long toWait = mDevicePolicyManager.forceNetworkLogs();
+            if (toWait == 0) {
+                break;
+            }
+            System.out.println("We have to wait for " + toWait + " milliseconds...");
+            Thread.sleep(toWait);
+        }
+        System.out.println("Success");
+    }
+
     private void runForceSecurityLogs() throws RemoteException, InterruptedException {
         while (true) {
             final long toWait = mDevicePolicyManager.forceSecurityLogs();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff38c1f..7ba4447 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8300,6 +8300,22 @@
     }
 
     /**
+     * Makes all accumulated network logs available to DPC in a new batch.
+     * Only callable by ADB. If throttled, returns time to wait in milliseconds, otherwise 0.
+     * @hide
+     */
+    public long forceNetworkLogs() {
+        if (mService == null) {
+            return -1;
+        }
+        try {
+            return mService.forceNetworkLogs();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Forces a batch of security logs to be fetched from logd and makes it available for DPC.
      * Only callable by ADB. If throttled, returns time to wait in milliseconds, otherwise 0.
      * @hide
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 37508cd..e3b160f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -347,6 +347,7 @@
     boolean isSecurityLoggingEnabled(in ComponentName admin);
     ParceledListSlice retrieveSecurityLogs(in ComponentName admin);
     ParceledListSlice retrievePreRebootSecurityLogs(in ComponentName admin);
+    long forceNetworkLogs();
     long forceSecurityLogs();
 
     boolean isUninstallInQueue(String packageName);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 950070e..033d2ee 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2206,10 +2206,10 @@
                             com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion);
                     if (val != null) {
                         if (val.type == TypedValue.TYPE_STRING && val.string != null) {
-                            targetCode = minCode = val.string.toString();
+                            minCode = val.string.toString();
                         } else {
                             // If it's not a string, it's an integer.
-                            targetVers = minVers = val.data;
+                            minVers = val.data;
                         }
                     }
 
@@ -2225,6 +2225,9 @@
                             // If it's not a string, it's an integer.
                             targetVers = val.data;
                         }
+                    } else {
+                        targetVers = minVers;
+                        targetCode = minCode;
                     }
 
                     sa.recycle();
@@ -3097,6 +3100,14 @@
                 0);
         perm.info.requestRes = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_request, 0);
+        perm.info.requestDetailResourceId = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup_requestDetail, 0);
+        perm.info.backgroundRequestResourceId = sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestPermissionGroup_backgroundRequest,
+                0);
+        perm.info.backgroundRequestDetailResourceId = sa.getResourceId(
+                com.android.internal.R.styleable
+                        .AndroidManifestPermissionGroup_backgroundRequestDetail, 0);
         perm.info.flags = sa.getInt(
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags, 0);
         perm.info.priority = sa.getInt(
@@ -3151,6 +3162,19 @@
         perm.info.requestRes = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestPermission_request, 0);
 
+        if (sa.hasValue(
+                com.android.internal.R.styleable.AndroidManifestPermission_backgroundPermission)) {
+            if ("android".equals(owner.packageName)) {
+                perm.info.backgroundPermission = sa.getNonResourceString(
+                        com.android.internal.R.styleable
+                                .AndroidManifestPermission_backgroundPermission);
+            } else {
+                Slog.w(TAG, owner.packageName + " defines permission '" + perm.info.name
+                        + "' with a background permission. Only the 'android' package can do "
+                        + "that.");
+            }
+        }
+
         perm.info.protectionLevel = sa.getInt(
                 com.android.internal.R.styleable.AndroidManifestPermission_protectionLevel,
                 PermissionInfo.PROTECTION_NORMAL);
diff --git a/core/java/android/content/pm/PermissionGroupInfo.java b/core/java/android/content/pm/PermissionGroupInfo.java
index 7c4478d..8cf66d8 100644
--- a/core/java/android/content/pm/PermissionGroupInfo.java
+++ b/core/java/android/content/pm/PermissionGroupInfo.java
@@ -45,6 +45,42 @@
     public @StringRes int requestRes;
 
     /**
+     * A string resource identifier (in the package's resources) used as subtitle when requesting
+     * only access while in the foreground.
+     *
+     * From the "requestDetail" attribute or, if not set, {@link
+     * android.content.res.ResourceId#ID_NULL}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @StringRes int requestDetailResourceId;
+
+    /**
+     * A string resource identifier (in the package's resources) used when requesting background
+     * access. Also used when requesting both foreground and background access.
+     *
+     * From the "backgroundRequest" attribute or, if not set, {@link
+     * android.content.res.ResourceId#ID_NULL}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @StringRes int backgroundRequestResourceId;
+
+    /**
+     * A string resource identifier (in the package's resources) used as subtitle when requesting
+     * background access.
+     *
+     * From the "backgroundRequestDetail" attribute or, if not set, {@link
+     * android.content.res.ResourceId#ID_NULL}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @StringRes int backgroundRequestDetailResourceId;
+
+    /**
      * The description string provided in the AndroidManifest file, if any.  You
      * probably don't want to use this, since it will be null if the description
      * is in a resource.  You probably want
@@ -76,6 +112,9 @@
         super(orig);
         descriptionRes = orig.descriptionRes;
         requestRes = orig.requestRes;
+        requestDetailResourceId = orig.requestDetailResourceId;
+        backgroundRequestResourceId = orig.backgroundRequestResourceId;
+        backgroundRequestDetailResourceId = orig.backgroundRequestDetailResourceId;
         nonLocalizedDescription = orig.nonLocalizedDescription;
         flags = orig.flags;
         priority = orig.priority;
@@ -119,6 +158,9 @@
         super.writeToParcel(dest, parcelableFlags);
         dest.writeInt(descriptionRes);
         dest.writeInt(requestRes);
+        dest.writeInt(requestDetailResourceId);
+        dest.writeInt(backgroundRequestResourceId);
+        dest.writeInt(backgroundRequestDetailResourceId);
         TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
         dest.writeInt(flags);
         dest.writeInt(priority);
@@ -138,6 +180,9 @@
         super(source);
         descriptionRes = source.readInt();
         requestRes = source.readInt();
+        requestDetailResourceId = source.readInt();
+        backgroundRequestResourceId = source.readInt();
+        backgroundRequestDetailResourceId = source.readInt();
         nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
         flags = source.readInt();
         priority = source.readInt();
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 938409a..535ef00 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -285,6 +285,21 @@
     public int requestRes;
 
     /**
+     * Some permissions only grant access while the app is in foreground. Some of these permissions
+     * allow to add background capabilities by adding another permission.
+     *
+     * If this is such a permission, this is the name of the permission adding the background
+     * access.
+     *
+     * From the "backgroundPermission" attribute or, if not set null
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public String backgroundPermission;
+
+    /**
      * The description string provided in the AndroidManifest file, if any.  You
      * probably don't want to use this, since it will be null if the description
      * is in a resource.  You probably want
@@ -373,6 +388,7 @@
         protectionLevel = orig.protectionLevel;
         flags = orig.flags;
         group = orig.group;
+        backgroundPermission = orig.backgroundPermission;
         descriptionRes = orig.descriptionRes;
         requestRes = orig.requestRes;
         nonLocalizedDescription = orig.nonLocalizedDescription;
@@ -436,6 +452,7 @@
         dest.writeInt(protectionLevel);
         dest.writeInt(flags);
         dest.writeString(group);
+        dest.writeString(backgroundPermission);
         dest.writeInt(descriptionRes);
         dest.writeInt(requestRes);
         TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
@@ -475,6 +492,7 @@
         protectionLevel = source.readInt();
         flags = source.readInt();
         group = source.readString();
+        backgroundPermission = source.readString();
         descriptionRes = source.readInt();
         requestRes = source.readInt();
         nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 87c64cd..32c6898 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2365,13 +2365,25 @@
      * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, is defined relative to the active array rectangle given in
      * this field, with <code>(0, 0)</code> being the top-left of this rectangle.</p>
      * <p>The active array may be smaller than the full pixel array, since the full array may
-     * include black calibration pixels or other inactive regions, and geometric correction
-     * resulting in scaling or cropping may have been applied.</p>
+     * include black calibration pixels or other inactive regions.</p>
+     * <p>For devices that do not support {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the active
+     * array must be the same as {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.</p>
+     * <p>For devices that support {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the active array must
+     * be enclosed by {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}. The difference between
+     * pre-correction active array and active array accounts for scaling or cropping caused
+     * by lens geometric distortion correction.</p>
+     * <p>In general, application should always refer to active array size for controls like
+     * metering regions or crop region. Two exceptions are when the application is dealing with
+     * RAW image buffers (RAW_SENSOR, RAW10, RAW12 etc), or when application explicitly set
+     * {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} to OFF. In these cases, application should refer
+     * to {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.</p>
      * <p><b>Units</b>: Pixel coordinates on the image sensor</p>
      * <p>This key is available on all devices.</p>
      *
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     @PublicKey
     public static final Key<android.graphics.Rect> SENSOR_INFO_ACTIVE_ARRAY_SIZE =
@@ -2616,9 +2628,9 @@
      * <ol>
      * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}.</li>
      * </ol>
-     * <p>If all of the geometric distortion fields are no-ops, this rectangle will be the same
-     * as the post-distortion-corrected rectangle given in
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</p>
+     * <p>If the camera device doesn't support geometric distortion correction, or all of the
+     * geometric distortion fields are no-ops, this rectangle will be the same as the
+     * post-distortion-corrected rectangle given in {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</p>
      * <p>This rectangle is defined relative to the full pixel array; (0,0) is the top-left of
      * the full pixel array, and the size of the full pixel array is given by
      * {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize}.</p>
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 411a97e..aca77a5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1269,11 +1269,26 @@
      * Otherwise will always be present.</p>
      * <p>The maximum number of regions supported by the device is determined by the value
      * of {@link CameraCharacteristics#CONTROL_MAX_REGIONS_AE android.control.maxRegionsAe}.</p>
-     * <p>The coordinate system is based on the active pixel array,
-     * with (0,0) being the top-left pixel in the active pixel array, and
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with (0,0) being
+     * the top-left pixel in the active pixel array, and
      * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
-     * bottom-right pixel in the active pixel array.</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.height - 1) being the bottom-right
+     * pixel in the pre-correction active pixel array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
      * <p>The weight must be within <code>[0, 1000]</code>, and represents a weight
      * for every pixel in the area. This means that a large metering area
      * with the same weight as a smaller area will have more effect in
@@ -1289,15 +1304,20 @@
      * region and output only the intersection rectangle as the metering region in the result
      * metadata.  If the region is entirely outside the crop region, it will be ignored and
      * not reported in the result metadata.</p>
-     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
+     * distortion correction capability and mode</p>
      * <p><b>Range of valid values:</b><br>
      * Coordinates must be between <code>[(0,0), (width, height))</code> of
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}
+     * depending on distortion correction capability and mode</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
      * @see CameraCharacteristics#CONTROL_MAX_REGIONS_AE
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     @PublicKey
     public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS =
@@ -1443,11 +1463,26 @@
      * Otherwise will always be present.</p>
      * <p>The maximum number of focus areas supported by the device is determined by the value
      * of {@link CameraCharacteristics#CONTROL_MAX_REGIONS_AF android.control.maxRegionsAf}.</p>
-     * <p>The coordinate system is based on the active pixel array,
-     * with (0,0) being the top-left pixel in the active pixel array, and
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with (0,0) being
+     * the top-left pixel in the active pixel array, and
      * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
-     * bottom-right pixel in the active pixel array.</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.height - 1) being the bottom-right
+     * pixel in the pre-correction active pixel array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
      * <p>The weight must be within <code>[0, 1000]</code>, and represents a weight
      * for every pixel in the area. This means that a large metering area
      * with the same weight as a smaller area will have more effect in
@@ -1464,15 +1499,20 @@
      * region and output only the intersection rectangle as the metering region in the result
      * metadata. If the region is entirely outside the crop region, it will be ignored and
      * not reported in the result metadata.</p>
-     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
+     * distortion correction capability and mode</p>
      * <p><b>Range of valid values:</b><br>
      * Coordinates must be between <code>[(0,0), (width, height))</code> of
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}
+     * depending on distortion correction capability and mode</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
      * @see CameraCharacteristics#CONTROL_MAX_REGIONS_AF
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     @PublicKey
     public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS =
@@ -1612,11 +1652,26 @@
      * Otherwise will always be present.</p>
      * <p>The maximum number of regions supported by the device is determined by the value
      * of {@link CameraCharacteristics#CONTROL_MAX_REGIONS_AWB android.control.maxRegionsAwb}.</p>
-     * <p>The coordinate system is based on the active pixel array,
-     * with (0,0) being the top-left pixel in the active pixel array, and
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with (0,0) being
+     * the top-left pixel in the active pixel array, and
      * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
-     * bottom-right pixel in the active pixel array.</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.height - 1) being the bottom-right
+     * pixel in the pre-correction active pixel array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
      * <p>The weight must range from 0 to 1000, and represents a weight
      * for every pixel in the area. This means that a large metering area
      * with the same weight as a smaller area will have more effect in
@@ -1632,15 +1687,20 @@
      * region and output only the intersection rectangle as the metering region in the result
      * metadata.  If the region is entirely outside the crop region, it will be ignored and
      * not reported in the result metadata.</p>
-     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
+     * distortion correction capability and mode</p>
      * <p><b>Range of valid values:</b><br>
      * Coordinates must be between <code>[(0,0), (width, height))</code> of
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}
+     * depending on distortion correction capability and mode</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
      * @see CameraCharacteristics#CONTROL_MAX_REGIONS_AWB
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     @PublicKey
     public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS =
@@ -2433,9 +2493,17 @@
     /**
      * <p>The desired region of the sensor to read out for this capture.</p>
      * <p>This control can be used to implement digital zoom.</p>
-     * <p>The crop region coordinate system is based off
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being the
-     * top-left corner of the sensor active array.</p>
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being
+     * the top-left pixel of the active array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the active array.</p>
      * <p>Output streams use this rectangle to produce their output,
      * cropping to a smaller region if necessary to maintain the
      * stream's aspect ratio, then scaling the sensor input to
@@ -2454,20 +2522,30 @@
      * outputs will crop horizontally (pillarbox), and 16:9
      * streams will match exactly. These additional crops will
      * be centered within the crop region.</p>
-     * <p>The width and height of the crop region cannot
-     * be set to be smaller than
+     * <p>If the coordinate system is android.sensor.info.activeArraysSize, the width and height
+     * of the crop region cannot be set to be smaller than
      * <code>floor( activeArraySize.width / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code> and
      * <code>floor( activeArraySize.height / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code>, respectively.</p>
+     * <p>If the coordinate system is {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, the width
+     * and height of the crop region cannot be set to be smaller than
+     * <code>floor( preCorrectionActiveArraySize.width / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code>
+     * and
+     * <code>floor( preCorrectionActiveArraySize.height / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code>,
+     * respectively.</p>
      * <p>The camera device may adjust the crop region to account
      * for rounding and other hardware requirements; the final
      * crop region used will be included in the output capture
      * result.</p>
      * <p><b>Units</b>: Pixel coordinates relative to
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction
+     * capability and mode</p>
      * <p>This key is available on all devices.</p>
      *
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     @PublicKey
     public static final Key<android.graphics.Rect> SCALER_CROP_REGION =
@@ -3186,15 +3264,14 @@
      * any correction at all would slow down capture rate.  Every output stream will have a
      * similar amount of enhancement applied.</p>
      * <p>The correction only applies to processed outputs such as YUV, JPEG, or DEPTH16; it is not
-     * applied to any RAW output.  Metadata coordinates such as face rectangles or metering
+     * applied to any RAW output. Metadata coordinates such as face rectangles or metering
      * regions are also not affected by correction.</p>
-     * <p>Applications enabling distortion correction need to pay extra attention when converting
-     * image coordinates between corrected output buffers and the sensor array. For example, if
-     * the app supports tap-to-focus and enables correction, it then has to apply the distortion
-     * model described in {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} to the image buffer tap coordinates to properly
-     * calculate the tap position on the sensor active array to be used with
-     * {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}. The same applies in reverse to detected face rectangles if
-     * they need to be drawn on top of the corrected output buffers.</p>
+     * <p>This control will be on by default on devices that support this control. Applications
+     * disabling distortion correction need to pay extra attention with the coordinate system of
+     * metering regions, crop region, and face rectangles. When distortion correction is OFF,
+     * metadata coordinates follow the coordinate system of
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}. When distortion is not OFF, metadata
+     * coordinates follow the coordinate system of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</p>
      * <p><b>Possible values:</b>
      * <ul>
      *   <li>{@link #DISTORTION_CORRECTION_MODE_OFF OFF}</li>
@@ -3205,9 +3282,10 @@
      * {@link CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES android.distortionCorrection.availableModes}</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
-     * @see CaptureRequest#CONTROL_AF_REGIONS
      * @see CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES
      * @see CameraCharacteristics#LENS_DISTORTION
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      * @see #DISTORTION_CORRECTION_MODE_OFF
      * @see #DISTORTION_CORRECTION_MODE_FAST
      * @see #DISTORTION_CORRECTION_MODE_HIGH_QUALITY
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 361d83d..d003f9a 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -730,11 +730,26 @@
      * Otherwise will always be present.</p>
      * <p>The maximum number of regions supported by the device is determined by the value
      * of {@link CameraCharacteristics#CONTROL_MAX_REGIONS_AE android.control.maxRegionsAe}.</p>
-     * <p>The coordinate system is based on the active pixel array,
-     * with (0,0) being the top-left pixel in the active pixel array, and
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with (0,0) being
+     * the top-left pixel in the active pixel array, and
      * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
-     * bottom-right pixel in the active pixel array.</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.height - 1) being the bottom-right
+     * pixel in the pre-correction active pixel array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
      * <p>The weight must be within <code>[0, 1000]</code>, and represents a weight
      * for every pixel in the area. This means that a large metering area
      * with the same weight as a smaller area will have more effect in
@@ -750,15 +765,20 @@
      * region and output only the intersection rectangle as the metering region in the result
      * metadata.  If the region is entirely outside the crop region, it will be ignored and
      * not reported in the result metadata.</p>
-     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
+     * distortion correction capability and mode</p>
      * <p><b>Range of valid values:</b><br>
      * Coordinates must be between <code>[(0,0), (width, height))</code> of
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}
+     * depending on distortion correction capability and mode</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
      * @see CameraCharacteristics#CONTROL_MAX_REGIONS_AE
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     @PublicKey
     public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS =
@@ -1152,11 +1172,26 @@
      * Otherwise will always be present.</p>
      * <p>The maximum number of focus areas supported by the device is determined by the value
      * of {@link CameraCharacteristics#CONTROL_MAX_REGIONS_AF android.control.maxRegionsAf}.</p>
-     * <p>The coordinate system is based on the active pixel array,
-     * with (0,0) being the top-left pixel in the active pixel array, and
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with (0,0) being
+     * the top-left pixel in the active pixel array, and
      * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
-     * bottom-right pixel in the active pixel array.</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.height - 1) being the bottom-right
+     * pixel in the pre-correction active pixel array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
      * <p>The weight must be within <code>[0, 1000]</code>, and represents a weight
      * for every pixel in the area. This means that a large metering area
      * with the same weight as a smaller area will have more effect in
@@ -1173,15 +1208,20 @@
      * region and output only the intersection rectangle as the metering region in the result
      * metadata. If the region is entirely outside the crop region, it will be ignored and
      * not reported in the result metadata.</p>
-     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
+     * distortion correction capability and mode</p>
      * <p><b>Range of valid values:</b><br>
      * Coordinates must be between <code>[(0,0), (width, height))</code> of
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}
+     * depending on distortion correction capability and mode</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
      * @see CameraCharacteristics#CONTROL_MAX_REGIONS_AF
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     @PublicKey
     public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS =
@@ -1730,11 +1770,26 @@
      * Otherwise will always be present.</p>
      * <p>The maximum number of regions supported by the device is determined by the value
      * of {@link CameraCharacteristics#CONTROL_MAX_REGIONS_AWB android.control.maxRegionsAwb}.</p>
-     * <p>The coordinate system is based on the active pixel array,
-     * with (0,0) being the top-left pixel in the active pixel array, and
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with (0,0) being
+     * the top-left pixel in the active pixel array, and
      * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
-     * bottom-right pixel in the active pixel array.</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.height - 1) being the bottom-right
+     * pixel in the pre-correction active pixel array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the active array, and
+     * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the bottom-right pixel in the
+     * active pixel array.</p>
      * <p>The weight must range from 0 to 1000, and represents a weight
      * for every pixel in the area. This means that a large metering area
      * with the same weight as a smaller area will have more effect in
@@ -1750,15 +1805,20 @@
      * region and output only the intersection rectangle as the metering region in the result
      * metadata.  If the region is entirely outside the crop region, it will be ignored and
      * not reported in the result metadata.</p>
-     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on
+     * distortion correction capability and mode</p>
      * <p><b>Range of valid values:</b><br>
      * Coordinates must be between <code>[(0,0), (width, height))</code> of
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}
+     * depending on distortion correction capability and mode</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
      * @see CameraCharacteristics#CONTROL_MAX_REGIONS_AWB
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     @PublicKey
     public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS =
@@ -3099,9 +3159,17 @@
     /**
      * <p>The desired region of the sensor to read out for this capture.</p>
      * <p>This control can be used to implement digital zoom.</p>
-     * <p>The crop region coordinate system is based off
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being the
-     * top-left corner of the sensor active array.</p>
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being
+     * the top-left pixel of the active array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the active array.</p>
      * <p>Output streams use this rectangle to produce their output,
      * cropping to a smaller region if necessary to maintain the
      * stream's aspect ratio, then scaling the sensor input to
@@ -3120,20 +3188,30 @@
      * outputs will crop horizontally (pillarbox), and 16:9
      * streams will match exactly. These additional crops will
      * be centered within the crop region.</p>
-     * <p>The width and height of the crop region cannot
-     * be set to be smaller than
+     * <p>If the coordinate system is android.sensor.info.activeArraysSize, the width and height
+     * of the crop region cannot be set to be smaller than
      * <code>floor( activeArraySize.width / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code> and
      * <code>floor( activeArraySize.height / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code>, respectively.</p>
+     * <p>If the coordinate system is {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, the width
+     * and height of the crop region cannot be set to be smaller than
+     * <code>floor( preCorrectionActiveArraySize.width / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code>
+     * and
+     * <code>floor( preCorrectionActiveArraySize.height / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code>,
+     * respectively.</p>
      * <p>The camera device may adjust the crop region to account
      * for rounding and other hardware requirements; the final
      * crop region used will be included in the output capture
      * result.</p>
      * <p><b>Units</b>: Pixel coordinates relative to
-     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</p>
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction
+     * capability and mode</p>
      * <p>This key is available on all devices.</p>
      *
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      */
     @PublicKey
     public static final Key<android.graphics.Rect> SCALER_CROP_REGION =
@@ -3624,12 +3702,23 @@
     /**
      * <p>List of landmarks for detected
      * faces.</p>
-     * <p>The coordinate system is that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being
+     * the top-left pixel of the active array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
      * <code>(0, 0)</code> being the top-left pixel of the active array.</p>
      * <p>Only available if {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} == FULL
      * This key is available on all devices.</p>
      *
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
      * @hide
      */
@@ -3639,12 +3728,23 @@
     /**
      * <p>List of the bounding rectangles for detected
      * faces.</p>
-     * <p>The coordinate system is that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
+     * <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being
+     * the top-left pixel of the active array.</p>
+     * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
+     * system depends on the mode being set.
+     * When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
+     * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array.
+     * When the distortion correction mode is not OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
      * <code>(0, 0)</code> being the top-left pixel of the active array.</p>
      * <p>Only available if {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} != OFF
      * This key is available on all devices.</p>
      *
+     * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
      * @hide
      */
@@ -4478,15 +4578,14 @@
      * any correction at all would slow down capture rate.  Every output stream will have a
      * similar amount of enhancement applied.</p>
      * <p>The correction only applies to processed outputs such as YUV, JPEG, or DEPTH16; it is not
-     * applied to any RAW output.  Metadata coordinates such as face rectangles or metering
+     * applied to any RAW output. Metadata coordinates such as face rectangles or metering
      * regions are also not affected by correction.</p>
-     * <p>Applications enabling distortion correction need to pay extra attention when converting
-     * image coordinates between corrected output buffers and the sensor array. For example, if
-     * the app supports tap-to-focus and enables correction, it then has to apply the distortion
-     * model described in {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion} to the image buffer tap coordinates to properly
-     * calculate the tap position on the sensor active array to be used with
-     * {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}. The same applies in reverse to detected face rectangles if
-     * they need to be drawn on top of the corrected output buffers.</p>
+     * <p>This control will be on by default on devices that support this control. Applications
+     * disabling distortion correction need to pay extra attention with the coordinate system of
+     * metering regions, crop region, and face rectangles. When distortion correction is OFF,
+     * metadata coordinates follow the coordinate system of
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}. When distortion is not OFF, metadata
+     * coordinates follow the coordinate system of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</p>
      * <p><b>Possible values:</b>
      * <ul>
      *   <li>{@link #DISTORTION_CORRECTION_MODE_OFF OFF}</li>
@@ -4497,9 +4596,10 @@
      * {@link CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES android.distortionCorrection.availableModes}</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
-     * @see CaptureRequest#CONTROL_AF_REGIONS
      * @see CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES
      * @see CameraCharacteristics#LENS_DISTORTION
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      * @see #DISTORTION_CORRECTION_MODE_OFF
      * @see #DISTORTION_CORRECTION_MODE_FAST
      * @see #DISTORTION_CORRECTION_MODE_HIGH_QUALITY
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4534f48..6edcec1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9910,6 +9910,39 @@
         public static final String WIFI_SCORE_PARAMS =
                 "wifi_score_params";
 
+        /**
+         * Setting to enable logging WifiIsUnusableEvent in metrics
+         * which gets triggered when wifi becomes unusable.
+         * Disabled by default, and setting it to 1 will enable it.
+         * @hide
+         */
+        public static final String WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED =
+                "wifi_is_unusable_event_metrics_enabled";
+
+        /**
+         * The minimum number of txBad the framework has to observe
+         * to trigger a wifi data stall.
+         * @hide
+         */
+        public static final String WIFI_DATA_STALL_MIN_TX_BAD =
+                "wifi_data_stall_min_tx_bad";
+
+        /**
+         * The minimum number of txSuccess the framework has to observe
+         * to trigger a wifi data stall when rxSuccess is 0.
+         * @hide
+         */
+        public static final String WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX =
+                "wifi_data_stall_min_tx_success_without_rx";
+
+        /**
+         * Setting to enable logging Wifi LinkSpeedCounts in metrics.
+         * Disabled by default, and setting it to 1 will enable it.
+         * @hide
+         */
+        public static final String WIFI_LINK_SPEED_METRICS_ENABLED =
+                "wifi_link_speed_metrics_enabled";
+
        /**
         * The maximum number of times we will retry a connection to an access
         * point for which we have failed in acquiring an IP address from DHCP.
@@ -11061,6 +11094,7 @@
          *
          * <pre>
          * enabled                  (boolean)
+         * disable_home             (boolean)
          * disable_tilt_to_wake     (boolean)
          * disable_touch_to_wake    (boolean)
          * </pre>
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index ec045b1..6b7b89c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -41,6 +41,7 @@
         DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false");
         DEFAULT_FLAGS.put("settings_audio_switcher", "true");
         DEFAULT_FLAGS.put("settings_systemui_theme", "true");
+        DEFAULT_FLAGS.put("settings_dynamic_homepage", "false");
     }
 
     /**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7944319..d0539ae 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4652,7 +4652,7 @@
      * which is added in order to fade it out in its old location should be removed
      * once the animation is complete.</p>
      *
-     * @param view The view to be added
+     * @param view The view to be added. The view must not have a parent.
      * @param index The index at which this view should be drawn, must be >= 0.
      * This value is relative to the {@link #getChildAt(int) index} values in the normal
      * child list of this container, where any transient view at a particular index will
@@ -4661,9 +4661,14 @@
      * @hide
      */
     public void addTransientView(View view, int index) {
-        if (index < 0) {
+        if (index < 0 || view == null) {
             return;
         }
+        if (view.mParent != null) {
+            throw new IllegalStateException("The specified view already has a parent "
+                    + view.mParent);
+        }
+
         if (mTransientIndices == null) {
             mTransientIndices = new ArrayList<Integer>();
             mTransientViews = new ArrayList<View>();
@@ -4683,7 +4688,9 @@
             mTransientViews.add(view);
         }
         view.mParent = this;
-        view.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
+        if (mAttachInfo != null) {
+            view.dispatchAttachedToWindow(mAttachInfo, (mViewFlags & VISIBILITY_MASK));
+        }
         invalidate(true);
     }
 
@@ -4705,7 +4712,9 @@
                 mTransientViews.remove(i);
                 mTransientIndices.remove(i);
                 view.mParent = null;
-                view.dispatchDetachedFromWindow();
+                if (view.mAttachInfo != null) {
+                    view.dispatchDetachedFromWindow();
+                }
                 invalidate(true);
                 return;
             }
diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java
index 0c34e3c..4d3bbe4 100644
--- a/core/java/android/webkit/WebBackForwardList.java
+++ b/core/java/android/webkit/WebBackForwardList.java
@@ -57,7 +57,8 @@
     /**
      * Clone the entire object to be used in the UI thread by clients of
      * WebView. This creates a copy that should never be modified by any of the
-     * webkit package classes.
+     * webkit package classes. On Android 4.4 and later there is no need to use
+     * this, as the object is already a read-only copy of the internal state.
      */
     protected abstract WebBackForwardList clone();
 }
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
index 74db039..b9e7042 100644
--- a/core/java/android/webkit/WebHistoryItem.java
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -23,7 +23,7 @@
 /**
  * A convenience class for accessing fields in an entry in the back/forward list
  * of a WebView. Each WebHistoryItem is a snapshot of the requested history
- * item. Each history item may be updated during the load of a page.
+ * item.
  * @see WebBackForwardList
  */
 public abstract class WebHistoryItem implements Cloneable {
@@ -44,8 +44,6 @@
      * history item. See getTargetUrl() for the url that is the actual target of
      * this history item.
      * @return The base url of this history item.
-     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
-     * to synchronize this method.
      */
     public abstract String getUrl();
 
@@ -60,22 +58,20 @@
     /**
      * Return the document title of this history item.
      * @return The document title of this history item.
-     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
-     * to synchronize this method.
      */
     public abstract String getTitle();
 
     /**
      * Return the favicon of this history item or {@code null} if no favicon was found.
      * @return A Bitmap containing the favicon for this history item or {@code null}.
-     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
-     * to synchronize this method.
      */
     @Nullable
     public abstract Bitmap getFavicon();
 
     /**
-     * Clone the history item for use by clients of WebView.
+     * Clone the history item for use by clients of WebView. On Android 4.4 and later
+     * there is no need to use this, as the object is already a read-only copy of the
+     * internal state.
      */
     protected abstract WebHistoryItem clone();
 }
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index b591163..f89a9d9 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -136,7 +136,7 @@
             return null;
         }
 
-        String mimeType = getTypeForFile(file);
+        String mimeType = getDocumentType(documentId);
         if (!MetadataReader.isSupportedMimeType(mimeType)) {
             return null;
         }
@@ -418,7 +418,19 @@
     @Override
     public String getDocumentType(String documentId) throws FileNotFoundException {
         final File file = getFileForDocId(documentId);
-        return getTypeForFile(file);
+        if (file.isDirectory()) {
+            return Document.MIME_TYPE_DIR;
+        } else {
+            final int lastDot = documentId.lastIndexOf('.');
+            if (lastDot >= 0) {
+                final String extension = documentId.substring(lastDot + 1).toLowerCase();
+                final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+                if (mime != null) {
+                    return mime;
+                }
+            }
+            return MIMETYPE_OCTET_STREAM;
+        }
     }
 
     @Override
@@ -483,7 +495,7 @@
             }
         }
 
-        final String mimeType = getTypeForFile(file);
+        final String mimeType = getDocumentType(docId);
         final String displayName = file.getName();
         if (mimeType.startsWith("image/")) {
             flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
@@ -510,31 +522,10 @@
         return row;
     }
 
-    private static String getTypeForFile(File file) {
-        if (file.isDirectory()) {
-            return Document.MIME_TYPE_DIR;
-        } else {
-            return getTypeForName(file.getName());
-        }
-    }
-
     protected boolean typeSupportsMetadata(String mimeType) {
         return MetadataReader.isSupportedMimeType(mimeType);
     }
 
-    private static String getTypeForName(String name) {
-        final int lastDot = name.lastIndexOf('.');
-        if (lastDot >= 0) {
-            final String extension = name.substring(lastDot + 1).toLowerCase();
-            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
-            if (mime != null) {
-                return mime;
-            }
-        }
-
-        return MIMETYPE_OCTET_STREAM;
-    }
-
     protected final File getFileForDocId(String docId) throws FileNotFoundException {
         return getFileForDocId(docId, false);
     }
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 39e65ca..1d299f5 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -182,7 +182,7 @@
     ];
 
     // System Services
-    optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
+    optional com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
         (section).type = SECTION_DUMPSYS,
         (section).args = "fingerprint --proto --incident"
     ];
diff --git a/core/proto/android/server/fingerprint.proto b/core/proto/android/server/fingerprint.proto
index 2a7fbc3..c5eb85c 100644
--- a/core/proto/android/server/fingerprint.proto
+++ b/core/proto/android/server/fingerprint.proto
@@ -15,7 +15,7 @@
  */
 
 syntax = "proto2";
-package com.android.server.fingerprint;
+package com.android.server.biometrics.fingerprint;
 
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
 
@@ -46,7 +46,7 @@
     optional PerformanceStatsProto crypto = 4;
 }
 
-// A com.android.server.fingerprint.FingerpintService.PerformanceStats object.
+// A com.android.server.biometrics.fingerprint.FingerpintService.PerformanceStats object.
 message PerformanceStatsProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e33ecb8..d12a05b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -598,6 +598,7 @@
     <protected-broadcast android:name="android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE" />
     <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" />
     <protected-broadcast android:name="android.app.action.STATSD_STARTED" />
+    <protected-broadcast android:name="com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET" />
 
     <!-- For IdleController -->
     <protected-broadcast android:name="android.intent.action.DOCK_IDLE" />
@@ -821,6 +822,9 @@
         android:label="@string/permgrouplab_location"
         android:description="@string/permgroupdesc_location"
         android:request="@string/permgrouprequest_location"
+        android:requestDetail="@string/permgrouprequestdetail_location"
+        android:backgroundRequest="@string/permgroupbackgroundrequest_location"
+        android:backgroundRequestDetail="@string/permgroupbackgroundrequestdetail_location"
         android:priority="400" />
 
     <!-- Allows an app to access precise location.
@@ -831,6 +835,7 @@
         android:permissionGroup="android.permission-group.LOCATION"
         android:label="@string/permlab_accessFineLocation"
         android:description="@string/permdesc_accessFineLocation"
+        android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
         android:protectionLevel="dangerous|instant" />
 
     <!-- Allows an app to access approximate location.
@@ -841,6 +846,7 @@
         android:permissionGroup="android.permission-group.LOCATION"
         android:label="@string/permlab_accessCoarseLocation"
         android:description="@string/permdesc_accessCoarseLocation"
+        android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
         android:protectionLevel="dangerous|instant" />
 
     <!-- Allows an app to access location in the background.  If you
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c4fa190..4c96c1b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1580,6 +1580,7 @@
         <attr name="banner" />
         <attr name="logo" />
         <attr name="permissionGroup" />
+        <attr name="backgroundPermission" />
         <attr name="description" />
         <attr name="request" />
         <attr name="protectionLevel" />
@@ -1610,6 +1611,9 @@
         <attr name="logo" />
         <attr name="description" />
         <attr name="request" />
+        <attr name="requestDetail" />
+        <attr name="backgroundRequest" />
+        <attr name="backgroundRequestDetail" />
         <attr name="permissionGroupFlags" />
         <attr name="priority" />
     </declare-styleable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7ffe866..a129521 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -491,13 +491,21 @@
 
          Note also: the order of this is important. The first upstream type
          for which a satisfying network exists is used.
-      -->
+    -->
     <integer-array translatable="false" name="config_tether_upstream_types">
         <item>1</item>
         <item>7</item>
         <item>0</item>
     </integer-array>
 
+    <!-- When true, the tethering upstream network follows the current default
+         Internet network (except when the current default network is mobile,
+         in which case a DUN network will be used if required).
+
+         When true, overrides the config_tether_upstream_types setting above.
+    -->
+    <bool translatable="false" name="config_tether_upstream_automatic">false</bool>
+
     <!-- If the DUN connection for this CDMA device supports more than just DUN -->
     <!-- traffic you should list them here. -->
     <!-- If this device is not CDMA this is ignored.  If this list is empty on -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9dccc88..cf68934 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -682,6 +682,13 @@
     <!-- Message shown to the user when the apps requests permission from this group -->
     <string name="permgrouprequest_location">Allow
         &lt;b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g>&lt;/b> to access this device\'s location?</string>
+    <!-- Subtitle of the message shown to the user when the apps requests permission to use the location only while app is in foreground [CHAR LIMIT=none]-->
+    <string name="permgrouprequestdetail_location">The app will only have access to the location while you\u2019re using the app.</string>
+    <!-- Message shown to the user when the apps requests permission to use the location while app is in foreground and background [CHAR LIMIT=60] -->
+    <string name="permgroupbackgroundrequest_location">Always allow
+        &lt;b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g>&lt;/b> to access this device\u2019s location?</string>
+    <!-- Subtitle of the message shown to the user when the apps requests permission to use the location while app is in foreground and background [CHAR LIMIT=none] -->
+    <string name="permgroupbackgroundrequestdetail_location">The app will always have access to the location, even when you\u2019re not using the app.</string>
 
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_calendar">Calendar</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 17fb575..9ac80e7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1828,6 +1828,7 @@
   <java-symbol type="array" name="config_tether_bluetooth_regexs" />
   <java-symbol type="array" name="config_tether_dhcp_range" />
   <java-symbol type="array" name="config_tether_upstream_types" />
+  <java-symbol type="bool" name="config_tether_upstream_automatic" />
   <java-symbol type="array" name="config_tether_apndata" />
   <java-symbol type="array" name="config_tether_usb_regexs" />
   <java-symbol type="array" name="config_tether_wifi_regexs" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 14c641a..c22dd13 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -459,6 +459,8 @@
                     Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
                     Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED,
                     Settings.Global.WIFI_COUNTRY_CODE,
+                    Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD,
+                    Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX,
                     Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
                     Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON,
                     Settings.Global.WIFI_DISPLAY_ON,
@@ -468,6 +470,8 @@
                     Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
                     Settings.Global.WIFI_FREQUENCY_BAND,
                     Settings.Global.WIFI_IDLE_MS,
+                    Settings.Global.WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED,
+                    Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED,
                     Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
                     Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
                     Settings.Global.WIFI_NETWORK_SHOW_RSSI,
diff --git a/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java b/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java
new file mode 100644
index 0000000..93ad41f0
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.widget.FrameLayout;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewGroupTransientViewTest {
+
+    @Rule
+    public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
+
+    private FrameLayout mBasePanel;
+    private ViewGroup mTestViewGroup;
+    private TestView mTestView;
+
+    @Before
+    public void setUp() {
+        final Activity activity = mActivityRule.getActivity();
+        mBasePanel = new FrameLayout(activity);
+        mTestViewGroup = new FrameLayout(activity);
+        mTestView = new TestView(activity);
+        activity.runOnUiThread(() -> activity.setContentView(mBasePanel));
+    }
+
+    @UiThreadTest
+    @Test
+    public void addAndRemove_inNonAttachedViewGroup_shouldNotAttachAndDetach() {
+        mTestViewGroup.addTransientView(mTestView, 0);
+        assertEquals(0, mTestView.mAttachedCount);
+
+        mTestViewGroup.removeTransientView(mTestView);
+        assertEquals(0, mTestView.mDetachedCount);
+    }
+
+    @UiThreadTest
+    @Test
+    public void addAndRemove_inAttachedViewGroup_shouldAttachAndDetachOnce() {
+        mBasePanel.addView(mTestViewGroup);
+        mTestViewGroup.addTransientView(mTestView, 0);
+        assertEquals(mTestView, mTestViewGroup.getTransientView(0));
+        assertEquals(1, mTestViewGroup.getTransientViewCount());
+        assertEquals(1, mTestView.mAttachedCount);
+
+        mBasePanel.removeView(mTestViewGroup);
+        mTestViewGroup.removeTransientView(mTestView);
+        assertEquals(null, mTestViewGroup.getTransientView(0));
+        assertEquals(0, mTestViewGroup.getTransientViewCount());
+        assertEquals(1, mTestView.mDetachedCount);
+    }
+
+    @UiThreadTest
+    @Test
+    public void addRemoveAdd_noException() {
+        mBasePanel.addView(mTestViewGroup);
+        mTestViewGroup.addTransientView(mTestView, 1);
+        mTestViewGroup.removeTransientView(mTestView);
+        mTestViewGroup.addTransientView(mTestView, 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void reAddBeforeRemove_shouldThrowException() {
+        mTestViewGroup.addView(mTestView);
+
+        try {
+            mTestViewGroup.addTransientView(mTestView, 0);
+            fail("Not allow to add as transient view before removing it");
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        mTestViewGroup.removeView(mTestView);
+        mTestViewGroup.addTransientView(mTestView, 0);
+        try {
+            mTestViewGroup.addTransientView(mTestView, 1);
+            fail("Not allow to add the same transient view again");
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void drawTransientView() throws Exception {
+        // For view can be drawn if keyguard is active.
+        mActivityRule.getActivity().setShowWhenLocked(true);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        mTestView.mOnDraw = () -> latch.countDown();
+
+        mActivityRule.getActivity().runOnUiThread(() -> {
+            mBasePanel.addView(mTestViewGroup);
+            mTestViewGroup.addTransientView(mTestView, 0);
+        });
+
+        if (!latch.await(3, TimeUnit.SECONDS)) {
+            fail("Transient view does not draw");
+        }
+    }
+
+    private static class TestView extends View {
+        int mAttachedCount;
+        int mDetachedCount;
+        Runnable mOnDraw;
+
+        TestView(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+            mAttachedCount++;
+        }
+
+        @Override
+        protected void onDetachedFromWindow() {
+            super.onDetachedFromWindow();
+            mDetachedCount++;
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            if (mOnDraw != null) {
+                mOnDraw.run();
+                mOnDraw = null;
+            }
+        }
+    }
+}
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 1b816fe..c195a8e 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -142,7 +142,7 @@
             LOG_ALWAYS_FATAL_IF(!mProjectedDisplayList->mProjectedOutline);
             const bool shouldClip = mProjectedDisplayList->mProjectedOutline->getPath();
             SkAutoCanvasRestore acr2(canvas, shouldClip);
-            canvas->setMatrix(mProjectedDisplayList->mProjectedReceiverParentMatrix);
+            canvas->setMatrix(mProjectedDisplayList->mParentMatrix);
             if (shouldClip) {
                 clipOutline(*mProjectedDisplayList->mProjectedOutline, canvas, nullptr);
             }
@@ -200,9 +200,7 @@
         setViewProperties(properties, canvas, &alphaMultiplier);
     }
     SkiaDisplayList* displayList = (SkiaDisplayList*)mRenderNode->getDisplayList();
-    if (displayList->containsProjectionReceiver()) {
-        displayList->mProjectedReceiverParentMatrix = canvas->getTotalMatrix();
-    }
+    displayList->mParentMatrix = canvas->getTotalMatrix();
 
     // TODO should we let the bound of the drawable do this for us?
     const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index 6292a6c..dba97fe 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -55,6 +55,11 @@
         if (casterZ >= -NON_ZERO_EPSILON) {  // draw only children with negative Z
             return;
         }
+        SkAutoCanvasRestore acr(canvas, true);
+        // Since we're drawing out of recording order, the child's matrix needs to be applied to the
+        // canvas. In in-order drawing, the canvas already has the child's matrix applied.
+        canvas->setMatrix(mDisplayList->mParentMatrix);
+        canvas->concat(childNode->getRecordedMatrix());
         childNode->forceDraw(canvas);
         drawIndex++;
     }
@@ -102,6 +107,11 @@
 
         RenderNodeDrawable* childNode = zChildren[drawIndex];
         SkASSERT(childNode);
+        SkAutoCanvasRestore acr(canvas, true);
+        // Since we're drawing out of recording order, the child's matrix needs to be applied to the
+        // canvas. In in-order drawing, the canvas already has the child's matrix applied.
+        canvas->setMatrix(mStartBarrier->mDisplayList->mParentMatrix);
+        canvas->concat(childNode->getRecordedMatrix());
         childNode->forceDraw(canvas);
 
         drawIndex++;
@@ -153,10 +163,15 @@
     }
 
     SkAutoCanvasRestore acr(canvas, true);
+    // Since we're drawing out of recording order, the child's matrix needs to be applied to the
+    // canvas. In in-order drawing, the canvas already has the child's matrix applied.
+    canvas->setMatrix(mStartBarrier->mDisplayList->mParentMatrix);
 
     SkMatrix shadowMatrix;
-    mat4 hwuiMatrix;
+    mat4 hwuiMatrix(caster->getRecordedMatrix());
     // TODO we don't pass the optional boolean to treat it as a 4x4 matrix
+    // applyViewPropertyTransforms gets the same matrix, which render nodes apply with
+    // RenderNodeDrawable::setViewProperties as a part if their draw.
     caster->getRenderNode()->applyViewPropertyTransforms(hwuiMatrix);
     hwuiMatrix.copyTo(shadowMatrix);
     canvas->concat(shadowMatrix);
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 58b9242..6eff589 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -173,12 +173,12 @@
     // node is drawn.
     const Outline* mProjectedOutline = nullptr;
 
-    // mProjectedReceiverParentMatrix is valid when render node tree is traversed during the draw
-    // pass. Render nodes that have a child receiver node, will store their matrix in
-    // mProjectedReceiverParentMatrix. Child receiver node will set the matrix and then clip with
-    // the
-    // outline of their parent.
-    SkMatrix mProjectedReceiverParentMatrix;
+    // mParentMatrix is set and valid when render node tree is traversed during the draw
+    // pass. Render nodes, which draw in a order different than recording order (e.g. nodes with a
+    // child receiver node or Z elevation), can use mParentMatrix to calculate the final transform
+    // without replaying the matrix transform OPs from the display list.
+    // Child receiver node will set the matrix and then clip with the outline of their parent.
+    SkMatrix mParentMatrix;
 };
 
 };  // namespace skiapipeline
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 15c0ab1..eb67e6c 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -1094,7 +1094,7 @@
     class ShadowTestCanvas : public SkCanvas {
     public:
         ShadowTestCanvas(int width, int height) : SkCanvas(width, height) {}
-        int getIndex() { return mDrawCounter; }
+        int getDrawCounter() { return mDrawCounter; }
 
         virtual void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
             // expect to draw 2 RenderNodeDrawable, 1 StartReorderBarrierDrawable,
@@ -1109,17 +1109,36 @@
             EXPECT_EQ(dy, TRANSLATE_Y);
         }
 
-        virtual void didConcat(const SkMatrix& matrix) override {
-            // This function is invoked by EndReorderBarrierDrawable::drawShadow to apply shadow
-            // matrix.
+        virtual void didSetMatrix(const SkMatrix& matrix) override {
             mDrawCounter++;
-            EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X, CASTER_Y), matrix);
-            EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y),
-                      getTotalMatrix());
+            // First invocation is EndReorderBarrierDrawable::drawShadow to apply shadow matrix.
+            // Second invocation is preparing the matrix for an elevated RenderNodeDrawable.
+            EXPECT_TRUE(matrix.isIdentity());
+            EXPECT_TRUE(getTotalMatrix().isIdentity());
+        }
+
+        virtual void didConcat(const SkMatrix& matrix) override {
+            mDrawCounter++;
+            if (mFirstDidConcat) {
+                // First invocation is EndReorderBarrierDrawable::drawShadow to apply shadow matrix.
+                mFirstDidConcat = false;
+                EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y),
+                          matrix);
+                EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y),
+                          getTotalMatrix());
+            } else {
+                // Second invocation is preparing the matrix for an elevated RenderNodeDrawable.
+                EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y),
+                          matrix);
+                EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y),
+                          getTotalMatrix());
+            }
         }
 
     protected:
         int mDrawCounter = 0;
+    private:
+        bool mFirstDidConcat = true;
     };
 
     auto parent = TestUtils::createSkiaNode(
@@ -1143,7 +1162,7 @@
     ShadowTestCanvas canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
     RenderNodeDrawable drawable(parent.get(), &canvas, false);
     canvas.drawDrawable(&drawable);
-    EXPECT_EQ(6, canvas.getIndex());
+    EXPECT_EQ(9, canvas.getDrawCounter());
 }
 
 // Draw a vector drawable twice but with different bounds and verify correct bounds are used.
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index dcc872c..e2935d7 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -116,9 +116,9 @@
  *         these circumstances. Sometimes, due to programming errors, invoking a playback
  *         control operation in an invalid state may also occur. Under all these
  *         error conditions, the internal player engine invokes a user supplied
- *         MediaPlayer2EventCallback.onError() method if an MediaPlayer2EventCallback has been
+ *         EventCallback.onError() method if an EventCallback has been
  *         registered beforehand via
- *         {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)}.
+ *         {@link #setEventCallback(Executor, EventCallback)}.
  *         <ul>
  *         <li>It is important to note that once an error occurs, the
  *         MediaPlayer2 object enters the <em>Error</em> state (except as noted
@@ -159,9 +159,9 @@
  *         player engine continues working on the rest of preparation work
  *         until the preparation work completes. When the preparation completes,
  *         the internal player engine then calls a user supplied callback method,
- *         onInfo() of the MediaPlayer2EventCallback interface with {@link #MEDIA_INFO_PREPARED},
- *         if an MediaPlayer2EventCallback is registered beforehand via
- *         {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)}.</li>
+ *         onInfo() of the EventCallback interface with {@link #MEDIA_INFO_PREPARED},
+ *         if an EventCallback is registered beforehand via
+ *         {@link #setEventCallback(Executor, EventCallback)}.</li>
  *         <li>It is important to note that
  *         the <em>Preparing</em> state is a transient state, and the behavior
  *         of calling any method with side effect while a MediaPlayer2 object is
@@ -180,10 +180,10 @@
  *         whether the MediaPlayer2 object is in the <em>Started</em> state.
  *         <ul>
  *         <li>While in the <em>Started</em> state, the internal player engine calls
- *         a user supplied callback method MediaPlayer2EventCallback.onInfo() with
- *         {@link #MEDIA_INFO_BUFFERING_UPDATE} if an MediaPlayer2EventCallback has been
+ *         a user supplied callback method EventCallback.onInfo() with
+ *         {@link #MEDIA_INFO_BUFFERING_UPDATE} if an EventCallback has been
  *         registered beforehand via
- *         {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)}.
+ *         {@link #setEventCallback(Executor, EventCallback)}.
  *         This callback allows applications to keep track of the buffering status
  *         while streaming audio/video.</li>
  *         <li>Calling {@link #play()} has not effect
@@ -215,10 +215,10 @@
  *         call returns right away, the actual seek operation may take a while to
  *         finish, especially for audio/video being streamed. When the actual
  *         seek operation completes, the internal player engine calls a user
- *         supplied MediaPlayer2EventCallback.onCallCompleted() with
+ *         supplied EventCallback.onCallCompleted() with
  *         {@link #CALL_COMPLETED_SEEK_TO}
- *         if an MediaPlayer2EventCallback has been registered beforehand via
- *         {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)}.</li>
+ *         if an EventCallback has been registered beforehand via
+ *         {@link #setEventCallback(Executor, EventCallback)}.</li>
  *         <li>Please
  *         note that {@link #seekTo(long, int)} can also be called in the other states,
  *         such as <em>Prepared</em>, <em>Paused</em> and <em>PlaybackCompleted
@@ -238,9 +238,9 @@
  *         the MediaPlayer2 object shall remain in the <em>Started</em> state.</li>
  *         <li>If the looping mode was set to <var>false
  *         </var>, the player engine calls a user supplied callback method,
- *         MediaPlayer2EventCallback.onCompletion(), if an MediaPlayer2EventCallback is
+ *         EventCallback.onCompletion(), if an EventCallback is
  *         registered beforehand via
- *         {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)}.
+ *         {@link #setEventCallback(Executor, EventCallback)}.
  *         The invoke of the callback signals that the object is now in the <em>
  *         PlaybackCompleted</em> state.</li>
  *         <li>While in the <em>PlaybackCompleted</em>
@@ -387,7 +387,7 @@
  *     <td>{} </p></td>
  *     <td>This method can be called in any state and calling it does not change
  *         the object state. </p></td></tr>
- * <tr><td>setMediaPlayer2EventCallback </p></td>
+ * <tr><td>setEventCallback </p></td>
  *     <td>any </p></td>
  *     <td>{} </p></td>
  *     <td>This method can be called in any state and calling it does not change
@@ -445,7 +445,7 @@
  * possible runtime errors during playback or streaming. Registration for
  * these events is done by properly setting the appropriate listeners (via calls
  * to
- * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)},
+ * {@link #setEventCallback(Executor, EventCallback)},
  * {@link #setDrmEventCallback(Executor, DrmEventCallback)}).
  * In order to receive the respective callback
  * associated with these listeners, applications are required to create
@@ -812,10 +812,10 @@
     /**
      * Insert a task in the command queue to help the client to identify whether a batch
      * of commands has been finished. When this command is processed, a notification
-     * {@code MediaPlayer2EventCallback.onCommandLabelReached} will be fired with the
+     * {@code EventCallback.onCommandLabelReached} will be fired with the
      * given {@code label}.
      *
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCommandLabelReached
+     * @see android.media.MediaPlayer2.EventCallback#onCommandLabelReached
      *
      * @param label An application specific Object used to help to identify the completeness
      * of a batch of commands.
@@ -1010,9 +1010,9 @@
      *
      * @return the width of the video, or 0 if there is no video,
      * no display surface was set, or the width has not been determined
-     * yet. The {@code MediaPlayer2EventCallback} can be registered via
-     * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a
-     * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the width
+     * yet. The {@code EventCallback} can be registered via
+     * {@link #setEventCallback(Executor, EventCallback)} to provide a
+     * notification {@code EventCallback.onVideoSizeChanged} when the width
      * is available.
      */
     public abstract int getVideoWidth();
@@ -1022,9 +1022,9 @@
      *
      * @return the height of the video, or 0 if there is no video,
      * no display surface was set, or the height has not been determined
-     * yet. The {@code MediaPlayer2EventCallback} can be registered via
-     * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a
-     * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the height is available.
+     * yet. The {@code EventCallback} can be registered via
+     * {@link #setEventCallback(Executor, EventCallback)} to provide a
+     * notification {@code EventCallback.onVideoSizeChanged} when the height is available.
      */
     public abstract int getVideoHeight();
 
@@ -1706,7 +1706,7 @@
      * Interface definition for callbacks to be invoked when the player has the corresponding
      * events.
      */
-    public abstract static class MediaPlayer2EventCallback {
+    public abstract static class EventCallback {
         /**
          * Called to indicate the video size
          *
@@ -1814,14 +1814,14 @@
      * @param executor the executor through which the callback should be invoked
      */
     // This is a synchronous call.
-    public abstract void setMediaPlayer2EventCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull MediaPlayer2EventCallback eventCallback);
+    public abstract void setEventCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull EventCallback eventCallback);
 
     /**
-     * Clears the {@link MediaPlayer2EventCallback}.
+     * Clears the {@link EventCallback}.
      */
     // This is a synchronous call.
-    public abstract void clearMediaPlayer2EventCallback();
+    public abstract void clearEventCallback();
 
     /**
      * Interface definition of a callback to be invoked when a
@@ -1849,14 +1849,14 @@
      * in include/media/mediaplayer2.h!
      */
     /** Unspecified media player error.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onError
+     * @see android.media.MediaPlayer2.EventCallback#onError
      */
     public static final int MEDIA_ERROR_UNKNOWN = 1;
 
     /** The video is streamed and its container is not valid for progressive
      * playback i.e the video's index (e.g moov atom) is not at the start of the
      * file.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onError
+     * @see android.media.MediaPlayer2.EventCallback#onError
      */
     public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;
 
@@ -1872,7 +1872,7 @@
 
     /** Unspecified low-level system error. This value originated from UNKNOWN_ERROR in
      * system/core/include/utils/Errors.h
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onError
+     * @see android.media.MediaPlayer2.EventCallback#onError
      * @hide
      */
     public static final int MEDIA_ERROR_SYSTEM = -2147483648;
@@ -1896,62 +1896,62 @@
      * in include/media/mediaplayer2.h!
      */
     /** Unspecified media player info.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_UNKNOWN = 1;
 
     /** The player switched to this datas source because it is the
      * next-to-be-played in the playlist.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_STARTED_AS_NEXT = 2;
 
     /** The player just pushed the very first video frame for rendering.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3;
 
     /** The player just rendered the very first audio sample.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4;
 
     /** The player just completed the playback of this data source.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_PLAYBACK_COMPLETE = 5;
 
     /** The player just completed the playback of the full playlist.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_PLAYLIST_END = 6;
 
     /** The player just prepared a data source.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_PREPARED = 100;
 
     /** The video is too complex for the decoder: it can't decode frames fast
      *  enough. Possibly only the audio plays fine at this stage.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
 
     /** MediaPlayer2 is temporarily pausing playback internally in order to
      * buffer more data.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_BUFFERING_START = 701;
 
     /** MediaPlayer2 is resuming playback after filling buffers.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_BUFFERING_END = 702;
 
     /** Estimated network bandwidth information (kbps) is available; currently this event fires
      * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END}
      * when playing network files.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      * @hide
      */
     public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
@@ -1963,26 +1963,26 @@
      * has already been played indicates that the next 30 percent of the
      * content to play has been buffered.
      *
-     * The {@code extra} parameter in {@code MediaPlayer2EventCallback.onInfo} is the
+     * The {@code extra} parameter in {@code EventCallback.onInfo} is the
      * percentage (0-100) of the content that has been buffered or played thus far.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_BUFFERING_UPDATE = 704;
 
     /** Bad interleaving means that a media has been improperly interleaved or
      * not interleaved at all, e.g has all the video samples first then all the
      * audio ones. Video is playing but a lot of disk seeks may be happening.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_BAD_INTERLEAVING = 800;
 
     /** The media cannot be seeked (e.g live stream)
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_NOT_SEEKABLE = 801;
 
     /** A new set of metadata is available.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_METADATA_UPDATE = 802;
 
@@ -1994,30 +1994,30 @@
 
     /** Informs that audio is not playing. Note that playback of the video
      * is not interrupted.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804;
 
     /** Informs that video is not playing. Note that playback of the audio
      * is not interrupted.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805;
 
     /** Failed to handle timed text track properly.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      *
      * {@hide}
      */
     public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
 
     /** Subtitle track was not supported by the media framework.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
 
     /** Reading the subtitle track takes too long.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onInfo
+     * @see android.media.MediaPlayer2.EventCallback#onInfo
      */
     public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
 
@@ -2052,129 +2052,129 @@
 
     //--------------------------------------------------------------------------
     /** The player just completed a call {@link #attachAuxEffect}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1;
 
     /** The player just completed a call {@link #deselectTrack}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_DESELECT_TRACK = 2;
 
     /** The player just completed a call {@link #loopCurrent}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_LOOP_CURRENT = 3;
 
     /** The player just completed a call {@link #pause}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_PAUSE = 4;
 
     /** The player just completed a call {@link #play}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_PLAY = 5;
 
     /** The player just completed a call {@link #prepare}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_PREPARE = 6;
 
     /** The player just completed a call {@link #releaseDrm}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_RELEASE_DRM = 12;
 
     /** The player just completed a call {@link #restoreDrmKeys}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_RESTORE_DRM_KEYS = 13;
 
     /** The player just completed a call {@link #seekTo}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SEEK_TO = 14;
 
     /** The player just completed a call {@link #selectTrack}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SELECT_TRACK = 15;
 
     /** The player just completed a call {@link #setAudioAttributes}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16;
 
     /** The player just completed a call {@link #setAudioSessionId}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17;
 
     /** The player just completed a call {@link #setAuxEffectSendLevel}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18;
 
     /** The player just completed a call {@link #setDataSource}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19;
 
     /** The player just completed a call {@link #setNextDataSource}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22;
 
     /** The player just completed a call {@link #setNextDataSources}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23;
 
     /** The player just completed a call {@link #setPlaybackParams}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24;
 
     /** The player just completed a call {@link #setPlaybackSpeed}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_PLAYBACK_SPEED = 25;
 
     /** The player just completed a call {@link #setPlayerVolume}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26;
 
     /** The player just completed a call {@link #setSurface}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_SURFACE = 27;
 
     /** The player just completed a call {@link #setSyncParams}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SET_SYNC_PARAMS = 28;
 
     /** The player just completed a call {@link #skipToNext}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29;
 
     /** The player just completed a call {@link #setBufferingParams}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      * @hide
      */
     public static final int CALL_COMPLETED_SET_BUFFERING_PARAMS = 1001;
 
     /** The player just completed a call {@code setVideoScalingMode}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      * @hide
      */
     public static final int CALL_COMPLETED_SET_VIDEO_SCALING_MODE = 1002;
 
     /** The player just completed a call {@code notifyWhenCommandLabelReached}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCommandLabelReached
+     * @see android.media.MediaPlayer2.EventCallback#onCommandLabelReached
      * @hide
      */
     public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED = 1003;
@@ -2213,38 +2213,38 @@
     public @interface CallCompleted {}
 
     /** Status code represents that call is completed without an error.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_STATUS_NO_ERROR = 0;
 
     /** Status code represents that call is ended with an unknown error.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_STATUS_ERROR_UNKNOWN = Integer.MIN_VALUE;
 
     /** Status code represents that the player is not in valid state for the operation.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_STATUS_INVALID_OPERATION = 1;
 
     /** Status code represents that the argument is illegal.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_STATUS_BAD_VALUE = 2;
 
     /** Status code represents that the operation is not allowed.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_STATUS_PERMISSION_DENIED = 3;
 
     /** Status code represents a file or network related operation error.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_STATUS_ERROR_IO = 4;
 
     /** Status code represents that DRM operation is called before preparing a DRM scheme through
      *  {@link #prepareDrm}.
-     * @see android.media.MediaPlayer2.MediaPlayer2EventCallback#onCallCompleted
+     * @see android.media.MediaPlayer2.EventCallback#onCallCompleted
      */
     public static final int CALL_STATUS_NO_DRM_SCHEME = 5;
 
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index 56423fd..0d339a1 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -666,7 +666,7 @@
             @Override
             void process() {
                 synchronized (mEventCbLock) {
-                    for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                    for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                         cb.first.execute(() -> cb.second.onCommandLabelReached(
                                 MediaPlayer2Impl.this, label));
                     }
@@ -1305,9 +1305,9 @@
      *
      * @return the width of the video, or 0 if there is no video,
      * no display surface was set, or the width has not been determined
-     * yet. The {@code MediaPlayer2EventCallback} can be registered via
-     * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a
-     * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the width
+     * yet. The {@code EventCallback} can be registered via
+     * {@link #setEventCallback(Executor, EventCallback)} to provide a
+     * notification {@code EventCallback.onVideoSizeChanged} when the width
      * is available.
      */
     @Override
@@ -1318,9 +1318,9 @@
      *
      * @return the height of the video, or 0 if there is no video,
      * no display surface was set, or the height has not been determined
-     * yet. The {@code MediaPlayer2EventCallback} can be registered via
-     * {@link #setMediaPlayer2EventCallback(Executor, MediaPlayer2EventCallback)} to provide a
-     * notification {@code MediaPlayer2EventCallback.onVideoSizeChanged} when the height
+     * yet. The {@code EventCallback} can be registered via
+     * {@link #setEventCallback(Executor, EventCallback)} to provide a
+     * notification {@code EventCallback.onVideoSizeChanged} when the height
      * is available.
      */
     @Override
@@ -2820,7 +2820,7 @@
 
                 if (dsd != null) {
                     synchronized (mEventCbLock) {
-                        for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                        for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                             cb.first.execute(() -> cb.second.onInfo(
                                     mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0));
                         }
@@ -2882,7 +2882,7 @@
                 }
 
                 synchronized (mEventCbLock) {
-                    for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                    for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                         cb.first.execute(() -> cb.second.onInfo(
                                 mMediaPlayer, dsd, MEDIA_INFO_PLAYBACK_COMPLETE, 0));
                     }
@@ -2916,7 +2916,7 @@
                 synchronized (mEventCbLock) {
                     if (srcId == mCurrentSrcId) {
                         mBufferedPercentageCurrent.set(percent);
-                        for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                        for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                             cb.first.execute(() -> cb.second.onInfo(
                                     mMediaPlayer, mCurrentDSD, MEDIA_INFO_BUFFERING_UPDATE,
                                     percent));
@@ -2924,7 +2924,7 @@
                     } else if (srcId == mNextSrcId && !mNextDSDs.isEmpty()) {
                         mBufferedPercentageNext.set(percent);
                         DataSourceDesc nextDSD = mNextDSDs.get(0);
-                        for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                        for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                             cb.first.execute(() -> cb.second.onInfo(
                                     mMediaPlayer, nextDSD, MEDIA_INFO_BUFFERING_UPDATE,
                                     percent));
@@ -2962,7 +2962,7 @@
                 final int width = msg.arg1;
                 final int height = msg.arg2;
                 synchronized (mEventCbLock) {
-                    for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                    for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                         cb.first.execute(() -> cb.second.onVideoSizeChanged(
                                 mMediaPlayer, mCurrentDSD, width, height));
                     }
@@ -2974,7 +2974,7 @@
             {
                 Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
                 synchronized (mEventCbLock) {
-                    for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                    for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                         cb.first.execute(() -> cb.second.onError(
                                 mMediaPlayer, mCurrentDSD, what, extra));
                         cb.first.execute(() -> cb.second.onInfo(
@@ -3027,7 +3027,7 @@
                 }
 
                 synchronized (mEventCbLock) {
-                    for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                    for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                         cb.first.execute(() -> cb.second.onInfo(
                                 mMediaPlayer, mCurrentDSD, what, extra));
                     }
@@ -3057,7 +3057,7 @@
                 }
 
                 synchronized (mEventCbLock) {
-                    for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                    for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                         cb.first.execute(() -> cb.second.onTimedText(mMediaPlayer, mCurrentDSD, text));
                     }
                 }
@@ -3091,7 +3091,7 @@
                 }
 
                 synchronized (mEventCbLock) {
-                    for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                    for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                         cb.first.execute(() -> cb.second.onTimedMetaDataAvailable(
                                 mMediaPlayer, mCurrentDSD, data));
                     }
@@ -3196,8 +3196,8 @@
     }
 
     private final Object mEventCbLock = new Object();
-    private ArrayList<Pair<Executor, MediaPlayer2EventCallback> > mEventCallbackRecords
-        = new ArrayList<Pair<Executor, MediaPlayer2EventCallback> >();
+    private ArrayList<Pair<Executor, EventCallback> > mEventCallbackRecords
+        = new ArrayList<Pair<Executor, EventCallback> >();
 
     /**
      * Register a callback to be invoked when the media source is ready
@@ -3207,14 +3207,14 @@
      * @param executor the executor through which the callback should be invoked
      */
     @Override
-    public void setMediaPlayer2EventCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull MediaPlayer2EventCallback eventCallback) {
+    public void setEventCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull EventCallback eventCallback) {
         if (eventCallback == null) {
-            throw new IllegalArgumentException("Illegal null MediaPlayer2EventCallback");
+            throw new IllegalArgumentException("Illegal null EventCallback");
         }
         if (executor == null) {
             throw new IllegalArgumentException(
-                    "Illegal null Executor for the MediaPlayer2EventCallback");
+                    "Illegal null Executor for the EventCallback");
         }
         synchronized (mEventCbLock) {
             mEventCallbackRecords.add(new Pair(executor, eventCallback));
@@ -3222,10 +3222,10 @@
     }
 
     /**
-     * Clears the {@link MediaPlayer2EventCallback}.
+     * Clears the {@link EventCallback}.
      */
     @Override
-    public void clearMediaPlayer2EventCallback() {
+    public void clearEventCallback() {
         synchronized (mEventCbLock) {
             mEventCallbackRecords.clear();
         }
@@ -3281,11 +3281,11 @@
     public void setDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull DrmEventCallback eventCallback) {
         if (eventCallback == null) {
-            throw new IllegalArgumentException("Illegal null MediaPlayer2EventCallback");
+            throw new IllegalArgumentException("Illegal null EventCallback");
         }
         if (executor == null) {
             throw new IllegalArgumentException(
-                    "Illegal null Executor for the MediaPlayer2EventCallback");
+                    "Illegal null Executor for the EventCallback");
         }
         synchronized (mDrmEventCbLock) {
             mDrmEventCallbackRecords.add(new Pair(executor, eventCallback));
@@ -4786,7 +4786,7 @@
                 return;
             }
             synchronized (mEventCbLock) {
-                for (Pair<Executor, MediaPlayer2EventCallback> cb : mEventCallbackRecords) {
+                for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
                     cb.first.execute(() -> cb.second.onCallCompleted(
                             MediaPlayer2Impl.this, mDSD, mMediaCallType, status));
                 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index 8473c06..a18600a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -40,9 +40,6 @@
 public class AccessibilityUtils {
     public static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
 
-    final static TextUtils.SimpleStringSplitter sStringColonSplitter =
-            new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
-
     /**
      * @return the set of enabled accessibility services. If there are no services,
      * it returns the unmodifiable {@link Collections#emptySet()}.
@@ -72,16 +69,16 @@
         final String enabledServicesSetting = Settings.Secure.getStringForUser(
                 context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 userId);
-        if (enabledServicesSetting == null) {
+        if (TextUtils.isEmpty(enabledServicesSetting)) {
             return Collections.emptySet();
         }
 
         final Set<ComponentName> enabledServices = new HashSet<>();
-        final TextUtils.SimpleStringSplitter colonSplitter = sStringColonSplitter;
+        final TextUtils.StringSplitter colonSplitter =
+                new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
         colonSplitter.setString(enabledServicesSetting);
 
-        while (colonSplitter.hasNext()) {
-            final String componentNameString = colonSplitter.next();
+        for (String componentNameString : colonSplitter) {
             final ComponentName enabledService = ComponentName.unflattenFromString(
                     componentNameString);
             if (enabledService != null) {
@@ -168,8 +165,7 @@
      * an OEM-configurable default if the setting has never been set.
      *
      * @param context A valid context
-     * @param userId The user whose settings should be checked
-     *
+     * @param userId  The user whose settings should be checked
      * @return The component name, flattened to a string, of the target service.
      */
     public static String getShortcutTargetServiceComponentNameString(
@@ -187,9 +183,9 @@
      * Check if the accessibility shortcut is enabled for a user
      *
      * @param context A valid context
-     * @param userId The user of interest
+     * @param userId  The user of interest
      * @return {@code true} if the shortcut is enabled for the user. {@code false} otherwise.
-     *         Note that the shortcut may be enabled, but no action associated with it.
+     * Note that the shortcut may be enabled, but no action associated with it.
      */
     public static boolean isShortcutEnabled(Context context, int userId) {
         return Settings.Secure.getIntForUser(context.getContentResolver(),
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 0f0e4e5..3549abc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -34,7 +34,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
-import android.widget.RemoteViews;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -96,11 +95,7 @@
     /**
      * The key used to get the category from metadata of activities of action
      * {@link #EXTRA_SETTINGS_ACTION}
-     * The value must be one of:
-     * <li>com.android.settings.category.wireless</li>
-     * <li>com.android.settings.category.device</li>
-     * <li>com.android.settings.category.personal</li>
-     * <li>com.android.settings.category.system</li>
+     * The value must be from {@link CategoryKey}.
      */
     private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
 
@@ -171,17 +166,6 @@
     public static final String META_DATA_PREFERENCE_SUMMARY_URI =
             "com.android.settings.summary_uri";
 
-    /**
-     * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
-     * custom view which should be displayed for the preference. The custom view will be inflated
-     * as a remote view.
-     *
-     * This also can be used with {@link #META_DATA_PREFERENCE_SUMMARY_URI}, by setting the id
-     * of the summary TextView to '@android:id/summary'.
-     */
-    public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
-            "com.android.settings.custom_view";
-
     public static final String SETTING_PKG = "com.android.settings";
 
     /**
@@ -442,11 +426,6 @@
                             keyHint = metaData.getString(META_DATA_PREFERENCE_KEYHINT);
                         }
                     }
-                    if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
-                        int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
-                        tile.remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
-                        updateSummaryAndTitle(context, providerMap, tile);
-                    }
                 }
             } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
                 if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java
deleted file mode 100644
index 19e556a..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java
+++ /dev/null
@@ -1,25 +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.settingslib.suggestions;
-
-public class SuggestionCategory {
-    public String category;
-    public String pkg;
-    public boolean multiple;
-    public boolean exclusive;
-    public long exclusiveExpireDaysInMillis;
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java
deleted file mode 100644
index a890920..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java
+++ /dev/null
@@ -1,85 +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.settingslib.suggestions;
-
-import android.content.Intent;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import com.android.settingslib.drawer.Tile;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class SuggestionList {
-    // Category -> list of suggestion map
-    private final Map<SuggestionCategory, List<Tile>> mSuggestions;
-
-    // A flatten list of all suggestions.
-    private List<Tile> mSuggestionList;
-
-    public SuggestionList() {
-        mSuggestions = new ArrayMap<>();
-    }
-
-    public void addSuggestions(SuggestionCategory category, List<Tile> suggestions) {
-        mSuggestions.put(category, suggestions);
-    }
-
-    public List<Tile> getSuggestions() {
-        if (mSuggestionList != null) {
-            return mSuggestionList;
-        }
-        mSuggestionList = new ArrayList<>();
-        for (List<Tile> suggestions : mSuggestions.values()) {
-            mSuggestionList.addAll(suggestions);
-        }
-        dedupeSuggestions(mSuggestionList);
-        return mSuggestionList;
-    }
-
-    public boolean isExclusiveSuggestionCategory() {
-        if (mSuggestions.size() != 1) {
-            // If there is no category, or more than 1 category, it's not exclusive by definition.
-            return false;
-        }
-        for (SuggestionCategory category : mSuggestions.keySet()) {
-            if (category.exclusive) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Filter suggestions list so they are all unique.
-     */
-    private void dedupeSuggestions(List<Tile> suggestions) {
-        final Set<String> intents = new ArraySet<>();
-        for (int i = suggestions.size() - 1; i >= 0; i--) {
-            final Tile suggestion = suggestions.get(i);
-            final String intentUri = suggestion.intent.toUri(Intent.URI_INTENT_SCHEME);
-            if (intents.contains(intentUri)) {
-                suggestions.remove(i);
-            } else {
-                intents.add(intentUri);
-            }
-        }
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
deleted file mode 100644
index 8705c98..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
+++ /dev/null
@@ -1,498 +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.settingslib.suggestions;
-
-import android.Manifest;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.annotation.RequiresPermission;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import androidx.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Xml;
-import android.view.InflateException;
-
-import com.android.settingslib.drawer.Tile;
-import com.android.settingslib.drawer.TileUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class SuggestionParser {
-
-    private static final String TAG = "SuggestionParser";
-
-    // If defined, only returns this suggestion if the feature is supported.
-    public static final String META_DATA_REQUIRE_FEATURE = "com.android.settings.require_feature";
-
-    // If defined, only display this optional step if an account of that type exists.
-    private static final String META_DATA_REQUIRE_ACCOUNT = "com.android.settings.require_account";
-
-    // If defined and not true, do not should optional step.
-    private static final String META_DATA_IS_SUPPORTED = "com.android.settings.is_supported";
-
-    // If defined, only display this optional step if the current user is of that type.
-    private static final String META_DATA_REQUIRE_USER_TYPE =
-            "com.android.settings.require_user_type";
-
-    // If defined, only display this optional step if a connection is available.
-    private static final String META_DATA_IS_CONNECTION_REQUIRED =
-            "com.android.settings.require_connection";
-
-    // The valid values that setup wizard recognizes for differentiating user types.
-    private static final String META_DATA_PRIMARY_USER_TYPE_VALUE = "primary";
-    private static final String META_DATA_ADMIN_USER_TYPE_VALUE = "admin";
-    private static final String META_DATA_GUEST_USER_TYPE_VALUE = "guest";
-    private static final String META_DATA_RESTRICTED_USER_TYPE_VALUE = "restricted";
-
-    /**
-     * Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
-     * For instance:
-     * 0,10
-     * Will appear immediately, but if the user removes it, it will come back after 10 days.
-     *
-     * Another example:
-     * 10,30
-     * Will only show up after 10 days, and then again after 30.
-     */
-    public static final String META_DATA_DISMISS_CONTROL = "com.android.settings.dismiss";
-
-    // Shared prefs keys for storing dismissed state.
-    // Index into current dismissed state.
-    public static final String SETUP_TIME = "_setup_time";
-    private static final String IS_DISMISSED = "_is_dismissed";
-
-    // Default dismiss control for smart suggestions.
-    private static final String DEFAULT_SMART_DISMISS_CONTROL = "0";
-
-    private final Context mContext;
-    private final List<SuggestionCategory> mSuggestionList;
-    private final ArrayMap<Pair<String, String>, Tile> mAddCache = new ArrayMap<>();
-    private final SharedPreferences mSharedPrefs;
-    private final String mDefaultDismissControl;
-
-    public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml,
-            String defaultDismissControl) {
-        this(
-                context,
-                sharedPrefs,
-                (List<SuggestionCategory>) new SuggestionOrderInflater(context).parse(orderXml),
-                defaultDismissControl);
-    }
-
-    public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
-        this(context, sharedPrefs, orderXml, DEFAULT_SMART_DISMISS_CONTROL);
-    }
-
-    @VisibleForTesting
-    public SuggestionParser(
-            Context context,
-            SharedPreferences sharedPrefs,
-            List<SuggestionCategory> suggestionList,
-            String defaultDismissControl) {
-        mContext = context;
-        mSuggestionList = suggestionList;
-        mSharedPrefs = sharedPrefs;
-        mDefaultDismissControl = defaultDismissControl;
-    }
-
-    public SuggestionList getSuggestions(boolean isSmartSuggestionEnabled) {
-        final SuggestionList suggestionList = new SuggestionList();
-        final int N = mSuggestionList.size();
-        for (int i = 0; i < N; i++) {
-            final SuggestionCategory category = mSuggestionList.get(i);
-            if (category.exclusive && !isExclusiveCategoryExpired(category)) {
-                // If suggestions from an exclusive category are present, parsing is stopped
-                // and only suggestions from that category are displayed. Note that subsequent
-                // exclusive categories are also ignored.
-                final List<Tile> exclusiveSuggestions = new ArrayList<>();
-
-                // Read suggestion and force isSmartSuggestion to be false so the rule defined
-                // from each suggestion itself is used.
-                readSuggestions(category, exclusiveSuggestions, false /* isSmartSuggestion */);
-                if (!exclusiveSuggestions.isEmpty()) {
-                    final SuggestionList exclusiveList = new SuggestionList();
-                    exclusiveList.addSuggestions(category, exclusiveSuggestions);
-                    return exclusiveList;
-                }
-            } else {
-                // Either the category is not exclusive, or the exclusiveness expired so we should
-                // treat it as a normal category.
-                final List<Tile> suggestions = new ArrayList<>();
-                readSuggestions(category, suggestions, isSmartSuggestionEnabled);
-                suggestionList.addSuggestions(category, suggestions);
-            }
-        }
-        return suggestionList;
-    }
-
-    /**
-     * Dismisses a suggestion, returns true if the suggestion has no more dismisses left and should
-     * be disabled.
-     */
-    public boolean dismissSuggestion(Tile suggestion) {
-        final String keyBase = suggestion.intent.getComponent().flattenToShortString();
-        mSharedPrefs.edit()
-                .putBoolean(keyBase + IS_DISMISSED, true)
-                .commit();
-        return true;
-    }
-
-    @VisibleForTesting
-    public void filterSuggestions(
-            List<Tile> suggestions, int countBefore, boolean isSmartSuggestionEnabled) {
-        for (int i = countBefore; i < suggestions.size(); i++) {
-            if (!isAvailable(suggestions.get(i)) ||
-                    !isSupported(suggestions.get(i)) ||
-                    !satisifesRequiredUserType(suggestions.get(i)) ||
-                    !satisfiesRequiredAccount(suggestions.get(i)) ||
-                    !satisfiesConnectivity(suggestions.get(i)) ||
-                    isDismissed(suggestions.get(i), isSmartSuggestionEnabled)) {
-                suggestions.remove(i--);
-            }
-        }
-    }
-
-    @VisibleForTesting
-    void readSuggestions(
-            SuggestionCategory category, List<Tile> suggestions, boolean isSmartSuggestionEnabled) {
-        int countBefore = suggestions.size();
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.addCategory(category.category);
-        if (category.pkg != null) {
-            intent.setPackage(category.pkg);
-        }
-        TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
-                mAddCache, null, suggestions, true, false, false, true /* shouldUpdateTiles */);
-        filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);
-        if (!category.multiple && suggestions.size() > (countBefore + 1)) {
-            // If there are too many, remove them all and only re-add the one with the highest
-            // priority.
-            Tile item = suggestions.remove(suggestions.size() - 1);
-            while (suggestions.size() > countBefore) {
-                Tile last = suggestions.remove(suggestions.size() - 1);
-                if (last.priority > item.priority) {
-                    item = last;
-                }
-            }
-            // If category is marked as done, do not add any item.
-            if (!isCategoryDone(category.category)) {
-                suggestions.add(item);
-            }
-        }
-    }
-
-    private boolean isAvailable(Tile suggestion) {
-        final String featuresRequired = suggestion.metaData.getString(META_DATA_REQUIRE_FEATURE);
-        if (featuresRequired != null) {
-            for (String feature : featuresRequired.split(",")) {
-                if (TextUtils.isEmpty(feature)) {
-                    Log.w(TAG, "Found empty substring when parsing required features: "
-                            + featuresRequired);
-                } else if (!mContext.getPackageManager().hasSystemFeature(feature)) {
-                    Log.i(TAG, suggestion.title + " requires unavailable feature " + feature);
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    @RequiresPermission(Manifest.permission.MANAGE_USERS)
-    private boolean satisifesRequiredUserType(Tile suggestion) {
-        final String requiredUser = suggestion.metaData.getString(META_DATA_REQUIRE_USER_TYPE);
-        if (requiredUser != null) {
-            final UserManager userManager = mContext.getSystemService(UserManager.class);
-            UserInfo userInfo = userManager.getUserInfo(UserHandle.myUserId());
-            for (String userType : requiredUser.split("\\|")) {
-                final boolean primaryUserCondtionMet = userInfo.isPrimary()
-                        && META_DATA_PRIMARY_USER_TYPE_VALUE.equals(userType);
-                final boolean adminUserConditionMet = userInfo.isAdmin()
-                        && META_DATA_ADMIN_USER_TYPE_VALUE.equals(userType);
-                final boolean guestUserCondtionMet = userInfo.isGuest()
-                        && META_DATA_GUEST_USER_TYPE_VALUE.equals(userType);
-                final boolean restrictedUserCondtionMet = userInfo.isRestricted()
-                        && META_DATA_RESTRICTED_USER_TYPE_VALUE.equals(userType);
-                if (primaryUserCondtionMet || adminUserConditionMet || guestUserCondtionMet
-                        || restrictedUserCondtionMet) {
-                    return true;
-                }
-            }
-            Log.i(TAG, suggestion.title + " requires user type " + requiredUser);
-            return false;
-        }
-        return true;
-    }
-
-    public boolean satisfiesRequiredAccount(Tile suggestion) {
-        final String requiredAccountType = suggestion.metaData.getString(META_DATA_REQUIRE_ACCOUNT);
-        if (requiredAccountType == null) {
-            return true;
-        }
-        AccountManager accountManager = mContext.getSystemService(AccountManager.class);
-        Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
-        boolean satisfiesRequiredAccount = accounts.length > 0;
-        if (!satisfiesRequiredAccount) {
-            Log.i(TAG, suggestion.title + " requires unavailable account type "
-                    + requiredAccountType);
-        }
-        return satisfiesRequiredAccount;
-    }
-
-    public boolean isSupported(Tile suggestion) {
-        final int isSupportedResource = suggestion.metaData.getInt(META_DATA_IS_SUPPORTED);
-        try {
-            if (suggestion.intent == null) {
-                return false;
-            }
-            final Resources res = mContext.getPackageManager().getResourcesForActivity(
-                    suggestion.intent.getComponent());
-            boolean isSupported =
-                    isSupportedResource != 0 ? res.getBoolean(isSupportedResource) : true;
-            if (!isSupported) {
-                Log.i(TAG, suggestion.title + " requires unsupported resource "
-                        + isSupportedResource);
-            }
-            return isSupported;
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent());
-            return false;
-        } catch (Resources.NotFoundException e) {
-            Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent(), e);
-            return false;
-        }
-    }
-
-    private boolean satisfiesConnectivity(Tile suggestion) {
-        final boolean isConnectionRequired =
-                suggestion.metaData.getBoolean(META_DATA_IS_CONNECTION_REQUIRED);
-        if (!isConnectionRequired) {
-            return true;
-        }
-        ConnectivityManager cm =
-                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-        NetworkInfo netInfo = cm.getActiveNetworkInfo();
-        boolean satisfiesConnectivity = netInfo != null && netInfo.isConnectedOrConnecting();
-        if (!satisfiesConnectivity) {
-            Log.i(TAG, suggestion.title + " is missing required connection.");
-        }
-        return satisfiesConnectivity;
-    }
-
-    public boolean isCategoryDone(String category) {
-        String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
-        return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
-    }
-
-    public void markCategoryDone(String category) {
-        String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
-        Settings.Secure.putInt(mContext.getContentResolver(), name, 1);
-    }
-
-    /**
-     * Whether or not the category's exclusiveness has expired.
-     */
-    private boolean isExclusiveCategoryExpired(SuggestionCategory category) {
-        final String keySetupTime = category.category + SETUP_TIME;
-        final long currentTime = System.currentTimeMillis();
-        if (!mSharedPrefs.contains(keySetupTime)) {
-            mSharedPrefs.edit()
-                    .putLong(keySetupTime, currentTime)
-                    .commit();
-        }
-        if (category.exclusiveExpireDaysInMillis < 0) {
-            // negative means never expires
-            return false;
-        }
-        final long setupTime = mSharedPrefs.getLong(keySetupTime, 0);
-        final long elapsedTime = currentTime - setupTime;
-        Log.d(TAG, "Day " + elapsedTime / DateUtils.DAY_IN_MILLIS + " for " + category.category);
-        return elapsedTime > category.exclusiveExpireDaysInMillis;
-    }
-
-    @VisibleForTesting
-    boolean isDismissed(Tile suggestion, boolean isSmartSuggestionEnabled) {
-        String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled);
-        String keyBase = suggestion.intent.getComponent().flattenToShortString();
-        if (!mSharedPrefs.contains(keyBase + SETUP_TIME)) {
-            mSharedPrefs.edit()
-                    .putLong(keyBase + SETUP_TIME, System.currentTimeMillis())
-                    .commit();
-        }
-        // Check if it's already manually dismissed
-        final boolean isDismissed = mSharedPrefs.getBoolean(keyBase + IS_DISMISSED, false);
-        if (isDismissed) {
-            return true;
-        }
-        if (dismissControl == null) {
-            return false;
-        }
-        // Parse when suggestion should first appear. return true to artificially hide suggestion
-        // before then.
-        int firstAppearDay = parseDismissString(dismissControl);
-        long firstAppearDayInMs = getEndTime(mSharedPrefs.getLong(keyBase + SETUP_TIME, 0),
-                firstAppearDay);
-        if (System.currentTimeMillis() >= firstAppearDayInMs) {
-            // Dismiss timeout has passed, undismiss it.
-            mSharedPrefs.edit()
-                    .putBoolean(keyBase + IS_DISMISSED, false)
-                    .commit();
-            return false;
-        }
-        return true;
-    }
-
-    private long getEndTime(long startTime, int daysDelay) {
-        long days = daysDelay * DateUtils.DAY_IN_MILLIS;
-        return startTime + days;
-    }
-
-    /**
-     * Parse the first int from a string formatted as "0,1,2..."
-     * The value means suggestion should first appear on Day X.
-     */
-    private int parseDismissString(String dismissControl) {
-        final String[] dismissStrs = dismissControl.split(",");
-        return Integer.parseInt(dismissStrs[0]);
-    }
-
-    private String getDismissControl(Tile suggestion, boolean isSmartSuggestionEnabled) {
-        if (isSmartSuggestionEnabled) {
-            return mDefaultDismissControl;
-        } else {
-            return suggestion.metaData.getString(META_DATA_DISMISS_CONTROL);
-        }
-    }
-
-    private static class SuggestionOrderInflater {
-        private static final String TAG_LIST = "optional-steps";
-        private static final String TAG_ITEM = "step";
-
-        private static final String ATTR_CATEGORY = "category";
-        private static final String ATTR_PACKAGE = "package";
-        private static final String ATTR_MULTIPLE = "multiple";
-        private static final String ATTR_EXCLUSIVE = "exclusive";
-        private static final String ATTR_EXCLUSIVE_EXPIRE_DAYS = "exclusiveExpireDays";
-
-        private final Context mContext;
-
-        public SuggestionOrderInflater(Context context) {
-            mContext = context;
-        }
-
-        public Object parse(int resource) {
-            XmlPullParser parser = mContext.getResources().getXml(resource);
-            final AttributeSet attrs = Xml.asAttributeSet(parser);
-            try {
-                // Look for the root node.
-                int type;
-                do {
-                    type = parser.next();
-                } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
-
-                if (type != XmlPullParser.START_TAG) {
-                    throw new InflateException(parser.getPositionDescription()
-                            + ": No start tag found!");
-                }
-
-                // Temp is the root that was found in the xml
-                Object xmlRoot = onCreateItem(parser.getName(), attrs);
-
-                // Inflate all children under temp
-                rParse(parser, xmlRoot, attrs);
-                return xmlRoot;
-            } catch (XmlPullParserException | IOException e) {
-                Log.w(TAG, "Problem parser resource " + resource, e);
-                return null;
-            }
-        }
-
-        /**
-         * Recursive method used to descend down the xml hierarchy and instantiate
-         * items, instantiate their children.
-         */
-        private void rParse(XmlPullParser parser, Object parent, final AttributeSet attrs)
-                throws XmlPullParserException, IOException {
-            final int depth = parser.getDepth();
-
-            int type;
-            while (((type = parser.next()) != XmlPullParser.END_TAG ||
-                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-                if (type != XmlPullParser.START_TAG) {
-                    continue;
-                }
-
-                final String name = parser.getName();
-
-                Object item = onCreateItem(name, attrs);
-                onAddChildItem(parent, item);
-                rParse(parser, item, attrs);
-            }
-        }
-
-        protected void onAddChildItem(Object parent, Object child) {
-            if (parent instanceof List<?> && child instanceof SuggestionCategory) {
-                ((List<SuggestionCategory>) parent).add((SuggestionCategory) child);
-            } else {
-                throw new IllegalArgumentException("Parent was not a list");
-            }
-        }
-
-        protected Object onCreateItem(String name, AttributeSet attrs) {
-            if (name.equals(TAG_LIST)) {
-                return new ArrayList<SuggestionCategory>();
-            } else if (name.equals(TAG_ITEM)) {
-                SuggestionCategory category = new SuggestionCategory();
-                category.category = attrs.getAttributeValue(null, ATTR_CATEGORY);
-                category.pkg = attrs.getAttributeValue(null, ATTR_PACKAGE);
-                String multiple = attrs.getAttributeValue(null, ATTR_MULTIPLE);
-                category.multiple = !TextUtils.isEmpty(multiple) && Boolean.parseBoolean(multiple);
-                String exclusive = attrs.getAttributeValue(null, ATTR_EXCLUSIVE);
-                category.exclusive =
-                        !TextUtils.isEmpty(exclusive) && Boolean.parseBoolean(exclusive);
-                String expireDaysAttr = attrs.getAttributeValue(null,
-                        ATTR_EXCLUSIVE_EXPIRE_DAYS);
-                long expireDays = !TextUtils.isEmpty(expireDaysAttr)
-                        ? Integer.parseInt(expireDaysAttr)
-                        : -1;
-                category.exclusiveExpireDaysInMillis = DateUtils.DAY_IN_MILLIS * expireDays;
-                return category;
-            } else {
-                throw new IllegalArgumentException("Unknown item " + name);
-            }
-        }
-    }
-}
-
diff --git a/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml b/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
deleted file mode 100644
index f02ac15..0000000
--- a/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<optional-steps>
-    <step category="com.android.settings.suggested.category.DEFERRED_SETUP"
-        exclusive="true" />
-    <step category="com.android.settings.suggested.category.LOCK_SCREEN" />
-    <step category="com.android.settings.suggested.category.TRUST_AGENT" />
-    <step category="com.android.settings.suggested.category.EMAIL" />
-    <step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"
-        multiple="true" />
-    <step category="com.android.settings.suggested.category.GESTURE" />
-    <step category="com.android.settings.suggested.category.HOTWORD" />
-    <step category="com.android.settings.suggested.category.DEFAULT"
-        multiple="true" />
-    <step category="com.android.settings.suggested.category.SETTINGS_ONLY"
-        multiple="true" />
-</optional-steps>
diff --git a/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecConfig.java b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecConfig.java
deleted file mode 100644
index 14b0d59..0000000
--- a/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecConfig.java
+++ /dev/null
@@ -1,26 +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 android.bluetooth;
-
-/**
- * A placeholder class to prevent ClassNotFound exceptions caused by lack of visibility.
- */
-public class BluetoothCodecConfig {
-    public boolean isMandatoryCodec() { return true; }
-    public String getCodecName() { return null; }
-    public int getCodecType() { return -1; }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecStatus.java b/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecStatus.java
deleted file mode 100644
index 919ec3f..0000000
--- a/packages/SettingsLib/tests/robotests/src/android/bluetooth/BluetoothCodecStatus.java
+++ /dev/null
@@ -1,25 +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 android.bluetooth;
-
-/**
- * A placeholder class to prevent ClassNotFound exceptions caused by lack of visibility.
- */
-public class BluetoothCodecStatus {
-    public BluetoothCodecConfig getCodecConfig() { return null; }
-    public BluetoothCodecConfig[] getCodecsSelectableCapabilities() { return null; }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
new file mode 100644
index 0000000..152d024
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+public class AccessibilityUtilsTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @Test
+    public void getEnabledServicesFromSettings_noService_emptyResult() {
+        assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext)).isEmpty();
+    }
+
+    @Test
+    public void getEnabledServicesFromSettings_badFormat_emptyResult() {
+        Settings.Secure.putStringForUser(
+                mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                ":",
+                UserHandle.myUserId());
+
+        assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext)).isEmpty();
+    }
+
+    @Test
+    public void getEnabledServicesFromSettings_1Service_1result() {
+        final ComponentName cn = new ComponentName("pkg", "serv");
+        Settings.Secure.putStringForUser(
+                mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                cn.flattenToString() + ":",
+                UserHandle.myUserId());
+
+        assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext))
+                .containsExactly(cn);
+    }
+
+    @Test
+    public void getEnabledServicesFromSettings_2Services_2results() {
+        final ComponentName cn1 = new ComponentName("pkg", "serv");
+        final ComponentName cn2 = new ComponentName("pkg", "serv2");
+        Settings.Secure.putStringForUser(
+                mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                cn1.flattenToString() + ":" + cn2.flattenToString(),
+                UserHandle.myUserId());
+
+        assertThat(AccessibilityUtils.getEnabledServicesFromSettings(mContext))
+                .containsExactly(cn1, cn2);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index fc1b2238..6e66805 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -28,7 +28,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.RuntimeEnvironment.application;
-import static org.robolectric.shadow.api.Shadow.extract;
 
 import android.app.ActivityManager;
 import android.content.ContentResolver;
@@ -52,9 +51,6 @@
 import android.util.Pair;
 import android.widget.RemoteViews;
 
-import com.android.settingslib.R;
-import com.android.settingslib.suggestions.SuggestionParser;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -67,7 +63,6 @@
 import org.robolectric.annotation.Implements;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -164,35 +159,6 @@
     }
 
     @Test
-    public void getTilesForIntent_shouldSkipFilteredApps() {
-        Intent intent = new Intent();
-        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
-        List<Tile> outTiles = new ArrayList<>();
-        List<ResolveInfo> info = new ArrayList<>();
-        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
-                URI_GET_SUMMARY);
-        addMetadataToInfo(resolveInfo, "com.android.settings.require_account", "com.google");
-        addMetadataToInfo(resolveInfo, "com.android.settings.require_connection", "true");
-        info.add(resolveInfo);
-
-        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
-                .thenReturn(info);
-
-        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
-                null /* defaultCategory */, outTiles, false /* usePriority */,
-                false /* checkCategory */, true /* forceTintExternalIcon */);
-
-        assertThat(outTiles.size()).isEqualTo(1);
-        SuggestionParser parser = new SuggestionParser(
-                mContext,
-                null,
-                Collections.emptyList(),
-                "0,10");
-        parser.filterSuggestions(outTiles, 0, false);
-        assertThat(outTiles.size()).isEqualTo(0);
-    }
-
-    @Test
     public void getCategories_shouldHandleExtraIntentAction() {
         final String testCategory = "category1";
         final String testAction = "action1";
@@ -392,108 +358,6 @@
         assertThat(outTiles.size()).isEqualTo(1);
     }
 
-    @Test
-    public void getTilesForIntent_shouldShowRemoteViewIfSpecified() {
-        Intent intent = new Intent();
-        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
-        List<Tile> outTiles = new ArrayList<>();
-        List<ResolveInfo> info = new ArrayList<>();
-        ResolveInfo resolveInfo = newInfo(true, null /* category */);
-        resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
-                R.layout.user_preference);
-        info.add(resolveInfo);
-
-        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
-                .thenReturn(info);
-
-        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
-                null /* defaultCategory */, outTiles, false /* usePriority */,
-                false /* checkCategory */, true /* forceTintExternalIcon */);
-
-        assertThat(outTiles.size()).isEqualTo(1);
-        Tile tile = outTiles.get(0);
-        assertThat(tile.remoteViews).isNotNull();
-        assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
-    }
-
-    @Test
-    public void getTilesForIntent_summaryUriSpecified_shouldOverrideRemoteViewSummary()
-            throws RemoteException {
-        Intent intent = new Intent();
-        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
-        List<Tile> outTiles = new ArrayList<>();
-        List<ResolveInfo> info = new ArrayList<>();
-        ResolveInfo resolveInfo = newInfo(true, null /* category */, null,
-                null, URI_GET_SUMMARY);
-        resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
-                R.layout.user_preference);
-        info.add(resolveInfo);
-
-        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
-                .thenReturn(info);
-
-        // Mock the content provider interaction.
-        Bundle bundle = new Bundle();
-        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text");
-        when(mIContentProvider.call(anyString(),
-                eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
-                any())).thenReturn(bundle);
-        when(mContentResolver.acquireUnstableProvider(anyString()))
-                .thenReturn(mIContentProvider);
-        when(mContentResolver.acquireUnstableProvider(any(Uri.class)))
-                .thenReturn(mIContentProvider);
-
-        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
-                null /* defaultCategory */, outTiles, false /* usePriority */,
-                false /* checkCategory */, true /* forceTintExternalIcon */);
-
-        assertThat(outTiles.size()).isEqualTo(1);
-        Tile tile = outTiles.get(0);
-        assertThat(tile.remoteViews).isNotNull();
-        assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
-        // Make sure the summary TextView got a new text string.
-        TileUtilsShadowRemoteViews shadowRemoteViews = extract(tile.remoteViews);
-        assertThat(shadowRemoteViews.overrideViewId).isEqualTo(android.R.id.summary);
-        assertThat(shadowRemoteViews.overrideText).isEqualTo("new summary text");
-    }
-
-    @Test
-    public void getTilesForIntent_providerUnavailable_shouldNotOverrideRemoteViewSummary()
-            throws RemoteException {
-        Intent intent = new Intent();
-        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
-        List<Tile> outTiles = new ArrayList<>();
-        List<ResolveInfo> info = new ArrayList<>();
-        ResolveInfo resolveInfo = newInfo(true, null /* category */, null,
-                null, URI_GET_SUMMARY);
-        resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
-                R.layout.user_preference);
-        info.add(resolveInfo);
-
-        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
-                .thenReturn(info);
-
-        // Mock the content provider interaction.
-        Bundle bundle = new Bundle();
-        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text");
-        when(mIContentProvider.call(anyString(),
-                eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
-                any())).thenReturn(bundle);
-
-        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
-                null /* defaultCategory */, outTiles, false /* usePriority */,
-                false /* checkCategory */, true /* forceTintExternalIcon */);
-
-        assertThat(outTiles.size()).isEqualTo(1);
-        Tile tile = outTiles.get(0);
-        assertThat(tile.remoteViews).isNotNull();
-        assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
-        // Make sure the summary TextView didn't get any text view updates.
-        TileUtilsShadowRemoteViews shadowRemoteViews = extract(tile.remoteViews);
-        assertThat(shadowRemoteViews.overrideViewId).isNull();
-        assertThat(shadowRemoteViews.overrideText).isNull();
-    }
-
     public static ResolveInfo newInfo(boolean systemApp, String category) {
         return newInfo(systemApp, category, null);
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java
deleted file mode 100644
index d05bcfd..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java
+++ /dev/null
@@ -1,200 +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.settingslib.suggestions;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.robolectric.RuntimeEnvironment.application;
-import static org.robolectric.shadow.api.Shadow.extract;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-import com.android.settingslib.drawer.Tile;
-import com.android.settingslib.drawer.TileUtilsTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.shadows.ShadowPackageManager;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(SettingsLibRobolectricTestRunner.class)
-public class SuggestionParserTest {
-
-    private ShadowPackageManager mPackageManager;
-    private SuggestionParser mSuggestionParser;
-    private SuggestionCategory mMultipleCategory;
-    private SuggestionCategory mExclusiveCategory;
-    private SuggestionCategory mExpiredExclusiveCategory;
-    private List<Tile> mSuggestionsBeforeDismiss;
-    private List<Tile> mSuggestionsAfterDismiss;
-    private SharedPreferences mPrefs;
-    private Tile mSuggestion;
-
-    @Before
-    public void setUp() {
-        mPackageManager = extract(application.getPackageManager());
-        mPrefs = PreferenceManager.getDefaultSharedPreferences(application);
-        mSuggestion = new Tile();
-        mSuggestion.intent = new Intent("action");
-        mSuggestion.intent.setComponent(new ComponentName("pkg", "cls"));
-        mSuggestion.metaData = new Bundle();
-        mMultipleCategory = new SuggestionCategory();
-        mMultipleCategory.category = "category1";
-        mMultipleCategory.multiple = true;
-        mExclusiveCategory = new SuggestionCategory();
-        mExclusiveCategory.category = "category2";
-        mExclusiveCategory.exclusive = true;
-        mExpiredExclusiveCategory = new SuggestionCategory();
-        mExpiredExclusiveCategory.category = "category3";
-        mExpiredExclusiveCategory.exclusive = true;
-        mExpiredExclusiveCategory.exclusiveExpireDaysInMillis = 0;
-
-        mSuggestionParser = new SuggestionParser(application, mPrefs,
-                Arrays.asList(mMultipleCategory, mExclusiveCategory, mExpiredExclusiveCategory),
-                "0");
-
-        ResolveInfo info1 = TileUtilsTest.newInfo(true, null);
-        info1.activityInfo.packageName = "pkg";
-        ResolveInfo infoDupe1 = TileUtilsTest.newInfo(true, null);
-        infoDupe1.activityInfo.packageName = "pkg";
-
-        ResolveInfo info2 = TileUtilsTest.newInfo(true, null);
-        info2.activityInfo.packageName = "pkg2";
-        ResolveInfo info3 = TileUtilsTest.newInfo(true, null);
-        info3.activityInfo.packageName = "pkg3";
-        ResolveInfo info4 = TileUtilsTest.newInfo(true, null);
-        info4.activityInfo.packageName = "pkg4";
-
-        Intent intent1 = new Intent(Intent.ACTION_MAIN).addCategory("category1");
-        Intent intent2 = new Intent(Intent.ACTION_MAIN).addCategory("category2");
-        Intent intent3 = new Intent(Intent.ACTION_MAIN).addCategory("category3");
-
-        mPackageManager.addResolveInfoForIntent(intent1, info1);
-        mPackageManager.addResolveInfoForIntent(intent1, info2);
-        mPackageManager.addResolveInfoForIntent(intent1, infoDupe1);
-        mPackageManager.addResolveInfoForIntent(intent2, info3);
-        mPackageManager.addResolveInfoForIntent(intent3, info4);
-    }
-
-    @Test
-    public void dismissSuggestion_shouldDismiss() {
-        assertThat(mSuggestionParser.dismissSuggestion(mSuggestion)).isTrue();
-    }
-
-    @Test
-    public void testGetSuggestions_withoutSmartSuggestions_shouldDismiss() {
-        readAndDismissSuggestion(false);
-        mSuggestionParser.readSuggestions(mMultipleCategory, mSuggestionsAfterDismiss, false);
-        assertThat(mSuggestionsBeforeDismiss).hasSize(2);
-        assertThat(mSuggestionsAfterDismiss).hasSize(1);
-        assertThat(mSuggestionsBeforeDismiss.get(1)).isEqualTo(mSuggestionsAfterDismiss.get(0));
-    }
-
-    @Test
-    public void testGetSuggestions_withSmartSuggestions_shouldDismiss() {
-        readAndDismissSuggestion(true);
-        assertThat(mSuggestionsBeforeDismiss).hasSize(2);
-        assertThat(mSuggestionsAfterDismiss).hasSize(1);
-    }
-
-    @Test
-    public void testGetSuggestion_exclusiveNotAvailable_onlyRegularCategoryAndNoDupe() {
-        mPackageManager.removeResolveInfosForIntent(
-                new Intent(Intent.ACTION_MAIN).addCategory("category2"),
-                "pkg3");
-        mPackageManager.removeResolveInfosForIntent(
-                new Intent(Intent.ACTION_MAIN).addCategory("category3"),
-                "pkg4");
-
-        // If exclusive item is not available, the other categories should be shown
-        final SuggestionList sl =
-                mSuggestionParser.getSuggestions(false /* isSmartSuggestionEnabled */);
-        final List<Tile> suggestions = sl.getSuggestions();
-        assertThat(suggestions).hasSize(2);
-
-        assertThat(suggestions.get(0).intent.getComponent().getPackageName()).isEqualTo("pkg");
-        assertThat(suggestions.get(1).intent.getComponent().getPackageName()).isEqualTo("pkg2");
-    }
-
-    @Test
-    public void testGetSuggestion_exclusiveExpiredAvailable_shouldLoadWithRegularCategory() {
-        // First remove permanent exclusive
-        mPackageManager.removeResolveInfosForIntent(
-                new Intent(Intent.ACTION_MAIN).addCategory("category2"),
-                "pkg3");
-        // Set the other exclusive to be expired.
-        mPrefs.edit()
-                .putLong(mExpiredExclusiveCategory.category + "_setup_time",
-                        System.currentTimeMillis() - 1000)
-                .commit();
-
-        // If exclusive is expired, they should be shown together with the other categories
-        final SuggestionList sl =
-                mSuggestionParser.getSuggestions(true /* isSmartSuggestionEnabled */);
-        final List<Tile> suggestions = sl.getSuggestions();
-
-        assertThat(suggestions).hasSize(3);
-    }
-
-    @Test
-    public void testGetSuggestions_exclusive() {
-        final SuggestionList sl =
-                mSuggestionParser.getSuggestions(false /* isSmartSuggestionEnabled */);
-        final List<Tile> suggestions = sl.getSuggestions();
-
-        assertThat(suggestions).hasSize(1);
-    }
-
-    @Test
-    public void isSuggestionDismissed_dismissedSuggestion_shouldReturnTrue() {
-        final Tile suggestion = new Tile();
-        suggestion.metaData = new Bundle();
-        suggestion.metaData.putString(SuggestionParser.META_DATA_DISMISS_CONTROL, "1,2,3");
-        suggestion.intent = new Intent().setComponent(new ComponentName("pkg", "cls"));
-
-        // Dismiss suggestion when smart suggestion is not enabled.
-        mSuggestionParser.dismissSuggestion(suggestion);
-
-        assertThat(mSuggestionParser.isDismissed(suggestion, true /* isSmartSuggestionEnabled */))
-                .isTrue();
-    }
-
-    private void readAndDismissSuggestion(boolean isSmartSuggestionEnabled) {
-        mSuggestionsBeforeDismiss = new ArrayList<>();
-        mSuggestionsAfterDismiss = new ArrayList<>();
-        mSuggestionParser.readSuggestions(
-                mMultipleCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled);
-
-        final Tile suggestion = mSuggestionsBeforeDismiss.get(0);
-        if (mSuggestionParser.dismissSuggestion(suggestion)) {
-            mPackageManager.removeResolveInfosForIntent(
-                    new Intent(Intent.ACTION_MAIN).addCategory(mMultipleCategory.category),
-                    suggestion.intent.getComponent().getPackageName());
-        }
-        mSuggestionParser.readSuggestions(
-                mMultipleCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
-    }
-}
diff --git a/packages/SystemUI/res/layout/screen_pinning_request_buttons.xml b/packages/SystemUI/res/layout/screen_pinning_request_buttons.xml
index 25b117f..f13f019 100644
--- a/packages/SystemUI/res/layout/screen_pinning_request_buttons.xml
+++ b/packages/SystemUI/res/layout/screen_pinning_request_buttons.xml
@@ -43,7 +43,7 @@
         android:layout_weight="0"
         android:paddingStart="@dimen/screen_pinning_request_frame_padding"
         android:paddingEnd="@dimen/screen_pinning_request_frame_padding"
-        android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent">
+        android:theme="@style/ScreenPinningRequestTheme">
 
         <ImageView
             android:id="@+id/screen_pinning_back_bg_light"
@@ -86,7 +86,7 @@
         android:layout_weight="0"
         android:paddingStart="@dimen/screen_pinning_request_frame_padding"
         android:paddingEnd="@dimen/screen_pinning_request_frame_padding"
-        android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent">
+        android:theme="@style/ScreenPinningRequestTheme">
 
         <ImageView
             android:id="@+id/screen_pinning_home_bg_light"
@@ -129,7 +129,7 @@
         android:layout_weight="0"
         android:paddingStart="@dimen/screen_pinning_request_frame_padding"
         android:paddingEnd="@dimen/screen_pinning_request_frame_padding"
-        android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent">
+        android:theme="@style/ScreenPinningRequestTheme">
 
         <ImageView
             android:id="@+id/screen_pinning_recents_bg_light"
diff --git a/packages/SystemUI/res/layout/screen_pinning_request_buttons_land.xml b/packages/SystemUI/res/layout/screen_pinning_request_buttons_land.xml
index 367c13c..420b072 100644
--- a/packages/SystemUI/res/layout/screen_pinning_request_buttons_land.xml
+++ b/packages/SystemUI/res/layout/screen_pinning_request_buttons_land.xml
@@ -39,7 +39,7 @@
         android:layout_height="@dimen/screen_pinning_request_button_width"
         android:layout_width="@dimen/screen_pinning_request_button_height"
         android:layout_weight="0"
-        android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent">
+        android:theme="@style/ScreenPinningRequestTheme">
 
         <ImageView
             android:id="@+id/screen_pinning_recents_bg_light"
@@ -79,7 +79,7 @@
         android:layout_height="@dimen/screen_pinning_request_button_width"
         android:layout_width="@dimen/screen_pinning_request_button_height"
         android:layout_weight="0"
-        android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent">
+        android:theme="@style/ScreenPinningRequestTheme">
 
         <ImageView
             android:id="@+id/screen_pinning_home_bg_light"
@@ -120,7 +120,7 @@
         android:layout_height="@dimen/screen_pinning_request_button_width"
         android:layout_width="@dimen/screen_pinning_request_button_height"
         android:layout_weight="0"
-        android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent">
+        android:theme="@style/ScreenPinningRequestTheme">
 
         <ImageView
             android:id="@+id/screen_pinning_back_bg_light"
diff --git a/packages/SystemUI/res/layout/screen_pinning_request_buttons_sea.xml b/packages/SystemUI/res/layout/screen_pinning_request_buttons_sea.xml
index bac02aa..f9308cd 100644
--- a/packages/SystemUI/res/layout/screen_pinning_request_buttons_sea.xml
+++ b/packages/SystemUI/res/layout/screen_pinning_request_buttons_sea.xml
@@ -39,7 +39,7 @@
         android:layout_height="@dimen/screen_pinning_request_button_width"
         android:layout_width="@dimen/screen_pinning_request_button_height"
         android:layout_weight="0"
-        android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent">
+        android:theme="@style/ScreenPinningRequestTheme">
 
         <ImageView
             android:id="@+id/screen_pinning_back_bg_light"
@@ -82,7 +82,7 @@
         android:layout_height="@dimen/screen_pinning_request_button_width"
         android:layout_width="@dimen/screen_pinning_request_button_height"
         android:layout_weight="0"
-        android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent" >
+        android:theme="@style/ScreenPinningRequestTheme" >
 
         <ImageView
             android:id="@+id/screen_pinning_home_bg_light"
@@ -125,7 +125,7 @@
         android:layout_height="@dimen/screen_pinning_request_button_width"
         android:layout_width="@dimen/screen_pinning_request_button_height"
         android:layout_weight="0"
-        android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent">
+        android:theme="@style/ScreenPinningRequestTheme">
 
         <ImageView
             android:id="@+id/screen_pinning_recents_bg_light"
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
index 8b56b68..42d541e3 100644
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -58,6 +58,7 @@
             android:layout_height="wrap_content"
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical"
+            android:paddingStart="2.5dp"
             android:paddingEnd="1dp"
             android:visibility="gone" />
         <Space
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index c59dbdc..bb0c6f6 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -24,8 +24,6 @@
 
     <dimen name="brightness_mirror_height">40dp</dimen>
 
-    <!-- Width for the spacer, used between QS tiles. -->
-    <dimen name="qs_quick_tile_space_width">38dp</dimen>
     <dimen name="qs_tile_margin_top">2dp</dimen>
     <dimen name="qs_header_tooltip_height">24dp</dimen>
 
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 74acfa2..eb5c180 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -19,9 +19,6 @@
     <!-- Standard notification width + gravity -->
     <dimen name="notification_panel_width">416dp</dimen>
 
-    <!-- Width for the spacer, used between QS tiles depend on notification_panel_width -->
-    <dimen name="qs_quick_tile_space_width">0dp</dimen>
-
     <!-- Diameter of outer shape drawable shown in navbar search-->
     <dimen name="navbar_search_outerring_diameter">430dip</dimen>
 
diff --git a/packages/SystemUI/res/values-sw900dp-land/dimen.xml b/packages/SystemUI/res/values-sw900dp-land/dimen.xml
index 55f23dd..ac7e6b8 100644
--- a/packages/SystemUI/res/values-sw900dp-land/dimen.xml
+++ b/packages/SystemUI/res/values-sw900dp-land/dimen.xml
@@ -18,4 +18,9 @@
 <resources>
     <!-- Standard notification width + gravity for tablet large screen device -->
     <dimen name="notification_panel_width">544dp</dimen>
-</resources>
\ No newline at end of file
+
+    <!-- Maximum width of quick quick settings panel. -->
+    <dimen name="qs_quick_layout_width">478dp</dimen>
+
+</resources>
+
diff --git a/packages/SystemUI/res/values-w550dp-land/dimens.xml b/packages/SystemUI/res/values-w550dp-land/dimens.xml
index eaca9d7..2c66454 100644
--- a/packages/SystemUI/res/values-w550dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-w550dp-land/dimens.xml
@@ -18,4 +18,7 @@
 <resources>
     <!-- Standard notification width + gravity -->
     <dimen name="notification_panel_width">544dp</dimen>
+
+    <!-- Maximum width of quick quick settings panel. -->
+    <dimen name="qs_quick_layout_width">478dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index de69c6b..e45c367 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -55,7 +55,7 @@
     <dimen name="status_bar_left_clock_end_padding">7dp</dimen>
 
     <!-- Spacing after the wifi signals that is present if there are any icons following it. -->
-    <dimen name="status_bar_wifi_signal_spacer_width">4dp</dimen>
+    <dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
 
     <!-- Spacing before the airplane mode icon if there are any icons preceding it. -->
     <dimen name="status_bar_airplane_spacer_width">4dp</dimen>
@@ -348,8 +348,8 @@
     <dimen name="qs_tile_margin_top_bottom">12dp</dimen>
     <dimen name="qs_tile_margin_top">18dp</dimen>
     <dimen name="qs_quick_tile_size">48dp</dimen>
-    <!-- Width for the spacer, used between QS tiles. -->
-    <dimen name="qs_quick_tile_space_width">0dp</dimen>
+    <!-- Maximum width of quick quick settings panel. Defaults to MATCH_PARENT-->
+    <dimen name="qs_quick_layout_width">-1px</dimen>
     <dimen name="qs_quick_tile_padding">12dp</dimen>
     <dimen name="qs_header_gear_translation">16dp</dimen>
     <dimen name="qs_header_tile_margin_horizontal">0dp</dimen>
@@ -763,9 +763,6 @@
     <dimen name="volume_expander_margin_end">2dp</dimen>
     <dimen name="volume_expander_margin_top">6dp</dimen>
 
-    <!-- Padding between icon and text for managed profile toast -->
-    <dimen name="managed_profile_toast_padding">4dp</dimen>
-
     <!-- Thickness of the assist disclosure beams -->
     <dimen name="assist_disclosure_thickness">2.5dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 84a76bc..915fc6e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1326,9 +1326,6 @@
     <!-- Hide quick settings tile confirmation button -->
     <string name="quick_settings_reset_confirmation_button">Hide</string>
 
-    <!-- Toast shown when user unlocks screen and managed profile activity is in the foreground -->
-    <string name="managed_profile_foreground_toast">You\'re using your work profile</string>
-
     <!-- volume stream names. All nouns. -->
     <string name="stream_voice_call">Call</string> <!-- STREAM_VOICE_CALL -->
     <string name="stream_system">System</string> <!-- STREAM_SYSTEM -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index e4f5989..988a516 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -403,6 +403,9 @@
         <item name="fillColor">?android:attr/textColorPrimary</item>
         <item name="singleToneColor">?android:attr/textColorPrimary</item>
     </style>
+    <style name="ScreenPinningRequestTheme" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent">
+        <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
+    </style>
 
     <style name="TextAppearance.Volume">
         <item name="android:textStyle">normal</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d9a1b11..075faa1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1590,6 +1590,10 @@
         }
     }
 
+    public boolean isKeyguardVisible() {
+        return mKeyguardIsVisible;
+    }
+
     /**
      * Notifies that the visibility state of Keyguard has changed.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index 9a43d9e..3c8a461 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.Display;
@@ -46,7 +45,7 @@
 public class SysuiColorExtractor extends ColorExtractor implements Dumpable {
     private static final String TAG = "SysuiColorExtractor";
     private boolean mWallpaperVisible;
-    private boolean mMediaBackdropVisible;
+    private boolean mHasBackdrop;
     // Colors to return when the wallpaper isn't visible
     private final GradientColors mWpHiddenColors;
 
@@ -165,7 +164,7 @@
                 return mWpHiddenColors;
             }
         } else {
-            if (mMediaBackdropVisible) {
+            if (mHasBackdrop) {
                 return mWpHiddenColors;
             } else {
                 return super.getColors(which, type);
@@ -181,9 +180,9 @@
         }
     }
 
-    public void setMediaBackdropVisible(boolean visible) {
-        if (mMediaBackdropVisible != visible) {
-            mMediaBackdropVisible = visible;
+    public void setHasBackdrop(boolean hasBackdrop) {
+        if (mHasBackdrop != hasBackdrop) {
+            mHasBackdrop = hasBackdrop;
             triggerColorsChanged(WallpaperManager.FLAG_LOCK);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 100751c..f13f489 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -189,8 +189,9 @@
         // marquee. This will ensure that accessibility doesn't announce the TYPE_VIEW_SELECTED
         // event on any of the children.
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+        int currentItem = isLayoutRtl() ? mPages.size() - 1 - getCurrentItem() : getCurrentItem();
         for (int i = 0; i < mPages.size(); i++) {
-            mPages.get(i).setSelected(i == getCurrentItem() ? selected : false);
+            mPages.get(i).setSelected(i == currentItem ? selected : false);
         }
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 6dbe119..1c50f79 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -192,29 +192,21 @@
 
             mTileDimensionSize = mContext.getResources().getDimensionPixelSize(
                     R.dimen.qs_quick_tile_size);
-
-            setGravity(Gravity.CENTER);
-            setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+            updateLayoutParams();
         }
 
         @Override
         protected void onConfigurationChanged(Configuration newConfig) {
             super.onConfigurationChanged(newConfig);
+            updateLayoutParams();
+        }
 
+        private void updateLayoutParams() {
             setGravity(Gravity.CENTER);
-            LayoutParams staticSpaceLayoutParams = generateSpaceLayoutParams(
-                    mContext.getResources().getDimensionPixelSize(
-                            R.dimen.qs_quick_tile_space_width));
-
-            // Update space params since they fill any open space in portrait orientation and have
-            // a static width in landscape orientation.
-            final int childViewCount = getChildCount();
-            for (int i = 0; i < childViewCount; i++) {
-                View childView = getChildAt(i);
-                if (childView instanceof Space) {
-                    childView.setLayoutParams(staticSpaceLayoutParams);
-                }
-            }
+            int width = getResources().getDimensionPixelSize(R.dimen.qs_quick_layout_width);
+            LayoutParams lp = new LayoutParams(width, LayoutParams.MATCH_PARENT);
+            lp.gravity = Gravity.CENTER_HORIZONTAL;
+            setLayoutParams(lp);
         }
 
         /**
@@ -222,11 +214,9 @@
          * then we're going to have the space expand to take up as much space as possible. If the
          * width is non-zero, we want the inter-tile spacers to be fixed.
          */
-        private LayoutParams generateSpaceLayoutParams(int spaceWidth) {
-            LayoutParams lp = new LayoutParams(spaceWidth, mTileDimensionSize);
-            if (spaceWidth == 0) {
-                lp.weight = 1;
-            }
+        private LayoutParams generateSpaceLayoutParams() {
+            LayoutParams lp = new LayoutParams(0, mTileDimensionSize);
+            lp.weight = 1;
             lp.gravity = Gravity.CENTER;
             return lp;
         }
@@ -243,13 +233,7 @@
         @Override
         public void addTile(TileRecord tile) {
             if (getChildCount() != 0) {
-                // Add a spacer between tiles. We want static-width spaces if we're in landscape to
-                // keep the tiles close. For portrait, we stick with spaces that fill up any
-                // available space.
-                LayoutParams spaceLayoutParams = generateSpaceLayoutParams(
-                        mContext.getResources().getDimensionPixelSize(
-                                R.dimen.qs_quick_tile_space_width));
-                addView(new Space(mContext), getChildCount(), spaceLayoutParams);
+                addView(new Space(mContext), getChildCount(), generateSpaceLayoutParams());
             }
 
             addView(tile.tileView, getChildCount(), generateTileLayoutParams());
@@ -305,6 +289,10 @@
         @Override
         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            if (hideOverflowingChildren(widthMeasureSpec)) {
+                return; // Rely on visibility change to trigger remeasure.
+            }
+
             if (mRecords != null && mRecords.size() > 0) {
                 View previousView = this;
                 for (TileRecord record : mRecords) {
@@ -317,5 +305,32 @@
                         R.id.expand_indicator);
             }
         }
+
+        /**
+         * Hide child views that would otherwise be clipped.
+         * @return {@code true} if any child visibilities have changed.
+         */
+        private boolean hideOverflowingChildren(int widthMeasureSpec) {
+            if (getChildCount() == 0) {
+                return false;
+            }
+            boolean childVisibilityChanged = false;
+            int widthRemaining = MeasureSpec.getSize(widthMeasureSpec)
+                - getChildAt(0).getMeasuredWidth() - getPaddingStart() - getPaddingEnd();
+            for (int i = 2; i < getChildCount(); i += 2) {
+                View tileChild = getChildAt(i);
+                LayoutParams lp = (LayoutParams) tileChild.getLayoutParams();
+                // All Space views have 0 width; only tiles contribute to the total width.
+                widthRemaining = widthRemaining
+                    - tileChild.getMeasuredWidth() - lp.getMarginEnd() - lp.getMarginStart();
+                int newVisibility = widthRemaining < 0 ? View.GONE : View.VISIBLE;
+                if (tileChild.getVisibility() != newVisibility) {
+                    tileChild.setVisibility(newVisibility);
+                    getChildAt(i - 1).setVisibility(newVisibility); // Hide spacer as well.
+                    childVisibilityChanged = true;
+                }
+            }
+            return childVisibilityChanged;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index af88911..83db6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -37,6 +37,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Build;
+import android.os.SystemClock;
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -108,6 +109,7 @@
     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
     private static final int MENU_VIEW_INDEX = 0;
     private static final String TAG = "ExpandableNotifRow";
+    public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
 
     /**
      * Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -157,7 +159,7 @@
     private boolean mSensitiveHiddenInGeneral;
     private boolean mShowingPublicInitialized;
     private boolean mHideSensitiveForIntrinsicHeight;
-    private float mHeaderVisibleAmount = 1.0f;
+    private float mHeaderVisibleAmount = DEFAULT_HEADER_VISIBLE_AMOUNT;
 
     /**
      * Is this notification expanded by the system. The expansion state can be overridden by the
@@ -992,6 +994,7 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
+        mEntry.setInitializationTime(SystemClock.elapsedRealtime());
         Dependency.get(PluginManager.class).addPluginListener(this,
                 NotificationMenuRowPlugin.class, false /* Allow multiple */);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index 39485c3..ac289d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -21,6 +21,8 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.view.Display;
 import android.view.DisplayCutout;
@@ -38,6 +40,12 @@
  * The view in the statusBar that contains part of the heads-up information
  */
 public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
+    private static final String HEADS_UP_STATUS_BAR_VIEW_SUPER_PARCELABLE =
+            "heads_up_status_bar_view_super_parcelable";
+    private static final String FIRST_LAYOUT = "first_layout";
+    private static final String PUBLIC_MODE = "public_mode";
+    private static final String VISIBILITY = "visibility";
+    private static final String ALPHA = "alpha";
     private int mAbsoluteStartPadding;
     private int mEndMargin;
     private View mIconPlaceholder;
@@ -107,6 +115,39 @@
         updateMaxWidth();
     }
 
+    @Override
+    public Bundle onSaveInstanceState() {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(HEADS_UP_STATUS_BAR_VIEW_SUPER_PARCELABLE,
+                super.onSaveInstanceState());
+        bundle.putBoolean(FIRST_LAYOUT, mFirstLayout);
+        bundle.putBoolean(PUBLIC_MODE, mPublicMode);
+        bundle.putInt(VISIBILITY, getVisibility());
+        bundle.putFloat(ALPHA, getAlpha());
+
+        return bundle;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !(state instanceof Bundle)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        Bundle bundle = (Bundle) state;
+        Parcelable superState = bundle.getParcelable(HEADS_UP_STATUS_BAR_VIEW_SUPER_PARCELABLE);
+        super.onRestoreInstanceState(superState);
+        mFirstLayout = bundle.getBoolean(FIRST_LAYOUT, true);
+        mPublicMode = bundle.getBoolean(PUBLIC_MODE, false);
+        if (bundle.containsKey(VISIBILITY)) {
+            setVisibility(bundle.getInt(VISIBILITY));
+        }
+        if (bundle.containsKey(ALPHA)) {
+            setAlpha(bundle.getFloat(ALPHA));
+        }
+    }
+
     @VisibleForTesting
     public HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView) {
         this(context);
@@ -178,11 +219,7 @@
      * @param translationX how to translate the horizontal position
      */
     public void setPanelTranslation(float translationX) {
-        if (isLayoutRtl()) {
-            setTranslationX(translationX + mCutOutInset);
-        } else {
-            setTranslationX(translationX - mCutOutInset);
-        }
+        setTranslationX(translationX);
         updateDrawingRect();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 1a645d1..a58752c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -84,6 +84,7 @@
     public static final class Entry {
         private static final long LAUNCH_COOLDOWN = 2000;
         private static final long REMOTE_INPUT_COOLDOWN = 500;
+        private static final long INITIALIZATION_DELAY = 400;
         private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
         private static final int COLOR_INVALID = 1;
         public String key;
@@ -114,6 +115,9 @@
         public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
         public CharSequence headsUpStatusBarText;
         public CharSequence headsUpStatusBarTextPublic;
+
+        private long initializationTime = -1;
+
         /**
          * Whether or not this row represents a system notification. Note that if this is
          * {@code null}, that means we were either unable to retrieve the info or have yet to
@@ -169,6 +173,11 @@
             return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
         }
 
+        public boolean hasFinishedInitialization() {
+            return initializationTime == -1 ||
+                    SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
+        }
+
         /**
          * Create the icons for a notification
          * @param context the context to create the icons with
@@ -341,6 +350,12 @@
             }
             return false;
         }
+
+        public void setInitializationTime(long time) {
+            if (initializationTime == -1) {
+                initializationTime = time;
+            }
+        }
     }
 
     private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 7e397e8..2f62d59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -109,16 +109,6 @@
             } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
                 // Start the overview connection to the launcher service
                 Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
-            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                try {
-                    final int lastResumedActivityUserId =
-                            ActivityTaskManager.getService().getLastResumedActivityUserId();
-                    if (mUserManager.isManagedProfile(lastResumedActivityUserId)) {
-                        showForegroundManagedProfileActivityToast();
-                    }
-                } catch (RemoteException e) {
-                    // Abandon hope activity manager not running.
-                }
             } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
                 final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
                 final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
@@ -225,7 +215,6 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_ADDED);
-        filter.addAction(Intent.ACTION_USER_PRESENT);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiver(mBaseBroadcastReceiver, filter);
 
@@ -238,19 +227,6 @@
         mSettingsObserver.onChange(false);  // set up
     }
 
-    private void showForegroundManagedProfileActivityToast() {
-        Toast toast = Toast.makeText(mContext,
-                R.string.managed_profile_foreground_toast,
-                Toast.LENGTH_SHORT);
-        TextView text = toast.getView().findViewById(android.R.id.message);
-        text.setCompoundDrawablesRelativeWithIntrinsicBounds(
-                R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
-        int paddingPx = mContext.getResources().getDimensionPixelSize(
-                R.dimen.managed_profile_toast_padding);
-        text.setCompoundDrawablePadding(paddingPx);
-        toast.show();
-    }
-
     public boolean shouldShowLockscreenNotifications() {
         return mShowLockscreenNotifications;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 2fc2cdb..ade27f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification;
 
+import static com.android.systemui.statusbar.ExpandableNotificationRow
+        .DEFAULT_HEADER_VISIBLE_AMOUNT;
 import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
 
 import android.app.Notification;
@@ -123,6 +125,9 @@
 
         // Reinspect the notification.
         resolveHeaderViews();
+        if (row.getHeaderVisibleAmount() != DEFAULT_HEADER_VISIBLE_AMOUNT) {
+            setHeaderVisibleAmount(row.getHeaderVisibleAmount());
+        }
         updateTransformedTypes();
         addRemainingTransformTypes();
         updateCropToPaddingForImageViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index f7b7eeb..ea70ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -22,6 +22,8 @@
 import android.app.Fragment;
 import android.app.StatusBarManager;
 import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -89,7 +91,8 @@
         super.onViewCreated(view, savedInstanceState);
         mStatusBar = (PhoneStatusBarView) view;
         if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
-            mStatusBar.go(savedInstanceState.getInt(EXTRA_PANEL_STATE));
+            mStatusBar.restoreHierarchyState(
+                    savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
         }
         mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
         mDarkIconManager.setShouldLog(true);
@@ -105,7 +108,9 @@
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        outState.putInt(EXTRA_PANEL_STATE, mStatusBar.getState());
+        SparseArray<Parcelable> states = new SparseArray<>();
+        mStatusBar.saveHierarchyState(states);
+        outState.putSparseParcelableArray(EXTRA_PANEL_STATE, states);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 409a783..e1936fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.view.DisplayCutout;
 import android.view.View;
 import android.view.WindowInsets;
 
@@ -53,9 +54,12 @@
             mSetTrackingHeadsUp = this::setTrackingHeadsUp;
     private final Runnable mUpdatePanelTranslation = this::updatePanelTranslation;
     private final BiConsumer<Float, Float> mSetExpandedHeight = this::setExpandedHeight;
-    private float mExpandedHeight;
-    private boolean mIsExpanded;
-    private float mExpandFraction;
+    @VisibleForTesting
+    float mExpandedHeight;
+    @VisibleForTesting
+    boolean mIsExpanded;
+    @VisibleForTesting
+    float mExpandFraction;
     private ExpandableNotificationRow mTrackedChild;
     private boolean mShown;
     private final View.OnLayoutChangeListener mStackScrollLayoutChangeListener =
@@ -99,6 +103,20 @@
         mClockView = clockView;
         mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
         mDarkIconDispatcher.addDarkReceiver(this);
+
+        mHeadsUpStatusBarView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                if (shouldBeVisible()) {
+                    updateTopEntry();
+
+                    // trigger scroller to notify the latest panel translation
+                    mStackScroller.requestLayout();
+                }
+                mHeadsUpStatusBarView.removeOnLayoutChangeListener(this);
+            }
+        });
     }
 
 
@@ -159,8 +177,15 @@
         }
 
         WindowInsets windowInset = mStackScroller.getRootWindowInsets();
-        return windowInset.getSystemWindowInsetLeft() + mStackScroller.getRight()
-                + windowInset.getSystemWindowInsetRight() - realDisplaySize;
+        DisplayCutout cutout = (windowInset != null) ? windowInset.getDisplayCutout() : null;
+        int sysWinLeft = (windowInset != null) ? windowInset.getStableInsetLeft() : 0;
+        int sysWinRight = (windowInset != null) ? windowInset.getStableInsetRight() : 0;
+        int cutoutLeft = (cutout != null) ? cutout.getSafeInsetLeft() : 0;
+        int cutoutRight = (cutout != null) ? cutout.getSafeInsetRight() : 0;
+        int leftInset = Math.max(sysWinLeft, cutoutLeft);
+        int rightInset = Math.max(sysWinRight, cutoutRight);
+
+        return leftInset + mStackScroller.getRight() + rightInset - realDisplaySize;
     }
 
     public void updatePanelTranslation() {
@@ -293,4 +318,13 @@
         mHeadsUpStatusBarView.setPublicMode(publicMode);
         updateTopEntry();
     }
+
+    void readFrom(HeadsUpAppearanceController oldController) {
+        if (oldController != null) {
+            mTrackedChild = oldController.mTrackedChild;
+            mExpandedHeight = oldController.mExpandedHeight;
+            mIsExpanded = oldController.mIsExpanded;
+            mExpandFraction = oldController.mExpandFraction;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 46d9827..e2f3319 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -367,10 +367,10 @@
         }
     }
 
-    private void startFinishingCircleAnimation(float velocity, Runnable mAnimationEndRunnable,
+    private void startFinishingCircleAnimation(float velocity, Runnable animationEndRunnable,
             boolean right) {
         KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
-        targetView.finishAnimation(velocity, mAnimationEndRunnable);
+        targetView.finishAnimation(velocity, animationEndRunnable);
     }
 
     private void setTranslation(float translation, boolean isReset, boolean animateReset) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index dce7537..ed1ae10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -75,7 +75,6 @@
     private int mTouchDownY;
     private boolean mDownOnRecents;
     private VelocityTracker mVelocityTracker;
-    private boolean mIsInScreenPinning;
     private boolean mNotificationsVisibleOnDown;
 
     private boolean mDockWindowEnabled;
@@ -110,7 +109,6 @@
 
     public boolean onInterceptTouchEvent(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mIsInScreenPinning = mNavigationBarView.inScreenPinning();
             mNotificationsVisibleOnDown = !mStatusBar.isPresenterFullyCollapsed();
         }
         if (!canHandleGestures()) {
@@ -277,8 +275,7 @@
     }
 
     private boolean canHandleGestures() {
-        return !mIsInScreenPinning && !mStatusBar.isKeyguardShowing()
-                && !mNotificationsVisibleOnDown;
+        return !mStatusBar.isKeyguardShowing() && !mNotificationsVisibleOnDown;
     }
 
     private int calculateDragMode() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 8b9e12c..4a98262 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -161,6 +161,13 @@
 
     private int mRotateBtnStyle = R.style.RotateButtonCCWStart90;
 
+    /**
+     * Helper that is responsible for showing the right toast when a disallowed activity operation
+     * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
+     * fully locked mode we only show that unlocking is blocked.
+     */
+    private ScreenPinningNotify mScreenPinningNotify;
+
     private class NavTransitionListener implements TransitionListener {
         private boolean mBackTransitioning;
         private boolean mHomeAppearing;
@@ -286,6 +293,7 @@
         mConfiguration.updateFrom(context.getResources().getConfiguration());
         reloadNavIcons();
 
+        mScreenPinningNotify = new ScreenPinningNotify(mContext);
         mBarTransitions = new NavigationBarTransitions(this);
 
         mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
@@ -983,6 +991,18 @@
         mBarTransitions.reapplyDarkIntensity();
     }
 
+    public void showPinningEnterExitToast(boolean entering) {
+        if (entering) {
+            mScreenPinningNotify.showPinningStartToast();
+        } else {
+            mScreenPinningNotify.showPinningExitToast();
+        }
+    }
+
+    public void showPinningEscapeToast() {
+        mScreenPinningNotify.showEscapeToast(isRecentsButtonVisible());
+    }
+
     public boolean isVertical() {
         return mVertical;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 165b9b4..969b1b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2491,7 +2491,7 @@
     }
 
     protected void setVerticalPanelTranslation(float translation) {
-        mNotificationStackScroller.setTranslationX(translation);
+        mNotificationStackScroller.setVerticalPanelTranslation(translation);
         mQsFrame.setTranslationX(translation);
         int size = mVerticalTranslationListener.size();
         for (int i = 0; i < size; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index c4d7e72..18bc4e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -27,6 +29,8 @@
     public static final boolean DEBUG = false;
     public static final String TAG = PanelBar.class.getSimpleName();
     private static final boolean SPEW = false;
+    private static final String PANEL_BAR_SUPER_PARCELABLE = "panel_bar_super_parcelable";
+    private static final String STATE = "state";
     private boolean mBouncerShowing;
     private boolean mExpanded;
 
@@ -48,8 +52,26 @@
         mState = state;
     }
 
-    public int getState() {
-        return mState;
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(PANEL_BAR_SUPER_PARCELABLE, super.onSaveInstanceState());
+        bundle.putInt(STATE, mState);
+        return bundle;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !(state instanceof Bundle)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        Bundle bundle = (Bundle) state;
+        super.onRestoreInstanceState(bundle.getParcelable(PANEL_BAR_SUPER_PARCELABLE));
+        if (((Bundle) state).containsKey(STATE)) {
+            go(bundle.getInt(STATE, STATE_CLOSED));
+        }
     }
 
     public PanelBar(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 5477f88..075883a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -331,30 +331,25 @@
         // or letterboxing from the right or left sides.
 
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        if (mDisplayCutout == null) {
+        if (mDisplayCutout == null || mDisplayCutout.isEmpty()
+                || mLastOrientation != ORIENTATION_PORTRAIT || cornerCutoutMargins == null) {
             lp.leftMargin = 0;
             lp.rightMargin = 0;
             return;
         }
 
-        lp.leftMargin = mDisplayCutout.getSafeInsetLeft();
-        lp.rightMargin = mDisplayCutout.getSafeInsetRight();
+        lp.leftMargin = Math.max(lp.leftMargin, cornerCutoutMargins.first);
+        lp.rightMargin = Math.max(lp.rightMargin, cornerCutoutMargins.second);
 
-        if (cornerCutoutMargins != null) {
-            lp.leftMargin = Math.max(lp.leftMargin, cornerCutoutMargins.first);
-            lp.rightMargin = Math.max(lp.rightMargin, cornerCutoutMargins.second);
-
-            // If we're already inset enough (e.g. on the status bar side), we can have 0 margin
-            WindowInsets insets = getRootWindowInsets();
-            int leftInset = insets.getSystemWindowInsetLeft();
-            int rightInset = insets.getSystemWindowInsetRight();
-            if (lp.leftMargin <= leftInset) {
-                lp.leftMargin = 0;
-            }
-            if (lp.rightMargin <= rightInset) {
-                lp.rightMargin = 0;
-            }
-
+        // If we're already inset enough (e.g. on the status bar side), we can have 0 margin
+        WindowInsets insets = getRootWindowInsets();
+        int leftInset = insets.getSystemWindowInsetLeft();
+        int rightInset = insets.getSystemWindowInsetRight();
+        if (lp.leftMargin <= leftInset) {
+            lp.leftMargin = 0;
+        }
+        if (lp.rightMargin <= rightInset) {
+            lp.rightMargin = 0;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index ea1b980..15790d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -84,6 +84,7 @@
     private AnimatorSet mTrackAnimator;
     private ButtonDispatcher mHitTarget;
     private View mCurrentNavigationBarView;
+    private boolean mIsInScreenPinning;
 
     private final Handler mHandler = new Handler();
     private final Rect mTrackRect = new Rect();
@@ -195,6 +196,7 @@
             case MotionEvent.ACTION_DOWN: {
                 int x = (int) event.getX();
                 int y = (int) event.getY();
+                mIsInScreenPinning = mNavigationBarView.inScreenPinning();
 
                 // End any existing quickscrub animations before starting the new transition
                 if (mTrackAnimator != null) {
@@ -298,8 +300,8 @@
 
         // Proxy motion events to launcher if not handled by quick scrub
         // Proxy motion events up/cancel that would be sent after long press on any nav button
-        if (!mQuickScrubActive && (mAllowGestureDetection || action == MotionEvent.ACTION_CANCEL
-                || action == MotionEvent.ACTION_UP)) {
+        if (!mQuickScrubActive && !mIsInScreenPinning && (mAllowGestureDetection
+                || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP)) {
             proxyMotionEvents(event);
         }
         return mQuickScrubActive || mQuickStepStarted;
@@ -382,6 +384,12 @@
     }
 
     private void startQuickStep(MotionEvent event) {
+        if (mIsInScreenPinning) {
+            mNavigationBarView.showPinningEscapeToast();
+            mAllowGestureDetection = false;
+            return;
+        }
+
         mQuickStepStarted = true;
         event.transform(mTransformGlobalMatrix);
         try {
@@ -407,6 +415,12 @@
     }
 
     private void startQuickScrub() {
+        if (mIsInScreenPinning) {
+            mNavigationBarView.showPinningEscapeToast();
+            mAllowGestureDetection = false;
+            return;
+        }
+
         if (!mQuickScrubActive) {
             mQuickScrubActive = true;
             mLightTrackColor = mContext.getColor(R.color.quick_step_track_background_light);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index de499d6..fe141661 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -29,9 +29,7 @@
 import android.os.Trace;
 import android.util.Log;
 import android.util.MathUtils;
-import android.view.Choreographer;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -43,12 +41,11 @@
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.util.function.TriConsumer;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.stack.ViewState;
 import com.android.systemui.util.AlarmTimeout;
@@ -482,21 +479,13 @@
         // Make sure we have the right gradients and their opacities will satisfy GAR.
         if (mNeedsDrawableColorUpdate) {
             mNeedsDrawableColorUpdate = false;
-            final GradientColors currentScrimColors;
-            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER_SCRIMMED
-                    || mState == ScrimState.BOUNCER) {
-                // Always animate color changes if we're seeing the keyguard
-                mScrimInFront.setColors(mLockColors, true /* animated */);
-                mScrimBehind.setColors(mLockColors, true /* animated */);
-                currentScrimColors = mLockColors;
-            } else {
-                // Only animate scrim color if the scrim view is actually visible
-                boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0;
-                boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0;
-                mScrimInFront.setColors(mSystemColors, animateScrimInFront);
-                mScrimBehind.setColors(mSystemColors, animateScrimBehind);
-                currentScrimColors = mSystemColors;
-            }
+            boolean isKeyguard = mKeyguardUpdateMonitor.isKeyguardVisible() && !mKeyguardOccluded;
+            GradientColors currentScrimColors = isKeyguard ? mLockColors : mSystemColors;
+            // Only animate scrim color if the scrim view is actually visible
+            boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
+            boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
+            mScrimInFront.setColors(currentScrimColors, animateScrimInFront);
+            mScrimBehind.setColors(currentScrimColors, animateScrimBehind);
 
             // Calculate minimum scrim opacity for white or black text.
             int textColor = currentScrimColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
@@ -899,6 +888,13 @@
         updateScrims();
     }
 
+    public void setHasBackdrop(boolean hasBackdrop) {
+        ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].setHasBackdrop(hasBackdrop);
+        }
+    }
+
     public interface Callback {
         default void onStart() {
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 713356b..ec6ada4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -20,7 +20,6 @@
 import android.os.Trace;
 import android.util.MathUtils;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
@@ -106,8 +105,7 @@
         public void prepare(ScrimState previousState) {
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
             mBlankScreen = mDisplayRequiresBlanking;
-            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
-                    && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
+            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
             mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
             mCurrentInFrontTint = Color.BLACK;
             mCurrentBehindTint = Color.BLACK;
@@ -131,8 +129,7 @@
         public void prepare(ScrimState previousState) {
             mCurrentInFrontAlpha = 0;
             mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode
-                    && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
+            mCurrentBehindAlpha = mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
             mCurrentBehindTint = Color.BLACK;
             mBlankScreen = mDisplayRequiresBlanking;
         }
@@ -178,8 +175,8 @@
     DozeParameters mDozeParameters;
     boolean mDisplayRequiresBlanking;
     boolean mWallpaperSupportsAmbientMode;
-    KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     int mIndex;
+    boolean mHasBackdrop;
 
     ScrimState(int index) {
         mIndex = index;
@@ -190,7 +187,6 @@
         mScrimBehind = scrimBehind;
         mDozeParameters = dozeParameters;
         mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext());
     }
 
     public void prepare(ScrimState previousState) {
@@ -256,4 +252,8 @@
     public boolean isLowPowerState() {
         return false;
     }
+
+    public void setHasBackdrop(boolean hasBackdrop) {
+        mHasBackdrop = hasBackdrop;
+    }
 }
\ No newline at end of file
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 a7d27c5..c2f31bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -423,13 +423,6 @@
     protected KeyguardViewMediator mKeyguardViewMediator;
     private ZenModeController mZenController;
 
-    /**
-     * Helper that is responsible for showing the right toast when a disallowed activity operation
-     * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
-     * fully locked mode we only show that unlocking is blocked.
-     */
-    private ScreenPinningNotify mScreenPinningNotify;
-
     // for disabling the status bar
     private int mDisabled1 = 0;
     private int mDisabled2 = 0;
@@ -843,13 +836,28 @@
                     mStatusBarView.setBar(this);
                     mStatusBarView.setPanel(mNotificationPanel);
                     mStatusBarView.setScrimController(mScrimController);
+
+                    // CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
+                    // mStatusBarView.mExpanded and mStatusBarView.mBouncerShowing are false.
+                    // PhoneStatusBarView's new instance will set to be gone in
+                    // PanelBar.updateVisibility after calling mStatusBarView.setBouncerShowing
+                    // that will trigger PanelBar.updateVisibility. If there is a heads up showing,
+                    // it needs to notify PhoneStatusBarView's new instance to update the correct
+                    // status by calling mNotificationPanel.notifyBarPanelExpansionChanged().
+                    if (mHeadsUpManager.hasPinnedHeadsUp()) {
+                        mNotificationPanel.notifyBarPanelExpansionChanged();
+                    }
                     mStatusBarView.setBouncerShowing(mBouncerShowing);
+
+                    HeadsUpAppearanceController oldController = mHeadsUpAppearanceController;
                     if (mHeadsUpAppearanceController != null) {
                         // This view is being recreated, let's destroy the old one
                         mHeadsUpAppearanceController.destroy();
                     }
                     mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                             mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow);
+                    mHeadsUpAppearanceController.readFrom(oldController);
+                    mStatusBarWindow.setStatusBarView(mStatusBarView);
                     setAreThereNotifications();
                     checkBarModes();
                 }).getFragmentManager()
@@ -887,7 +895,6 @@
         } catch (RemoteException ex) {
             // no window manager? good luck with that
         }
-        mScreenPinningNotify = new ScreenPinningNotify(mContext);
         mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker());
         mStackScroller.setStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
@@ -1656,8 +1663,12 @@
                 && mStatusBarKeyguardViewManager.isOccluded();
 
         final boolean hasArtwork = artworkDrawable != null;
+        mColorExtractor.setHasBackdrop(hasArtwork);
+        if (mScrimController != null) {
+            mScrimController.setHasBackdrop(hasArtwork);
+        }
 
-        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) && !mDozing
+        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
                 && (mState != StatusBarState.SHADE || allowWhenShade)
                 && mFingerprintUnlockController.getMode()
                         != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
@@ -1673,7 +1684,6 @@
                     mBackdrop.setAlpha(1f);
                 }
                 mStatusBarWindowManager.setBackdropShowing(true);
-                mColorExtractor.setMediaBackdropVisible(true);
                 metaDataChanged = true;
                 if (DEBUG_MEDIA) {
                     Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
@@ -1725,7 +1735,6 @@
                 if (DEBUG_MEDIA) {
                     Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
                 }
-                mColorExtractor.setMediaBackdropVisible(false);
                 boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange();
                 if (mFingerprintUnlockController.getMode()
                         == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
@@ -2224,17 +2233,16 @@
 
     @Override
     public void showPinningEnterExitToast(boolean entering) {
-        if (entering) {
-            mScreenPinningNotify.showPinningStartToast();
-        } else {
-            mScreenPinningNotify.showPinningExitToast();
+        if (getNavigationBarView() != null) {
+            getNavigationBarView().showPinningEnterExitToast(entering);
         }
     }
 
     @Override
     public void showPinningEscapeToast() {
-        mScreenPinningNotify.showEscapeToast(getNavigationBarView() == null
-                || getNavigationBarView().isRecentsButtonVisible());
+        if (getNavigationBarView() != null) {
+            getNavigationBarView().showPinningEscapeToast();
+        }
     }
 
     boolean panelsEnabled() {
@@ -3857,7 +3865,7 @@
      * Switches theme from light to dark and vice-versa.
      */
     protected void updateTheme() {
-        final boolean inflated = mStackScroller != null;
+        final boolean inflated = mStackScroller != null && mStatusBarWindowManager != null;
 
         // The system wallpaper defines if QS should be light or dark.
         WallpaperColors systemColors = mColorExtractor
@@ -4738,10 +4746,8 @@
             // Bouncer needs the front scrim when it's on top of an activity,
             // tapping on a notification, editing QS or being dismissed by
             // FLAG_DISMISS_KEYGUARD_ACTIVITY.
-            ScrimState state = mIsOccluded || mStatusBarKeyguardViewManager.bouncerNeedsScrimming()
-                    || mStatusBarKeyguardViewManager.willDismissWithAction()
-                    || mStatusBarKeyguardViewManager.isFullscreenBouncer() ?
-                    ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
+            ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming()
+                    ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
             mScrimController.transitionTo(state);
         } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
             mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
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 914bba2..affc424 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -170,8 +170,7 @@
         // • Full-screen user switcher is displayed.
         if (mNotificationPanelView.isUnlockHintRunning()) {
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
-        } else if (mOccluded || mBouncer.willDismissWithAction() || mBouncer.isShowingScrimmed()
-                || mStatusBar.isFullScreenUserSwitcherState()) {
+        } else if (bouncerNeedsScrimming()) {
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
         } else if (mShowing && !mDozing) {
             if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) {
@@ -731,12 +730,9 @@
         }
     }
 
-    public boolean willDismissWithAction() {
-        return mBouncer.willDismissWithAction();
-    }
-
     public boolean bouncerNeedsScrimming() {
-        return mBouncer.isShowingScrimmed();
+        return mOccluded || mBouncer.willDismissWithAction()  || mBouncer.needsFullscreenBouncer()
+                || mStatusBar.isFullScreenUserSwitcherState() || mBouncer.isShowingScrimmed();
     }
 
     public void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 0390f60..fa763c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -37,6 +37,7 @@
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.view.ActionMode;
+import android.view.DisplayCutout;
 import android.view.InputDevice;
 import android.view.InputQueue;
 import android.view.KeyEvent;
@@ -75,6 +76,7 @@
     private NotificationStackScrollLayout mStackScrollLayout;
     private NotificationPanelView mNotificationPanel;
     private View mBrightnessMirror;
+    private PhoneStatusBarView mStatusBarView;
 
     private int mRightInset = 0;
     private int mLeftInset = 0;
@@ -118,10 +120,21 @@
             boolean paddingChanged = insets.top != getPaddingTop()
                     || insets.bottom != getPaddingBottom();
 
+            int rightCutout = 0;
+            int leftCutout = 0;
+            DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
+            if (displayCutout != null) {
+                leftCutout = displayCutout.getSafeInsetLeft();
+                rightCutout = displayCutout.getSafeInsetRight();
+            }
+
+            int targetLeft = Math.max(insets.left, leftCutout);
+            int targetRight = Math.max(insets.right, rightCutout);
+
             // Super-special right inset handling, because scrims and backdrop need to ignore it.
-            if (insets.right != mRightInset || insets.left != mLeftInset) {
-                mRightInset = insets.right;
-                mLeftInset = insets.left;
+            if (targetRight != mRightInset || targetLeft != mLeftInset) {
+                mRightInset = targetRight;
+                mLeftInset = targetLeft;
                 applyMargins();
             }
             // Drop top inset, and pass through bottom inset.
@@ -192,6 +205,10 @@
         }
     }
 
+    public void setStatusBarView(PhoneStatusBarView statusBarView) {
+        mStatusBarView = statusBarView;
+    }
+
     public void setService(StatusBar service) {
         mService = service;
         setDragDownHelper(new DragDownHelper(getContext(), this, mStackScrollLayout, mService));
@@ -314,7 +331,7 @@
             expandingBelowNotch = true;
         }
         if (expandingBelowNotch) {
-            return mNotificationPanel.dispatchTouchEvent(ev);
+            return mStatusBarView.dispatchTouchEvent(ev);
         }
 
         return super.dispatchTouchEvent(ev);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 9aa8044..8517d90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Parcelable;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.Spannable;
@@ -65,6 +66,12 @@
         DarkReceiver, ConfigurationListener {
 
     public static final String CLOCK_SECONDS = "clock_seconds";
+    private static final String CLOCK_SUPER_PARCELABLE = "clock_super_parcelable";
+    private static final String CURRENT_USER_ID = "current_user_id";
+    private static final String VISIBLE_BY_POLICY = "visible_by_policy";
+    private static final String VISIBLE_BY_USER = "visible_by_user";
+    private static final String SHOW_SECONDS = "show_seconds";
+    private static final String VISIBILITY = "visibility";
 
     private final CurrentUserTracker mCurrentUserTracker;
     private int mCurrentUserId;
@@ -129,6 +136,40 @@
     }
 
     @Override
+    public Parcelable onSaveInstanceState() {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(CLOCK_SUPER_PARCELABLE, super.onSaveInstanceState());
+        bundle.putInt(CURRENT_USER_ID, mCurrentUserId);
+        bundle.putBoolean(VISIBLE_BY_POLICY, mClockVisibleByPolicy);
+        bundle.putBoolean(VISIBLE_BY_USER, mClockVisibleByUser);
+        bundle.putBoolean(SHOW_SECONDS, mShowSeconds);
+        bundle.putInt(VISIBILITY, getVisibility());
+
+        return bundle;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (state == null || !(state instanceof Bundle)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        Bundle bundle = (Bundle) state;
+        Parcelable superState = bundle.getParcelable(CLOCK_SUPER_PARCELABLE);
+        super.onRestoreInstanceState(superState);
+        if (bundle.containsKey(CURRENT_USER_ID)) {
+            mCurrentUserId = bundle.getInt(CURRENT_USER_ID);
+        }
+        mClockVisibleByPolicy = bundle.getBoolean(VISIBLE_BY_POLICY, true);
+        mClockVisibleByUser = bundle.getBoolean(VISIBLE_BY_USER, true);
+        mShowSeconds = bundle.getBoolean(SHOW_SECONDS, false);
+        if (bundle.containsKey(VISIBILITY)) {
+            setVisibility(bundle.getInt(VISIBILITY));
+        }
+    }
+
+    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index d6d0673..3c16329 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -134,6 +134,10 @@
 
     @Override
     public void setHotspotEnabled(boolean enabled) {
+        if (mWaitingForCallback) {
+            if (DEBUG) Log.d(TAG, "Ignoring setHotspotEnabled; waiting for callback.");
+            return;
+        }
         if (enabled) {
             OnStartTetheringCallback callback = new OnStartTetheringCallback();
             mWaitingForCallback = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 50a0af3..312119b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -422,6 +422,7 @@
     private int mHeadsUpInset;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private NotificationIconAreaController mIconAreaController;
+    private float mVerticalPanelTranslation;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -4003,8 +4004,13 @@
         notifyHeightChangeListener(mShelf);
     }
 
-    private void updateAntiBurnInTranslation() {
-        setTranslationX(mAntiBurnInOffsetX * mDarkAmount);
+    private void updatePanelTranslation() {
+        setTranslationX(mVerticalPanelTranslation + mAntiBurnInOffsetX * mDarkAmount);
+    }
+
+    public void setVerticalPanelTranslation(float verticalPanelTranslation) {
+        mVerticalPanelTranslation = verticalPanelTranslation;
+        updatePanelTranslation();
     }
 
     /**
@@ -4034,7 +4040,7 @@
         }
         updateAlgorithmHeightAndPadding();
         updateBackgroundDimming();
-        updateAntiBurnInTranslation();
+        updatePanelTranslation();
         requestChildrenUpdate();
     }
 
@@ -4602,7 +4608,7 @@
 
     public void setAntiBurnInOffsetX(int antiBurnInOffsetX) {
         mAntiBurnInOffsetX = antiBurnInOffsetX;
-        updateAntiBurnInTranslation();
+        updatePanelTranslation();
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -4715,10 +4721,13 @@
 
             if (currView instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) currView;
-                mCurrMenuRow = row.createMenu();
-                mCurrMenuRow.setSwipeActionHelper(NotificationSwipeHelper.this);
-                mCurrMenuRow.setMenuClickListener(NotificationStackScrollLayout.this);
-                mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */);
+
+                if (row.getEntry().hasFinishedInitialization()) {
+                    mCurrMenuRow = row.createMenu();
+                    mCurrMenuRow.setSwipeActionHelper(NotificationSwipeHelper.this);
+                    mCurrMenuRow.setMenuClickListener(NotificationStackScrollLayout.this);
+                    mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */);
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 0d50f5a..ee006d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -178,7 +178,7 @@
             return false;
         }
         ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-        if (row.areGutsExposed()) {
+        if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
             return false;
         }
         return row.canViewBeDismissed();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index d7fad67..4a9856b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -193,11 +193,11 @@
   }
 
   public void show(int reason) {
-    mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
+    mHandler.obtainMessage(H.SHOW, reason).sendToTarget();
   }
 
   public void dismiss(int reason) {
-    mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
+    mHandler.obtainMessage(H.DISMISS, reason).sendToTarget();
   }
 
   private void showH(int reason) {
@@ -223,7 +223,7 @@
     mHandler.removeMessages(H.DISMISS);
     final int timeout = computeTimeoutH();
     mHandler.sendMessageDelayed(mHandler
-        .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+        .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT), timeout);
 
     if (D.BUG) {
       Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
@@ -246,7 +246,6 @@
     }
 
     mListView.animate().cancel();
-    mShowing = false;
 
     mListView.setTranslationY(0);
     mListView.setAlpha(1);
@@ -260,6 +259,7 @@
             Log.d(TAG, "mDialog.dismiss()");
           }
           mDialog.dismiss();
+          mShowing = false;
         }, DISMISS_DELAY_IN_MILLIS))
         .start();
 
@@ -436,7 +436,8 @@
     public boolean onTouchEvent(MotionEvent event) {
       if (isShowing()) {
         if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
-          dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
+          mHandler.obtainMessage(
+            H.DISMISS, Events.DISMISS_REASON_TOUCH_OUTSIDE).sendToTarget();
           return true;
         }
       }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
index 8153953..13dc36d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -93,7 +93,7 @@
         SysuiColorExtractor extractor = getTestableExtractor(colors);
         simulateEvent(extractor);
         extractor.setWallpaperVisible(true);
-        extractor.setMediaBackdropVisible(true);
+        extractor.setHasBackdrop(true);
 
         ColorExtractor.GradientColors fallbackColors = extractor.getFallbackColors();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 537bfd4..fe7bf25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -123,6 +123,27 @@
     }
 
     @Test
+    public void testHeaderReadFromOldController() {
+        mHeadsUpAppearanceController.setExpandedHeight(1.0f, 1.0f);
+
+        HeadsUpAppearanceController newController = new HeadsUpAppearanceController(
+                mock(NotificationIconAreaController.class),
+                mHeadsUpManager,
+                mHeadsUpStatusBarView,
+                mStackScroller,
+                mPanelView,
+                new View(mContext));
+        newController.readFrom(mHeadsUpAppearanceController);
+
+        Assert.assertEquals(mHeadsUpAppearanceController.mExpandedHeight,
+                newController.mExpandedHeight, 0.0f);
+        Assert.assertEquals(mHeadsUpAppearanceController.mExpandFraction,
+                newController.mExpandFraction, 0.0f);
+        Assert.assertEquals(mHeadsUpAppearanceController.mIsExpanded,
+                newController.mIsExpanded);
+    }
+
+    @Test
     public void testDestroy() {
         reset(mHeadsUpManager);
         reset(mDarkIconDispatcher);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index e95702c..89d562a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -44,7 +44,6 @@
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.util.function.TriConsumer;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -94,6 +93,7 @@
                     mScrimInFrontColor = scrimInFrontColor;
                 },
                 visible -> mScrimVisibility = visible, mDozeParamenters, mAlarmManager);
+        mScrimController.setHasBackdrop(false);
     }
 
     @Test
@@ -140,12 +140,7 @@
 
     @Test
     public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() {
-        ScrimState.AOD.mKeyguardUpdateMonitor = new KeyguardUpdateMonitor(getContext()) {
-            @Override
-            public boolean hasLockscreenWallpaper() {
-                return true;
-            }
-        };
+        mScrimController.setHasBackdrop(true);
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d2990de..a80ea02 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -6118,8 +6118,13 @@
 
     // ---- End P Constants, all P constants go above this line ----
 
-    // First Q constant in master goes here:
-    // Please delete these lines and use 1500 for your first enum.
+    // Time since this notification last interrupted (visibly or audible) the user
+    NOTIFICATION_SINCE_INTERRUPTION_MILLIS = 1500;
+
+    // OPEN: Notification interrupted the user, either audibly or visually.
+    //   Tagged data: NOTIFICATION_SINCE_INTERRUPTION_MILLIS
+    // CATEGORY: NOTIFICATION
+    NOTIFICATION_INTERRUPTION = 1501;
 
     // ---- End Q Constants, all Q constants go above this line ----
 
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 72f11e0..33fc5e5 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -457,6 +457,19 @@
   // Identifier for experimental scoring parameter settings.
   optional string score_experiment_id = 117;
 
+  // Data on wifi radio usage
+  optional WifiRadioUsage wifi_radio_usage = 118;
+
+  // Stores settings values used for metrics testing.
+  optional ExperimentValues experiment_values = 119;
+
+  // List of WifiIsUnusableEvents which get logged when we notice that WiFi is unusable.
+  // Collected only when WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED Settings is enabled.
+  repeated WifiIsUnusableEvent wifi_is_unusable_event_list = 120;
+
+  // Counts the occurrences of each link speed (Mbps) level
+  // with rssi (dBm) and rssi^2 sums (dBm^2)
+  repeated LinkSpeedCount link_speed_counts = 121;
 }
 
 // Information that gets logged for every WiFi connection.
@@ -642,6 +655,22 @@
   optional int32 count = 2;
 }
 
+// Number of occurrences of a specific link speed (Mbps)
+// and sum of rssi (dBm) and rssi^2 (dBm^2)
+message LinkSpeedCount {
+  // Link speed (Mbps)
+  optional int32 link_speed_mbps = 1;
+
+  // Number of RSSI polls with link_speed
+  optional int32 count = 2;
+
+  // Sum of absolute values of rssi values (dBm)
+  optional int32 rssi_sum_dbm = 3;
+
+  // Sum of squares of rssi values (dBm^2)
+  optional int64 rssi_sum_of_squares_dbm_sq = 4;
+}
+
 // Number of occurrences of Soft AP session durations
 message SoftApDurationBucket {
   // Bucket covers duration : [duration_sec, duration_sec + bucket_size_sec)
@@ -874,7 +903,7 @@
     STATE_INVALID = 12;
   }
 
-  // Bit mask of all supplicant state changes that occured since the last event
+  // Bit mask of all supplicant state changes that occurred since the last event
   optional uint32 supplicant_state_changes_bitmask = 9 [default = 0];
 
   // The number of milliseconds that have elapsed since the device booted
@@ -882,7 +911,7 @@
 
   optional FrameworkDisconnectReason framework_disconnect_reason = 11 [default = DISCONNECT_UNKNOWN];
 
-  // Flag which indicates if an association rejection event occured due to a timeout
+  // Flag which indicates if an association rejection event occurred due to a timeout
   optional bool association_timed_out = 12 [default = false];
 
   // Authentication failure reason, as reported by WifiManager (calculated from state & deauth code)
@@ -1503,3 +1532,83 @@
     optional int32 count = 2;
   }
 }
+
+// Usage data for the wifi radio while device is running on battery.
+message WifiRadioUsage {
+  // Duration of log (ms)
+  optional int64 logging_duration_ms = 1;
+
+  // Total time for which the radio is awake due to scan.
+  optional int64 scan_time_ms = 2;
+}
+
+message ExperimentValues {
+  // Indicates if we are logging WifiIsUnusableEvent in metrics
+  optional bool wifi_is_unusable_logging_enabled = 1;
+
+  // Minimum number of txBad to trigger a data stall
+  optional int32 wifi_data_stall_min_tx_bad = 2;
+
+  // Minimum number of txSuccess to trigger a data stall
+  // when rxSuccess is 0
+  optional int32 wifi_data_stall_min_tx_success_without_rx = 3;
+
+  // Indicates if we are logging LinkSpeedCount in metrics
+  optional bool link_speed_counts_logging_enabled = 4;
+}
+
+message WifiIsUnusableEvent {
+  enum TriggerType {
+    // Default/Invalid event
+    TYPE_UNKNOWN = 0;
+
+    // There is a data stall from tx failures
+    TYPE_DATA_STALL_BAD_TX = 1;
+
+    // There is a data stall from rx failures
+    TYPE_DATA_STALL_TX_WITHOUT_RX = 2;
+
+    // There is a data stall from both tx and rx failures
+    TYPE_DATA_STALL_BOTH = 3;
+
+    // Firmware generated an alert
+    TYPE_FIRMWARE_ALERT = 4;
+  }
+
+  // What event triggered WifiIsUnusableEvent.
+  optional TriggerType type = 1;
+
+  // The timestamp at which this event occurred.
+  // Measured in milliseconds that have elapsed since the device booted.
+  optional int64 start_time_millis = 2;
+
+  // NetworkAgent score of connected wifi.
+  // Defaults to -1 if the score was never set.
+  optional int32 last_score = 3 [default = -1];
+
+  // Delta of successfully transmitted (ACKed) unicast data packets
+  // between the last two WifiLinkLayerStats.
+  optional int64 tx_success_delta = 4;
+
+  // Delta of transmitted unicast data retry packets
+  // between the last two WifiLinkLayerStats.
+  optional int64 tx_retries_delta = 5;
+
+  // Delta of lost (not ACKed) transmitted unicast data packets
+  // between the last two WifiLinkLayerStats.
+  optional int64 tx_bad_delta = 6;
+
+  // Delta of received unicast data packets
+  // between the last two WifiLinkLayerStats.
+  optional int64 rx_success_delta = 7;
+
+  // Time in millisecond between the last two WifiLinkLayerStats.
+  optional int64 packet_update_time_delta = 8;
+
+  // The timestamp at which the last WifiLinkLayerStats was updated.
+  // Measured in milliseconds that have elapsed since the device booted.
+  optional int64 last_link_layer_stats_update_time = 9;
+
+  // Firmware alert code. Only valid when the event was triggered by a firmware alert, otherwise -1.
+  optional int32 firmware_alert_code = 10 [default = -1];
+}
\ No newline at end of file
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 8da6d1e..ee7a5e2 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -37,6 +37,7 @@
         "android.hardware.health-V1.0-java",
         "android.hardware.health-V2.0-java",
         "android.hardware.weaver-V1.0-java",
+        "android.hardware.biometrics.face-V1.0-java",
         "android.hardware.biometrics.fingerprint-V2.1-java",
         "android.hardware.oemlock-V1.0-java",
         "android.hardware.tetheroffload.control-V1.0-java",
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 786d757..1167e1d 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -198,7 +198,7 @@
     @VisibleForTesting
     final SparseArray<UidState> mUidStates = new SparseArray<>();
 
-    long mLastUptime;
+    long mLastRealtime;
 
     /*
      * These are app op restrictions imposed per user from various parties.
@@ -770,7 +770,7 @@
                     } else {
                         settleTime = mConstants.BG_STATE_SETTLE_TIME;
                     }
-                    uidState.pendingStateCommitTime = SystemClock.uptimeMillis() + settleTime;
+                    uidState.pendingStateCommitTime = SystemClock.elapsedRealtime() + settleTime;
                 }
                 if (uidState.startNesting != 0) {
                     // There is some actively running operation...  need to find it
@@ -1881,11 +1881,11 @@
             mUidStates.put(uid, uidState);
         } else {
             if (uidState.pendingStateCommitTime != 0) {
-                if (uidState.pendingStateCommitTime < mLastUptime) {
+                if (uidState.pendingStateCommitTime < mLastRealtime) {
                     commitUidPendingStateLocked(uidState);
                 } else {
-                    mLastUptime = SystemClock.uptimeMillis();
-                    if (uidState.pendingStateCommitTime < mLastUptime) {
+                    mLastRealtime = SystemClock.elapsedRealtime();
+                    if (uidState.pendingStateCommitTime < mLastRealtime) {
                         commitUidPendingStateLocked(uidState);
                     }
                 }
@@ -3284,7 +3284,7 @@
                 }
                 if (uidState.pendingStateCommitTime != 0) {
                     pw.print("    pendingStateCommitTime=");
-                    TimeUtils.formatDuration(uidState.pendingStateCommitTime, nowUptime, pw);
+                    TimeUtils.formatDuration(uidState.pendingStateCommitTime, nowElapsed, pw);
                     pw.println();
                 }
                 if (uidState.startNesting != 0) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5b55fa1..f096135 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -898,6 +898,10 @@
             public boolean isTetheringSupported() {
                 return ConnectivityService.this.isTetheringSupported();
             }
+            @Override
+            public NetworkRequest getDefaultNetworkRequest() {
+                return mDefaultRequest;
+            }
         };
         return new Tethering(mContext, mNetd, mStatsService, mPolicyManager,
                 IoThread.get().getLooper(), new MockableSystemProperties(),
@@ -915,7 +919,7 @@
 
     private NetworkRequest createDefaultInternetRequestForTransport(
             int transportType, NetworkRequest.Type type) {
-        NetworkCapabilities netCap = new NetworkCapabilities();
+        final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
         netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
         if (transportType > -1) {
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index 8ddf09a..0941bc8 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -69,6 +69,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Predicate;
 
 public class TextServicesManagerService extends ITextServicesManager.Stub {
@@ -535,38 +536,39 @@
                 && !allowImplicitlySelectedSubtype) {
             return null;
         }
-        String candidateLocale = null;
-        if (subtypeHashCode == 0) {
-            // Spell checker language settings == "auto"
-            // Use System locale if available in the spell checker
-            candidateLocale = systemLocale.toString();
+
+        final int numSubtypes = sci.getSubtypeCount();
+        if (subtypeHashCode != 0) {
+            // Use the user specified spell checker subtype
+            for (int i = 0; i < numSubtypes; ++i) {
+                final SpellCheckerSubtype scs = sci.getSubtypeAt(i);
+                if (scs.hashCode() == subtypeHashCode) {
+                    return scs;
+                }
+            }
+            return null;
         }
-        SpellCheckerSubtype candidate = null;
+
+        // subtypeHashCode == 0 means spell checker language settings is "auto"
+
+        if (systemLocale == null) {
+            return null;
+        }
+        SpellCheckerSubtype firstLanguageMatchingSubtype = null;
         for (int i = 0; i < sci.getSubtypeCount(); ++i) {
             final SpellCheckerSubtype scs = sci.getSubtypeAt(i);
-            if (subtypeHashCode == 0) {
-                final String scsLocale = scs.getLocale();
-                if (candidateLocale.equals(scsLocale)) {
-                    return scs;
-                } else if (candidate == null) {
-                    if (candidateLocale.length() >= 2 && scsLocale.length() >= 2
-                            && candidateLocale.startsWith(scsLocale)) {
-                        // Fall back to the applicable language
-                        candidate = scs;
-                    }
-                }
-            } else if (scs.hashCode() == subtypeHashCode) {
-                if (DBG) {
-                    Slog.w(TAG, "Return subtype " + scs.hashCode() + ", " + scs.getLocale());
-                }
-                // Use the user specified spell check language
+            final Locale scsLocale = scs.getLocaleObject();
+            if (Objects.equals(scsLocale, systemLocale)) {
+                // Exact match wins.
                 return scs;
             }
+            if (firstLanguageMatchingSubtype == null && scsLocale != null
+                    && TextUtils.equals(systemLocale.getLanguage(), scsLocale.getLanguage())) {
+                // Remember as a fall back candidate
+                firstLanguageMatchingSubtype = scs;
+            }
         }
-        // Fall back to the applicable language and return it if not null
-        // Simply just return it even if it's null which means we could find no suitable
-        // spell check languages
-        return candidate;
+        return firstLanguageMatchingSubtype;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 6de43de..ee93fc8 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -140,7 +140,7 @@
                 + " to displayId=" + mDisplayId + " position=" + position);
         addStackReferenceIfNeeded(stack);
         positionChildAt(stack, position);
-        mSupervisor.mService.updateSleepIfNeededLocked();
+        mSupervisor.mService.mAm.updateSleepIfNeededLocked();
     }
 
     void removeChild(ActivityStack stack) {
@@ -148,7 +148,7 @@
                 + " from displayId=" + mDisplayId);
         mStacks.remove(stack);
         removeStackReferenceIfNeeded(stack);
-        mSupervisor.mService.updateSleepIfNeededLocked();
+        mSupervisor.mService.mAm.updateSleepIfNeededLocked();
         onStackOrderChanged();
     }
 
@@ -300,7 +300,7 @@
             }
         }
 
-        final ActivityManagerService service = mSupervisor.mService;
+        final ActivityManagerService service = mSupervisor.mService.mAm;
         if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
                 service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
                 service.mSupportsPictureInPicture, activityType)) {
@@ -536,7 +536,7 @@
         }
 
         // Make sure the windowing mode we are trying to use makes sense for what is supported.
-        final ActivityManagerService service = mSupervisor.mService;
+        final ActivityManagerService service = mSupervisor.mService.mAm;
         boolean supportsMultiWindow = service.mSupportsMultiWindow;
         boolean supportsSplitScreen = service.mSupportsSplitScreenMultiWindow;
         boolean supportsFreeform = service.mSupportsFreeformWindowManagement;
@@ -714,7 +714,7 @@
 
     boolean shouldSleep() {
         return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
-                && (mSupervisor.mService.mRunningVoice == null);
+                && (mSupervisor.mService.mAm.mRunningVoice == null);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c49ec34..37253ec 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -615,8 +615,7 @@
     private Installer mInstaller;
 
     /** Run all ActivityStacks through this */
-    final ActivityStackSupervisor mStackSupervisor;
-    final KeyguardController mKeyguardController;
+    ActivityStackSupervisor mStackSupervisor;
 
     final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter();
 
@@ -734,7 +733,7 @@
 
     public boolean canShowErrorDialogs() {
         return mShowDialogs && !mSleeping && !mShuttingDown
-                && !mKeyguardController.isKeyguardOrAodShowing(DEFAULT_DISPLAY)
+                && !mActivityTaskManager.mKeyguardController.isKeyguardOrAodShowing(DEFAULT_DISPLAY)
                 && !mUserController.hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
                         mUserController.getCurrentUserId())
                 && !(UserManager.isDeviceInDemoMode(mContext)
@@ -1286,16 +1285,6 @@
      */
     final AppOpsService mAppOpsService;
 
-    /** Current sequencing integer of the configuration, for skipping old configurations. */
-    private int mConfigurationSeq;
-
-    /**
-     * Temp object used when global and/or display override configuration is updated. It is also
-     * sent to outer world instead of {@link #getGlobalConfiguration} because we don't trust
-     * anyone...
-     */
-    private Configuration mTempConfig = new Configuration();
-
     /**
      * Hardware-reported OpenGLES version.
      */
@@ -2049,6 +2038,9 @@
                     }
                 }
                 callbacks.finishBroadcast();
+                // We have to clean up the RemoteCallbackList here, because otherwise it will
+                // needlessly hold the enclosed callbacks until the remote process dies.
+                callbacks.kill();
             } break;
             case UPDATE_TIME_ZONE: {
                 synchronized (ActivityManagerService.this) {
@@ -2595,8 +2587,8 @@
     public void setActivityTaskManager(ActivityTaskManagerService atm) {
         synchronized (this) {
             mActivityTaskManager = atm;
-            mStackSupervisor.setActivityTaskManager(atm);
             mActivityTaskManager.setActivityManagerService(this);
+            mStackSupervisor = mActivityTaskManager.mStackSupervisor;
         }
     }
 
@@ -2845,13 +2837,11 @@
         mHandler = null;
         mHandlerThread = null;
         mIntentFirewall = null;
-        mKeyguardController = null;
         mPermissionReviewRequired = false;
         mProcessCpuThread = null;
         mProcessStats = null;
         mProviderMap = null;
         mServices = null;
-        mStackSupervisor = null;
         mSystemThread = null;
         mUiHandler = injector.getUiHandler(null);
         mUserController = null;
@@ -2938,12 +2928,6 @@
         }
 
         mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
-        mTempConfig.setToDefaults();
-        mTempConfig.setLocales(LocaleList.getDefault());
-        mConfigurationSeq = mTempConfig.seq = 1;
-        mStackSupervisor = createStackSupervisor();
-        mStackSupervisor.onConfigurationChanged(mTempConfig);
-        mKeyguardController = mStackSupervisor.getKeyguardController();
         mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
 
@@ -2999,12 +2983,6 @@
 
     }
 
-    protected ActivityStackSupervisor createStackSupervisor() {
-        final ActivityStackSupervisor supervisor = new ActivityStackSupervisor(this, mHandler.getLooper());
-        supervisor.initialize();
-        return supervisor;
-    }
-
     public void setSystemServiceManager(SystemServiceManager mgr) {
         mSystemServiceManager = mgr;
     }
@@ -10963,7 +10941,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (mUserController.shouldConfirmCredentials(userId)) {
-                    if (mKeyguardController.isKeyguardLocked()) {
+                    if (mActivityTaskManager.mKeyguardController.isKeyguardLocked()) {
                         // Showing launcher to avoid user entering credential twice.
                         final int currentUserId = mUserController.getCurrentUserId();
                         startHomeActivityLocked(currentUserId, "notifyLockedProfile");
@@ -19525,8 +19503,8 @@
     /** Update default (global) configuration and notify listeners about changes. */
     private int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
             boolean persistent, int userId, boolean deferResume) {
-        mTempConfig.setTo(getGlobalConfiguration());
-        final int changes = mTempConfig.updateFrom(values);
+        mActivityTaskManager.mTempConfig.setTo(getGlobalConfiguration());
+        final int changes = mActivityTaskManager.mTempConfig.updateFrom(values);
         if (changes == 0) {
             // Since calling to Activity.setRequestedOrientation leads to freezing the window with
             // setting WindowManagerService.mWaitingForConfig to true, it is important that we call
@@ -19576,34 +19554,34 @@
                     locales.get(bestLocaleIndex)));
         }
 
-        mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
-        mTempConfig.seq = mConfigurationSeq;
+        mActivityTaskManager.mConfigurationSeq = Math.max(++mActivityTaskManager.mConfigurationSeq, 1);
+        mActivityTaskManager.mTempConfig.seq = mActivityTaskManager.mConfigurationSeq;
 
         // Update stored global config and notify everyone about the change.
-        mStackSupervisor.onConfigurationChanged(mTempConfig);
+        mStackSupervisor.onConfigurationChanged(mActivityTaskManager.mTempConfig);
 
-        Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
+        Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mActivityTaskManager.mTempConfig);
         // TODO(multi-display): Update UsageEvents#Event to include displayId.
-        mUsageStatsService.reportConfigurationChange(mTempConfig,
+        mUsageStatsService.reportConfigurationChange(mActivityTaskManager.mTempConfig,
                 mUserController.getCurrentUserId());
 
         // TODO: If our config changes, should we auto dismiss any currently showing dialogs?
-        updateShouldShowDialogsLocked(mTempConfig);
+        updateShouldShowDialogsLocked(mActivityTaskManager.mTempConfig);
 
         AttributeCache ac = AttributeCache.instance();
         if (ac != null) {
-            ac.updateConfiguration(mTempConfig);
+            ac.updateConfiguration(mActivityTaskManager.mTempConfig);
         }
 
         // Make sure all resources in our process are updated right now, so that anyone who is going
         // to retrieve resource values after we return will be sure to get the new ones. This is
         // especially important during boot, where the first config change needs to guarantee all
         // resources have that config before following boot code is executed.
-        mSystemThread.applyConfigurationToResources(mTempConfig);
+        mSystemThread.applyConfigurationToResources(mActivityTaskManager.mTempConfig);
 
         // We need another copy of global config because we're scheduling some calls instead of
         // running them in place. We need to be sure that object we send will be handled unchanged.
-        final Configuration configCopy = new Configuration(mTempConfig);
+        final Configuration configCopy = new Configuration(mActivityTaskManager.mTempConfig);
         if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
             Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
             msg.obj = configCopy;
@@ -19714,12 +19692,12 @@
 
     private int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume,
             int displayId) {
-        mTempConfig.setTo(mStackSupervisor.getDisplayOverrideConfiguration(displayId));
-        final int changes = mTempConfig.updateFrom(values);
+        mActivityTaskManager.mTempConfig.setTo(mStackSupervisor.getDisplayOverrideConfiguration(displayId));
+        final int changes = mActivityTaskManager.mTempConfig.updateFrom(values);
         if (changes != 0) {
             Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
-                    + mTempConfig + " for displayId=" + displayId);
-            mStackSupervisor.setDisplayOverrideConfiguration(mTempConfig, displayId);
+                    + mActivityTaskManager.mTempConfig + " for displayId=" + displayId);
+            mStackSupervisor.setDisplayOverrideConfiguration(mActivityTaskManager.mTempConfig, displayId);
 
             final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
             if (isDensityChange && displayId == DEFAULT_DISPLAY) {
@@ -19736,7 +19714,7 @@
         // ensureActivityConfiguration().
         if (mWindowManager != null) {
             final int[] resizedStacks =
-                    mWindowManager.setNewDisplayOverrideConfiguration(mTempConfig, displayId);
+                    mWindowManager.setNewDisplayOverrideConfiguration(mActivityTaskManager.mTempConfig, displayId);
             if (resizedStacks != null) {
                 for (int stackId : resizedStacks) {
                     resizeStackWithBoundsFromWindowManager(stackId, deferResume);
@@ -21778,7 +21756,8 @@
     }
 
     private final ActivityRecord resumedAppLocked() {
-        ActivityRecord act = mStackSupervisor.getResumedActivityLocked();
+        final ActivityRecord act =
+                mStackSupervisor != null ? mStackSupervisor.getResumedActivityLocked() : null;
         String pkg;
         int uid;
         if (act != null) {
@@ -21860,7 +21839,9 @@
             uidRec.reset();
         }
 
-        mStackSupervisor.rankTaskLayersIfNeeded();
+        if (mStackSupervisor != null) {
+            mStackSupervisor.rankTaskLayersIfNeeded();
+        }
 
         mAdjSeq++;
         mNewNumServiceProcs = 0;
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 9e9f638..1611a38 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -185,6 +185,10 @@
 
         mWindowState = WINDOW_STATE_INVALID;
         ActivityStack stack = mSupervisor.getFocusedStack();
+        if (stack == null) {
+            return;
+        }
+
         if (stack.isActivityTypeAssistant()) {
             mWindowState = WINDOW_STATE_ASSISTANT;
             return;
@@ -407,7 +411,7 @@
     }
 
     private void checkVisibility(TaskRecord t, ActivityRecord r) {
-        synchronized (mSupervisor.mService) {
+        synchronized (mSupervisor.mService.mGlobalLock) {
 
             final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(
                     r.getWindowingMode());
@@ -663,8 +667,8 @@
 
     private ProcessRecord findProcessForActivity(ActivityRecord launchedActivity) {
         return launchedActivity != null
-                ? mSupervisor.mService.mProcessNames.get(launchedActivity.processName,
-                        launchedActivity.appInfo.uid)
+                ? mSupervisor.mService.mAm.mProcessNames.get(
+                        launchedActivity.processName, launchedActivity.appInfo.uid)
                 : null;
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index a55ffdb..0e427d6 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1098,7 +1098,7 @@
             return true;
         }
         // Allow the recents component to launch the home activity.
-        final RecentTasks recentTasks = mStackSupervisor.mService.mActivityTaskManager.getRecentTasks();
+        final RecentTasks recentTasks = mStackSupervisor.mService.getRecentTasks();
         if (recentTasks != null && recentTasks.isCallerRecents(uid)) {
             return true;
         }
@@ -2930,16 +2930,16 @@
             throw new XmlPullParserException("restoreActivity error intent=" + intent);
         }
 
-        final ActivityManagerService service = stackSupervisor.mService;
+        final ActivityTaskManagerService service = stackSupervisor.mService;
         final ActivityInfo aInfo = stackSupervisor.resolveActivity(intent, resolvedType, 0, null,
                 userId, Binder.getCallingUid());
         if (aInfo == null) {
             throw new XmlPullParserException("restoreActivity resolver error. Intent=" + intent +
                     " resolvedType=" + resolvedType);
         }
-        final ActivityRecord r = new ActivityRecord(service.mActivityTaskManager, null /* caller */,
+        final ActivityRecord r = new ActivityRecord(service, null /* caller */,
                 0 /* launchedFromPid */, launchedFromUid, launchedFromPackage, intent, resolvedType,
-                aInfo, service.getConfiguration(), null /* resultTo */, null /* resultWho */,
+                aInfo, service.mAm.getConfiguration(), null /* resultTo */, null /* resultWho */,
                 0 /* reqCode */, componentSpecified, false /* rootVoiceInteraction */,
                 stackSupervisor, null /* options */, null /* sourceRecord */);
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 64f62ed..6118131 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -453,7 +453,7 @@
     ActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
             int windowingMode, int activityType, boolean onTop) {
         mStackSupervisor = supervisor;
-        mService = supervisor.mService.mActivityTaskManager;
+        mService = supervisor.mService;
         mHandler = new ActivityStackHandler(supervisor.mLooper);
         mWindowManager = mService.mWindowManager;
         mStackId = stackId;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 08e19e2..a77d734 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -304,7 +304,7 @@
     /** The number of distinct task ids that can be assigned to the tasks of a single user */
     private static final int MAX_TASK_IDS_PER_USER = UserHandle.PER_USER_RANGE;
 
-    ActivityManagerService mService;
+    ActivityTaskManagerService mService;
 
     /** The historial list of recent tasks including inactive tasks */
     RecentTasks mRecentTasks;
@@ -318,7 +318,6 @@
     /** Short cut */
     WindowManagerService mWindowManager;
     DisplayManager mDisplayManager;
-    ActivityTaskManagerService mAtm;
 
     private LaunchParamsController mLaunchParamsController;
 
@@ -489,7 +488,7 @@
             // No restrictions for the default display.
             return true;
         }
-        if (!mService.mSupportsMultiDisplay) {
+        if (!mService.mAm.mSupportsMultiDisplay) {
             // Can't launch on secondary displays if feature is not supported.
             return false;
         }
@@ -604,14 +603,14 @@
         }
     }
 
-    public ActivityStackSupervisor(ActivityManagerService service, Looper looper) {
+    public ActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) {
         mService = service;
         mLooper = looper;
         mHandler = new ActivityStackSupervisorHandler(looper);
     }
 
     @VisibleForTesting
-    void setService(ActivityManagerService service) {
+    void setService(ActivityTaskManagerService service) {
         mService = service;
     }
 
@@ -622,11 +621,10 @@
 
         mInitialized = true;
         mRunningTasks = createRunningTasks();
-        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext,
-                mHandler.getLooper());
-        mKeyguardController = new KeyguardController(mService, this);
+        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext, mHandler.getLooper());
+        mKeyguardController = new KeyguardController(mService.mAm, this);
 
-        mLaunchParamsController = new LaunchParamsController(mService);
+        mLaunchParamsController = new LaunchParamsController(mService.mAm);
         mLaunchParamsController.registerDefaultModifiers(this);
     }
 
@@ -665,10 +663,6 @@
         mLaunchingActivity.setReferenceCounted(false);
     }
 
-    void setActivityTaskManager(ActivityTaskManagerService atm) {
-        mAtm = atm;
-    }
-
     void setWindowManager(WindowManagerService wm) {
         mWindowManager = wm;
         getKeyguardController().setWindowManager(wm);
@@ -715,6 +709,11 @@
         if (!focusCandidate.isFocusable()) {
             // The focus candidate isn't focusable. Move focus to the top stack that is focusable.
             focusCandidate = getNextFocusableStackLocked(focusCandidate, false /* ignoreCurrent */);
+            if (focusCandidate == null) {
+                Slog.w(TAG,
+                        "setFocusStackUnchecked: No focusable stack found, focus home as default");
+                focusCandidate = mHomeStack;
+            }
         }
 
         if (focusCandidate != mFocusedStack) {
@@ -727,7 +726,7 @@
         }
 
         final ActivityRecord r = topRunningActivityLocked();
-        if (mService.mBooting || !mService.mBooted) {
+        if (mService.mAm.mBooting || !mService.mAm.mBooted) {
             if (r != null && r.idle) {
                 checkFinishBootingLocked();
             }
@@ -759,7 +758,7 @@
     }
 
     boolean resumeHomeStackTask(ActivityRecord prev, String reason) {
-        if (!mService.mBooting && !mService.mBooted) {
+        if (!mService.mAm.mBooting && !mService.mAm.mBooted) {
             // Not ready yet!
             return false;
         }
@@ -773,7 +772,7 @@
             moveFocusableActivityStackToFrontLocked(r, myReason);
             return resumeFocusedStackTopActivityLocked(mHomeStack, prev, null);
         }
-        return mService.startHomeActivityLocked(mCurrentUser, myReason);
+        return mService.mAm.startHomeActivityLocked(mCurrentUser, myReason);
     }
 
     TaskRecord anyTaskForIdLocked(int id) {
@@ -911,7 +910,7 @@
                         // result to an activity belonging to userId. Example case: a document
                         // picker for personal files, opened by a work app, should still get locked.
                         if (taskTopActivityIsUser(task, userId)) {
-                            mService.mActivityTaskManager.getTaskChangeNotificationController().notifyTaskProfileLocked(
+                            mService.mAm.mActivityTaskManager.getTaskChangeNotificationController().notifyTaskProfileLocked(
                                     task.taskId, userId);
                         }
                     }
@@ -1166,7 +1165,7 @@
             }
         }
         if (changed) {
-            mService.notifyAll();
+            mService.mAm.notifyAll();
         }
     }
 
@@ -1196,7 +1195,7 @@
         }
 
         if (changed) {
-            mService.notifyAll();
+            mService.mAm.notifyAll();
         }
     }
 
@@ -1217,7 +1216,7 @@
             }
         }
         if (changed) {
-            mService.notifyAll();
+            mService.mAm.notifyAll();
         }
     }
 
@@ -1322,19 +1321,19 @@
             // Don't debug things in the system process
             if (!aInfo.processName.equals("system")) {
                 if ((startFlags & ActivityManager.START_FLAG_DEBUG) != 0) {
-                    mService.setDebugApp(aInfo.processName, true, false);
+                    mService.mAm.setDebugApp(aInfo.processName, true, false);
                 }
 
                 if ((startFlags & ActivityManager.START_FLAG_NATIVE_DEBUGGING) != 0) {
-                    mService.setNativeDebuggingAppLocked(aInfo.applicationInfo, aInfo.processName);
+                    mService.mAm.setNativeDebuggingAppLocked(aInfo.applicationInfo, aInfo.processName);
                 }
 
                 if ((startFlags & ActivityManager.START_FLAG_TRACK_ALLOCATION) != 0) {
-                    mService.setTrackAllocationApp(aInfo.applicationInfo, aInfo.processName);
+                    mService.mAm.setTrackAllocationApp(aInfo.applicationInfo, aInfo.processName);
                 }
 
                 if (profilerInfo != null) {
-                    mService.setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo);
+                    mService.mAm.setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo);
                 }
             }
             final String intentLaunchToken = intent.getLaunchToken();
@@ -1347,7 +1346,7 @@
 
     ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags,
             int filterCallingUid) {
-        synchronized (mService) {
+        synchronized (mService.mGlobalLock) {
             try {
                 Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent");
                 int modifiedFlags = flags
@@ -1364,7 +1363,7 @@
                 // (e.g. AMS.startActivityAsUser).
                 final long token = Binder.clearCallingIdentity();
                 try {
-                    return mService.getPackageManagerInternalLocked().resolveIntent(
+                    return mService.mAm.getPackageManagerInternalLocked().resolveIntent(
                             intent, resolvedType, modifiedFlags, userId, true, filterCallingUid);
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -1453,10 +1452,10 @@
             if (idx < 0) {
                 app.activities.add(r);
             }
-            mService.updateLruProcessLocked(app, true, null);
-            mService.updateOomAdjLocked();
+            mService.mAm.updateLruProcessLocked(app, true, null);
+            mService.mAm.updateOomAdjLocked();
 
-            final LockTaskController lockTaskController = mService.mActivityTaskManager.getLockTaskController();
+            final LockTaskController lockTaskController = mService.getLockTaskController();
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
                     || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV
                     || (task.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED
@@ -1484,20 +1483,20 @@
                         System.identityHashCode(r), task.taskId, r.shortComponentName);
                 if (r.isActivityTypeHome()) {
                     // Home process is the root process of the task.
-                    mService.mHomeProcess = task.mActivities.get(0).app;
+                    mService.mAm.mHomeProcess = task.mActivities.get(0).app;
                 }
-                mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
+                mService.mAm.notifyPackageUse(r.intent.getComponent().getPackageName(),
                         PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY);
                 r.sleeping = false;
                 r.forceNewConfig = false;
-                mService.getAppWarningsLocked().onStartActivity(r);
-                mService.showAskCompatModeDialogLocked(r);
-                r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
+                mService.mAm.getAppWarningsLocked().onStartActivity(r);
+                mService.mAm.showAskCompatModeDialogLocked(r);
+                r.compat = mService.mAm.compatibilityInfoForPackageLocked(r.info.applicationInfo);
                 ProfilerInfo profilerInfo = null;
-                if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) {
-                    if (mService.mProfileProc == null || mService.mProfileProc == app) {
-                        mService.mProfileProc = app;
-                        ProfilerInfo profilerInfoSvc = mService.mProfilerInfo;
+                if (mService.mAm.mProfileApp != null && mService.mAm.mProfileApp.equals(app.processName)) {
+                    if (mService.mAm.mProfileProc == null || mService.mAm.mProfileProc == app) {
+                        mService.mAm.mProfileProc = app;
+                        ProfilerInfo profilerInfoSvc = mService.mAm.mProfilerInfo;
                         if (profilerInfoSvc != null && profilerInfoSvc.profileFile != null) {
                             if (profilerInfoSvc.profileFd != null) {
                                 try {
@@ -1514,13 +1513,13 @@
 
                 app.hasShownUi = true;
                 app.pendingUiClean = true;
-                app.forceProcessStateUpTo(mService.mTopProcessState);
+                app.forceProcessStateUpTo(mService.mAm.mTopProcessState);
                 // Because we could be starting an Activity in the system process this may not go
                 // across a Binder interface which would create a new Configuration. Consequently
                 // we have to always create a new Configuration here.
 
                 final MergedConfiguration mergedConfiguration = new MergedConfiguration(
-                        mService.getGlobalConfiguration(), r.getMergedOverrideConfiguration());
+                        mService.mAm.getGlobalConfiguration(), r.getMergedOverrideConfiguration());
                 r.setLastReportedConfiguration(mergedConfiguration);
 
                 logIfTransactionTooLarge(r.intent, r.icicle);
@@ -1536,39 +1535,39 @@
                         mergedConfiguration.getGlobalConfiguration(),
                         mergedConfiguration.getOverrideConfiguration(), r.compat,
                         r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
-                        r.persistentState, results, newIntents, mService.mActivityTaskManager.isNextTransitionForward(),
+                        r.persistentState, results, newIntents, mService.isNextTransitionForward(),
                         profilerInfo));
 
                 // Set desired final state.
                 final ActivityLifecycleItem lifecycleItem;
                 if (andResume) {
-                    lifecycleItem = ResumeActivityItem.obtain(mService.mActivityTaskManager.isNextTransitionForward());
+                    lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
                 } else {
                     lifecycleItem = PauseActivityItem.obtain();
                 }
                 clientTransaction.setLifecycleStateRequest(lifecycleItem);
 
                 // Schedule transaction.
-                mService.mActivityTaskManager.getLifecycleManager().scheduleTransaction(clientTransaction);
+                mService.getLifecycleManager().scheduleTransaction(clientTransaction);
 
                 if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0
-                        && mService.mHasHeavyWeightFeature) {
+                        && mService.mAm.mHasHeavyWeightFeature) {
                     // This may be a heavy-weight process!  Note that the package
                     // manager will ensure that only activity can run in the main
                     // process of the .apk, which is the only thing that will be
                     // considered heavy-weight.
                     if (app.processName.equals(app.info.packageName)) {
-                        if (mService.mHeavyWeightProcess != null
-                                && mService.mHeavyWeightProcess != app) {
+                        if (mService.mAm.mHeavyWeightProcess != null
+                                && mService.mAm.mHeavyWeightProcess != app) {
                             Slog.w(TAG, "Starting new heavy weight process " + app
                                     + " when already running "
-                                    + mService.mHeavyWeightProcess);
+                                    + mService.mAm.mHeavyWeightProcess);
                         }
-                        mService.mHeavyWeightProcess = app;
-                        Message msg = mService.mHandler.obtainMessage(
+                        mService.mAm.mHeavyWeightProcess = app;
+                        Message msg = mService.mAm.mHandler.obtainMessage(
                                 ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG);
                         msg.obj = r;
-                        mService.mHandler.sendMessage(msg);
+                        mService.mAm.mHandler.sendMessage(msg);
                     }
                 }
 
@@ -1579,7 +1578,7 @@
                     Slog.e(TAG, "Second failure launching "
                             + r.intent.getComponent().flattenToShortString()
                             + ", giving up", e);
-                    mService.appDiedLocked(app);
+                    mService.mAm.appDiedLocked(app);
                     stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
                             "2nd-crash", false);
                     return false;
@@ -1620,13 +1619,13 @@
         // a chance to initialize itself while in the background, making the
         // switch back to it faster and look better.
         if (isFocusedStack(stack)) {
-            mService.mActivityTaskManager.getActivityStartController().startSetupActivity();
+            mService.getActivityStartController().startSetupActivity();
         }
 
         // Update any services we are bound to that might care about whether
         // their client may have activities.
         if (r.app != null) {
-            mService.mServices.updateServiceConnectionActivitiesLocked(r.app);
+            mService.mAm.mServices.updateServiceConnectionActivitiesLocked(r.app);
         }
 
         return true;
@@ -1662,7 +1661,7 @@
         }
 
         // Update the configuration of the activities on the display.
-        return mService.updateDisplayOverrideConfigurationLocked(config, starting, deferResume,
+        return mService.mAm.updateDisplayOverrideConfigurationLocked(config, starting, deferResume,
                 displayId);
     }
 
@@ -1684,7 +1683,7 @@
     void startSpecificActivityLocked(ActivityRecord r,
             boolean andResume, boolean checkConfig) {
         // Is this activity's application already running?
-        ProcessRecord app = mService.getProcessRecordLocked(r.processName,
+        ProcessRecord app = mService.mAm.getProcessRecordLocked(r.processName,
                 r.info.applicationInfo.uid, true);
 
         getLaunchTimeTracker().setLaunchTime(r);
@@ -1698,7 +1697,7 @@
                     // part of the framework so doesn't make sense to track as a
                     // separate apk in the process.
                     app.addPackage(r.info.packageName, r.info.applicationInfo.longVersionCode,
-                            mService.mProcessStats);
+                            mService.mAm.mProcessStats);
                 }
                 realStartActivityLocked(r, app, andResume, checkConfig);
                 return;
@@ -1711,7 +1710,7 @@
             // restart the application.
         }
 
-        mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
+        mService.mAm.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
                 "activity", r.intent.getComponent(), false, false, true);
     }
 
@@ -1727,16 +1726,16 @@
                     || !resumedActivity.app.equals(targetActivity.app);
         }
 
-        if (sendHint && mService.mLocalPowerManager != null) {
-            mService.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 1);
+        if (sendHint && mService.mAm.mLocalPowerManager != null) {
+            mService.mAm.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 1);
             mPowerHintSent = true;
         }
     }
 
     void sendPowerHintForLaunchEndIfNeeded() {
         // Trigger launch power hint if activity is launched
-        if (mPowerHintSent && mService.mLocalPowerManager != null) {
-            mService.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 0);
+        if (mPowerHintSent && mService.mAm.mLocalPowerManager != null) {
+            mService.mAm.mLocalPowerManager.powerHint(PowerHint.LAUNCH, 0);
             mPowerHintSent = false;
         }
     }
@@ -1745,9 +1744,9 @@
             String resultWho, int requestCode, int callingPid, int callingUid,
             String callingPackage, boolean ignoreTargetSecurity, boolean launchingInTask,
             ProcessRecord callerApp, ActivityRecord resultRecord, ActivityStack resultStack) {
-        final boolean isCallerRecents = mService.mActivityTaskManager.getRecentTasks() != null
-                && mService.mActivityTaskManager.getRecentTasks().isCallerRecents(callingUid);
-        final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
+        final boolean isCallerRecents = mService.getRecentTasks() != null
+                && mService.getRecentTasks().isCallerRecents(callingUid);
+        final int startAnyPerm = mService.mAm.checkPermission(START_ANY_ACTIVITY, callingPid,
                 callingUid);
         if (startAnyPerm == PERMISSION_GRANTED || (isCallerRecents && launchingInTask)) {
             // If the caller has START_ANY_ACTIVITY, ignore all checks below. In addition, if the
@@ -1826,7 +1825,7 @@
 
         // Check if the caller has enough privileges to embed activities and launch to private
         // displays.
-        final int startAnyPerm = mService.checkPermission(INTERNAL_SYSTEM_WINDOW, callingPid,
+        final int startAnyPerm = mService.mAm.checkPermission(INTERNAL_SYSTEM_WINDOW, callingPid,
                 callingUid);
         if (startAnyPerm == PERMISSION_GRANTED) {
             if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
@@ -1848,7 +1847,7 @@
                 return false;
             }
             // Check if the caller is allowed to embed activities from other apps.
-            if (mService.checkPermission(ACTIVITY_EMBEDDING, callingPid, callingUid)
+            if (mService.mAm.checkPermission(ACTIVITY_EMBEDDING, callingPid, callingUid)
                     == PERMISSION_DENIED && !uidPresentOnDisplay) {
                 if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
                         + " disallow activity embedding without permission.");
@@ -1906,7 +1905,7 @@
 
     private int getComponentRestrictionForCallingPackage(ActivityInfo activityInfo,
             String callingPackage, int callingPid, int callingUid, boolean ignoreTargetSecurity) {
-        if (!ignoreTargetSecurity && mService.checkComponentPermission(activityInfo.permission,
+        if (!ignoreTargetSecurity && mService.mAm.checkComponentPermission(activityInfo.permission,
                 callingPid, callingUid, activityInfo.applicationInfo.uid, activityInfo.exported)
                 == PERMISSION_DENIED) {
             return ACTIVITY_RESTRICTION_PERMISSION;
@@ -1921,7 +1920,7 @@
             return ACTIVITY_RESTRICTION_NONE;
         }
 
-        if (mService.mAppOpsService.noteOperation(opCode, callingUid,
+        if (mService.mAm.mAppOpsService.noteOperation(opCode, callingUid,
                 callingPackage) != AppOpsManager.MODE_ALLOWED) {
             if (!ignoreTargetSecurity) {
                 return ACTIVITY_RESTRICTION_APPOP;
@@ -1955,7 +1954,7 @@
             return ACTIVITY_RESTRICTION_NONE;
         }
 
-        if (mService.checkPermission(permission, callingPid, callingUid) == PERMISSION_DENIED) {
+        if (mService.mAm.checkPermission(permission, callingPid, callingUid) == PERMISSION_DENIED) {
             return ACTIVITY_RESTRICTION_PERMISSION;
         }
 
@@ -1964,7 +1963,7 @@
             return ACTIVITY_RESTRICTION_NONE;
         }
 
-        if (mService.mAppOpsService.noteOperation(opCode, callingUid,
+        if (mService.mAm.mAppOpsService.noteOperation(opCode, callingUid,
                 callingPackage) != AppOpsManager.MODE_ALLOWED) {
             return ACTIVITY_RESTRICTION_APPOP;
         }
@@ -1989,19 +1988,19 @@
 
     /**
      * Called when the frontmost task is idle.
-     * @return the state of mService.mBooting before this was called.
+     * @return the state of mService.mAm.mBooting before this was called.
      */
     @GuardedBy("mService")
     private boolean checkFinishBootingLocked() {
-        final boolean booting = mService.mBooting;
+        final boolean booting = mService.mAm.mBooting;
         boolean enableScreen = false;
-        mService.mBooting = false;
-        if (!mService.mBooted) {
-            mService.mBooted = true;
+        mService.mAm.mBooting = false;
+        if (!mService.mAm.mBooted) {
+            mService.mAm.mBooted = true;
             enableScreen = true;
         }
         if (booting || enableScreen) {
-            mService.postFinishBooting(booting, enableScreen);
+            mService.mAm.postFinishBooting(booting, enableScreen);
         }
         return booting;
     }
@@ -2050,7 +2049,7 @@
 
         if (allResumedActivitiesIdle()) {
             if (r != null) {
-                mService.scheduleAppGcsLocked();
+                mService.mAm.scheduleAppGcsLocked();
             }
 
             if (mLaunchingActivity.isHeld()) {
@@ -2107,12 +2106,12 @@
             // Complete user switch
             if (startingUsers != null) {
                 for (int i = 0; i < startingUsers.size(); i++) {
-                    mService.mUserController.finishUserSwitch(startingUsers.get(i));
+                    mService.mAm.mUserController.finishUserSwitch(startingUsers.get(i));
                 }
             }
         }
 
-        mService.trimApplications();
+        mService.mAm.trimApplications();
         //dump();
         //mWindowManager.dump();
 
@@ -2206,10 +2205,10 @@
         // Now set this one as the previous process, only if that really
         // makes sense to.
         if (r.app != null && fgApp != null && r.app != fgApp
-                && r.lastVisibleTime > mService.mPreviousProcessVisibleTime
-                && r.app != mService.mHomeProcess) {
-            mService.mPreviousProcess = r.app;
-            mService.mPreviousProcessVisibleTime = r.lastVisibleTime;
+                && r.lastVisibleTime > mService.mAm.mPreviousProcessVisibleTime
+                && r.app != mService.mAm.mHomeProcess) {
+            mService.mAm.mPreviousProcess = r.app;
+            mService.mAm.mPreviousProcessVisibleTime = r.lastVisibleTime;
         }
     }
 
@@ -2348,9 +2347,9 @@
         if (options == null || options.getLaunchBounds() == null) {
             return false;
         }
-        return (mService.mSupportsPictureInPicture
+        return (mService.mAm.mSupportsPictureInPicture
                 && options.getLaunchWindowingMode() == WINDOWING_MODE_PINNED)
-                || mService.mSupportsFreeformWindowManagement;
+                || mService.mAm.mSupportsFreeformWindowManagement;
     }
 
     LaunchParamsController getLaunchParamsController() {
@@ -3076,9 +3075,9 @@
         if (tr != null) {
             tr.removeTaskActivitiesLocked(pauseImmediately, reason);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
-            mService.mActivityTaskManager.getLockTaskController().clearLockedTask(tr);
+            mService.getLockTaskController().clearLockedTask(tr);
             if (tr.isPersistable) {
-                mService.mActivityTaskManager.notifyTaskPersisterLocked(null, true);
+                mService.notifyTaskPersisterLocked(null, true);
             }
             return true;
         }
@@ -3097,7 +3096,7 @@
         }
 
         // Find any running services associated with this app and stop if needed.
-        mService.mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent()));
+        mService.mAm.mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent()));
 
         if (!killProcess) {
             return;
@@ -3106,7 +3105,7 @@
         // Determine if the process(es) for this task should be killed.
         final String pkg = component.getPackageName();
         ArrayList<ProcessRecord> procsToKill = new ArrayList<>();
-        ArrayMap<String, SparseArray<ProcessRecord>> pmap = mService.mProcessNames.getMap();
+        ArrayMap<String, SparseArray<ProcessRecord>> pmap = mService.mAm.mProcessNames.getMap();
         for (int i = 0; i < pmap.size(); i++) {
 
             SparseArray<ProcessRecord> uids = pmap.valueAt(i);
@@ -3116,7 +3115,7 @@
                     // Don't kill process for a different user.
                     continue;
                 }
-                if (proc == mService.mHomeProcess) {
+                if (proc == mService.mAm.mHomeProcess) {
                     // Don't kill the home process along with tasks from the same package.
                     continue;
                 }
@@ -3259,21 +3258,21 @@
 
         // Ensure that we aren't trying to move into a multi-window stack without multi-window
         // support
-        if (inMultiWindowMode && !mService.mSupportsMultiWindow) {
+        if (inMultiWindowMode && !mService.mAm.mSupportsMultiWindow) {
             throw new IllegalArgumentException("Device doesn't support multi-window, can not"
                     + " reparent task=" + task + " to stack=" + stack);
         }
 
         // Ensure that we're not moving a task to a dynamic stack if device doesn't support
         // multi-display.
-        if (stack.mDisplayId != DEFAULT_DISPLAY && !mService.mSupportsMultiDisplay) {
+        if (stack.mDisplayId != DEFAULT_DISPLAY && !mService.mAm.mSupportsMultiDisplay) {
             throw new IllegalArgumentException("Device doesn't support multi-display, can not"
                     + " reparent task=" + task + " to stackId=" + stackId);
         }
 
         // Ensure that we aren't trying to move into a freeform stack without freeform support
         if (stack.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                && !mService.mSupportsFreeformWindowManagement) {
+                && !mService.mAm.mSupportsFreeformWindowManagement) {
             throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
                     + " task=" + task);
         }
@@ -3306,7 +3305,7 @@
             return false;
         }
 
-        if (!mService.mForceResizableActivities && !r.supportsPictureInPicture()) {
+        if (!mService.mAm.mForceResizableActivities && !r.supportsPictureInPicture()) {
             Slog.w(TAG,
                     "moveTopStackActivityToPinnedStackLocked: Picture-In-Picture not supported for "
                             + " r=" + r);
@@ -3387,7 +3386,7 @@
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        mService.mActivityTaskManager.getTaskChangeNotificationController().notifyActivityPinned(r);
+        mService.getTaskChangeNotificationController().notifyActivityPinned(r);
     }
 
     /** Move activity with its stack to front and make the stack focused. */
@@ -3494,7 +3493,7 @@
                     throw new IllegalStateException("Calling must be system uid");
                 }
                 mLaunchingActivity.release();
-                mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG);
+                mService.mAm.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG);
             }
         }
 
@@ -3519,7 +3518,7 @@
                 long timeRemaining = endTime - System.currentTimeMillis();
                 if (timeRemaining > 0) {
                     try {
-                        mService.wait(timeRemaining);
+                        mService.mAm.wait(timeRemaining);
                     } catch (InterruptedException e) {
                     }
                 } else {
@@ -3601,7 +3600,7 @@
     }
 
     void checkReadyForSleepLocked(boolean allowDelay) {
-        if (!mService.isSleepingOrShuttingDownLocked()) {
+        if (!mService.mAm.isSleepingOrShuttingDownLocked()) {
             // Do not care.
             return;
         }
@@ -3618,8 +3617,8 @@
         if (mGoingToSleep.isHeld()) {
             mGoingToSleep.release();
         }
-        if (mService.mShuttingDown) {
-            mService.notifyAll();
+        if (mService.mAm.mShuttingDown) {
+            mService.mAm.notifyAll();
         }
     }
 
@@ -3647,7 +3646,7 @@
 
         final ActivityStack stack = r.getStack();
         if (isFocusedStack(stack)) {
-            mService.updateUsageStats(r, true);
+            mService.mAm.updateUsageStats(r, true);
         }
         if (allResumedActivitiesComplete()) {
             ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
@@ -3674,7 +3673,7 @@
 
         r.mLaunchTaskBehind = false;
         mRecentTasks.add(task);
-        mService.mActivityTaskManager.getTaskChangeNotificationController().notifyTaskStackChanged();
+        mService.getTaskChangeNotificationController().notifyTaskStackChanged();
         r.setVisibility(false);
 
         // When launching tasks behind, update the last active time of the top task after the new
@@ -3874,7 +3873,7 @@
     /** Checks whether the userid is a profile of the current user. */
     boolean isCurrentProfileLocked(int userId) {
         if (userId == mCurrentUser) return true;
-        return mService.mUserController.isCurrentProfile(userId);
+        return mService.mAm.mUserController.isCurrentProfile(userId);
     }
 
     /**
@@ -3921,7 +3920,7 @@
                 final ActivityStack stack = s.getStack();
                 final boolean shouldSleepOrShutDown = stack != null
                         ? stack.shouldSleepOrShutDownActivities()
-                        : mService.isSleepingOrShuttingDownLocked();
+                        : mService.mAm.isSleepingOrShuttingDownLocked();
                 if (!waitingVisible || shouldSleepOrShutDown) {
                     if (!processPausingActivities && s.isState(PAUSING)) {
                         // Defer processing pausing activities in this iteration and reschedule
@@ -4004,7 +4003,7 @@
         pw.print(mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
 
         getKeyguardController().dump(pw, prefix);
-        mService.mActivityTaskManager.getLockTaskController().dump(pw, prefix);
+        mService.getLockTaskController().dump(pw, prefix);
     }
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
@@ -4275,7 +4274,7 @@
     }
 
     private void handleDisplayAdded(int displayId) {
-        synchronized (mService) {
+        synchronized (mService.mGlobalLock) {
             getActivityDisplayOrCreateLocked(displayId);
         }
     }
@@ -4338,7 +4337,7 @@
             throw new IllegalArgumentException("Can't remove the primary display.");
         }
 
-        synchronized (mService) {
+        synchronized (mService.mGlobalLock) {
             final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
             if (activityDisplay == null) {
                 return;
@@ -4353,7 +4352,7 @@
     }
 
     private void handleDisplayChanged(int displayId) {
-        synchronized (mService) {
+        synchronized (mService.mGlobalLock) {
             ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
             // TODO: The following code block should be moved into {@link ActivityDisplay}.
             if (activityDisplay != null) {
@@ -4362,7 +4361,7 @@
                     int displayState = activityDisplay.mDisplay.getState();
                     if (displayState == Display.STATE_OFF && activityDisplay.mOffToken == null) {
                         activityDisplay.mOffToken =
-                                mService.acquireSleepToken("Display-off", displayId);
+                                mService.mAm.acquireSleepToken("Display-off", displayId);
                     } else if (displayState == Display.STATE_ON
                             && activityDisplay.mOffToken != null) {
                         activityDisplay.mOffToken.release();
@@ -4395,7 +4394,7 @@
         if (display != null) {
             display.mAllSleepTokens.remove(token);
             if (display.mAllSleepTokens.isEmpty()) {
-                mService.updateSleepIfNeededLocked();
+                mService.mAm.updateSleepIfNeededLocked();
             }
         }
     }
@@ -4409,7 +4408,7 @@
         }
         display.mAllSleepTokens.clear();
 
-        mService.updateSleepIfNeededLocked();
+        mService.mAm.updateSleepIfNeededLocked();
     }
 
     private StackInfo getStackInfo(ActivityStack stack) {
@@ -4502,12 +4501,12 @@
             }
             // The task might have landed on a display different from requested.
             // TODO(multi-display): Find proper stack for the task on the default display.
-            mAtm.setTaskWindowingMode(task.taskId,
+            mService.setTaskWindowingMode(task.taskId,
                     WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
             if (preferredDisplayId != actualDisplayId) {
                 // Display a warning toast that we tried to put a non-resizeable task on a secondary
                 // display with config different from global config.
-                mService.mActivityTaskManager.getTaskChangeNotificationController()
+                mService.getTaskChangeNotificationController()
                         .notifyActivityLaunchOnSecondaryDisplayFailed();
                 return;
             }
@@ -4516,7 +4515,7 @@
         if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) {
             // Display a warning toast that we tried to put an app that doesn't support split-screen
             // in split-screen.
-            mService.mActivityTaskManager.getTaskChangeNotificationController().notifyActivityDismissingDockedStack();
+            mService.getTaskChangeNotificationController().notifyActivityDismissingDockedStack();
 
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
@@ -4536,7 +4535,7 @@
             final int reason = isSecondaryDisplayPreferred
                     ? FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY
                     : FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
-            mService.mActivityTaskManager.getTaskChangeNotificationController().notifyActivityForcedResizable(
+            mService.getTaskChangeNotificationController().notifyActivityForcedResizable(
                     task.taskId, reason, packageName);
         }
     }
@@ -4660,7 +4659,7 @@
         }
 
         void activityIdleInternal(ActivityRecord r, boolean processPausingActivities) {
-            synchronized (mService) {
+            synchronized (mService.mGlobalLock) {
                 activityIdleInternalLocked(r != null ? r.appToken : null, true /* fromTimeout */,
                         processPausingActivities, null);
             }
@@ -4670,7 +4669,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case REPORT_MULTI_WINDOW_MODE_CHANGED_MSG: {
-                    synchronized (mService) {
+                    synchronized (mService.mGlobalLock) {
                         for (int i = mMultiWindowModeChangedActivities.size() - 1; i >= 0; i--) {
                             final ActivityRecord r = mMultiWindowModeChangedActivities.remove(i);
                             r.updateMultiWindowMode();
@@ -4678,7 +4677,7 @@
                     }
                 } break;
                 case REPORT_PIP_MODE_CHANGED_MSG: {
-                    synchronized (mService) {
+                    synchronized (mService.mGlobalLock) {
                         for (int i = mPipModeChangedActivities.size() - 1; i >= 0; i--) {
                             final ActivityRecord r = mPipModeChangedActivities.remove(i);
                             r.updatePictureInPictureMode(mPipModeChangedTargetStackBounds,
@@ -4700,20 +4699,20 @@
                             false /* processPausingActivities */);
                 } break;
                 case RESUME_TOP_ACTIVITY_MSG: {
-                    synchronized (mService) {
+                    synchronized (mService.mGlobalLock) {
                         resumeFocusedStackTopActivityLocked();
                     }
                 } break;
                 case SLEEP_TIMEOUT_MSG: {
-                    synchronized (mService) {
-                        if (mService.isSleepingOrShuttingDownLocked()) {
+                    synchronized (mService.mGlobalLock) {
+                        if (mService.mAm.isSleepingOrShuttingDownLocked()) {
                             Slog.w(TAG, "Sleep timeout!  Sleeping now.");
                             checkReadyForSleepLocked(false /* allowDelay */);
                         }
                     }
                 } break;
                 case LAUNCH_TIMEOUT_MSG: {
-                    synchronized (mService) {
+                    synchronized (mService.mGlobalLock) {
                         if (mLaunchingActivity.isHeld()) {
                             Slog.w(TAG, "Launch timeout has expired, giving up wake lock!");
                             if (VALIDATE_WAKE_LOCK_CALLER
@@ -4734,7 +4733,7 @@
                     handleDisplayRemoved(msg.arg1);
                 } break;
                 case LAUNCH_TASK_BEHIND_COMPLETE: {
-                    synchronized (mService) {
+                    synchronized (mService.mGlobalLock) {
                         ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj);
                         if (r != null) {
                             handleLaunchTaskBehindCompleteLocked(r);
@@ -4822,21 +4821,21 @@
 
             // If the user must confirm credentials (e.g. when first launching a work app and the
             // Work Challenge is present) let startActivityInPackage handle the intercepting.
-            if (!mService.mUserController.shouldConfirmCredentials(task.userId)
+            if (!mService.mAm.mUserController.shouldConfirmCredentials(task.userId)
                     && task.getRootActivity() != null) {
                 final ActivityRecord targetActivity = task.getTopActivity();
 
                 sendPowerHintForLaunchStartIfNeeded(true /* forceSend */, targetActivity);
                 mActivityMetricsLogger.notifyActivityLaunching();
                 try {
-                    mService.mActivityTaskManager.moveTaskToFrontLocked(task.taskId, 0, options,
+                    mService.moveTaskToFrontLocked(task.taskId, 0, options,
                             true /* fromRecents */);
                 } finally {
                     mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
                             targetActivity);
                 }
 
-                mService.mActivityTaskManager.getActivityStartController().postStartActivityProcessingForLastStarter(
+                mService.getActivityStartController().postStartActivityProcessingForLastStarter(
                         task.getTopActivity(), ActivityManager.START_TASK_TO_FRONT,
                         task.getStack());
                 return ActivityManager.START_TASK_TO_FRONT;
@@ -4845,7 +4844,7 @@
             intent = task.intent;
             intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
             userId = task.userId;
-            return mService.mActivityTaskManager.getActivityStartController().startActivityInPackage(
+            return mService.getActivityStartController().startActivityInPackage(
                     task.mCallingUid, callingPid, callingUid, callingPackage, intent, null, null,
                     null, 0, 0, options, userId, task, "startActivityFromRecents",
                     false /* validateIncomingUser */);
@@ -4950,7 +4949,7 @@
 
         @Override
         public void release() {
-            synchronized (mService) {
+            synchronized (mService.mGlobalLock) {
                 removeSleepTokenLocked(this);
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityStartController.java b/services/core/java/com/android/server/am/ActivityStartController.java
index a7c3200..fa001df 100644
--- a/services/core/java/com/android/server/am/ActivityStartController.java
+++ b/services/core/java/com/android/server/am/ActivityStartController.java
@@ -65,7 +65,7 @@
 
     private static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 1;
 
-    private final ActivityManagerService mService;
+    private final ActivityTaskManagerService mService;
     private final ActivityStackSupervisor mSupervisor;
 
     /** Last home activity record we attempted to start. */
@@ -96,7 +96,7 @@
         public void handleMessage(Message msg) {
             switch(msg.what) {
                 case DO_PENDING_ACTIVITY_LAUNCHES_MSG:
-                    synchronized (mService) {
+                    synchronized (mService.mGlobalLock) {
                         doPendingActivityLaunches(true);
                     }
                     break;
@@ -111,22 +111,22 @@
      */
     private ActivityStarter mLastStarter;
 
-    ActivityStartController(ActivityManagerService service) {
+    ActivityStartController(ActivityTaskManagerService service) {
         this(service, service.mStackSupervisor,
                 new DefaultFactory(service, service.mStackSupervisor,
                     new ActivityStartInterceptor(service, service.mStackSupervisor)));
     }
 
     @VisibleForTesting
-    ActivityStartController(ActivityManagerService service, ActivityStackSupervisor supervisor,
+    ActivityStartController(ActivityTaskManagerService service, ActivityStackSupervisor supervisor,
             Factory factory) {
         mService = service;
         mSupervisor = supervisor;
-        mHandler = new StartHandler(mService.mHandlerThread.getLooper());
+        mHandler = new StartHandler(mService.mH.getLooper());
         mFactory = factory;
         mFactory.setController(this);
         mPendingRemoteAnimationRegistry = new PendingRemoteAnimationRegistry(service,
-                service.mHandler);
+                service.mH);
     }
 
     /**
@@ -182,7 +182,7 @@
      */
     void startSetupActivity() {
         // Only do this once per boot.
-        if (mService.getCheckedForSetup()) {
+        if (mService.mAm.getCheckedForSetup()) {
             return;
         }
 
@@ -190,10 +190,10 @@
         // version than the last one shown, and we are not running in
         // low-level factory test mode.
         final ContentResolver resolver = mService.mContext.getContentResolver();
-        if (mService.mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL &&
+        if (mService.mAm.mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL &&
                 Settings.Global.getInt(resolver,
                         Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
-            mService.setCheckedForSetup(true);
+            mService.mAm.setCheckedForSetup(true);
 
             // See if we should be showing the platform update setup UI.
             final Intent intent = new Intent(Intent.ACTION_UPGRADE_SETUP);
@@ -237,10 +237,10 @@
     int checkTargetUser(int targetUserId, boolean validateIncomingUser,
             int realCallingPid, int realCallingUid, String reason) {
         if (validateIncomingUser) {
-            return mService.mUserController.handleIncomingUser(realCallingPid, realCallingUid,
+            return mService.mAm.mUserController.handleIncomingUser(realCallingPid, realCallingUid,
                     targetUserId, false, ALLOW_FULL_ONLY, reason, null);
         } else {
-            mService.mUserController.ensureNotSpecialUser(targetUserId);
+            mService.mAm.mUserController.ensureNotSpecialUser(targetUserId);
             return targetUserId;
         }
     }
@@ -320,7 +320,7 @@
         }
         final long origId = Binder.clearCallingIdentity();
         try {
-            synchronized (mService) {
+            synchronized (mService.mGlobalLock) {
                 ActivityRecord[] outActivity = new ActivityRecord[1];
                 for (int i=0; i < intents.length; i++) {
                     Intent intent = intents[i];
@@ -343,7 +343,7 @@
                             null, userId, ActivityStarter.computeResolveFilterUid(
                                     callingUid, realCallingUid, UserHandle.USER_NULL));
                     // TODO: New, check if this is correct
-                    aInfo = mService.getActivityInfoForUser(aInfo, userId);
+                    aInfo = mService.mAm.getActivityInfoForUser(aInfo, userId);
 
                     if (aInfo != null &&
                             (aInfo.applicationInfo.privateFlags
diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
index ff97db8..171c0bb 100644
--- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
@@ -66,7 +66,7 @@
  */
 class ActivityStartInterceptor {
 
-    private final ActivityManagerService mService;
+    private final ActivityTaskManagerService mService;
     private final ActivityStackSupervisor mSupervisor;
     private final Context mServiceContext;
     private final UserController mUserController;
@@ -99,12 +99,13 @@
     TaskRecord mInTask;
     ActivityOptions mActivityOptions;
 
-    ActivityStartInterceptor(ActivityManagerService service, ActivityStackSupervisor supervisor) {
-        this(service, supervisor, service.mContext, service.mUserController);
+    ActivityStartInterceptor(
+            ActivityTaskManagerService service, ActivityStackSupervisor supervisor) {
+        this(service, supervisor, service.mContext, service.mAm.mUserController);
     }
 
     @VisibleForTesting
-    ActivityStartInterceptor(ActivityManagerService service, ActivityStackSupervisor supervisor,
+    ActivityStartInterceptor(ActivityTaskManagerService service, ActivityStackSupervisor supervisor,
             Context context, UserController userController) {
         mService = service;
         mSupervisor = supervisor;
@@ -127,7 +128,7 @@
 
     private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) {
         Bundle activityOptions = deferCrossProfileAppsAnimationIfNecessary();
-        final IIntentSender target = mService.getIntentSenderLocked(
+        final IIntentSender target = mService.mAm.getIntentSenderLocked(
                 INTENT_SENDER_ACTIVITY, mCallingPackage, callingUid, mUserId, null /*token*/,
                 null /*resultCode*/, 0 /*requestCode*/,
                 new Intent[] { mIntent }, new String[] { mResolvedType },
@@ -238,7 +239,7 @@
                 (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) {
             return false;
         }
-        final PackageManagerInternal pmi = mService.getPackageManagerInternalLocked();
+        final PackageManagerInternal pmi = mService.mAm.getPackageManagerInternalLocked();
         if (pmi == null) {
             return false;
         }
@@ -319,7 +320,7 @@
     private boolean interceptHarmfulAppIfNeeded() {
         CharSequence harmfulAppWarning;
         try {
-            harmfulAppWarning = mService.getPackageManager()
+            harmfulAppWarning = mService.mAm.getPackageManager()
                     .getHarmfulAppWarning(mAInfo.packageName, mUserId);
         } catch (RemoteException ex) {
             return false;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index d2becb1..f50bb37 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -132,7 +132,7 @@
     private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
     private static final int INVALID_LAUNCH_MODE = -1;
 
-    private final ActivityManagerService mService;
+    private final ActivityTaskManagerService mService;
     private final ActivityStackSupervisor mSupervisor;
     private final ActivityStartInterceptor mInterceptor;
     private final ActivityStartController mController;
@@ -233,14 +233,14 @@
         private final int MAX_STARTER_COUNT = 3;
 
         private ActivityStartController mController;
-        private ActivityManagerService mService;
+        private ActivityTaskManagerService mService;
         private ActivityStackSupervisor mSupervisor;
         private ActivityStartInterceptor mInterceptor;
 
         private SynchronizedPool<ActivityStarter> mStarterPool =
                 new SynchronizedPool<>(MAX_STARTER_COUNT);
 
-        DefaultFactory(ActivityManagerService service,
+        DefaultFactory(ActivityTaskManagerService service,
                 ActivityStackSupervisor supervisor, ActivityStartInterceptor interceptor) {
             mService = service;
             mSupervisor = supervisor;
@@ -410,7 +410,7 @@
         }
     }
 
-    ActivityStarter(ActivityStartController controller, ActivityManagerService service,
+    ActivityStarter(ActivityStartController controller, ActivityTaskManagerService service,
             ActivityStackSupervisor supervisor, ActivityStartInterceptor interceptor) {
         mController = controller;
         mService = service;
@@ -583,7 +583,7 @@
 
         ProcessRecord callerApp = null;
         if (caller != null) {
-            callerApp = mService.getRecordForAppLocked(caller);
+            callerApp = mService.mAm.getRecordForAppLocked(caller);
             if (callerApp != null) {
                 callingPid = callerApp.pid;
                 callingUid = callerApp.info.uid;
@@ -672,7 +672,7 @@
                     && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
                 try {
                     intent.addCategory(Intent.CATEGORY_VOICE);
-                    if (!mService.getPackageManager().activitySupportsIntent(
+                    if (!mService.mAm.getPackageManager().activitySupportsIntent(
                             intent.getComponent(), intent, resolvedType)) {
                         Slog.w(TAG,
                                 "Activity being started in current voice task does not support voice: "
@@ -690,7 +690,7 @@
             // If the caller is starting a new voice session, just make sure the target
             // is actually allowing it to run this way.
             try {
-                if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(),
+                if (!mService.mAm.getPackageManager().activitySupportsIntent(intent.getComponent(),
                         intent, resolvedType)) {
                     Slog.w(TAG,
                             "Activity being started in new voice task does not support: "
@@ -717,7 +717,7 @@
         boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
                 requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity,
                 inTask != null, callerApp, resultRecord, resultStack);
-        abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
+        abort |= !mService.mAm.mIntentFirewall.checkStartActivity(intent, callingUid,
                 callingPid, resolvedType, aInfo.applicationInfo);
 
         // Merge the two options bundles, while realCallerOptions takes precedence.
@@ -725,19 +725,19 @@
                 ? options.getOptions(intent, aInfo, callerApp, mSupervisor)
                 : null;
         if (allowPendingRemoteAnimationRegistryLookup) {
-            checkedOptions = mService.mActivityTaskManager.getActivityStartController()
+            checkedOptions = mService.getActivityStartController()
                     .getPendingRemoteAnimationRegistry()
                     .overrideOptionsIfNeeded(callingPackage, checkedOptions);
         }
-        if (mService.mController != null) {
+        if (mService.mAm.mController != null) {
             try {
                 // The Intent we give to the watcher has the extra data
                 // stripped off, since it can contain private information.
                 Intent watchIntent = intent.cloneFilter();
-                abort |= !mService.mController.activityStarting(watchIntent,
+                abort |= !mService.mAm.mController.activityStarting(watchIntent,
                         aInfo.applicationInfo.packageName);
             } catch (RemoteException e) {
-                mService.mController = null;
+                mService.mAm.mController = null;
             }
         }
 
@@ -770,10 +770,10 @@
         // If permissions need a review before any of the app components can run, we
         // launch the review activity and pass a pending intent to start the activity
         // we are to launching now after the review is completed.
-        if (mService.mPermissionReviewRequired && aInfo != null) {
-            if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+        if (mService.mAm.mPermissionReviewRequired && aInfo != null) {
+            if (mService.mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired(
                     aInfo.packageName, userId)) {
-                IIntentSender target = mService.getIntentSenderLocked(
+                IIntentSender target = mService.mAm.getIntentSenderLocked(
                         ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
                         callingUid, userId, null, null, 0, new Intent[]{intent},
                         new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
@@ -823,9 +823,9 @@
             aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
         }
 
-        ActivityRecord r = new ActivityRecord(mService.mActivityTaskManager, callerApp, callingPid,
+        ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid,
                 callingUid,
-                callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
+                callingPackage, intent, resolvedType, aInfo, mService.mAm.getGlobalConfiguration(),
                 resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
                 mSupervisor, checkedOptions, sourceRecord);
         if (outActivity != null) {
@@ -844,7 +844,7 @@
         // one, check whether app switches are allowed.
         if (voiceSession == null && (stack.getResumedActivity() == null
                 || stack.getResumedActivity().info.applicationInfo.uid != realCallingUid)) {
-            if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
+            if (!mService.mAm.checkAppSwitchAllowedLocked(callingPid, callingUid,
                     realCallingPid, realCallingUid, "Activity start")) {
                 mController.addPendingActivityLaunch(new PendingActivityLaunch(r,
                         sourceRecord, startFlags, stack, callerApp));
@@ -853,15 +853,15 @@
             }
         }
 
-        if (mService.mDidAppSwitch) {
+        if (mService.mAm.mDidAppSwitch) {
             // This is the second allowed switch since we stopped switches,
             // so now just generally allow switches.  Use case: user presses
             // home (switches disabled, switch to home, mDidAppSwitch now true);
             // user taps a home icon (coming from home so allowed, we hit here
             // and now allow anyone to switch again).
-            mService.mAppSwitchesAllowedTime = 0;
+            mService.mAm.mAppSwitchesAllowedTime = 0;
         } else {
-            mService.mDidAppSwitch = true;
+            mService.mAm.mDidAppSwitch = true;
         }
 
         mController.doPendingActivityLaunches(false);
@@ -879,7 +879,7 @@
             String resolvedType, int userId) {
         if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) {
             // request phase two resolution
-            mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo(
+            mService.mAm.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo(
                     auxiliaryResponse, originalIntent, resolvedType, callingPackage,
                     verificationBundle, userId);
         }
@@ -930,7 +930,7 @@
             // anyone interested in this piece of information.
             switch (startedActivityStack.getWindowingMode()) {
                 case WINDOWING_MODE_PINNED:
-                    mService.mActivityTaskManager.getTaskChangeNotificationController().notifyPinnedActivityRestartAttempt(
+                    mService.getTaskChangeNotificationController().notifyPinnedActivityRestartAttempt(
                             clearedTask);
                     break;
                 case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
@@ -979,7 +979,7 @@
                 && !(Intent.ACTION_VIEW.equals(intent.getAction()) && intent.getData() == null)
                 && !Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE.equals(intent.getAction())
                 && !Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE.equals(intent.getAction())
-                && mService.getPackageManagerInternalLocked()
+                && mService.mAm.getPackageManagerInternalLocked()
                         .isInstantAppInstallerComponent(intent.getComponent())) {
             // intercept intents targeted directly to the ephemeral installer the
             // ephemeral installer should never be started with a raw Intent; instead
@@ -1021,10 +1021,10 @@
         // Collect information about the target of the Intent.
         ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
 
-        synchronized (mService) {
+        synchronized (mService.mGlobalLock) {
             final ActivityStack stack = mSupervisor.mFocusedStack;
             stack.mConfigWillChange = globalConfig != null
-                    && mService.getGlobalConfiguration().diff(globalConfig) != 0;
+                    && mService.mAm.getGlobalConfiguration().diff(globalConfig) != 0;
             if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                     "Starting activity when config will change = " + stack.mConfigWillChange);
 
@@ -1033,16 +1033,16 @@
             if (aInfo != null &&
                     (aInfo.applicationInfo.privateFlags
                             & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0 &&
-                    mService.mHasHeavyWeightFeature) {
+                    mService.mAm.mHasHeavyWeightFeature) {
                 // This may be a heavy-weight process!  Check to see if we already
                 // have another, different heavy-weight process running.
                 if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) {
-                    final ProcessRecord heavy = mService.mHeavyWeightProcess;
+                    final ProcessRecord heavy = mService.mAm.mHeavyWeightProcess;
                     if (heavy != null && (heavy.info.uid != aInfo.applicationInfo.uid
                             || !heavy.processName.equals(aInfo.processName))) {
                         int appCallingUid = callingUid;
                         if (caller != null) {
-                            ProcessRecord callerApp = mService.getRecordForAppLocked(caller);
+                            ProcessRecord callerApp = mService.mAm.getRecordForAppLocked(caller);
                             if (callerApp != null) {
                                 appCallingUid = callerApp.info.uid;
                             } else {
@@ -1054,7 +1054,7 @@
                             }
                         }
 
-                        IIntentSender target = mService.getIntentSenderLocked(
+                        IIntentSender target = mService.mAm.getIntentSenderLocked(
                                 ActivityManager.INTENT_SENDER_ACTIVITY, "android",
                                 appCallingUid, userId, null, null, 0, new Intent[] { intent },
                                 new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT
@@ -1090,7 +1090,7 @@
                                         callingUid, realCallingUid, mRequest.filterCallingUid));
                         aInfo = rInfo != null ? rInfo.activityInfo : null;
                         if (aInfo != null) {
-                            aInfo = mService.getActivityInfoForUser(aInfo, userId);
+                            aInfo = mService.mAm.getActivityInfoForUser(aInfo, userId);
                         }
                     }
                 }
@@ -1110,12 +1110,12 @@
                 // do so now.  This allows a clean switch, as we are waiting
                 // for the current activity to pause (so we will not destroy
                 // it), and have not yet started the next activity.
-                mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
+                mService.mAm.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
                         "updateConfiguration()");
                 stack.mConfigWillChange = false;
                 if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                         "Updating to new configuration after starting activity.");
-                mService.updateConfigurationLocked(globalConfig, null, false);
+                mService.mAm.updateConfigurationLocked(globalConfig, null, false);
             }
 
             if (outResult != null) {
@@ -1128,7 +1128,7 @@
                         mSupervisor.mWaitingActivityLaunched.add(outResult);
                         do {
                             try {
-                                mService.wait();
+                                mService.mGlobalLock.wait();
                             } catch (InterruptedException e) {
                             }
                         } while (outResult.result != START_TASK_TO_FRONT
@@ -1159,7 +1159,7 @@
                             // Note: the timeout variable is not currently not ever set.
                             do {
                                 try {
-                                    mService.wait();
+                                    mService.mGlobalLock.wait();
                                 } catch (InterruptedException e) {
                                 }
                             } while (!outResult.timeout && outResult.who == null);
@@ -1257,7 +1257,7 @@
             // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
             // still needs to be a lock task mode violation since the task gets cleared out and
             // the device would otherwise leave the locked task.
-            if (mService.mActivityTaskManager.getLockTaskController().isLockTaskModeViolation(reusedActivity.getTask(),
+            if (mService.getLockTaskController().isLockTaskModeViolation(reusedActivity.getTask(),
                     (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                             == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
                 Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
@@ -1423,9 +1423,9 @@
             return result;
         }
 
-        mService.grantUriPermissionFromIntentLocked(mCallingUid, mStartActivity.packageName,
+        mService.mAm.grantUriPermissionFromIntentLocked(mCallingUid, mStartActivity.packageName,
                 mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId);
-        mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent,
+        mService.mAm.grantEphemeralAccessLocked(mStartActivity.userId, mIntent,
                 mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid));
         if (newTask) {
             EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.userId,
@@ -1821,7 +1821,7 @@
         }
 
         // Get the virtual display id from ActivityManagerService.
-        int displayId = mService.mVr2dDisplayId;
+        int displayId = mService.mAm.mVr2dDisplayId;
         if (displayId != INVALID_DISPLAY) {
             if (DEBUG_STACK) {
                 Slog.d(TAG, "getSourceDisplayId :" + displayId);
@@ -2070,7 +2070,7 @@
             mStartActivity.setTaskToAffiliateWith(taskToAffiliate);
         }
 
-        if (mService.mActivityTaskManager.getLockTaskController().isLockTaskModeViolation(mStartActivity.getTask())) {
+        if (mService.getLockTaskController().isLockTaskModeViolation(mStartActivity.getTask())) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
@@ -2093,7 +2093,7 @@
     }
 
     private int setTaskFromSourceRecord() {
-        if (mService.mActivityTaskManager.getLockTaskController().isLockTaskModeViolation(mSourceRecord.getTask())) {
+        if (mService.getLockTaskController().isLockTaskModeViolation(mSourceRecord.getTask())) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
@@ -2115,13 +2115,13 @@
             // be not suitable. Let's check other displays.
             if (mTargetStack == null && targetDisplayId != sourceStack.mDisplayId) {
                 // Can't use target display, lets find a stack on the source display.
-                mTargetStack = mService.mStackSupervisor.getValidLaunchStackOnDisplay(
+                mTargetStack = mSupervisor.getValidLaunchStackOnDisplay(
                         sourceStack.mDisplayId, mStartActivity);
             }
             if (mTargetStack == null) {
                 // There are no suitable stacks on the target and source display(s). Look on all
                 // displays.
-                mTargetStack = mService.mStackSupervisor.getNextValidLaunchStackLocked(
+                mTargetStack = mSupervisor.getNextValidLaunchStackLocked(
                         mStartActivity, -1 /* currentFocus */);
             }
         }
@@ -2187,7 +2187,7 @@
     private int setTaskFromInTask() {
         // The caller is asking that the new activity be started in an explicit
         // task it has provided to us.
-        if (mService.mActivityTaskManager.getLockTaskController().isLockTaskModeViolation(mInTask)) {
+        if (mService.getLockTaskController().isLockTaskModeViolation(mInTask)) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
@@ -2252,7 +2252,7 @@
 
         final ActivityStack stack = task.getStack();
         if (stack != null && stack.resizeStackWithLaunchBounds()) {
-            mService.mActivityTaskManager.resizeStack(
+            mService.resizeStack(
                     stack.mStackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
         } else {
             task.updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
index b27c480..90097fd 100644
--- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
@@ -128,6 +128,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -243,6 +244,16 @@
         }
     }
 
+    /** Current sequencing integer of the configuration, for skipping old configurations. */
+    int mConfigurationSeq;
+
+    /**
+     * Temp object used when global and/or display override configuration is updated. It is also
+     * sent to outer world instead of {@link #getGlobalConfiguration} because we don't trust
+     * anyone...
+     */
+    Configuration mTempConfig = new Configuration();
+
     ActivityTaskManagerService(Context context) {
         mContext = context;
         mLifecycleManager = new ClientLifecycleManager();
@@ -259,17 +270,29 @@
         mAm = am;
         mGlobalLock = mAm;
         mH = new H(mAm.mHandlerThread.getLooper());
-        mStackSupervisor = mAm.mStackSupervisor;
+
+        mTempConfig.setToDefaults();
+        mTempConfig.setLocales(LocaleList.getDefault());
+        mConfigurationSeq = mTempConfig.seq = 1;
+        mStackSupervisor = createStackSupervisor();
+        mStackSupervisor.onConfigurationChanged(mTempConfig);
+
         mTaskChangeNotificationController =
                 new TaskChangeNotificationController(mAm, mStackSupervisor, mH);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mH);
-        mActivityStartController = new ActivityStartController(mAm);
+        mActivityStartController = new ActivityStartController(this);
         mRecentTasks = createRecentTasks();
         mStackSupervisor.setRecentTasks(mRecentTasks);
         mVrController = new VrController(mAm);
         mKeyguardController = mStackSupervisor.getKeyguardController();
     }
 
+    protected ActivityStackSupervisor createStackSupervisor() {
+        final ActivityStackSupervisor supervisor = new ActivityStackSupervisor(this, mH.getLooper());
+        supervisor.initialize();
+        return supervisor;
+    }
+
     void setWindowManager(WindowManagerService wm) {
         mWindowManager = wm;
         mLockTaskController.setWindowManager(wm);
diff --git a/services/core/java/com/android/server/am/PendingRemoteAnimationRegistry.java b/services/core/java/com/android/server/am/PendingRemoteAnimationRegistry.java
index 77713f5..877d896 100644
--- a/services/core/java/com/android/server/am/PendingRemoteAnimationRegistry.java
+++ b/services/core/java/com/android/server/am/PendingRemoteAnimationRegistry.java
@@ -33,9 +33,9 @@
 
     private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
     private final Handler mHandler;
-    private final ActivityManagerService mService;
+    private final ActivityTaskManagerService mService;
 
-    PendingRemoteAnimationRegistry(ActivityManagerService service, Handler handler) {
+    PendingRemoteAnimationRegistry(ActivityTaskManagerService service, Handler handler) {
         mService = service;
         mHandler = handler;
     }
@@ -74,7 +74,7 @@
             this.packageName = packageName;
             this.adapter = adapter;
             mHandler.postDelayed(() -> {
-                synchronized (mService) {
+                synchronized (mService.mGlobalLock) {
                     final Entry entry = mEntries.get(packageName);
                     if (entry == this) {
                         mEntries.remove(packageName);
diff --git a/services/core/java/com/android/server/am/SafeActivityOptions.java b/services/core/java/com/android/server/am/SafeActivityOptions.java
index 837432d..778990b 100644
--- a/services/core/java/com/android/server/am/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/am/SafeActivityOptions.java
@@ -192,7 +192,7 @@
         // component or has the START_TASKS_FROM_RECENTS permission
         if (options.getLaunchTaskId() != INVALID_TASK_ID
                 && !supervisor.mRecentTasks.isCallerRecents(callingUid)) {
-            final int startInTaskPerm = supervisor.mService.checkPermission(
+            final int startInTaskPerm = supervisor.mService.mAm.checkPermission(
                     START_TASKS_FROM_RECENTS, callingPid, callingUid);
             if (startInTaskPerm == PERMISSION_DENIED) {
                 final String msg = "Permission Denial: starting " + getIntentString(intent)
@@ -219,7 +219,7 @@
         // Check if someone tries to launch an unwhitelisted activity into LockTask mode.
         final boolean lockTaskMode = options.getLockTaskMode();
         if (aInfo != null && lockTaskMode
-                && !supervisor.mService.mActivityTaskManager.getLockTaskController().isPackageWhitelisted(
+                && !supervisor.mService.getLockTaskController().isPackageWhitelisted(
                         UserHandle.getUserId(callingUid), aInfo.packageName)) {
             final String msg = "Permission Denial: starting " + getIntentString(intent)
                     + " from " + callerApp + " (pid=" + callingPid
@@ -230,7 +230,7 @@
 
         // Check permission for remote animations
         final RemoteAnimationAdapter adapter = options.getRemoteAnimationAdapter();
-        if (adapter != null && supervisor.mService.checkPermission(
+        if (adapter != null && supervisor.mService.mAm.checkPermission(
                 CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid)
                         != PERMISSION_GRANTED) {
             final String msg = "Permission Denial: starting " + getIntentString(intent)
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 7896e2d..efb80be 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -52,7 +52,8 @@
     // Delay in notifying task stack change listeners (in millis)
     private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
 
-    private final ActivityManagerService mService;
+    // Global lock used by the service the instantiate objects of this class.
+    private final Object mServiceLock;
     private final ActivityStackSupervisor mStackSupervisor;
     private final Handler mHandler;
 
@@ -149,7 +150,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case LOG_STACK_STATE_MSG: {
-                    synchronized (mService) {
+                    synchronized (mServiceLock) {
                         mStackSupervisor.logStackState();
                     }
                     break;
@@ -209,15 +210,15 @@
         }
     }
 
-    public TaskChangeNotificationController(ActivityManagerService service,
+    public TaskChangeNotificationController(Object serviceLock,
             ActivityStackSupervisor stackSupervisor, Handler handler) {
-        mService = service;
+        mServiceLock = serviceLock;
         mStackSupervisor = stackSupervisor;
         mHandler = new MainHandler(handler.getLooper());
     }
 
     public void registerTaskStackListener(ITaskStackListener listener) {
-        synchronized (mService) {
+        synchronized (mServiceLock) {
             if (listener != null) {
                 if (Binder.getCallingPid() == android.os.Process.myPid()) {
                     if (!mLocalTaskStackListeners.contains(listener)) {
@@ -231,7 +232,7 @@
     }
 
     public void unregisterTaskStackListener(ITaskStackListener listener) {
-        synchronized (mService) {
+        synchronized (mServiceLock) {
             if (listener != null) {
                 if (Binder.getCallingPid() == android.os.Process.myPid()) {
                     mLocalTaskStackListeners.remove(listener);
@@ -243,7 +244,7 @@
     }
 
     private void forAllRemoteListeners(TaskStackConsumer callback, Message message) {
-        synchronized (mService) {
+        synchronized (mServiceLock) {
             for (int i = mRemoteTaskStackListeners.beginBroadcast() - 1; i >= 0; i--) {
                 try {
                     // Make a one-way callback to the listener
@@ -257,7 +258,7 @@
     }
 
     private void forAllLocalListeners(TaskStackConsumer callback, Message message) {
-        synchronized (mService) {
+        synchronized (mServiceLock) {
             for (int i = mLocalTaskStackListeners.size() - 1; i >= 0; i--) {
                 try {
                     callback.accept(mLocalTaskStackListeners.get(i), message);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index d984016..6eac4bc 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -2469,7 +2469,7 @@
                 }
             }
 
-            final TaskRecord task = create(stackSupervisor.mService.mActivityTaskManager,
+            final TaskRecord task = create(stackSupervisor.mService,
                     taskId, intent, affinityIntent,
                     affinity, rootAffinity, realActivity, origActivity, rootHasReset,
                     autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription,
diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/common/AuthenticationClient.java
similarity index 72%
rename from services/core/java/com/android/server/fingerprint/AuthenticationClient.java
rename to services/core/java/com/android/server/biometrics/common/AuthenticationClient.java
index afd1a94..1ed2847 100644
--- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/common/AuthenticationClient.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2016 The Android Open Source Project
+/*
+ * 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.
@@ -11,25 +11,22 @@
  * 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.
+ * limitations under the License
  */
 
-package com.android.server.fingerprint;
+package com.android.server.biometrics.common;
 
 import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.IBiometricPromptReceiver;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.statusbar.IStatusBarService;
 
 /**
@@ -51,6 +48,7 @@
     private Bundle mBundle;
     private IStatusBarService mStatusBarService;
     private boolean mInLockout;
+    // TODO: BiometricManager, after other biometric modalities are introduced.
     private final FingerprintManager mFingerprintManager;
     protected boolean mDialogDismissed;
 
@@ -62,12 +60,12 @@
                 try {
                     mDialogReceiverFromClient.onDialogDismissed(reason);
                     if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
-                        onError(FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED,
+                        onError(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
                                 0 /* vendorCode */);
                     }
                     mDialogDismissed = true;
                 } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to notify dialog dismissed", e);
+                    Slog.e(getLogTag(), "Unable to notify dialog dismissed", e);
                 }
                 stop(true /* initiatedByClient */);
             }
@@ -85,11 +83,13 @@
      */
     public abstract void onStop();
 
-    public AuthenticationClient(Context context, long halDeviceId, IBinder token,
-            IFingerprintServiceReceiver receiver, int targetUserId, int groupId, long opId,
+    public AuthenticationClient(Context context, Metrics metrics,
+            BiometricService.DaemonWrapper daemon, long halDeviceId, IBinder token,
+            BiometricService.ServiceListener listener, int targetUserId, int groupId, long opId,
             boolean restricted, String owner, Bundle bundle,
             IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService) {
-        super(context, halDeviceId, token, receiver, targetUserId, groupId, restricted, owner);
+        super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId,
+                restricted, owner);
         mOpId = opId;
         mBundle = bundle;
         mDialogReceiverFromClient = dialogReceiver;
@@ -112,17 +112,17 @@
         // If the dialog is showing, the client doesn't need to receive onAcquired messages.
         if (mBundle != null) {
             try {
-                if (acquiredInfo != FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
+                if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
                     mStatusBarService.onFingerprintHelp(
                             mFingerprintManager.getAcquiredString(acquiredInfo, vendorCode));
                 }
                 return false; // acquisition continues
             } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when sending acquired message", e);
+                Slog.e(getLogTag(), "Remote exception when sending acquired message", e);
                 return true; // client failed
             } finally {
                 // Good scans will keep the device awake
-                if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
+                if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
                     notifyUserActivity();
                 }
             }
@@ -145,7 +145,7 @@
                 mStatusBarService.onFingerprintError(
                         mFingerprintManager.getErrorString(error, vendorCode));
             } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when sending error", e);
+                Slog.e(getLogTag(), "Remote exception when sending error", e);
             }
         }
         return super.onError(error, vendorCode);
@@ -166,36 +166,35 @@
                             com.android.internal.R.string.fingerprint_not_recognized));
                 }
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to notify Authenticated:", e);
+                Slog.e(getLogTag(), "Failed to notify Authenticated:", e);
             }
         }
 
-        IFingerprintServiceReceiver receiver = getReceiver();
-        if (receiver != null) {
+        final BiometricService.ServiceListener listener = getListener();
+        if (listener != null) {
             try {
-                MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_AUTH,
-                        authenticated);
+                mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
                 if (!authenticated) {
-                    receiver.onAuthenticationFailed(getHalDeviceId());
+                    listener.onAuthenticationFailed(getHalDeviceId());
                 } else {
                     if (DEBUG) {
-                        Slog.v(TAG, "onAuthenticated(owner=" + getOwnerString()
+                        Slog.v(getLogTag(), "onAuthenticated(owner=" + getOwnerString()
                                 + ", id=" + fingerId + ", gp=" + groupId + ")");
                     }
                     Fingerprint fp = !getIsRestricted()
                             ? new Fingerprint("" /* TODO */, groupId, fingerId, getHalDeviceId())
                             : null;
-                    receiver.onAuthenticationSucceeded(getHalDeviceId(), fp, getTargetUserId());
+                    listener.onAuthenticationSucceeded(getHalDeviceId(), fp, getTargetUserId());
                 }
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to notify Authenticated:", e);
+                Slog.w(getLogTag(), "Failed to notify Authenticated:", e);
                 result = true; // client failed
             }
         } else {
             result = true; // client not listening
         }
         if (!authenticated) {
-            if (receiver != null) {
+            if (listener != null) {
                 vibrateError();
             }
             // allow system-defined limit of number of attempts before giving up
@@ -203,17 +202,17 @@
             if (lockoutMode != LOCKOUT_NONE) {
                 try {
                     mInLockout = true;
-                    Slog.w(TAG, "Forcing lockout (fp driver code should do this!), mode(" +
+                    Slog.w(getLogTag(), "Forcing lockout (fp driver code should do this!), mode(" +
                             lockoutMode + ")");
                     stop(false);
                     int errorCode = lockoutMode == LOCKOUT_TIMED ?
-                            FingerprintManager.FINGERPRINT_ERROR_LOCKOUT :
-                            FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+                            BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
+                            BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
 
                     // TODO: if the dialog is showing, this error should be delayed. On a similar
                     // note, AuthenticationClient should override onError and delay all other errors
                     // as well, if the dialog is showing
-                    receiver.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
+                    listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
 
                     // Send the lockout message to the system dialog
                     if (mBundle != null) {
@@ -221,12 +220,12 @@
                                 mFingerprintManager.getErrorString(errorCode, 0 /* vendorCode */));
                     }
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to notify lockout:", e);
+                    Slog.w(getLogTag(), "Failed to notify lockout:", e);
                 }
             }
             result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
         } else {
-            if (receiver != null) {
+            if (listener != null) {
                 vibrateSuccess();
             }
             result |= true; // we have a valid fingerprint, done
@@ -241,32 +240,27 @@
      */
     @Override
     public int start() {
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "start authentication: no fingerprint HAL!");
-            return ERROR_ESRCH;
-        }
         onStart();
         try {
-            final int result = daemon.authenticate(mOpId, getGroupId());
+            final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());
             if (result != 0) {
-                Slog.w(TAG, "startAuthentication failed, result=" + result);
-                MetricsLogger.histogram(getContext(), "fingeprintd_auth_start_error", result);
-                onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+                Slog.w(getLogTag(), "startAuthentication failed, result=" + result);
+                mMetricsLogger.histogram(mMetrics.tagAuthStartError(), result);
+                onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
                 return result;
             }
-            if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating...");
+            if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
 
             // If authenticating with system dialog, show the dialog
             if (mBundle != null) {
                 try {
                     mStatusBarService.showFingerprintDialog(mBundle, mDialogReceiver);
                 } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to show fingerprint dialog", e);
+                    Slog.e(getLogTag(), "Unable to show fingerprint dialog", e);
                 }
             }
         } catch (RemoteException e) {
-            Slog.e(TAG, "startAuthentication failed", e);
+            Slog.e(getLogTag(), "startAuthentication failed", e);
             return ERROR_ESRCH;
         }
         return 0; // success
@@ -275,25 +269,21 @@
     @Override
     public int stop(boolean initiatedByClient) {
         if (mAlreadyCancelled) {
-            Slog.w(TAG, "stopAuthentication: already cancelled!");
+            Slog.w(getLogTag(), "stopAuthentication: already cancelled!");
             return 0;
         }
 
         onStop();
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "stopAuthentication: no fingerprint HAL!");
-            return ERROR_ESRCH;
-        }
+
         try {
-            final int result = daemon.cancel();
+            final int result = getDaemonWrapper().cancel();
             if (result != 0) {
-                Slog.w(TAG, "stopAuthentication failed, result=" + result);
+                Slog.w(getLogTag(), "stopAuthentication failed, result=" + result);
                 return result;
             }
-            if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer authenticating");
+            if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is no longer authenticating");
         } catch (RemoteException e) {
-            Slog.e(TAG, "stopAuthentication failed", e);
+            Slog.e(getLogTag(), "stopAuthentication failed", e);
             return ERROR_ESRCH;
         } finally {
             // If the user already cancelled authentication (via some interaction with the
@@ -304,29 +294,30 @@
                 try {
                     mStatusBarService.hideFingerprintDialog();
                 } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to hide fingerprint dialog", e);
+                    Slog.e(getLogTag(), "Unable to hide fingerprint dialog", e);
                 }
             }
         }
+
         mAlreadyCancelled = true;
         return 0; // success
     }
 
     @Override
     public boolean onEnrollResult(int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "onEnrollResult() called for authenticate!");
+        if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for authenticate!");
         return true; // Invalid for Authenticate
     }
 
     @Override
     public boolean onRemoved(int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "onRemoved() called for authenticate!");
+        if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for authenticate!");
         return true; // Invalid for Authenticate
     }
 
     @Override
     public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for authenticate!");
+        if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for authenticate!");
         return true; // Invalid for Authenticate
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/common/BiometricService.java b/services/core/java/com/android/server/biometrics/common/BiometricService.java
new file mode 100644
index 0000000..7d80955
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/common/BiometricService.java
@@ -0,0 +1,973 @@
+/*
+ * 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.biometrics.common;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.IActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.SynchronousUserSwitchObserver;
+import android.app.TaskStackListener;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IHwBinder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.security.KeyStore;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.server.SystemService;
+import com.android.server.biometrics.fingerprint.FingerprintService;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Abstract base class containing all of the business logic for biometric services, e.g.
+ * Fingerprint, Face, Iris.
+ *
+ * @hide
+ */
+public abstract class BiometricService extends SystemService implements IHwBinder.DeathRecipient {
+
+    protected static final boolean DEBUG = true;
+
+    private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user";
+    private static final int MSG_USER_SWITCHING = 10;
+    private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30 * 1000;
+    private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms
+
+    private final Context mContext;
+    private final String mKeyguardPackage;
+    private final AppOpsManager mAppOps;
+    private final SparseBooleanArray mTimedLockoutCleared;
+    private final SparseIntArray mFailedAttempts;
+    private final IActivityTaskManager mActivityTaskManager;
+    private final AlarmManager mAlarmManager;
+    private final PowerManager mPowerManager;
+    private final UserManager mUserManager;
+    private final MetricsLogger mMetricsLogger;
+    private final BiometricTaskStackListener mTaskStackListener = new BiometricTaskStackListener();
+    private final ResetClientStateRunnable mResetClientState = new ResetClientStateRunnable();
+    private final LockoutReceiver mLockoutReceiver = new LockoutReceiver();
+
+    protected final ResetFailedAttemptsForUserRunnablle mResetFailedAttemptsForCurrentUserRunnable =
+            new ResetFailedAttemptsForUserRunnablle();
+    protected final H mHandler = new H();
+
+    private ClientMonitor mCurrentClient;
+    private ClientMonitor mPendingClient;
+    private PerformanceStats mPerformanceStats;
+    protected int mCurrentUserId = UserHandle.USER_NULL;
+    // Normal authentications are tracked by mPerformanceMap.
+    protected HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>();
+    // Transactions that make use of CryptoObjects are tracked by mCryptoPerformaceMap.
+    protected HashMap<Integer, PerformanceStats> mCryptoPerformanceMap = new HashMap<>();
+
+    protected class PerformanceStats {
+        public int accept; // number of accepted biometrics
+        public int reject; // number of rejected biometrics
+        public int acquire; // total number of acquisitions. Should be >= accept+reject due to poor
+        // image acquisition in some cases (too fast, too slow, dirty sensor, etc.)
+        public int lockout; // total number of lockouts
+        public int permanentLockout; // total number of permanent lockouts
+    }
+
+    /**
+     * @return the log tag.
+     */
+    protected abstract String getTag();
+
+    /**
+     * @return the number of failed attempts after which the user will be temporarily locked out
+     *         from using the biometric. A strong auth (pin/pattern/pass) clears this counter.
+     */
+    protected abstract int getFailedAttemptsLockoutTimed();
+
+    /**
+     * @return the number of failed attempts after which the user will be permanently locked out
+     *         from using the biometric. A strong auth (pin/pattern/pass) clears this counter.
+     */
+    protected abstract int getFailedAttemptsLockoutPermanent();
+
+    /**
+     * @return the metrics constants for a biometric implementation.
+     */
+    protected abstract Metrics getMetrics();
+
+    /**
+     * @param userId
+     * @return true if the enrollment limit has been reached.
+     */
+    protected abstract boolean hasReachedEnrollmentLimit(int userId);
+
+    /**
+     * Notifies the HAL that the user has changed.
+     * @param userId
+     * @param clientPackage
+     */
+    protected abstract void updateActiveGroup(int userId, String clientPackage);
+
+    /**
+     * @return The protected intent to reset lockout for a specific biometric.
+     */
+    protected abstract String getLockoutResetIntent();
+
+    /**
+     * @return The permission the sender is required to have in order for the lockout reset intent
+     *         to be received by the BiometricService implementation.
+     */
+    protected abstract String getLockoutBroadcastPermission();
+
+    /**
+     * @return The HAL ID.
+     */
+    protected abstract long getHalDeviceId();
+
+    /**
+     * This method is called when the user switches. Implementations should probably notify the
+     * HAL.
+     * @param userId
+     */
+    protected abstract void handleUserSwitching(int userId);
+
+    /**
+     * @param userId
+     * @return Returns true if the user has any enrolled biometrics.
+     */
+    protected abstract boolean hasEnrolledBiometrics(int userId);
+
+    /**
+     * @return Returns the MANAGE_* permission string, which is required for enrollment, removal
+     * etc.
+     */
+    protected abstract String getManageBiometricPermission();
+
+    /**
+     * Checks if the caller has permission to use the biometric service - throws a SecurityException
+     * if not.
+     */
+    protected abstract void checkUseBiometricPermission();
+
+    /**
+     * @return Returns one of the {@link AppOpsManager} constants which pertains to the specific
+     *         biometric service.
+     */
+    protected abstract int getAppOp();
+
+    /**
+     * Notifies clients that lockout has been reset.
+     */
+    protected abstract void notifyLockoutResetMonitors();
+
+    /**
+     * Notifies clients of any change in the biometric state (active / idle). This is mainly for
+     * Fingerprint navigation gestures.
+     * @param isActive
+     */
+    protected void notifyClientActiveCallbacks(boolean isActive) {}
+
+    protected class AuthenticationClientImpl extends AuthenticationClient {
+
+        public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
+                IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId,
+                boolean restricted, String owner, Bundle bundle,
+                IBiometricPromptReceiver dialogReceiver,
+                IStatusBarService statusBarService) {
+            super(context, getMetrics(), daemon, halDeviceId, token, listener,
+                    targetUserId, groupId, opId, restricted, owner, bundle, dialogReceiver,
+                    statusBarService);
+        }
+
+        @Override
+        public void onStart() {
+            try {
+                mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+            } catch (RemoteException e) {
+                Slog.e(getTag(), "Could not register task stack listener", e);
+            }
+        }
+
+        @Override
+        public void onStop() {
+            try {
+                mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+            } catch (RemoteException e) {
+                Slog.e(getTag(), "Could not unregister task stack listener", e);
+            }
+        }
+
+        @Override
+        public void resetFailedAttempts() {
+            resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                    ActivityManager.getCurrentUser());
+        }
+
+        @Override
+        public void notifyUserActivity() {
+            userActivity();
+        }
+
+        @Override
+        public int handleFailedAttempt() {
+            final int currentUser = ActivityManager.getCurrentUser();
+            mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1);
+            mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false);
+            final int lockoutMode = getLockoutMode();
+            if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
+                mPerformanceStats.permanentLockout++;
+            } else if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
+                mPerformanceStats.lockout++;
+            }
+
+            // Failing multiple times will continue to push out the lockout time
+            if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
+                scheduleLockoutResetForUser(currentUser);
+                return lockoutMode;
+            }
+            return AuthenticationClient.LOCKOUT_NONE;
+        }
+    }
+
+    protected class EnrollClientImpl extends EnrollClient {
+
+        public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
+                IBinder token, ServiceListener listener, int userId, int groupId,
+                byte[] cryptoToken, boolean restricted, String owner) {
+            super(context, getMetrics(), daemon, halDeviceId, token, listener,
+                    userId, groupId, cryptoToken, restricted, owner);
+        }
+
+        @Override
+        public void notifyUserActivity() {
+            userActivity();
+        }
+    }
+
+    protected class RemovalClientImpl extends RemovalClient {
+        private boolean mShouldNotify;
+
+        public RemovalClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
+                IBinder token, ServiceListener listener, int fingerId, int groupId, int userId,
+                boolean restricted, String owner) {
+            super(context, getMetrics(), daemon, halDeviceId, token, listener, fingerId, groupId,
+                    userId, restricted, owner);
+        }
+
+        public void setShouldNotifyUserActivity(boolean shouldNotify) {
+            mShouldNotify = shouldNotify;
+        }
+
+        @Override
+        public void notifyUserActivity() {
+            if (mShouldNotify) {
+                userActivity();
+            }
+        }
+    }
+
+    protected class EnumerateClientImpl extends EnumerateClient {
+
+        public EnumerateClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
+                IBinder token, ServiceListener listener, int groupId, int userId,
+                boolean restricted, String owner) {
+            super(context, getMetrics(), daemon, halDeviceId, token, listener, groupId, userId,
+                    restricted, owner);
+        }
+
+        @Override
+        public void notifyUserActivity() {
+            userActivity();
+        }
+    }
+
+    /**
+     * Wraps the callback interface from Service -> Manager
+     */
+    protected interface ServiceListener {
+        void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining)
+                throws RemoteException;
+
+        void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
+                throws RemoteException;
+
+        void onAuthenticationSucceeded(long deviceId,
+                BiometricAuthenticator.BiometricIdentifier biometric, int userId)
+                throws RemoteException;
+
+        void onAuthenticationFailed(long deviceId)
+                throws RemoteException;
+
+        void onError(long deviceId, int error, int vendorCode)
+                throws RemoteException;
+
+        void onRemoved(long deviceId, int fingerId, int groupId, int remaining)
+                throws RemoteException;
+
+        void onEnumerated(long deviceId, int fingerId, int groupId, int remaining)
+                throws RemoteException;
+    }
+
+    /**
+     * Wraps a portion of the interface from Service -> Daemon that is used by the ClientMonitor
+     * subclasses.
+     */
+    protected interface DaemonWrapper {
+        int authenticate(long operationId, int groupId) throws RemoteException;
+        int cancel() throws RemoteException;
+        int remove(int groupId, int biometricId) throws RemoteException;
+        int enumerate() throws RemoteException;
+        int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException;
+    }
+
+    /**
+     * Handler which all subclasses should post events to.
+     */
+    protected final class H extends Handler {
+        @Override
+        public void handleMessage(android.os.Message msg) {
+            switch (msg.what) {
+                case MSG_USER_SWITCHING:
+                    handleUserSwitching(msg.arg1);
+                    break;
+
+                default:
+                    Slog.w(getTag(), "Unknown message:" + msg.what);
+            }
+        }
+    };
+
+    private final class BiometricTaskStackListener extends TaskStackListener {
+        @Override
+        public void onTaskStackChanged() {
+            try {
+                if (!(mCurrentClient instanceof AuthenticationClient)) {
+                    return;
+                }
+                final String currentClient = mCurrentClient.getOwnerString();
+                if (isKeyguard(currentClient)) {
+                    return; // Keyguard is always allowed
+                }
+                List<ActivityManager.RunningTaskInfo> runningTasks =
+                        mActivityTaskManager.getTasks(1);
+                if (!runningTasks.isEmpty()) {
+                    final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+                    if (!topPackage.contentEquals(currentClient)) {
+                        Slog.e(getTag(), "Stopping background authentication, top: " + topPackage
+                                + " currentClient: " + currentClient);
+                        mCurrentClient.stop(false /* initiatedByClient */);
+                    }
+                }
+            } catch (RemoteException e) {
+                Slog.e(getTag(), "Unable to get running tasks", e);
+            }
+        }
+    };
+
+    private final class ResetClientStateRunnable implements Runnable {
+        @Override
+        public void run() {
+            /**
+             * Warning: if we get here, the driver never confirmed our call to cancel the current
+             * operation (authenticate, enroll, remove, enumerate, etc), which is
+             * really bad.  The result will be a 3-second delay in starting each new client.
+             * If you see this on a device, make certain the driver notifies with
+             * {@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} in response to cancel()
+             * once it has successfully switched to the IDLE state in the HAL.
+             * Additionally,{@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} should only be sent
+             * in response to an actual cancel() call.
+             */
+            Slog.w(getTag(), "Client "
+                    + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null")
+                    + " failed to respond to cancel, starting client "
+                    + (mPendingClient != null ? mPendingClient.getOwnerString() : "null"));
+
+            mCurrentClient = null;
+            startClient(mPendingClient, false);
+        }
+    };
+
+    private final class LockoutReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (getLockoutResetIntent().equals(intent.getAction())) {
+                final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
+                resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
+            }
+        }
+    };
+
+    private final class ResetFailedAttemptsForUserRunnablle implements Runnable {
+        @Override
+        public void run() {
+            resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                    ActivityManager.getCurrentUser());
+        }
+    };
+
+    /**
+     * Initializes the system service.
+     * <p>
+     * Subclasses must define a single argument constructor that accepts the context
+     * and passes it to super.
+     * </p>
+     *
+     * @param context The system server context.
+     */
+    public BiometricService(Context context) {
+        super(context);
+        mContext = context;
+        mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString(
+                com.android.internal.R.string.config_keyguardComponent)).getPackageName();
+        mAppOps = context.getSystemService(AppOpsManager.class);
+        mTimedLockoutCleared = new SparseBooleanArray();
+        mFailedAttempts = new SparseIntArray();
+        mActivityTaskManager = ((ActivityTaskManager) context.getSystemService(
+                Context.ACTIVITY_TASK_SERVICE)).getService();
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        mAlarmManager = mContext.getSystemService(AlarmManager.class);
+        mUserManager = UserManager.get(mContext);
+        mMetricsLogger = new MetricsLogger();
+        mContext.registerReceiver(mLockoutReceiver, new IntentFilter(getLockoutResetIntent()),
+                getLockoutBroadcastPermission(), null /* handler */);
+    }
+
+    @Override
+    public void onStart() {
+        listenForUserSwitches();
+    }
+
+    @Override
+    public void serviceDied(long cookie) {
+        Slog.e(getTag(), "HAL died");
+        mMetricsLogger.count(getMetrics().tagHalDied(), 1);
+        handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+                0 /*vendorCode */);
+    }
+
+    protected ClientMonitor getCurrentClient() {
+        return mCurrentClient;
+    }
+
+    protected ClientMonitor getPendingClient() {
+        return mPendingClient;
+    }
+
+    /**
+     * Callback handlers from the daemon. The caller must put this on a handler.
+     */
+
+    protected void handleAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+        ClientMonitor client = mCurrentClient;
+        if (client != null && client.onAcquired(acquiredInfo, vendorCode)) {
+            removeClient(client);
+        }
+        if (mPerformanceStats != null && getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
+                && client instanceof AuthenticationClient) {
+            // ignore enrollment acquisitions or acquisitions when we're locked out
+            mPerformanceStats.acquire++;
+        }
+    }
+
+    protected void handleAuthenticated(long deviceId, int biometricId, int groupId,
+            ArrayList<Byte> token) {
+        ClientMonitor client = mCurrentClient;
+        if (biometricId != 0) {
+            final byte[] byteToken = new byte[token.size()];
+            for (int i = 0; i < token.size(); i++) {
+                byteToken[i] = token.get(i);
+            }
+            KeyStore.getInstance().addAuthToken(byteToken);
+        }
+        if (client != null && client.onAuthenticated(biometricId, groupId)) {
+            removeClient(client);
+        }
+        if (biometricId != 0) {
+            mPerformanceStats.accept++;
+        } else {
+            mPerformanceStats.reject++;
+        }
+    }
+
+    protected void handleEnrollResult(long deviceId, int biometricId, int groupId, int remaining) {
+        ClientMonitor client = mCurrentClient;
+        if (client != null && client.onEnrollResult(biometricId, groupId, remaining)) {
+            removeClient(client);
+            // When enrollment finishes, update this group's authenticator id, as the HAL has
+            // already generated a new authenticator id when the new biometric is enrolled.
+            updateActiveGroup(groupId, null);
+        }
+    }
+
+    protected void handleError(long deviceId, int error, int vendorCode) {
+        final ClientMonitor client = mCurrentClient;
+
+        if (DEBUG) Slog.v(getTag(), "handleError(client="
+                + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
+
+        if (client != null && client.onError(error, vendorCode)) {
+            removeClient(client);
+        }
+
+        if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
+            mHandler.removeCallbacks(mResetClientState);
+            if (mPendingClient != null) {
+                if (DEBUG) Slog.v(getTag(), "start pending client " + mPendingClient.getOwnerString());
+                startClient(mPendingClient, false);
+                mPendingClient = null;
+            }
+        }
+    }
+
+    protected void handleRemoved(final long deviceId,
+            final int biometricId,
+            final int groupId,
+            final int remaining) {
+        if (DEBUG) Slog.w(getTag(), "Removed: fid=" + biometricId
+                + ", gid=" + groupId
+                + ", dev=" + deviceId
+                + ", rem=" + remaining);
+
+        ClientMonitor client = mCurrentClient;
+        if (client != null && client.onRemoved(biometricId, groupId, remaining)) {
+            removeClient(client);
+            // When the last biometric of a group is removed, update the authenticator id
+            if (!hasEnrolledBiometrics(groupId)) {
+                updateActiveGroup(groupId, null);
+            }
+        }
+    }
+
+    /**
+     * Calls from the Manager. These are still on the calling binder's thread.
+     */
+
+    protected void enrollInternal(EnrollClientImpl client, int userId) {
+        if (hasReachedEnrollmentLimit(userId)) {
+            return;
+        }
+
+        // Group ID is arbitrarily set to parent profile user ID. It just represents
+        // the default biometrics for the user.
+        if (!isCurrentUserOrProfile(userId)) {
+            return;
+        }
+
+        mHandler.post(() -> {
+            startClient(client, true /* initiatedByClient */);
+        });
+    }
+
+    protected void cancelEnrollmentInternal(IBinder token) {
+        mHandler.post(() -> {
+            ClientMonitor client = mCurrentClient;
+            if (client instanceof EnrollClient && client.getToken() == token) {
+                client.stop(client.getToken() == token);
+            }
+        });
+    }
+
+    protected void authenticateInternal(AuthenticationClientImpl client, long opId,
+            String opPackageName) {
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        final int callingUserId = UserHandle.getCallingUserId();
+
+        if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
+                callingUserId)) {
+            if (DEBUG) Slog.v(getTag(), "authenticate(): reject " + opPackageName);
+            return;
+        }
+
+        mHandler.post(() -> {
+            mMetricsLogger.histogram(getMetrics().tagAuthToken(), opId != 0L ? 1 : 0);
+
+            // Get performance stats object for this user.
+            HashMap<Integer, PerformanceStats> pmap
+                    = (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap;
+            PerformanceStats stats = pmap.get(mCurrentUserId);
+            if (stats == null) {
+                stats = new PerformanceStats();
+                pmap.put(mCurrentUserId, stats);
+            }
+            mPerformanceStats = stats;
+
+            startAuthentication(client, opPackageName);
+        });
+    }
+
+    protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName) {
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        final int callingUserId = UserHandle.getCallingUserId();
+
+        if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
+                callingUserId)) {
+            if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
+            return;
+        }
+
+        mHandler.post(() -> {
+            ClientMonitor client = mCurrentClient;
+            if (client instanceof AuthenticationClient) {
+                if (client.getToken() == token) {
+                    if (DEBUG) Slog.v(getTag(), "stop client " + client.getOwnerString());
+                    client.stop(client.getToken() == token);
+                } else {
+                    if (DEBUG) Slog.v(getTag(), "can't stop client "
+                            + client.getOwnerString() + " since tokens don't match");
+                }
+            } else if (client != null) {
+                if (DEBUG) Slog.v(getTag(), "can't cancel non-authenticating client "
+                        + client.getOwnerString());
+            }
+        });
+    }
+
+    protected void setActiveUserInternal(int userId) {
+        mHandler.post(() -> {
+            updateActiveGroup(userId, null /* clientPackage */);
+        });
+    }
+
+    protected void removeInternal(RemovalClientImpl client) {
+        mHandler.post(() -> {
+            startClient(client, true /* initiatedByClient */);
+        });
+    }
+
+    protected void enumerateInternal(EnumerateClientImpl client) {
+        mHandler.post(() -> {
+            startClient(client, true /* initiatedByClient */);
+        });
+    }
+
+    // Should be done on a handler thread - not on the Binder's thread.
+    private void startAuthentication(AuthenticationClientImpl client, String opPackageName) {
+        updateActiveGroup(client.getGroupId(), opPackageName);
+
+        if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")");
+
+        int lockoutMode = getLockoutMode();
+        if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
+            Slog.v(getTag(), "In lockout mode(" + lockoutMode +
+                    ") ; disallowing authentication");
+            int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
+                    BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
+                    BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+            if (!client.onError(errorCode, 0 /* vendorCode */)) {
+                Slog.w(getTag(), "Cannot send permanent lockout message to client");
+            }
+            return;
+        }
+        startClient(client, true /* initiatedByClient */);
+    }
+
+    /**
+     * Helper methods.
+     */
+
+    /**
+     * @param opPackageName name of package for caller
+     * @param requireForeground only allow this call while app is in the foreground
+     * @return true if caller can use the biometric API
+     */
+    protected boolean canUseBiometric(String opPackageName, boolean requireForeground, int uid,
+            int pid, int userId) {
+        checkUseBiometricPermission();
+
+        if (isKeyguard(opPackageName)) {
+            return true; // Keyguard is always allowed
+        }
+        if (!isCurrentUserOrProfile(userId)) {
+            Slog.w(getTag(), "Rejecting " + opPackageName + "; not a current user or profile");
+            return false;
+        }
+        if (mAppOps.noteOp(getAppOp(), uid, opPackageName) != AppOpsManager.MODE_ALLOWED) {
+            Slog.w(getTag(), "Rejecting " + opPackageName + "; permission denied");
+            return false;
+        }
+        if (requireForeground && !(isForegroundActivity(uid, pid) || isCurrentClient(
+                opPackageName))) {
+            Slog.w(getTag(), "Rejecting " + opPackageName + "; not in foreground");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @param opPackageName package of the caller
+     * @return true if this is the same client currently using the biometric
+     */
+    private boolean isCurrentClient(String opPackageName) {
+        return mCurrentClient != null && mCurrentClient.getOwnerString().equals(opPackageName);
+    }
+
+    /**
+     * @return true if this is keyguard package
+     */
+    private boolean isKeyguard(String clientPackage) {
+        return mKeyguardPackage.equals(clientPackage);
+    }
+
+    private int getLockoutMode() {
+        final int currentUser = ActivityManager.getCurrentUser();
+        final int failedAttempts = mFailedAttempts.get(currentUser, 0);
+        if (failedAttempts >= getFailedAttemptsLockoutPermanent()) {
+            return AuthenticationClient.LOCKOUT_PERMANENT;
+        } else if (failedAttempts > 0 &&
+                mTimedLockoutCleared.get(currentUser, false) == false
+                && (failedAttempts % getFailedAttemptsLockoutTimed() == 0)) {
+            return AuthenticationClient.LOCKOUT_TIMED;
+        }
+        return AuthenticationClient.LOCKOUT_NONE;
+    }
+
+    private boolean isForegroundActivity(int uid, int pid) {
+        try {
+            List<ActivityManager.RunningAppProcessInfo> procs =
+                    ActivityManager.getService().getRunningAppProcesses();
+            int N = procs.size();
+            for (int i = 0; i < N; i++) {
+                ActivityManager.RunningAppProcessInfo proc = procs.get(i);
+                if (proc.pid == pid && proc.uid == uid
+                        && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
+                    return true;
+                }
+            }
+        } catch (RemoteException e) {
+            Slog.w(getTag(), "am.getRunningAppProcesses() failed");
+        }
+        return false;
+    }
+
+    /**
+     * Calls the HAL to switch states to the new task. If there's already a current task,
+     * it calls cancel() and sets mPendingClient to begin when the current task finishes
+     * ({@link BiometricConstants#BIOMETRIC_ERROR_CANCELED}).
+     *
+     * @param newClient the new client that wants to connect
+     * @param initiatedByClient true for authenticate, remove and enroll
+     */
+    private void startClient(ClientMonitor newClient, boolean initiatedByClient) {
+        ClientMonitor currentClient = mCurrentClient;
+        if (currentClient != null) {
+            if (DEBUG) Slog.v(getTag(), "request stop current client " +
+                    currentClient.getOwnerString());
+
+            // This check only matters for FingerprintService, since enumerate may call back
+            // multiple times.
+            if (currentClient instanceof FingerprintService.EnumerateClientImpl ||
+                    currentClient instanceof FingerprintService.RemovalClientImpl) {
+                // This condition means we're currently running internal diagnostics to
+                // remove extra fingerprints in the hardware and/or the software
+                // TODO: design an escape hatch in case client never finishes
+                if (newClient != null) {
+                    Slog.w(getTag(), "Internal cleanup in progress but trying to start client "
+                            + newClient.getClass().getSuperclass().getSimpleName()
+                            + "(" + newClient.getOwnerString() + ")"
+                            + ", initiatedByClient = " + initiatedByClient);
+                }
+            } else {
+                currentClient.stop(initiatedByClient);
+            }
+            mPendingClient = newClient;
+            mHandler.removeCallbacks(mResetClientState);
+            mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
+        } else if (newClient != null) {
+            mCurrentClient = newClient;
+            if (DEBUG) Slog.v(getTag(), "starting client "
+                    + newClient.getClass().getSuperclass().getSimpleName()
+                    + "(" + newClient.getOwnerString() + ")"
+                    + ", initiatedByClient = " + initiatedByClient);
+            notifyClientActiveCallbacks(true);
+
+            newClient.start();
+        }
+    }
+
+    protected void removeClient(ClientMonitor client) {
+        if (client != null) {
+            client.destroy();
+            if (client != mCurrentClient && mCurrentClient != null) {
+                Slog.w(getTag(), "Unexpected client: " + client.getOwnerString() + "expected: "
+                        + mCurrentClient.getOwnerString());
+            }
+        }
+        if (mCurrentClient != null) {
+            if (DEBUG) Slog.v(getTag(), "Done with client: " + client.getOwnerString());
+            mCurrentClient = null;
+        }
+        if (mPendingClient == null) {
+            notifyClientActiveCallbacks(false);
+        }
+    }
+
+    /**
+     * @param clientPackage the package of the caller
+     * @return the profile id
+     */
+    protected int getUserOrWorkProfileId(String clientPackage, int userId) {
+        if (!isKeyguard(clientPackage) && isWorkProfile(userId)) {
+            return userId;
+        }
+        return getEffectiveUserId(userId);
+    }
+
+    protected boolean isRestricted() {
+        // Only give privileged apps (like Settings) access to biometric info
+        final boolean restricted = !hasPermission(getManageBiometricPermission());
+        return restricted;
+    }
+
+    protected boolean hasPermission(String permission) {
+        return getContext().checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    protected void checkPermission(String permission) {
+        getContext().enforceCallingOrSelfPermission(permission,
+                "Must have " + permission + " permission.");
+    }
+
+    protected boolean isCurrentUserOrProfile(int userId) {
+        UserManager um = UserManager.get(mContext);
+        if (um == null) {
+            Slog.e(getTag(), "Unable to acquire UserManager");
+            return false;
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // Allow current user or profiles of the current user...
+            for (int profileId : um.getEnabledProfileIds(ActivityManager.getCurrentUser())) {
+                if (profileId == userId) {
+                    return true;
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        return false;
+    }
+
+    private void scheduleLockoutResetForUser(int userId) {
+        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+                getLockoutResetIntentForUser(userId));
+    }
+
+    private PendingIntent getLockoutResetIntentForUser(int userId) {
+        return PendingIntent.getBroadcast(mContext, userId,
+                new Intent(getLockoutResetIntent()).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+                PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+    private void userActivity() {
+        long now = SystemClock.uptimeMillis();
+        mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+    }
+
+    /**
+     * @param userId
+     * @return true if this is a work profile
+     */
+    private boolean isWorkProfile(int userId) {
+        UserInfo userInfo = null;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            userInfo = mUserManager.getUserInfo(userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return userInfo != null && userInfo.isManagedProfile();
+    }
+
+
+    private int getEffectiveUserId(int userId) {
+        UserManager um = UserManager.get(mContext);
+        if (um != null) {
+            final long callingIdentity = Binder.clearCallingIdentity();
+            userId = um.getCredentialOwnerProfile(userId);
+            Binder.restoreCallingIdentity(callingIdentity);
+        } else {
+            Slog.e(getTag(), "Unable to acquire UserManager");
+        }
+        return userId;
+    }
+
+    // Attempt counter should only be cleared when Keyguard goes away or when
+    // a biometric is successfully authenticated.
+    private void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
+        if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
+            Slog.v(getTag(), "Reset biometric lockout, clearAttemptCounter=" + clearAttemptCounter);
+        }
+        if (clearAttemptCounter) {
+            mFailedAttempts.put(userId, 0);
+        }
+        mTimedLockoutCleared.put(userId, true);
+        // If we're asked to reset failed attempts externally (i.e. from Keyguard),
+        // the alarm might still be pending; remove it.
+        cancelLockoutResetForUser(userId);
+        notifyLockoutResetMonitors();
+    }
+
+    private void cancelLockoutResetForUser(int userId) {
+        mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
+    }
+
+    private void listenForUserSwitches() {
+        try {
+            ActivityManager.getService().registerUserSwitchObserver(
+                    new SynchronousUserSwitchObserver() {
+                        @Override
+                        public void onUserSwitching(int newUserId) throws RemoteException {
+                            mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
+                                    .sendToTarget();
+                        }
+                    }, getTag());
+        } catch (RemoteException e) {
+            Slog.w(getTag(), "Failed to listen for user switching event" ,e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/fingerprint/ClientMonitor.java b/services/core/java/com/android/server/biometrics/common/ClientMonitor.java
similarity index 66%
rename from services/core/java/com/android/server/fingerprint/ClientMonitor.java
rename to services/core/java/com/android/server/biometrics/common/ClientMonitor.java
index b935ba2..9147a40 100644
--- a/services/core/java/com/android/server/fingerprint/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/common/ClientMonitor.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2016 The Android Open Source Project
+/*
+ * 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.
@@ -11,16 +11,13 @@
  * 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.
+ * limitations under the License
  */
 
-package com.android.server.fingerprint;
+package com.android.server.biometrics.common;
 
-import android.Manifest;
 import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.biometrics.BiometricConstants;
 import android.media.AudioAttributes;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -28,23 +25,24 @@
 import android.os.Vibrator;
 import android.util.Slog;
 
+import com.android.internal.logging.MetricsLogger;
+
 import java.util.NoSuchElementException;
 
 /**
- * Abstract base class for keeping track and dispatching events from fingerprint HAL to the
+ * Abstract base class for keeping track and dispatching events from the biometric's HAL to the
  * the current client.  Subclasses are responsible for coordinating the interaction with
- * fingerprint HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
+ * the biometric's HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
  */
 public abstract class ClientMonitor implements IBinder.DeathRecipient {
-    protected static final String TAG = FingerprintService.TAG; // TODO: get specific name
-    protected static final int ERROR_ESRCH = 3; // Likely fingerprint HAL is dead. See errno.h.
-    protected static final boolean DEBUG = FingerprintService.DEBUG;
-    private static final long[] DEFAULT_SUCCESS_VIBRATION_PATTERN = new long[] {0, 30};
+    protected static final int ERROR_ESRCH = 3; // Likely HAL is dead. See errno.h.
+    protected static final boolean DEBUG = BiometricService.DEBUG;
     private static final AudioAttributes FINGERPRINT_SONFICATION_ATTRIBUTES =
             new AudioAttributes.Builder()
                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                     .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                     .build();
+
     private final Context mContext;
     private final long mHalDeviceId;
     private final int mTargetUserId;
@@ -54,51 +52,65 @@
     private final String mOwner;
     private final VibrationEffect mSuccessVibrationEffect;
     private final VibrationEffect mErrorVibrationEffect;
+    private final BiometricService.DaemonWrapper mDaemon;
+
     private IBinder mToken;
-    private IFingerprintServiceReceiver mReceiver;
+    private BiometricService.ServiceListener mListener;
+
+    protected final MetricsLogger mMetricsLogger;
+    protected final Metrics mMetrics;
+
     protected boolean mAlreadyCancelled;
 
     /**
-     * @param context context of FingerprintService
-     * @param halDeviceId the HAL device ID of the associated fingerprint hardware
+     * @param context context of BiometricService
+     * @param daemon interface to call back to a specific biometric's daemon
+     * @param halDeviceId the HAL device ID of the associated biometric hardware
      * @param token a unique token for the client
-     * @param receiver recipient of related events (e.g. authentication)
+     * @param listener recipient of related events (e.g. authentication)
      * @param userId target user id for operation
      * @param groupId groupId for the fingerprint set
-     * @param restricted whether or not client has the {@link Manifest#MANAGE_FINGERPRINT}
+     * @param restricted whether or not client has the MANAGE_* permission
      * permission
      * @param owner name of the client that owns this
      */
-    public ClientMonitor(Context context, long halDeviceId, IBinder token,
-            IFingerprintServiceReceiver receiver, int userId, int groupId,boolean restricted,
-            String owner) {
+    public ClientMonitor(Context context, Metrics metrics, BiometricService.DaemonWrapper daemon,
+            long halDeviceId, IBinder token, BiometricService.ServiceListener listener, int userId,
+            int groupId, boolean restricted, String owner) {
         mContext = context;
+        mMetrics = metrics;
+        mDaemon = daemon;
         mHalDeviceId = halDeviceId;
         mToken = token;
-        mReceiver = receiver;
+        mListener = listener;
         mTargetUserId = userId;
         mGroupId = groupId;
         mIsRestricted = restricted;
         mOwner = owner;
         mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
         mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+        mMetricsLogger = new MetricsLogger();
         try {
             if (token != null) {
                 token.linkToDeath(this, 0);
             }
         } catch (RemoteException e) {
-            Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
+            Slog.w(getLogTag(), "caught remote exception in linkToDeath: ", e);
         }
     }
 
+    protected String getLogTag() {
+        return mMetrics.logTag();
+    }
+
     /**
-     * Contacts fingerprint HAL to start the client.
+     * Contacts the biometric's HAL to start the client.
      * @return 0 on success, errno from driver on failure
      */
     public abstract int start();
 
     /**
-     * Contacts fingerprint HAL to stop the client.
+     * Contacts the biometric's HAL to stop the client.
      * @param initiatedByClient whether the operation is at the request of a client
      */
     public abstract int stop(boolean initiatedByClient);
@@ -108,56 +120,47 @@
      */
     public abstract void notifyUserActivity();
 
-    /**
-     * Gets the fingerprint daemon from the cached state in the container class.
-     */
-    public abstract IBiometricsFingerprint getFingerprintDaemon();
-
     // Event callbacks from driver. Inappropriate calls is flagged/logged by the
     // respective client (e.g. enrolling shouldn't get authenticate events).
     // All of these return 'true' if the operation is completed and it's ok to move
-    // to the next client (e.g. authentication accepts or rejects a fingerprint).
+    // to the next client (e.g. authentication accepts or rejects a biometric).
     public abstract boolean onEnrollResult(int fingerId, int groupId, int rem);
     public abstract boolean onAuthenticated(int fingerId, int groupId);
     public abstract boolean onRemoved(int fingerId, int groupId, int remaining);
     public abstract boolean onEnumerationResult(int fingerId, int groupId, int remaining);
 
     /**
-     * Called when we get notification from fingerprint HAL that an image has been acquired.
+     * Called when we get notification from the biometric's HAL that an image has been acquired.
      * Common to authenticate and enroll.
      * @param acquiredInfo info about the current image acquisition
      * @return true if client should be removed
      */
     public boolean onAcquired(int acquiredInfo, int vendorCode) {
-        if (mReceiver == null)
-            return true; // client not connected
         try {
-            mReceiver.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode);
+            mListener.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode);
             return false; // acquisition continues...
         } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to invoke sendAcquired:", e);
-            return true; // client failed
+            Slog.w(getLogTag(), "Failed to invoke sendAcquired", e);
+            return true;
         } finally {
             // Good scans will keep the device awake
-            if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
+            if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
                 notifyUserActivity();
             }
         }
     }
 
     /**
-     * Called when we get notification from fingerprint HAL that an error has occurred with the
+     * Called when we get notification from the biometric's HAL that an error has occurred with the
      * current operation. Common to authenticate, enroll, enumerate and remove.
      * @param error
      * @return true if client should be removed
      */
     public boolean onError(int error, int vendorCode) {
-        if (mReceiver != null) {
-            try {
-                mReceiver.onError(getHalDeviceId(), error, vendorCode);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to invoke sendError:", e);
-            }
+        try {
+            mListener.onError(getHalDeviceId(), error, vendorCode);
+        } catch (RemoteException e) {
+            Slog.w(getLogTag(), "Failed to invoke sendError", e);
         }
         return true; // errors always remove current client
     }
@@ -168,26 +171,26 @@
                 mToken.unlinkToDeath(this, 0);
             } catch (NoSuchElementException e) {
                 // TODO: remove when duplicate call bug is found
-                Slog.e(TAG, "destroy(): " + this + ":", new Exception("here"));
+                Slog.e(getLogTag(), "destroy(): " + this + ":", new Exception("here"));
             }
             mToken = null;
         }
-        mReceiver = null;
+        mListener = null;
     }
 
     @Override
     public void binderDied() {
         mToken = null;
-        mReceiver = null;
-        onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+        mListener = null;
+        onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
     }
 
     @Override
     protected void finalize() throws Throwable {
         try {
             if (mToken != null) {
-                if (DEBUG) Slog.w(TAG, "removing leaked reference: " + mToken);
-                onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+                if (DEBUG) Slog.w(getLogTag(), "removing leaked reference: " + mToken);
+                onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
             }
         } finally {
             super.finalize();
@@ -206,8 +209,12 @@
         return mOwner;
     }
 
-    public final IFingerprintServiceReceiver getReceiver() {
-        return mReceiver;
+    public final BiometricService.ServiceListener getListener() {
+        return mListener;
+    }
+
+    public final BiometricService.DaemonWrapper getDaemonWrapper() {
+        return mDaemon;
     }
 
     public final boolean getIsRestricted() {
diff --git a/services/core/java/com/android/server/biometrics/common/EnrollClient.java b/services/core/java/com/android/server/biometrics/common/EnrollClient.java
new file mode 100644
index 0000000..6fb8ffe
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/common/EnrollClient.java
@@ -0,0 +1,132 @@
+/*
+ * 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.biometrics.common;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricConstants;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.fingerprint.FingerprintUtils;
+
+import java.util.Arrays;
+
+/**
+ * A class to keep track of the enrollment state for a given client.
+ */
+public abstract class EnrollClient extends ClientMonitor {
+    private static final long MS_PER_SEC = 1000;
+    private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute
+    private byte[] mCryptoToken;
+
+    public EnrollClient(Context context, Metrics metrics, BiometricService.DaemonWrapper daemon,
+            long halDeviceId, IBinder token, BiometricService.ServiceListener listener, int userId,
+            int groupId, byte[] cryptoToken, boolean restricted, String owner) {
+        super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
+                owner);
+        mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
+    }
+
+    @Override
+    public boolean onEnrollResult(int fingerId, int groupId, int remaining) {
+        if (groupId != getGroupId()) {
+            Slog.w(getLogTag(), "groupId != getGroupId(), groupId: " + groupId +
+                    " getGroupId():" + getGroupId());
+        }
+        if (remaining == 0) {
+            // TODO: handle other biometrics
+            FingerprintUtils.getInstance().addFingerprintForUser(getContext(), fingerId,
+                    getTargetUserId());
+        }
+        return sendEnrollResult(fingerId, groupId, remaining);
+    }
+
+    /*
+     * @return true if we're done.
+     */
+    private boolean sendEnrollResult(int fpId, int groupId, int remaining) {
+        vibrateSuccess();
+        mMetricsLogger.action(mMetrics.actionBiometricEnroll());
+        try {
+            getListener().onEnrollResult(getHalDeviceId(), fpId, groupId, remaining);
+            return remaining == 0;
+        } catch (RemoteException e) {
+            Slog.w(getLogTag(), "Failed to notify EnrollResult:", e);
+            return true;
+        }
+    }
+
+    @Override
+    public int start() {
+        final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
+        try {
+            final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout);
+            if (result != 0) {
+                Slog.w(getLogTag(), "startEnroll failed, result=" + result);
+                mMetricsLogger.histogram(mMetrics.tagEnrollStartError(), result);
+                onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+                return result;
+            }
+        } catch (RemoteException e) {
+            Slog.e(getLogTag(), "startEnroll failed", e);
+        }
+        return 0; // success
+    }
+
+    @Override
+    public int stop(boolean initiatedByClient) {
+        if (mAlreadyCancelled) {
+            Slog.w(getLogTag(), "stopEnroll: already cancelled!");
+            return 0;
+        }
+
+        try {
+            final int result = getDaemonWrapper().cancel();
+            if (result != 0) {
+                Slog.w(getLogTag(), "startEnrollCancel failed, result = " + result);
+                return result;
+            }
+        } catch (RemoteException e) {
+            Slog.e(getLogTag(), "stopEnrollment failed", e);
+        }
+        if (initiatedByClient) {
+            onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
+        }
+        mAlreadyCancelled = true;
+        return 0;
+    }
+
+    @Override
+    public boolean onRemoved(int fingerId, int groupId, int remaining) {
+        if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for enroll!");
+        return true; // Invalid for EnrollClient
+    }
+
+    @Override
+    public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
+        if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for enroll!");
+        return true; // Invalid for EnrollClient
+    }
+
+    @Override
+    public boolean onAuthenticated(int fingerId, int groupId) {
+        if (DEBUG) Slog.w(getLogTag(), "onAuthenticated() called for enroll!");
+        return true; // Invalid for EnrollClient
+    }
+
+}
diff --git a/services/core/java/com/android/server/biometrics/common/EnumerateClient.java b/services/core/java/com/android/server/biometrics/common/EnumerateClient.java
new file mode 100644
index 0000000..fa3a077
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/common/EnumerateClient.java
@@ -0,0 +1,108 @@
+/*
+ * 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.biometrics.common;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricConstants;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * A class to keep track of the enumeration state for a given client.
+ */
+public abstract class EnumerateClient extends ClientMonitor {
+    public EnumerateClient(Context context, Metrics metrics, BiometricService.DaemonWrapper daemon,
+            long halDeviceId, IBinder token, BiometricService.ServiceListener listener, int groupId,
+            int userId, boolean restricted, String owner) {
+        super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
+                owner);
+    }
+
+    @Override
+    public int start() {
+        // The biometric template ids will be removed when we get confirmation from the HAL
+        try {
+            final int result = getDaemonWrapper().enumerate();
+            if (result != 0) {
+                Slog.w(getLogTag(), "start enumerate for user " + getTargetUserId()
+                    + " failed, result=" + result);
+                mMetricsLogger.histogram(mMetrics.tagEnumerateStartError(), result);
+                onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+                return result;
+            }
+        } catch (RemoteException e) {
+            Slog.e(getLogTag(), "startEnumeration failed", e);
+        }
+        return 0;
+    }
+
+    @Override
+    public int stop(boolean initiatedByClient) {
+        if (mAlreadyCancelled) {
+            Slog.w(getLogTag(), "stopEnumerate: already cancelled!");
+            return 0;
+        }
+
+        try {
+            final int result = getDaemonWrapper().cancel();
+            if (result != 0) {
+                Slog.w(getLogTag(), "stop enumeration failed, result=" + result);
+                return result;
+            }
+        } catch (RemoteException e) {
+            Slog.e(getLogTag(), "stopEnumeration failed", e);
+            return ERROR_ESRCH;
+        }
+
+        // We don't actually stop enumerate, but inform the client that the cancel operation
+        // succeeded so we can start the next operation.
+        if (initiatedByClient) {
+            onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
+        }
+        mAlreadyCancelled = true;
+        return 0; // success
+    }
+
+    @Override
+    public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
+        try {
+            getListener().onEnumerated(getHalDeviceId(), fingerId, groupId, remaining);
+        } catch (RemoteException e) {
+            Slog.w(getLogTag(), "Failed to notify enumerated:", e);
+        }
+        return remaining == 0;
+    }
+
+    @Override
+    public boolean onAuthenticated(int fingerId, int groupId) {
+        if (DEBUG) Slog.w(getLogTag(), "onAuthenticated() called for enumerate!");
+        return true; // Invalid for Enumerate.
+    }
+
+    @Override
+    public boolean onEnrollResult(int fingerId, int groupId, int rem) {
+        if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for enumerate!");
+        return true; // Invalid for Enumerate.
+    }
+
+    @Override
+    public boolean onRemoved(int fingerId, int groupId, int remaining) {
+        if (DEBUG) Slog.w(getLogTag(), "onRemoved() called for enumerate!");
+        return true; // Invalid for Enumerate.
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/common/Metrics.java b/services/core/java/com/android/server/biometrics/common/Metrics.java
new file mode 100644
index 0000000..eb1a1f8
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/common/Metrics.java
@@ -0,0 +1,34 @@
+/*
+ * 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.biometrics.common;
+
+public interface Metrics {
+    /** The log tag */
+    String logTag();
+
+    /** Strings for MetricsLogger.count() and MetricsLogger.histogram() */
+    String tagHalDied();
+    String tagAuthToken();
+    String tagAuthStartError();
+    String tagEnrollStartError();
+    String tagEnumerateStartError();
+    String tagRemoveStartError();
+
+    /** Integers for MetricsLogger.action() */
+    int actionBiometricAuth();
+    int actionBiometricEnroll();
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/common/RemovalClient.java b/services/core/java/com/android/server/biometrics/common/RemovalClient.java
new file mode 100644
index 0000000..1555a6c
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/common/RemovalClient.java
@@ -0,0 +1,120 @@
+/*
+ * 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.biometrics.common;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricConstants;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.fingerprint.FingerprintUtils;
+
+/**
+ * A class to keep track of the remove state for a given client.
+ */
+public abstract class RemovalClient extends ClientMonitor {
+    private int mFingerId;
+
+    public RemovalClient(Context context, Metrics metrics, BiometricService.DaemonWrapper daemon,
+            long halDeviceId, IBinder token, BiometricService.ServiceListener listener,
+            int fingerId, int groupId, int userId, boolean restricted, String owner) {
+        super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
+                owner);
+        mFingerId = fingerId;
+    }
+
+    @Override
+    public int start() {
+        // The biometric template ids will be removed when we get confirmation from the HAL
+        try {
+            final int result = getDaemonWrapper().remove(getGroupId(), mFingerId);
+            if (result != 0) {
+                Slog.w(getLogTag(), "startRemove with id = " + mFingerId + " failed, result=" +
+                        result);
+                mMetricsLogger.histogram(mMetrics.tagRemoveStartError(), result);
+                onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+                return result;
+            }
+        } catch (RemoteException e) {
+            Slog.e(getLogTag(), "startRemove failed", e);
+        }
+        return 0;
+    }
+
+    @Override
+    public int stop(boolean initiatedByClient) {
+        if (mAlreadyCancelled) {
+            Slog.w(getLogTag(), "stopRemove: already cancelled!");
+            return 0;
+        }
+
+        try {
+            final int result = getDaemonWrapper().cancel();
+            if (result != 0) {
+                Slog.w(getLogTag(), "stopRemoval failed, result=" + result);
+                return result;
+            }
+            if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is no longer removing");
+        } catch (RemoteException e) {
+            Slog.e(getLogTag(), "stopRemoval failed", e);
+            return ERROR_ESRCH;
+        }
+        mAlreadyCancelled = true;
+        return 0; // success
+    }
+
+    /*
+     * @return true if we're done.
+     */
+    private boolean sendRemoved(int fingerId, int groupId, int remaining) {
+        try {
+            getListener().onRemoved(getHalDeviceId(), fingerId, groupId, remaining);
+        } catch (RemoteException e) {
+            Slog.w(getLogTag(), "Failed to notify Removed:", e);
+        }
+        return remaining == 0;
+    }
+
+    @Override
+    public boolean onRemoved(int fingerId, int groupId, int remaining) {
+        if (fingerId != 0) {
+            // TODO: biometric
+            FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(), fingerId,
+                    getTargetUserId());
+        }
+        return sendRemoved(fingerId, getGroupId(), remaining);
+    }
+
+    @Override
+    public boolean onEnrollResult(int fingerId, int groupId, int rem) {
+        if (DEBUG) Slog.w(getLogTag(), "onEnrollResult() called for remove!");
+        return true; // Invalid for Remove
+    }
+
+    @Override
+    public boolean onAuthenticated(int fingerId, int groupId) {
+        if (DEBUG) Slog.w(getLogTag(), "onAuthenticated() called for remove!");
+        return true; // Invalid for Remove.
+    }
+
+    @Override
+    public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
+        if (DEBUG) Slog.w(getLogTag(), "onEnumerationResult() called for remove!");
+        return true; // Invalid for Remove.
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintMetrics.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintMetrics.java
new file mode 100644
index 0000000..ba8b3b3
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintMetrics.java
@@ -0,0 +1,68 @@
+/*
+ * 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.biometrics.fingerprint;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.server.biometrics.common.Metrics;
+
+public class FingerprintMetrics implements Metrics {
+
+    @Override
+    public String logTag() {
+        return FingerprintService.TAG;
+    }
+
+    @Override
+    public String tagHalDied() {
+        return "fingerprintd_died";
+    }
+
+    @Override
+    public String tagAuthToken() {
+        return "fingerprint_token";
+    }
+
+    @Override
+    public String tagAuthStartError() {
+        return "fingerprintd_auth_start_error";
+    }
+
+    @Override
+    public String tagEnrollStartError() {
+        return "fingerprintd_enroll_start_error";
+    }
+
+    @Override
+    public String tagEnumerateStartError() {
+        return "fingerprintd_enum_start_error";
+    }
+
+    @Override
+    public String tagRemoveStartError() {
+        return "fingerprintd_remove_start_error";
+    }
+
+    @Override
+    public int actionBiometricAuth() {
+        return MetricsProto.MetricsEvent.ACTION_FINGERPRINT_AUTH;
+    }
+
+    @Override
+    public int actionBiometricEnroll() {
+        return MetricsProto.MetricsEvent.ACTION_FINGERPRINT_ENROLL;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
new file mode 100644
index 0000000..9397418
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -0,0 +1,1130 @@
+/*
+ * Copyright (C) 2014 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.biometrics.fingerprint;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_FINGERPRINT;
+import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
+import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.Manifest.permission.USE_FINGERPRINT;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintClientActiveCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.biometrics.common.BiometricService;
+import com.android.server.biometrics.common.ClientMonitor;
+import com.android.server.biometrics.common.EnumerateClient;
+import com.android.server.biometrics.common.Metrics;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A service to manage multiple clients that want to access the fingerprint HAL API.
+ * The service is responsible for maintaining a list of clients and dispatching all
+ * fingerprint-related events.
+ *
+ * @hide
+ */
+public class FingerprintService extends BiometricService {
+
+    protected static final String TAG = "FingerprintService";
+    private static final boolean DEBUG = true;
+    private static final boolean CLEANUP_UNUSED_FP = true;
+    private static final String FP_DATA_DIR = "fpdata";
+    private static final String ACTION_LOCKOUT_RESET =
+            "com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET";
+    private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
+    private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20;
+
+    private final class UserFingerprint {
+        Fingerprint f;
+        int userId;
+        public UserFingerprint(Fingerprint f, int userId) {
+            this.f = f;
+            this.userId = userId;
+        }
+    }
+
+    /**
+     * Receives the incoming binder calls from FingerprintManager.
+     */
+    private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
+
+        /**
+         * The following methods contain common code which is shared in biometrics/common.
+         */
+
+        @Override // Binder call
+        public long preEnroll(IBinder token) {
+            checkPermission(MANAGE_FINGERPRINT);
+            return startPreEnroll(token);
+        }
+
+        @Override // Binder call
+        public int postEnroll(IBinder token) {
+            checkPermission(MANAGE_FINGERPRINT);
+            return startPostEnroll(token);
+        }
+
+        @Override // Binder call
+        public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
+                final IFingerprintServiceReceiver receiver, final int flags,
+                final String opPackageName) {
+            checkPermission(MANAGE_FINGERPRINT);
+
+            final boolean restricted = isRestricted();
+            final int groupId = userId; // default group for fingerprint enrollment
+            final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
+                    mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId,
+                    cryptoToken, restricted, opPackageName);
+
+
+            enrollInternal(client, userId);
+        }
+
+        @Override // Binder call
+        public void cancelEnrollment(final IBinder token) {
+            checkPermission(MANAGE_FINGERPRINT);
+            cancelEnrollmentInternal(token);
+        }
+
+        @Override // Binder call
+        public void authenticate(final IBinder token, final long opId, final int groupId,
+                final IFingerprintServiceReceiver receiver, final int flags,
+                final String opPackageName, final Bundle bundle,
+                final IBiometricPromptReceiver dialogReceiver) {
+            final boolean restricted = isRestricted();
+            final AuthenticationClientImpl client = new AuthenticationClientImpl(getContext(),
+                    mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
+                    mCurrentUserId, groupId, opId, restricted, opPackageName, bundle,
+                    dialogReceiver, mStatusBarService) {
+
+            };
+            authenticateInternal(client, opId, opPackageName);
+        }
+
+        @Override // Binder call
+        public void cancelAuthentication(final IBinder token, final String opPackageName) {
+            cancelAuthenticationInternal(token, opPackageName);
+        }
+
+        @Override // Binder call
+        public void setActiveUser(final int userId) {
+            checkPermission(MANAGE_FINGERPRINT);
+            setActiveUserInternal(userId);
+        }
+
+        @Override // Binder call
+        public void remove(final IBinder token, final int fingerId, final int groupId,
+                final int userId, final IFingerprintServiceReceiver receiver) {
+            checkPermission(MANAGE_FINGERPRINT);
+
+            if (token == null) {
+                Slog.w(TAG, "remove(): token is null");
+                return;
+            }
+
+            final boolean restricted = isRestricted();
+            final RemovalClientImpl client = new RemovalClientImpl(getContext(), mDaemonWrapper,
+                    mHalDeviceId, token, new ServiceListenerImpl(receiver), fingerId, groupId,
+                    userId, restricted, token.toString());
+            client.setShouldNotifyUserActivity(true);
+            removeInternal(client);
+        }
+
+        @Override // Binder call
+        public void enumerate(final IBinder token, final int userId,
+                final IFingerprintServiceReceiver receiver) {
+            checkPermission(MANAGE_FINGERPRINT);
+
+            final boolean restricted = isRestricted();
+            final EnumerateClientImpl client = new EnumerateClientImpl(getContext(), mDaemonWrapper,
+                    mHalDeviceId, token, new ServiceListenerImpl(receiver), userId, userId,
+                    restricted, getContext().getOpPackageName());
+            enumerateInternal(client);
+        }
+
+        @Override // Binder call
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+                return;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                if (args.length > 0 && "--proto".equals(args[0])) {
+                    dumpProto(fd);
+                } else {
+                    dumpInternal(pw);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        /**
+         * The following methods don't use any common code from BiometricService
+         */
+
+        @Override // Binder call
+        public boolean isHardwareDetected(long deviceId, String opPackageName) {
+            if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
+                    Binder.getCallingUid(), Binder.getCallingPid(),
+                    UserHandle.getCallingUserId())) {
+                return false;
+            }
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                IBiometricsFingerprint daemon = getFingerprintDaemon();
+                return daemon != null && mHalDeviceId != 0;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
+        public void rename(final int fingerId, final int groupId, final String name) {
+            checkPermission(MANAGE_FINGERPRINT);
+            if (!isCurrentUserOrProfile(groupId)) {
+                return;
+            }
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mFingerprintUtils.renameFingerprintForUser(mContext, fingerId,
+                            groupId, name);
+                }
+            });
+        }
+
+        @Override // Binder call
+        public List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) {
+            if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
+                    Binder.getCallingUid(), Binder.getCallingPid(),
+                    UserHandle.getCallingUserId())) {
+                return Collections.emptyList();
+            }
+
+            return FingerprintService.this.getEnrolledFingerprints(userId);
+        }
+
+        @Override // Binder call
+        public boolean hasEnrolledFingerprints(int userId, String opPackageName) {
+            if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
+                    Binder.getCallingUid(), Binder.getCallingPid(),
+                    UserHandle.getCallingUserId())) {
+                return false;
+            }
+
+            return FingerprintService.this.hasEnrolledBiometrics(userId);
+        }
+
+        @Override // Binder call
+        public long getAuthenticatorId(String opPackageName) {
+            // In this method, we're not checking whether the caller is permitted to use fingerprint
+            // API because current authenticator ID is leaked (in a more contrived way) via Android
+            // Keystore (android.security.keystore package): the user of that API can create a key
+            // which requires fingerprint authentication for its use, and then query the key's
+            // characteristics (hidden API) which returns, among other things, fingerprint
+            // authenticator ID which was active at key creation time.
+            //
+            // Reason: The part of Android Keystore which runs inside an app's process invokes this
+            // method in certain cases. Those cases are not always where the developer demonstrates
+            // explicit intent to use fingerprint functionality. Thus, to avoiding throwing an
+            // unexpected SecurityException this method does not check whether its caller is
+            // permitted to use fingerprint API.
+            //
+            // The permission check should be restored once Android Keystore no longer invokes this
+            // method from inside app processes.
+
+            return FingerprintService.this.getAuthenticatorId(opPackageName);
+        }
+
+        @Override // Binder call
+        public void resetTimeout(byte [] token) {
+            checkPermission(RESET_FINGERPRINT_LOCKOUT);
+            // TODO: confirm security token when we move timeout management into the HAL layer.
+            mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
+        }
+
+        @Override
+        public void addLockoutResetCallback(final IFingerprintServiceLockoutResetCallback callback)
+                throws RemoteException {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    addLockoutResetMonitor(
+                            new FingerprintServiceLockoutResetMonitor(callback));
+                }
+            });
+        }
+
+        @Override
+        public boolean isClientActive() {
+            checkPermission(MANAGE_FINGERPRINT);
+            synchronized(FingerprintService.this) {
+                return (getCurrentClient() != null) || (getPendingClient() != null);
+            }
+        }
+
+        @Override
+        public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
+            checkPermission(MANAGE_FINGERPRINT);
+            mClientActiveCallbacks.add(callback);
+        }
+
+        @Override
+        public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
+            checkPermission(MANAGE_FINGERPRINT);
+            mClientActiveCallbacks.remove(callback);
+        }
+    }
+
+    /**
+     * Receives callbacks from the ClientMonitor implementations. The results are forwarded to
+     * the FingerprintManager.
+     */
+    private class ServiceListenerImpl implements ServiceListener {
+
+        private IFingerprintServiceReceiver mFingerprintServiceReceiver;
+
+        public ServiceListenerImpl(IFingerprintServiceReceiver receiver) {
+            mFingerprintServiceReceiver = receiver;
+        }
+
+        @Override
+        public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining)
+                throws RemoteException {
+            if (mFingerprintServiceReceiver != null) {
+                mFingerprintServiceReceiver.onEnrollResult(deviceId, fingerId, groupId, remaining);
+            }
+        }
+
+        @Override
+        public void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
+                throws RemoteException {
+            if (mFingerprintServiceReceiver != null) {
+                mFingerprintServiceReceiver.onAcquired(deviceId, acquiredInfo, vendorCode);
+            }
+        }
+
+        @Override
+        public void onAuthenticationSucceeded(long deviceId,
+                BiometricAuthenticator.BiometricIdentifier biometric, int userId)
+                throws RemoteException {
+            if (mFingerprintServiceReceiver != null) {
+                mFingerprintServiceReceiver
+                        .onAuthenticationSucceeded(deviceId, (Fingerprint)biometric, userId);
+            }
+        }
+
+        @Override
+        public void onAuthenticationFailed(long deviceId) throws RemoteException {
+            if (mFingerprintServiceReceiver != null) {
+                mFingerprintServiceReceiver.onAuthenticationFailed(deviceId);
+            }
+        }
+
+        @Override
+        public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
+            if (mFingerprintServiceReceiver != null) {
+                mFingerprintServiceReceiver.onError(deviceId, error, vendorCode);
+            }
+        }
+
+        @Override
+        public void onRemoved(long deviceId, int fingerId, int groupId, int remaining)
+                throws RemoteException {
+            if (mFingerprintServiceReceiver != null) {
+                mFingerprintServiceReceiver.onRemoved(deviceId, fingerId, groupId, remaining);
+            }
+        }
+
+        @Override
+        public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining)
+                throws RemoteException {
+            if (mFingerprintServiceReceiver != null) {
+                mFingerprintServiceReceiver.onEnumerated(deviceId, fingerId, groupId, remaining);
+            }
+        }
+    }
+
+    private final class FingerprintServiceLockoutResetMonitor implements IBinder.DeathRecipient {
+
+        private static final long WAKELOCK_TIMEOUT_MS = 2000;
+        private final IFingerprintServiceLockoutResetCallback mCallback;
+        private final WakeLock mWakeLock;
+
+        public FingerprintServiceLockoutResetMonitor(
+                IFingerprintServiceLockoutResetCallback callback) {
+            mCallback = callback;
+            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    "lockout reset callback");
+            try {
+                mCallback.asBinder().linkToDeath(FingerprintServiceLockoutResetMonitor.this, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "caught remote exception in linkToDeath", e);
+            }
+        }
+
+        public void sendLockoutReset() {
+            if (mCallback != null) {
+                try {
+                    mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
+                    mCallback.onLockoutReset(mHalDeviceId, new IRemoteCallback.Stub() {
+
+                        @Override
+                        public void sendResult(Bundle data) throws RemoteException {
+                            releaseWakelock();
+                        }
+                    });
+                } catch (DeadObjectException e) {
+                    Slog.w(TAG, "Death object while invoking onLockoutReset: ", e);
+                    mHandler.post(mRemoveCallbackRunnable);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to invoke onLockoutReset: ", e);
+                    releaseWakelock();
+                }
+            }
+        }
+
+        private final Runnable mRemoveCallbackRunnable = new Runnable() {
+            @Override
+            public void run() {
+                releaseWakelock();
+                removeLockoutResetCallback(FingerprintServiceLockoutResetMonitor.this);
+            }
+        };
+
+        @Override
+        public void binderDied() {
+            Slog.e(TAG, "Lockout reset callback binder died");
+            mHandler.post(mRemoveCallbackRunnable);
+        }
+
+        private void releaseWakelock() {
+            if (mWakeLock.isHeld()) {
+                mWakeLock.release();
+            }
+        }
+    }
+
+    /**
+     * An internal class to help clean up unknown fingerprints in the hardware and software.
+     */
+    private final class InternalEnumerateClient extends BiometricService.EnumerateClientImpl {
+
+        private List<Fingerprint> mEnrolledList;
+        private List<Fingerprint> mUnknownFingerprints = new ArrayList<>(); // list of fp to delete
+
+        public InternalEnumerateClient(Context context, DaemonWrapper daemon, long halDeviceId,
+                IBinder token, ServiceListener listener, int groupId, int userId,
+                boolean restricted, String owner, List<Fingerprint> enrolledList) {
+            super(context, daemon, halDeviceId, token, listener, groupId, userId, restricted,
+                    owner);
+            mEnrolledList = enrolledList;
+        }
+
+        private void handleEnumeratedFingerprint(int fingerId, int groupId, int remaining) {
+            boolean matched = false;
+            for (int i = 0; i < mEnrolledList.size(); i++) {
+                if (mEnrolledList.get(i).getFingerId() == fingerId) {
+                    mEnrolledList.remove(i);
+                    matched = true;
+                    break;
+                }
+            }
+
+            // fingerId 0 means no fingerprints are in hardware
+            if (!matched && fingerId != 0) {
+                Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
+                mUnknownFingerprints.add(fingerprint);
+            }
+        }
+
+        private void doFingerprintCleanup() {
+            if (mEnrolledList == null) {
+                return;
+            }
+
+            for (Fingerprint f : mEnrolledList) {
+                Slog.e(TAG, "doFingerprintCleanup(): Removing dangling enrolled fingerprint: "
+                        + f.getName() + " " + f.getFingerId() + " " + f.getGroupId()
+                        + " " + f.getDeviceId());
+                FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(),
+                        f.getFingerId(), getTargetUserId());
+            }
+            mEnrolledList.clear();
+        }
+
+        public List<Fingerprint> getUnknownFingerprints() {
+            return mUnknownFingerprints;
+        }
+
+        @Override
+        public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
+            handleEnumeratedFingerprint(fingerId, groupId, remaining);
+            if (remaining == 0) {
+                doFingerprintCleanup();
+            }
+            return remaining == 0;
+        }
+    }
+
+    /**
+     * An internal class to help clean up unknown fingerprints in hardware and software.
+     */
+    private final class InternalRemovalClient extends BiometricService.RemovalClientImpl {
+        public InternalRemovalClient(Context context,
+                DaemonWrapper daemon, long halDeviceId, IBinder token,
+                ServiceListener listener, int fingerId, int groupId, int userId, boolean restricted,
+                String owner) {
+            super(context, daemon, halDeviceId, token, listener, fingerId, groupId, userId,
+                    restricted,
+                    owner);
+        }
+    }
+
+    private final FingerprintMetrics mFingerprintMetrics = new FingerprintMetrics();
+    private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors =
+            new ArrayList<>();
+    private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks =
+            new CopyOnWriteArrayList<>();
+    private final Map<Integer, Long> mAuthenticatorIds =
+            Collections.synchronizedMap(new HashMap<>());
+    private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance();
+    private final PowerManager mPowerManager;
+
+    @GuardedBy("this")
+    private IBiometricsFingerprint mDaemon;
+
+    private Context mContext;
+    private long mHalDeviceId;
+    private IStatusBarService mStatusBarService;
+    private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
+    private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw fingerprints
+
+    /**
+     * Receives callbacks from the HAL.
+     */
+    private IBiometricsFingerprintClientCallback mDaemonCallback =
+            new IBiometricsFingerprintClientCallback.Stub() {
+        @Override
+        public void onEnrollResult(final long deviceId, final int fingerId, final int groupId,
+                final int remaining) {
+            mHandler.post(() -> {
+                FingerprintService.super.handleEnrollResult(deviceId, fingerId, groupId, remaining);
+            });
+        }
+
+        @Override
+        public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) {
+            mHandler.post(() -> {
+                FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode);
+            });
+        }
+
+        @Override
+        public void onAuthenticated(final long deviceId, final int fingerId, final int groupId,
+                ArrayList<Byte> token) {
+            mHandler.post(() -> {
+                FingerprintService.super.handleAuthenticated(deviceId, fingerId, groupId, token);
+            });
+        }
+
+        @Override
+        public void onError(final long deviceId, final int error, final int vendorCode) {
+            mHandler.post(() -> {
+                ClientMonitor client = getCurrentClient();
+                if (client instanceof InternalRemovalClient
+                        || client instanceof InternalEnumerateClient) {
+                    clearEnumerateState();
+                }
+                FingerprintService.super.handleError(deviceId, error, vendorCode);
+
+                // TODO: this chunk of code should be common to all biometric services
+                if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
+                    // If we get HW_UNAVAILABLE, try to connect again later...
+                    Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client.");
+                    synchronized (this) {
+                        mDaemon = null;
+                        mHalDeviceId = 0;
+                        mCurrentUserId = UserHandle.USER_NULL;
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onRemoved(final long deviceId, final int fingerId, final int groupId,
+                final int remaining) {
+            mHandler.post(() -> {
+                ClientMonitor client = getCurrentClient();
+                FingerprintService.super.handleRemoved(deviceId, fingerId, groupId, remaining);
+                if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
+                    cleanupUnknownFingerprints();
+                } else if (client instanceof InternalRemovalClient){
+                    clearEnumerateState();
+                }
+            });
+        }
+
+        @Override
+        public void onEnumerate(final long deviceId, final int fingerId, final int groupId,
+                final int remaining) {
+            mHandler.post(() -> {
+                // TODO: factor out common enumerate logic if possible
+                FingerprintService.this.handleEnumerate(deviceId, fingerId, groupId, remaining);
+            });
+
+        }
+    };
+
+    /**
+     * Wraps the HAL-specific code and is passed to the ClientMonitor implementations so that they
+     * can be shared between the multiple biometric services.
+     */
+    private final DaemonWrapper mDaemonWrapper = new DaemonWrapper() {
+        protected static final int ERROR_ESRCH = 3; // Likely fingerprint HAL is dead. see errno.h.
+        @Override
+        public int authenticate(long operationId, int groupId) throws RemoteException {
+            IBiometricsFingerprint daemon = getFingerprintDaemon();
+            if (daemon == null) {
+                Slog.w(TAG, "authenticate(): no fingerprint HAL!");
+                return ERROR_ESRCH;
+            }
+            return daemon.authenticate(operationId, groupId);
+        }
+
+        @Override
+        public int cancel() throws RemoteException {
+            IBiometricsFingerprint daemon = getFingerprintDaemon();
+            if (daemon == null) {
+                Slog.w(TAG, "cancel(): no fingerprint HAL!");
+                return ERROR_ESRCH;
+            }
+            return daemon.cancel();
+        }
+
+        @Override
+        public int remove(int groupId, int biometricId) throws RemoteException {
+            IBiometricsFingerprint daemon = getFingerprintDaemon();
+            if (daemon == null) {
+                Slog.w(TAG, "remove(): no fingerprint HAL!");
+                return ERROR_ESRCH;
+            }
+            return daemon.remove(groupId, biometricId);
+        }
+
+        @Override
+        public int enumerate() throws RemoteException {
+            IBiometricsFingerprint daemon = getFingerprintDaemon();
+            if (daemon == null) {
+                Slog.w(TAG, "enumerate(): no fingerprint HAL!");
+                return ERROR_ESRCH;
+            }
+            return daemon.enumerate();
+        }
+
+        @Override
+        public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException {
+            Slog.v(TAG, "startEnroll()");
+            IBiometricsFingerprint daemon = getFingerprintDaemon();
+            if (daemon == null) {
+                Slog.w(TAG, "enroll(): no fingerprint HAL!");
+                return ERROR_ESRCH;
+            }
+            return daemon.enroll(cryptoToken, groupId, timeout);
+        }
+    };
+
+    public FingerprintService(Context context) {
+        super(context);
+        mContext = context;
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        // TODO: can this be retrieved from AuthenticationClient, or BiometricService?
+        mStatusBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
+        SystemServerInitThreadPool.get().submit(this::getFingerprintDaemon, TAG + ".onStart");
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getFailedAttemptsLockoutTimed() {
+        return MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED;
+    }
+
+    @Override
+    protected int getFailedAttemptsLockoutPermanent() {
+        return MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT;
+    }
+
+    @Override
+    protected Metrics getMetrics() {
+        return mFingerprintMetrics;
+    }
+
+    @Override
+    protected boolean hasReachedEnrollmentLimit(int userId) {
+        final int limit = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
+        final int enrolled = FingerprintService.this.getEnrolledFingerprints(userId).size();
+        if (enrolled >= limit) {
+            Slog.w(TAG, "Too many fingerprints registered");
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void updateActiveGroup(int userId, String clientPackage) {
+        IBiometricsFingerprint daemon = getFingerprintDaemon();
+
+        if (daemon != null) {
+            try {
+                userId = getUserOrWorkProfileId(clientPackage, userId);
+                if (userId != mCurrentUserId) {
+                    int firstSdkInt = Build.VERSION.FIRST_SDK_INT;
+                    if (firstSdkInt < Build.VERSION_CODES.BASE) {
+                        Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be " +
+                                "at least VERSION_CODES.BASE");
+                    }
+                    File baseDir;
+                    if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
+                        baseDir = Environment.getUserSystemDirectory(userId);
+                    } else {
+                        baseDir = Environment.getDataVendorDeDirectory(userId);
+                    }
+
+                    File fpDir = new File(baseDir, FP_DATA_DIR);
+                    if (!fpDir.exists()) {
+                        if (!fpDir.mkdir()) {
+                            Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath());
+                            return;
+                        }
+                        // Calling mkdir() from this process will create a directory with our
+                        // permissions (inherited from the containing dir). This command fixes
+                        // the label.
+                        if (!SELinux.restorecon(fpDir)) {
+                            Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
+                            return;
+                        }
+                    }
+
+                    daemon.setActiveGroup(userId, fpDir.getAbsolutePath());
+                    mCurrentUserId = userId;
+                }
+                mAuthenticatorIds.put(userId,
+                        hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId() : 0L);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to setActiveGroup():", e);
+            }
+        }
+    }
+
+    @Override
+    protected String getLockoutResetIntent() {
+        return ACTION_LOCKOUT_RESET;
+    }
+
+    @Override
+    protected String getLockoutBroadcastPermission() {
+        return RESET_FINGERPRINT_LOCKOUT;
+    }
+
+    @Override
+    protected long getHalDeviceId() {
+        return mHalDeviceId;
+    }
+
+    @Override
+    protected void handleUserSwitching(int userId) {
+        if (getCurrentClient() instanceof InternalRemovalClient
+                || getCurrentClient() instanceof InternalEnumerateClient) {
+            Slog.w(TAG, "User switched while performing cleanup");
+            removeClient(getCurrentClient());
+            clearEnumerateState();
+        }
+        updateActiveGroup(userId, null);
+        doFingerprintCleanupForUser(userId);
+    }
+
+
+    @Override
+    protected boolean hasEnrolledBiometrics(int userId) {
+        if (userId != UserHandle.getCallingUserId()) {
+            checkPermission(INTERACT_ACROSS_USERS);
+        }
+        return mFingerprintUtils.getFingerprintsForUser(mContext, userId).size() > 0;
+    }
+
+    @Override
+    protected String getManageBiometricPermission() {
+        return MANAGE_FINGERPRINT;
+    }
+
+    @Override
+    protected void checkUseBiometricPermission() {
+        if (getContext().checkCallingPermission(USE_FINGERPRINT)
+                != PackageManager.PERMISSION_GRANTED) {
+            checkPermission(USE_BIOMETRIC);
+        }
+    }
+
+    @Override
+    protected int getAppOp() {
+        return AppOpsManager.OP_USE_FINGERPRINT;
+    }
+
+    @Override
+    protected void notifyLockoutResetMonitors() {
+        for (int i = 0; i < mLockoutMonitors.size(); i++) {
+            mLockoutMonitors.get(i).sendLockoutReset();
+        }
+    }
+
+    @Override
+    protected void notifyClientActiveCallbacks(boolean isActive) {
+        List<IFingerprintClientActiveCallback> callbacks = mClientActiveCallbacks;
+        for (int i = 0; i < callbacks.size(); i++) {
+            try {
+                callbacks.get(i).onClientActiveChanged(isActive);
+            } catch (RemoteException re) {
+                // If the remote is dead, stop notifying it
+                mClientActiveCallbacks.remove(callbacks.get(i));
+            }
+        }
+    }
+
+    /** Gets the fingerprint daemon */
+    private synchronized IBiometricsFingerprint getFingerprintDaemon() {
+        if (mDaemon == null) {
+            Slog.v(TAG, "mDaemon was null, reconnect to fingerprint");
+            try {
+                mDaemon = IBiometricsFingerprint.getService();
+            } catch (java.util.NoSuchElementException e) {
+                // Service doesn't exist or cannot be opened. Logged below.
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get biometric interface", e);
+            }
+            if (mDaemon == null) {
+                Slog.w(TAG, "fingerprint HIDL not available");
+                return null;
+            }
+
+            mDaemon.asBinder().linkToDeath(this, 0);
+
+            try {
+                mHalDeviceId = mDaemon.setNotify(mDaemonCallback);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to open fingerprint HAL", e);
+                mDaemon = null; // try again later!
+            }
+
+            if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + mHalDeviceId);
+            if (mHalDeviceId != 0) {
+                loadAuthenticatorIds();
+                updateActiveGroup(ActivityManager.getCurrentUser(), null);
+                doFingerprintCleanupForUser(ActivityManager.getCurrentUser());
+            } else {
+                Slog.w(TAG, "Failed to open Fingerprint HAL!");
+                MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1);
+                mDaemon = null;
+            }
+        }
+        return mDaemon;
+    }
+
+    /** Populates existing authenticator ids. To be used only during the start of the service. */
+    private void loadAuthenticatorIds() {
+        // This operation can be expensive, so keep track of the elapsed time. Might need to move to
+        // background if it takes too long.
+        long t = System.currentTimeMillis();
+        mAuthenticatorIds.clear();
+        for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+            int userId = getUserOrWorkProfileId(null, user.id);
+            if (!mAuthenticatorIds.containsKey(userId)) {
+                updateActiveGroup(userId, null);
+            }
+        }
+
+        t = System.currentTimeMillis() - t;
+        if (t > 1000) {
+            Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms");
+        }
+    }
+
+    /**
+     * This method should be called upon connection to the daemon, and when user switches.
+     * @param userId
+     */
+    private void doFingerprintCleanupForUser(int userId) {
+        if (CLEANUP_UNUSED_FP) {
+            enumerateUser(userId);
+        }
+    }
+
+    private void clearEnumerateState() {
+        if (DEBUG) Slog.v(TAG, "clearEnumerateState()");
+        mUnknownFingerprints.clear();
+    }
+
+    private void enumerateUser(int userId) {
+        if (DEBUG) Slog.v(TAG, "Enumerating user(" + userId + ")");
+
+        final boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
+        final List<Fingerprint> enrolledList = getEnrolledFingerprints(userId);
+
+        InternalEnumerateClient client = new InternalEnumerateClient(getContext(), mDaemonWrapper,
+                mHalDeviceId, mToken, new ServiceListenerImpl(null), userId, userId, restricted,
+                getContext().getOpPackageName(), enrolledList);
+        enumerateInternal(client);
+    }
+
+    // Remove unknown fingerprints from hardware
+    private void cleanupUnknownFingerprints() {
+        if (!mUnknownFingerprints.isEmpty()) {
+            UserFingerprint uf = mUnknownFingerprints.get(0);
+            mUnknownFingerprints.remove(uf);
+            boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
+            InternalRemovalClient client = new InternalRemovalClient(getContext(), mDaemonWrapper,
+                    mHalDeviceId, mToken, new ServiceListenerImpl(null), uf.f.getFingerId(),
+                    uf.f.getGroupId(), uf.userId, restricted, getContext().getOpPackageName());
+            removeInternal(client);
+        } else {
+            clearEnumerateState();
+        }
+    }
+
+    private void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
+        ClientMonitor client = getCurrentClient();
+
+        if ( !(client instanceof InternalRemovalClient) && !(client instanceof EnumerateClient) ) {
+            return;
+        }
+        client.onEnumerationResult(fingerId, groupId, remaining);
+
+        // All fingerprints in hardware for this user were enumerated
+        if (remaining == 0) {
+            if (client instanceof InternalEnumerateClient) {
+                List<Fingerprint> unknownFingerprints =
+                        ((InternalEnumerateClient) client).getUnknownFingerprints();
+
+                if (!unknownFingerprints.isEmpty()) {
+                    Slog.w(TAG, "Adding " + unknownFingerprints.size() +
+                            " fingerprints for deletion");
+                }
+                for (Fingerprint f : unknownFingerprints) {
+                    mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
+                }
+                removeClient(client);
+                cleanupUnknownFingerprints();
+            } else {
+                removeClient(client);
+            }
+        }
+    }
+
+    private long startPreEnroll(IBinder token) {
+        IBiometricsFingerprint daemon = getFingerprintDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "startPreEnroll: no fingerprint HAL!");
+            return 0;
+        }
+        try {
+            return daemon.preEnroll();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "startPreEnroll failed", e);
+        }
+        return 0;
+    }
+
+    private int startPostEnroll(IBinder token) {
+        IBiometricsFingerprint daemon = getFingerprintDaemon();
+        if (daemon == null) {
+            Slog.w(TAG, "startPostEnroll: no fingerprint HAL!");
+            return 0;
+        }
+        try {
+            return daemon.postEnroll();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "startPostEnroll failed", e);
+        }
+        return 0;
+    }
+
+    private List<Fingerprint> getEnrolledFingerprints(int userId) {
+        return mFingerprintUtils.getFingerprintsForUser(mContext, userId);
+    }
+
+    private void addLockoutResetMonitor(FingerprintServiceLockoutResetMonitor monitor) {
+        if (!mLockoutMonitors.contains(monitor)) {
+            mLockoutMonitors.add(monitor);
+        }
+    }
+
+    private void removeLockoutResetCallback(
+            FingerprintServiceLockoutResetMonitor monitor) {
+        mLockoutMonitors.remove(monitor);
+    }
+
+    /***
+     * @param opPackageName the name of the calling package
+     * @return authenticator id for the calling user
+     */
+    private long getAuthenticatorId(String opPackageName) {
+        final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
+        return mAuthenticatorIds.getOrDefault(userId, 0L);
+    }
+
+    private void dumpInternal(PrintWriter pw) {
+        JSONObject dump = new JSONObject();
+        try {
+            dump.put("service", "Fingerprint Manager");
+
+            JSONArray sets = new JSONArray();
+            for (UserInfo user : UserManager.get(getContext()).getUsers()) {
+                final int userId = user.getUserHandle().getIdentifier();
+                final int N = mFingerprintUtils.getFingerprintsForUser(mContext, userId).size();
+                PerformanceStats stats = mPerformanceMap.get(userId);
+                PerformanceStats cryptoStats = mCryptoPerformanceMap.get(userId);
+                JSONObject set = new JSONObject();
+                set.put("id", userId);
+                set.put("count", N);
+                set.put("accept", (stats != null) ? stats.accept : 0);
+                set.put("reject", (stats != null) ? stats.reject : 0);
+                set.put("acquire", (stats != null) ? stats.acquire : 0);
+                set.put("lockout", (stats != null) ? stats.lockout : 0);
+                set.put("permanentLockout", (stats != null) ? stats.permanentLockout : 0);
+                // cryptoStats measures statistics about secure fingerprint transactions
+                // (e.g. to unlock password storage, make secure purchases, etc.)
+                set.put("acceptCrypto", (cryptoStats != null) ? cryptoStats.accept : 0);
+                set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
+                set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
+                set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
+                set.put("permanentLockoutCrypto",
+                    (cryptoStats != null) ? cryptoStats.permanentLockout : 0);
+                sets.put(set);
+            }
+
+            dump.put("prints", sets);
+        } catch (JSONException e) {
+            Slog.e(TAG, "dump formatting failure", e);
+        }
+        pw.println(dump);
+    }
+
+    private void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        for (UserInfo user : UserManager.get(getContext()).getUsers()) {
+            final int userId = user.getUserHandle().getIdentifier();
+
+            final long userToken = proto.start(FingerprintServiceDumpProto.USERS);
+
+            proto.write(FingerprintUserStatsProto.USER_ID, userId);
+            proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS,
+                    mFingerprintUtils.getFingerprintsForUser(mContext, userId).size());
+
+            // Normal fingerprint authentications (e.g. lockscreen)
+            final PerformanceStats normal = mPerformanceMap.get(userId);
+            if (normal != null) {
+                final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
+                proto.write(PerformanceStatsProto.ACCEPT, normal.accept);
+                proto.write(PerformanceStatsProto.REJECT, normal.reject);
+                proto.write(PerformanceStatsProto.ACQUIRE, normal.acquire);
+                proto.write(PerformanceStatsProto.LOCKOUT, normal.lockout);
+                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, normal.permanentLockout);
+                proto.end(countsToken);
+            }
+
+            // Statistics about secure fingerprint transactions (e.g. to unlock password
+            // storage, make secure purchases, etc.)
+            final PerformanceStats crypto = mCryptoPerformanceMap.get(userId);
+            if (crypto != null) {
+                final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
+                proto.write(PerformanceStatsProto.ACCEPT, crypto.accept);
+                proto.write(PerformanceStatsProto.REJECT, crypto.reject);
+                proto.write(PerformanceStatsProto.ACQUIRE, crypto.acquire);
+                proto.write(PerformanceStatsProto.LOCKOUT, crypto.lockout);
+                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, crypto.permanentLockout);
+                proto.end(countsToken);
+            }
+
+            proto.end(userToken);
+        }
+        proto.flush();
+        mPerformanceMap.clear();
+        mCryptoPerformanceMap.clear();
+    }
+}
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintUtils.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintUtils.java
similarity index 97%
rename from services/core/java/com/android/server/fingerprint/FingerprintUtils.java
rename to services/core/java/com/android/server/biometrics/fingerprint/FingerprintUtils.java
index 5fbd735..cfb2b75 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintUtils.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.fingerprint;
+package com.android.server.biometrics.fingerprint;
 
 import android.content.Context;
 import android.hardware.fingerprint.Fingerprint;
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintsUserState.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintsUserState.java
similarity index 99%
rename from services/core/java/com/android/server/fingerprint/FingerprintsUserState.java
rename to services/core/java/com/android/server/biometrics/fingerprint/FingerprintsUserState.java
index b0cde2d..2d9d87e 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintsUserState.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintsUserState.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.server.fingerprint;
+package com.android.server.biometrics.fingerprint;
 
 import android.content.Context;
 import android.hardware.fingerprint.Fingerprint;
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index df6a6f8..5105941 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1350,8 +1350,11 @@
             // do not currently know how to watch for changes in DUN settings.
             maybeUpdateConfiguration();
 
-            final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType(
-                    mConfig.preferredUpstreamIfaceTypes);
+            final TetheringConfiguration config = mConfig;
+            final NetworkState ns = (config.chooseUpstreamAutomatically)
+                    ? mUpstreamNetworkMonitor.getCurrentPreferredUpstream()
+                    : mUpstreamNetworkMonitor.selectPreferredUpstreamType(
+                            config.preferredUpstreamIfaceTypes);
             if (ns == null) {
                 if (tryCell) {
                     mUpstreamNetworkMonitor.registerMobileNetworkRequest();
@@ -1380,9 +1383,7 @@
             }
             notifyDownstreamsOfNewUpstreamIface(ifaces);
             if (ns != null && pertainsToCurrentUpstream(ns)) {
-                // If we already have NetworkState for this network examine
-                // it immediately, because there likely will be no second
-                // EVENT_ON_AVAILABLE (it was already received).
+                // If we already have NetworkState for this network update it immediately.
                 handleNewUpstreamNetworkState(ns);
             } else if (mCurrentUpstreamIfaceSet == null) {
                 // There are no available upstream networks.
@@ -1498,15 +1499,6 @@
             }
 
             switch (arg1) {
-                case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE:
-                    // The default network changed, or DUN connected
-                    // before this callback was processed. Updates
-                    // for the current NetworkCapabilities and
-                    // LinkProperties have been requested (default
-                    // request) or are being sent shortly (DUN). Do
-                    // nothing until they arrive; if no updates
-                    // arrive there's nothing to do.
-                    break;
                 case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES:
                     handleNewUpstreamNetworkState(ns);
                     break;
@@ -1539,7 +1531,7 @@
                 }
 
                 mSimChange.startListening();
-                mUpstreamNetworkMonitor.start();
+                mUpstreamNetworkMonitor.start(mDeps.getDefaultNetworkRequest());
 
                 // TODO: De-duplicate with updateUpstreamWanted() below.
                 if (upstreamWanted()) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 454c579..dd9fe05 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -27,6 +27,7 @@
 import static com.android.internal.R.array.config_tether_usb_regexs;
 import static com.android.internal.R.array.config_tether_upstream_types;
 import static com.android.internal.R.array.config_tether_wifi_regexs;
+import static com.android.internal.R.bool.config_tether_upstream_automatic;
 import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
 
 import android.content.Context;
@@ -86,6 +87,7 @@
     public final String[] tetherableBluetoothRegexs;
     public final int dunCheck;
     public final boolean isDunRequired;
+    public final boolean chooseUpstreamAutomatically;
     public final Collection<Integer> preferredUpstreamIfaceTypes;
     public final String[] dhcpRanges;
     public final String[] defaultIPv4DNS;
@@ -106,6 +108,7 @@
         dunCheck = checkDunRequired(ctx);
         configLog.log("DUN check returned: " + dunCheckString(dunCheck));
 
+        chooseUpstreamAutomatically = getResourceBoolean(ctx, config_tether_upstream_automatic);
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, dunCheck);
         isDunRequired = preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN);
 
@@ -142,6 +145,8 @@
         pw.print("isDunRequired: ");
         pw.println(isDunRequired);
 
+        pw.print("chooseUpstreamAutomatically: ");
+        pw.println(chooseUpstreamAutomatically);
         dumpStringArray(pw, "preferredUpstreamIfaceTypes",
                 preferredUpstreamNames(preferredUpstreamIfaceTypes));
 
@@ -160,6 +165,7 @@
         sj.add(String.format("tetherableBluetoothRegexs:%s",
                 makeString(tetherableBluetoothRegexs)));
         sj.add(String.format("isDunRequired:%s", isDunRequired));
+        sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically));
         sj.add(String.format("preferredUpstreamIfaceTypes:%s",
                 makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
         sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
@@ -286,6 +292,14 @@
         }
     }
 
+    private static boolean getResourceBoolean(Context ctx, int resId) {
+        try {
+            return ctx.getResources().getBoolean(resId);
+        } catch (Resources.NotFoundException e404) {
+            return false;
+        }
+    }
+
     private static String[] getResourceStringArray(Context ctx, int resId) {
         try {
             final String[] strArray = ctx.getResources().getStringArray(resId);
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
index 0ac7a36..605ee9c 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.net.INetd;
+import android.net.NetworkRequest;
 import android.net.ip.RouterAdvertisementDaemon;
 import android.net.util.InterfaceParams;
 import android.net.util.NetdService;
@@ -64,4 +65,8 @@
     public boolean isTetheringSupported() {
         return true;
     }
+
+    public NetworkRequest getDefaultNetworkRequest() {
+        return null;
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 3413291..f488be7 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -20,6 +20,9 @@
 import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
 import android.content.Context;
 import android.os.Handler;
@@ -74,14 +77,13 @@
     private static final boolean DBG = false;
     private static final boolean VDBG = false;
 
-    public static final int EVENT_ON_AVAILABLE      = 1;
-    public static final int EVENT_ON_CAPABILITIES   = 2;
-    public static final int EVENT_ON_LINKPROPERTIES = 3;
-    public static final int EVENT_ON_LOST           = 4;
+    public static final int EVENT_ON_CAPABILITIES   = 1;
+    public static final int EVENT_ON_LINKPROPERTIES = 2;
+    public static final int EVENT_ON_LOST           = 3;
     public static final int NOTIFY_LOCAL_PREFIXES   = 10;
 
     private static final int CALLBACK_LISTEN_ALL = 1;
-    private static final int CALLBACK_TRACK_DEFAULT = 2;
+    private static final int CALLBACK_DEFAULT_INTERNET = 2;
     private static final int CALLBACK_MOBILE_REQUEST = 3;
 
     private final Context mContext;
@@ -117,7 +119,7 @@
         mCM = cm;
     }
 
-    public void start() {
+    public void start(NetworkRequest defaultNetworkRequest) {
         stop();
 
         final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
@@ -125,8 +127,16 @@
         mListenAllCallback = new UpstreamNetworkCallback(CALLBACK_LISTEN_ALL);
         cm().registerNetworkCallback(listenAllRequest, mListenAllCallback, mHandler);
 
-        mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_TRACK_DEFAULT);
-        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+        if (defaultNetworkRequest != null) {
+            // This is not really a "request", just a way of tracking the system default network.
+            // It's guaranteed not to actually bring up any networks because it's the same request
+            // as the ConnectivityService default request, and thus shares fate with it. We can't
+            // use registerDefaultNetworkCallback because it will not track the system default
+            // network if there is a VPN that applies to our UID.
+            final NetworkRequest trackDefaultRequest = new NetworkRequest(defaultNetworkRequest);
+            mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
+            cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler);
+        }
     }
 
     public void stop() {
@@ -225,6 +235,20 @@
         return typeStatePair.ns;
     }
 
+    // Returns null if no current upstream available.
+    public NetworkState getCurrentPreferredUpstream() {
+        final NetworkState dfltState = (mDefaultInternetNetwork != null)
+                ? mNetworkMap.get(mDefaultInternetNetwork)
+                : null;
+        if (!mDunRequired) return dfltState;
+
+        if (isNetworkUsableAndNotCellular(dfltState)) return dfltState;
+
+        // Find a DUN network. Note that code in Tethering causes a DUN request
+        // to be filed, but this might be moved into this class in future.
+        return findFirstDunNetwork(mNetworkMap.values());
+    }
+
     public void setCurrentUpstream(Network upstream) {
         mTetheringUpstreamNetwork = upstream;
     }
@@ -233,72 +257,16 @@
         return (Set<IpPrefix>) mLocalPrefixes.clone();
     }
 
-    private void handleAvailable(int callbackType, Network network) {
-        if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
+    private void handleAvailable(Network network) {
+        if (mNetworkMap.containsKey(network)) return;
 
-        if (!mNetworkMap.containsKey(network)) {
-            mNetworkMap.put(network,
-                    new NetworkState(null, null, null, network, null, null));
-        }
-
-        // Always request whatever extra information we can, in case this
-        // was already up when start() was called, in which case we would
-        // not have been notified of any information that had not changed.
-        switch (callbackType) {
-            case CALLBACK_LISTEN_ALL:
-                break;
-
-            case CALLBACK_TRACK_DEFAULT:
-                if (mDefaultNetworkCallback == null) {
-                    // The callback was unregistered in the interval between
-                    // ConnectivityService enqueueing onAvailable() and our
-                    // handling of it here on the mHandler thread.
-                    //
-                    // Clean-up of this network entry is deferred to the
-                    // handling of onLost() by other callbacks.
-                    //
-                    // These request*() calls can be deleted post oag/339444.
-                    return;
-                }
-                mDefaultInternetNetwork = network;
-                break;
-
-            case CALLBACK_MOBILE_REQUEST:
-                if (mMobileNetworkCallback == null) {
-                    // The callback was unregistered in the interval between
-                    // ConnectivityService enqueueing onAvailable() and our
-                    // handling of it here on the mHandler thread.
-                    //
-                    // Clean-up of this network entry is deferred to the
-                    // handling of onLost() by other callbacks.
-                    return;
-                }
-                break;
-        }
-
-        // Requesting updates for mListenAllCallback is not currently possible
-        // because it's a "listen". Two possible solutions to getting updates
-        // about networks without waiting for a change (which might never come)
-        // are:
-        //
-        //     [1] extend request{NetworkCapabilities,LinkProperties}() to
-        //         take a Network argument and have ConnectivityService do
-        //         what's required (if the network satisfies the request)
-        //
-        //     [2] explicitly file a NetworkRequest for each connectivity type
-        //         listed as a preferred upstream and wait for these callbacks
-        //         to be notified (requires tracking many more callbacks).
-        //
-        // Until this is addressed, networks that exist prior to the "listen"
-        // registration and which do not subsequently change will not cause
-        // us to learn their NetworkCapabilities nor their LinkProperties.
-
-        // TODO: If sufficient information is available to select a more
-        // preferable upstream, do so now and notify the target.
-        notifyTarget(EVENT_ON_AVAILABLE, network);
+        if (VDBG) Log.d(TAG, "onAvailable for " + network);
+        mNetworkMap.put(network, new NetworkState(null, null, null, network, null, null));
     }
 
-    private void handleNetCap(Network network, NetworkCapabilities newNc) {
+    private void handleNetCap(int callbackType, Network network, NetworkCapabilities newNc) {
+        if (callbackType == CALLBACK_DEFAULT_INTERNET) mDefaultInternetNetwork = network;
+
         final NetworkState prev = mNetworkMap.get(network);
         if (prev == null || newNc.equals(prev.networkCapabilities)) {
             // Ignore notifications about networks for which we have not yet
@@ -360,13 +328,17 @@
     }
 
     private void handleLost(int callbackType, Network network) {
-        if (callbackType == CALLBACK_TRACK_DEFAULT) {
+        if (network.equals(mDefaultInternetNetwork)) {
             mDefaultInternetNetwork = null;
-            // Receiving onLost() for a default network does not necessarily
-            // mean the network is gone.  We wait for a separate notification
-            // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
-            // clearing all state.
-            return;
+            // There are few TODOs within ConnectivityService's rematching code
+            // pertaining to spurious onLost() notifications.
+            //
+            // TODO: simplify this, probably if favor of code that:
+            //     - selects a new upstream if mTetheringUpstreamNetwork has
+            //       been lost (by any callback)
+            //     - deletes the entry from the map only when the LISTEN_ALL
+            //       callback gets  notified.
+            if (callbackType == CALLBACK_DEFAULT_INTERNET) return;
         }
 
         if (!mNetworkMap.containsKey(network)) {
@@ -416,17 +388,19 @@
 
         @Override
         public void onAvailable(Network network) {
-            handleAvailable(mCallbackType, network);
+            handleAvailable(network);
         }
 
         @Override
         public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
-            handleNetCap(network, newNc);
+            handleNetCap(mCallbackType, network, newNc);
         }
 
         @Override
         public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
             handleLinkProp(network, newLp);
+            // TODO(b/110335330): reduce the number of times this is called by
+            // only recomputing on the LISTEN_ALL callback.
             recomputeLocalPrefixes();
         }
 
@@ -443,6 +417,8 @@
         @Override
         public void onLost(Network network) {
             handleLost(mCallbackType, network);
+            // TODO(b/110335330): reduce the number of times this is called by
+            // only recomputing on the LISTEN_ALL callback.
             recomputeLocalPrefixes();
         }
     }
@@ -509,4 +485,31 @@
         if (nc == null || !nc.hasSignalStrength()) return "unknown";
         return Integer.toString(nc.getSignalStrength());
     }
+
+    private static boolean isCellular(NetworkState ns) {
+        return (ns != null) && isCellular(ns.networkCapabilities);
+    }
+
+    private static boolean isCellular(NetworkCapabilities nc) {
+        return (nc != null) && nc.hasTransport(TRANSPORT_CELLULAR) &&
+               nc.hasCapability(NET_CAPABILITY_NOT_VPN);
+    }
+
+    private static boolean hasCapability(NetworkState ns, int netCap) {
+        return (ns != null) && (ns.networkCapabilities != null) &&
+               ns.networkCapabilities.hasCapability(netCap);
+    }
+
+    private static boolean isNetworkUsableAndNotCellular(NetworkState ns) {
+        return (ns != null) && (ns.networkCapabilities != null) && (ns.linkProperties != null) &&
+               !isCellular(ns.networkCapabilities);
+    }
+
+    private static NetworkState findFirstDunNetwork(Iterable<NetworkState> netStates) {
+        for (NetworkState ns : netStates) {
+            if (isCellular(ns) && hasCapability(ns, NET_CAPABILITY_DUN)) return ns;
+        }
+
+        return null;
+    }
 }
diff --git a/services/core/java/com/android/server/fingerprint/EnrollClient.java b/services/core/java/com/android/server/fingerprint/EnrollClient.java
deleted file mode 100644
index c9efcf2..0000000
--- a/services/core/java/com/android/server/fingerprint/EnrollClient.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.fingerprint;
-
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-import java.util.Arrays;
-
-/**
- * A class to keep track of the enrollment state for a given client.
- */
-public abstract class EnrollClient extends ClientMonitor {
-    private static final long MS_PER_SEC = 1000;
-    private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute
-    private byte[] mCryptoToken;
-
-    public EnrollClient(Context context, long halDeviceId, IBinder token,
-            IFingerprintServiceReceiver receiver, int userId, int groupId, byte [] cryptoToken,
-            boolean restricted, String owner) {
-        super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
-        mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
-    }
-
-    @Override
-    public boolean onEnrollResult(int fingerId, int groupId, int remaining) {
-        if (groupId != getGroupId()) {
-            Slog.w(TAG, "groupId != getGroupId(), groupId: " + groupId +
-                    " getGroupId():" + getGroupId());
-        }
-        if (remaining == 0) {
-            FingerprintUtils.getInstance().addFingerprintForUser(getContext(), fingerId,
-                    getTargetUserId());
-        }
-        return sendEnrollResult(fingerId, groupId, remaining);
-    }
-
-    /*
-     * @return true if we're done.
-     */
-    private boolean sendEnrollResult(int fpId, int groupId, int remaining) {
-        IFingerprintServiceReceiver receiver = getReceiver();
-        if (receiver == null)
-            return true; // client not listening
-
-        vibrateSuccess();
-        MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_ENROLL);
-        try {
-            receiver.onEnrollResult(getHalDeviceId(), fpId, groupId, remaining);
-            return remaining == 0;
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to notify EnrollResult:", e);
-            return true;
-        }
-    }
-
-    @Override
-    public int start() {
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "enroll: no fingerprint HAL!");
-            return ERROR_ESRCH;
-        }
-        final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
-        try {
-            final int result = daemon.enroll(mCryptoToken, getGroupId(), timeout);
-            if (result != 0) {
-                Slog.w(TAG, "startEnroll failed, result=" + result);
-                MetricsLogger.histogram(getContext(), "fingerprintd_enroll_start_error", result);
-                onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-                return result;
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "startEnroll failed", e);
-        }
-        return 0; // success
-    }
-
-    @Override
-    public int stop(boolean initiatedByClient) {
-        if (mAlreadyCancelled) {
-            Slog.w(TAG, "stopEnroll: already cancelled!");
-            return 0;
-        }
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "stopEnrollment: no fingerprint HAL!");
-            return ERROR_ESRCH;
-        }
-        try {
-            final int result = daemon.cancel();
-            if (result != 0) {
-                Slog.w(TAG, "startEnrollCancel failed, result = " + result);
-                return result;
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "stopEnrollment failed", e);
-        }
-        if (initiatedByClient) {
-            onError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, 0 /* vendorCode */);
-        }
-        mAlreadyCancelled = true;
-        return 0;
-    }
-
-    @Override
-    public boolean onRemoved(int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "onRemoved() called for enroll!");
-        return true; // Invalid for EnrollClient
-    }
-
-    @Override
-    public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for enroll!");
-        return true; // Invalid for EnrollClient
-    }
-
-    @Override
-    public boolean onAuthenticated(int fingerId, int groupId) {
-        if (DEBUG) Slog.w(TAG, "onAuthenticated() called for enroll!");
-        return true; // Invalid for EnrollClient
-    }
-
-}
diff --git a/services/core/java/com/android/server/fingerprint/EnumerateClient.java b/services/core/java/com/android/server/fingerprint/EnumerateClient.java
deleted file mode 100644
index b6bbd1b..0000000
--- a/services/core/java/com/android/server/fingerprint/EnumerateClient.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.fingerprint;
-
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-import com.android.internal.logging.MetricsLogger;
-
-/**
- * A class to keep track of the enumeration state for a given client.
- */
-public abstract class EnumerateClient extends ClientMonitor {
-    public EnumerateClient(Context context, long halDeviceId, IBinder token,
-        IFingerprintServiceReceiver receiver, int groupId, int userId,
-        boolean restricted, String owner) {
-        super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
-    }
-
-    @Override
-    public int start() {
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        // The fingerprint template ids will be removed when we get confirmation from the HAL
-        try {
-            final int result = daemon.enumerate();
-            if (result != 0) {
-                Slog.w(TAG, "start enumerate for user " + getTargetUserId()
-                    + " failed, result=" + result);
-                MetricsLogger.histogram(getContext(), "fingerprintd_enum_start_error", result);
-                onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-                return result;
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "startEnumeration failed", e);
-        }
-        return 0;
-    }
-
-    @Override
-    public int stop(boolean initiatedByClient) {
-        if (mAlreadyCancelled) {
-            Slog.w(TAG, "stopEnumerate: already cancelled!");
-            return 0;
-        }
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "stopEnumeration: no fingerprint HAL!");
-            return ERROR_ESRCH;
-        }
-        try {
-            final int result = daemon.cancel();
-            if (result != 0) {
-                Slog.w(TAG, "stop enumeration failed, result=" + result);
-                return result;
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "stopEnumeration failed", e);
-            return ERROR_ESRCH;
-        }
-
-        // We don't actually stop enumerate, but inform the client that the cancel operation
-        // succeeded so we can start the next operation.
-        if (initiatedByClient) {
-            onError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, 0 /* vendorCode */);
-        }
-        mAlreadyCancelled = true;
-        return 0; // success
-    }
-
-    @Override
-    public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
-        IFingerprintServiceReceiver receiver = getReceiver();
-        if (receiver == null)
-            return true; // client not listening
-        try {
-            receiver.onEnumerated(getHalDeviceId(), fingerId, groupId, remaining);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to notify enumerated:", e);
-        }
-        return remaining == 0;
-    }
-
-    @Override
-    public boolean onAuthenticated(int fingerId, int groupId) {
-        if (DEBUG) Slog.w(TAG, "onAuthenticated() called for enumerate!");
-        return true; // Invalid for Enumerate.
-    }
-
-    @Override
-    public boolean onEnrollResult(int fingerId, int groupId, int rem) {
-        if (DEBUG) Slog.w(TAG, "onEnrollResult() called for enumerate!");
-        return true; // Invalid for Enumerate.
-    }
-
-    @Override
-    public boolean onRemoved(int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "onRemoved() called for enumerate!");
-        return true; // Invalid for Enumerate.
-    }
-}
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
deleted file mode 100644
index 8e77820..0000000
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ /dev/null
@@ -1,1595 +0,0 @@
-/*
- * Copyright (C) 2014 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.fingerprint;
-
-import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_FINGERPRINT;
-import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
-import static android.Manifest.permission.USE_BIOMETRIC;
-import static android.Manifest.permission.USE_FINGERPRINT;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.app.ActivityTaskManager;
-import android.app.AlarmManager;
-import android.app.AppOpsManager;
-import android.app.IActivityManager;
-import android.app.IActivityTaskManager;
-import android.app.PendingIntent;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.TaskStackListener;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.IBiometricPromptReceiver;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintClientActiveCallback;
-import android.hardware.fingerprint.IFingerprintService;
-import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.DeadObjectException;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.IRemoteCallback;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.RemoteException;
-import android.os.SELinux;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.security.KeyStore;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.DumpUtils;
-import com.android.server.SystemServerInitThreadPool;
-import com.android.server.SystemService;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * A service to manage multiple clients that want to access the fingerprint HAL API.
- * The service is responsible for maintaining a list of clients and dispatching all
- * fingerprint-related events.
- *
- * @hide
- */
-public class FingerprintService extends SystemService implements IHwBinder.DeathRecipient {
-    static final String TAG = "FingerprintService";
-    static final boolean DEBUG = true;
-    private static final boolean CLEANUP_UNUSED_FP = true;
-    private static final String FP_DATA_DIR = "fpdata";
-    private static final int MSG_USER_SWITCHING = 10;
-    private static final String ACTION_LOCKOUT_RESET =
-            "com.android.server.fingerprint.ACTION_LOCKOUT_RESET";
-    private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user";
-
-    private class PerformanceStats {
-        int accept; // number of accepted fingerprints
-        int reject; // number of rejected fingerprints
-        int acquire; // total number of acquisitions. Should be >= accept+reject due to poor image
-                     // acquisition in some cases (too fast, too slow, dirty sensor, etc.)
-        int lockout; // total number of lockouts
-        int permanentLockout; // total number of permanent lockouts
-    }
-
-    private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors =
-            new ArrayList<>();
-    private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks =
-            new CopyOnWriteArrayList<>();
-    private final Map<Integer, Long> mAuthenticatorIds =
-            Collections.synchronizedMap(new HashMap<>());
-    private final AppOpsManager mAppOps;
-    private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000;
-    private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
-    private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20;
-
-    private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms
-    private final String mKeyguardPackage;
-    private int mCurrentUserId = UserHandle.USER_NULL;
-    private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance();
-    private Context mContext;
-    private long mHalDeviceId;
-    private SparseBooleanArray mTimedLockoutCleared;
-    private SparseIntArray mFailedAttempts;
-    @GuardedBy("this")
-    private IBiometricsFingerprint mDaemon;
-    private IStatusBarService mStatusBarService;
-    private final IActivityManager mActivityManager;
-    private final IActivityTaskManager mActivityTaskManager;
-    private final PowerManager mPowerManager;
-    private final AlarmManager mAlarmManager;
-    private final UserManager mUserManager;
-    private ClientMonitor mCurrentClient;
-    private ClientMonitor mPendingClient;
-    private PerformanceStats mPerformanceStats;
-
-    private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
-    private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw fingerprints
-
-    private class UserFingerprint {
-        Fingerprint f;
-        int userId;
-        public UserFingerprint(Fingerprint f, int userId) {
-            this.f = f;
-            this.userId = userId;
-        }
-    }
-
-    // Normal fingerprint authentications are tracked by mPerformanceMap.
-    private HashMap<Integer, PerformanceStats> mPerformanceMap = new HashMap<>();
-
-    // Transactions that make use of CryptoObjects are tracked by mCryptoPerformaceMap.
-    private HashMap<Integer, PerformanceStats> mCryptoPerformanceMap = new HashMap<>();
-
-    private Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(android.os.Message msg) {
-            switch (msg.what) {
-                case MSG_USER_SWITCHING:
-                    handleUserSwitching(msg.arg1);
-                    break;
-
-                default:
-                    Slog.w(TAG, "Unknown message:" + msg.what);
-            }
-        }
-    };
-
-    private final BroadcastReceiver mLockoutReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) {
-                final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
-                resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
-            }
-        }
-    };
-
-    private final Runnable mResetFailedAttemptsForCurrentUserRunnable = new Runnable() {
-        @Override
-        public void run() {
-            resetFailedAttemptsForUser(true /* clearAttemptCounter */,
-                    ActivityManager.getCurrentUser());
-        }
-    };
-
-    private final Runnable mResetClientState = new Runnable() {
-        @Override
-        public void run() {
-            // Warning: if we get here, the driver never confirmed our call to cancel the current
-            // operation (authenticate, enroll, remove, enumerate, etc), which is
-            // really bad.  The result will be a 3-second delay in starting each new client.
-            // If you see this on a device, make certain the driver notifies with
-            // {@link FingerprintManager#FINGERPRINT_ERROR_CANCEL} in response to cancel()
-            // once it has successfully switched to the IDLE state in the fingerprint HAL.
-            // Additionally,{@link FingerprintManager#FINGERPRINT_ERROR_CANCEL} should only be sent
-            // in response to an actual cancel() call.
-            Slog.w(TAG, "Client "
-                    + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null")
-                    + " failed to respond to cancel, starting client "
-                    + (mPendingClient != null ? mPendingClient.getOwnerString() : "null"));
-
-            mCurrentClient = null;
-            startClient(mPendingClient, false);
-        }
-    };
-
-    private final TaskStackListener mTaskStackListener = new TaskStackListener() {
-        @Override
-        public void onTaskStackChanged() {
-            try {
-                if (!(mCurrentClient instanceof AuthenticationClient)) {
-                    return;
-                }
-                final String currentClient = mCurrentClient.getOwnerString();
-                if (isKeyguard(currentClient)) {
-                    return; // Keyguard is always allowed
-                }
-                List<ActivityManager.RunningTaskInfo> runningTasks =
-                        mActivityTaskManager.getTasks(1);
-                if (!runningTasks.isEmpty()) {
-                    final String topPackage = runningTasks.get(0).topActivity.getPackageName();
-                    if (!topPackage.contentEquals(currentClient)) {
-                        Slog.e(TAG, "Stopping background authentication, top: " + topPackage
-                                + " currentClient: " + currentClient);
-                        mCurrentClient.stop(false /* initiatedByClient */);
-                    }
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to get running tasks", e);
-            }
-        }
-    };
-
-    public FingerprintService(Context context) {
-        super(context);
-        mContext = context;
-        mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString(
-                com.android.internal.R.string.config_keyguardComponent)).getPackageName();
-        mAppOps = context.getSystemService(AppOpsManager.class);
-        mPowerManager = mContext.getSystemService(PowerManager.class);
-        mAlarmManager = mContext.getSystemService(AlarmManager.class);
-        mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
-                RESET_FINGERPRINT_LOCKOUT, null /* handler */);
-        mUserManager = UserManager.get(mContext);
-        mTimedLockoutCleared = new SparseBooleanArray();
-        mFailedAttempts = new SparseIntArray();
-        mStatusBarService = IStatusBarService.Stub.asInterface(
-                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-        mActivityManager = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
-                .getService();
-        mActivityTaskManager = ((ActivityTaskManager) context.getSystemService(
-                Context.ACTIVITY_TASK_SERVICE)).getService();
-    }
-
-    @Override
-    public void serviceDied(long cookie) {
-        Slog.v(TAG, "fingerprint HAL died");
-        MetricsLogger.count(mContext, "fingerprintd_died", 1);
-        handleError(mHalDeviceId, FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                0 /*vendorCode */);
-    }
-
-    public synchronized IBiometricsFingerprint getFingerprintDaemon() {
-        if (mDaemon == null) {
-            Slog.v(TAG, "mDaemon was null, reconnect to fingerprint");
-            try {
-                mDaemon = IBiometricsFingerprint.getService();
-            } catch (java.util.NoSuchElementException e) {
-                // Service doesn't exist or cannot be opened. Logged below.
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to get biometric interface", e);
-            }
-            if (mDaemon == null) {
-                Slog.w(TAG, "fingerprint HIDL not available");
-                return null;
-            }
-
-            mDaemon.asBinder().linkToDeath(this, 0);
-
-            try {
-                mHalDeviceId = mDaemon.setNotify(mDaemonCallback);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to open fingerprint HAL", e);
-                mDaemon = null; // try again later!
-            }
-
-            if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + mHalDeviceId);
-            if (mHalDeviceId != 0) {
-                loadAuthenticatorIds();
-                updateActiveGroup(ActivityManager.getCurrentUser(), null);
-                doFingerprintCleanupForUser(ActivityManager.getCurrentUser());
-            } else {
-                Slog.w(TAG, "Failed to open Fingerprint HAL!");
-                MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1);
-                mDaemon = null;
-            }
-        }
-        return mDaemon;
-    }
-
-    /** Populates existing authenticator ids. To be used only during the start of the service. */
-    private void loadAuthenticatorIds() {
-        // This operation can be expensive, so keep track of the elapsed time. Might need to move to
-        // background if it takes too long.
-        long t = System.currentTimeMillis();
-        mAuthenticatorIds.clear();
-        for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
-            int userId = getUserOrWorkProfileId(null, user.id);
-            if (!mAuthenticatorIds.containsKey(userId)) {
-                updateActiveGroup(userId, null);
-            }
-        }
-
-        t = System.currentTimeMillis() - t;
-        if (t > 1000) {
-            Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms");
-        }
-    }
-
-    /**
-     * This method should be called upon connection to the daemon, and when user switches.
-     * @param userId
-     */
-    private void doFingerprintCleanupForUser(int userId) {
-        if (CLEANUP_UNUSED_FP) {
-            enumerateUser(userId);
-        }
-    }
-
-    private void clearEnumerateState() {
-        if (DEBUG) Slog.v(TAG, "clearEnumerateState()");
-        mUnknownFingerprints.clear();
-    }
-
-    private void enumerateUser(int userId) {
-        if (DEBUG) Slog.v(TAG, "Enumerating user(" + userId + ")");
-        boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-        startEnumerate(mToken, userId, null, restricted, true /* internal */);
-    }
-
-    // Remove unknown fingerprints from hardware
-    private void cleanupUnknownFingerprints() {
-        if (!mUnknownFingerprints.isEmpty()) {
-            UserFingerprint uf = mUnknownFingerprints.get(0);
-            mUnknownFingerprints.remove(uf);
-            boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-            startRemove(mToken, uf.f.getFingerId(), uf.f.getGroupId(), uf.userId, null,
-                    restricted, true /* internal */);
-        } else {
-            clearEnumerateState();
-        }
-    }
-
-    protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
-        ClientMonitor client = mCurrentClient;
-
-        if ( !(client instanceof InternalRemovalClient) && !(client instanceof EnumerateClient) ) {
-            return;
-        }
-        client.onEnumerationResult(fingerId, groupId, remaining);
-
-        // All fingerprints in hardware for this user were enumerated
-        if (remaining == 0) {
-            if (client instanceof InternalEnumerateClient) {
-                List<Fingerprint> unknownFingerprints =
-                        ((InternalEnumerateClient) client).getUnknownFingerprints();
-
-                if (!unknownFingerprints.isEmpty()) {
-                    Slog.w(TAG, "Adding " + unknownFingerprints.size() +
-                            " fingerprints for deletion");
-                }
-                for (Fingerprint f : unknownFingerprints) {
-                    mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
-                }
-                removeClient(client);
-                cleanupUnknownFingerprints();
-            } else {
-                removeClient(client);
-            }
-        }
-    }
-
-    protected void handleError(long deviceId, int error, int vendorCode) {
-        ClientMonitor client = mCurrentClient;
-        if (client instanceof InternalRemovalClient || client instanceof InternalEnumerateClient) {
-            clearEnumerateState();
-        }
-        if (client != null && client.onError(error, vendorCode)) {
-            removeClient(client);
-        }
-
-        if (DEBUG) Slog.v(TAG, "handleError(client="
-                + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
-        // This is the magic code that starts the next client when the old client finishes.
-        if (error == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
-            mHandler.removeCallbacks(mResetClientState);
-            if (mPendingClient != null) {
-                if (DEBUG) Slog.v(TAG, "start pending client " + mPendingClient.getOwnerString());
-                startClient(mPendingClient, false);
-                mPendingClient = null;
-            }
-        } else if (error == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
-            // If we get HW_UNAVAILABLE, try to connect again later...
-            Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client.");
-            synchronized (this) {
-                mDaemon = null;
-                mHalDeviceId = 0;
-                mCurrentUserId = UserHandle.USER_NULL;
-            }
-        }
-    }
-
-    protected void handleRemoved(long deviceId, int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "Removed: fid=" + fingerId
-                + ", gid=" + groupId
-                + ", dev=" + deviceId
-                + ", rem=" + remaining);
-
-        ClientMonitor client = mCurrentClient;
-        if (client != null && client.onRemoved(fingerId, groupId, remaining)) {
-            removeClient(client);
-            // When the last fingerprint of a group is removed, update the authenticator id
-            if (!hasEnrolledFingerprints(groupId)) {
-                updateActiveGroup(groupId, null);
-            }
-        }
-        if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
-            cleanupUnknownFingerprints();
-        } else if (client instanceof InternalRemovalClient){
-            clearEnumerateState();
-        }
-    }
-
-    protected void handleAuthenticated(long deviceId, int fingerId, int groupId,
-            ArrayList<Byte> token) {
-        ClientMonitor client = mCurrentClient;
-        if (fingerId != 0) {
-            // Ugh...
-            final byte[] byteToken = new byte[token.size()];
-            for (int i = 0; i < token.size(); i++) {
-                byteToken[i] = token.get(i);
-            }
-            // Send to Keystore
-            KeyStore.getInstance().addAuthToken(byteToken);
-        }
-        if (client != null && client.onAuthenticated(fingerId, groupId)) {
-            removeClient(client);
-        }
-        if (fingerId != 0) {
-            mPerformanceStats.accept++;
-        } else {
-            mPerformanceStats.reject++;
-        }
-    }
-
-    protected void handleAcquired(long deviceId, int acquiredInfo, int vendorCode) {
-        ClientMonitor client = mCurrentClient;
-        if (client != null && client.onAcquired(acquiredInfo, vendorCode)) {
-            removeClient(client);
-        }
-        if (mPerformanceStats != null && getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
-                && client instanceof AuthenticationClient) {
-            // ignore enrollment acquisitions or acquisitions when we're locked out
-            mPerformanceStats.acquire++;
-        }
-    }
-
-    protected void handleEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
-        ClientMonitor client = mCurrentClient;
-        if (client != null && client.onEnrollResult(fingerId, groupId, remaining)) {
-            removeClient(client);
-            // When enrollment finishes, update this group's authenticator id, as the HAL has
-            // already generated a new authenticator id when the new fingerprint is enrolled.
-            updateActiveGroup(groupId, null);
-        }
-    }
-
-    private void userActivity() {
-        long now = SystemClock.uptimeMillis();
-        mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
-    }
-
-    void handleUserSwitching(int userId) {
-        if (mCurrentClient instanceof InternalRemovalClient
-                || mCurrentClient instanceof InternalEnumerateClient) {
-            Slog.w(TAG, "User switched while performing cleanup");
-            removeClient(mCurrentClient);
-            clearEnumerateState();
-        }
-        updateActiveGroup(userId, null);
-        doFingerprintCleanupForUser(userId);
-    }
-
-    private void removeClient(ClientMonitor client) {
-        if (client != null) {
-            client.destroy();
-            if (client != mCurrentClient && mCurrentClient != null) {
-                Slog.w(TAG, "Unexpected client: " + client.getOwnerString() + "expected: "
-                        + mCurrentClient != null ? mCurrentClient.getOwnerString() : "null");
-            }
-        }
-        if (mCurrentClient != null) {
-            if (DEBUG) Slog.v(TAG, "Done with client: " + client.getOwnerString());
-            mCurrentClient = null;
-        }
-        if (mPendingClient == null) {
-            notifyClientActiveCallbacks(false);
-        }
-    }
-
-    private int getLockoutMode() {
-        final int currentUser = ActivityManager.getCurrentUser();
-        final int failedAttempts = mFailedAttempts.get(currentUser, 0);
-        if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
-            return AuthenticationClient.LOCKOUT_PERMANENT;
-        } else if (failedAttempts > 0 &&
-                mTimedLockoutCleared.get(currentUser, false) == false
-                && (failedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
-            return AuthenticationClient.LOCKOUT_TIMED;
-        }
-        return AuthenticationClient.LOCKOUT_NONE;
-    }
-
-    private void scheduleLockoutResetForUser(int userId) {
-        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
-                getLockoutResetIntentForUser(userId));
-    }
-
-    private void cancelLockoutResetForUser(int userId) {
-        mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
-    }
-
-    private PendingIntent getLockoutResetIntentForUser(int userId) {
-        return PendingIntent.getBroadcast(mContext, userId,
-                new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
-                PendingIntent.FLAG_UPDATE_CURRENT);
-    }
-
-    public long startPreEnroll(IBinder token) {
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "startPreEnroll: no fingerprint HAL!");
-            return 0;
-        }
-        try {
-            return daemon.preEnroll();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "startPreEnroll failed", e);
-        }
-        return 0;
-    }
-
-    public int startPostEnroll(IBinder token) {
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "startPostEnroll: no fingerprint HAL!");
-            return 0;
-        }
-        try {
-            return daemon.postEnroll();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "startPostEnroll failed", e);
-        }
-        return 0;
-    }
-
-    /**
-     * Calls fingerprint HAL to switch states to the new task. If there's already a current task,
-     * it calls cancel() and sets mPendingClient to begin when the current task finishes
-     * ({@link FingerprintManager#FINGERPRINT_ERROR_CANCELED}).
-     * @param newClient the new client that wants to connect
-     * @param initiatedByClient true for authenticate, remove and enroll
-     */
-    private void startClient(ClientMonitor newClient, boolean initiatedByClient) {
-        ClientMonitor currentClient = mCurrentClient;
-        if (currentClient != null) {
-            if (DEBUG) Slog.v(TAG, "request stop current client " + currentClient.getOwnerString());
-            if (currentClient instanceof InternalEnumerateClient ||
-                    currentClient instanceof InternalRemovalClient) {
-                // This condition means we're currently running internal diagnostics to
-                // remove extra fingerprints in the hardware and/or the software
-                // TODO: design an escape hatch in case client never finishes
-                if (newClient != null) {
-                    Slog.w(TAG, "Internal cleanup in progress but trying to start client "
-                            + newClient.getClass().getSuperclass().getSimpleName()
-                            + "(" + newClient.getOwnerString() + ")"
-                            + ", initiatedByClient = " + initiatedByClient);
-                }
-            }
-            else {
-                currentClient.stop(initiatedByClient);
-            }
-            mPendingClient = newClient;
-            mHandler.removeCallbacks(mResetClientState);
-            mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
-        } else if (newClient != null) {
-            mCurrentClient = newClient;
-            if (DEBUG) Slog.v(TAG, "starting client "
-                    + newClient.getClass().getSuperclass().getSimpleName()
-                    + "(" + newClient.getOwnerString() + ")"
-                    + ", initiatedByClient = " + initiatedByClient);
-            notifyClientActiveCallbacks(true);
-
-            newClient.start();
-        }
-    }
-
-    void startRemove(IBinder token, int fingerId, int groupId, int userId,
-            IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) {
-        if (token == null) {
-            Slog.w(TAG, "startRemove: token is null");
-            return;
-        }
-        if (receiver == null) {
-            Slog.w(TAG, "startRemove: receiver is null");
-            return;
-        }
-
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "startRemove: no fingerprint HAL!");
-            return;
-        }
-
-        if (internal) {
-            Context context = getContext();
-            InternalRemovalClient client = new InternalRemovalClient(context, mHalDeviceId,
-                    token, receiver, fingerId, groupId, userId, restricted,
-                    context.getOpPackageName()) {
-                @Override
-                public void notifyUserActivity() {
-
-                }
-                @Override
-                public IBiometricsFingerprint getFingerprintDaemon() {
-                    return FingerprintService.this.getFingerprintDaemon();
-                }
-            };
-            startClient(client, true);
-        }
-        else {
-            RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token,
-                    receiver, fingerId, groupId, userId, restricted, token.toString()) {
-                @Override
-                public void notifyUserActivity() {
-                    FingerprintService.this.userActivity();
-                }
-
-                @Override
-                public IBiometricsFingerprint getFingerprintDaemon() {
-                    return FingerprintService.this.getFingerprintDaemon();
-                }
-            };
-            startClient(client, true);
-        }
-    }
-
-    void startEnumerate(IBinder token, int userId,
-        IFingerprintServiceReceiver receiver, boolean restricted, boolean internal) {
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "startEnumerate: no fingerprint HAL!");
-            return;
-        }
-        if (internal) {
-            List<Fingerprint> enrolledList = getEnrolledFingerprints(userId);
-            Context context = getContext();
-            InternalEnumerateClient client = new InternalEnumerateClient(context, mHalDeviceId,
-                    token, receiver, userId, userId, restricted, context.getOpPackageName(),
-                    enrolledList) {
-                @Override
-                public void notifyUserActivity() {
-
-                }
-
-                @Override
-                public IBiometricsFingerprint getFingerprintDaemon() {
-                    return FingerprintService.this.getFingerprintDaemon();
-                }
-            };
-            startClient(client, true);
-        }
-        else {
-            EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token,
-                    receiver, userId, userId, restricted, token.toString()) {
-                @Override
-                public void notifyUserActivity() {
-                    FingerprintService.this.userActivity();
-                }
-
-                @Override
-                public IBiometricsFingerprint getFingerprintDaemon() {
-                    return FingerprintService.this.getFingerprintDaemon();
-                }
-            };
-            startClient(client, true);
-        }
-    }
-
-    public List<Fingerprint> getEnrolledFingerprints(int userId) {
-        return mFingerprintUtils.getFingerprintsForUser(mContext, userId);
-    }
-
-    public boolean hasEnrolledFingerprints(int userId) {
-        if (userId != UserHandle.getCallingUserId()) {
-            checkPermission(INTERACT_ACROSS_USERS);
-        }
-        return mFingerprintUtils.getFingerprintsForUser(mContext, userId).size() > 0;
-    }
-
-    boolean hasPermission(String permission) {
-        return getContext().checkCallingOrSelfPermission(permission)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    void checkPermission(String permission) {
-        getContext().enforceCallingOrSelfPermission(permission,
-                "Must have " + permission + " permission.");
-    }
-
-    int getEffectiveUserId(int userId) {
-        UserManager um = UserManager.get(mContext);
-        if (um != null) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            userId = um.getCredentialOwnerProfile(userId);
-            Binder.restoreCallingIdentity(callingIdentity);
-        } else {
-            Slog.e(TAG, "Unable to acquire UserManager");
-        }
-        return userId;
-    }
-
-    boolean isCurrentUserOrProfile(int userId) {
-        UserManager um = UserManager.get(mContext);
-        if (um == null) {
-            Slog.e(TAG, "Unable to acquire UserManager");
-            return false;
-        }
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            // Allow current user or profiles of the current user...
-            for (int profileId : um.getEnabledProfileIds(ActivityManager.getCurrentUser())) {
-                if (profileId == userId) {
-                    return true;
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-
-        return false;
-    }
-
-    private boolean isForegroundActivity(int uid, int pid) {
-        try {
-            List<RunningAppProcessInfo> procs =
-                    ActivityManager.getService().getRunningAppProcesses();
-            int N = procs.size();
-            for (int i = 0; i < N; i++) {
-                RunningAppProcessInfo proc = procs.get(i);
-                if (proc.pid == pid && proc.uid == uid
-                        && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
-                    return true;
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.w(TAG, "am.getRunningAppProcesses() failed");
-        }
-        return false;
-    }
-
-    /**
-     * @param opPackageName name of package for caller
-     * @param requireForeground only allow this call while app is in the foreground
-     * @return true if caller can use fingerprint API
-     */
-    private boolean canUseFingerprint(String opPackageName, boolean requireForeground, int uid,
-            int pid, int userId) {
-        if (getContext().checkCallingPermission(USE_FINGERPRINT)
-                != PackageManager.PERMISSION_GRANTED) {
-            checkPermission(USE_BIOMETRIC);
-        }
-
-        if (isKeyguard(opPackageName)) {
-            return true; // Keyguard is always allowed
-        }
-        if (!isCurrentUserOrProfile(userId)) {
-            Slog.w(TAG,"Rejecting " + opPackageName + " ; not a current user or profile");
-            return false;
-        }
-        if (mAppOps.noteOp(AppOpsManager.OP_USE_FINGERPRINT, uid, opPackageName)
-                != AppOpsManager.MODE_ALLOWED) {
-            Slog.w(TAG, "Rejecting " + opPackageName + " ; permission denied");
-            return false;
-        }
-        if (requireForeground && !(isForegroundActivity(uid, pid) || currentClient(opPackageName))){
-            Slog.w(TAG, "Rejecting " + opPackageName + " ; not in foreground");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * @param opPackageName package of the caller
-     * @return true if this is the same client currently using fingerprint
-     */
-    private boolean currentClient(String opPackageName) {
-        return mCurrentClient != null && mCurrentClient.getOwnerString().equals(opPackageName);
-    }
-
-    /**
-     * @param clientPackage
-     * @return true if this is keyguard package
-     */
-    private boolean isKeyguard(String clientPackage) {
-        return mKeyguardPackage.equals(clientPackage);
-    }
-
-    private void addLockoutResetMonitor(FingerprintServiceLockoutResetMonitor monitor) {
-        if (!mLockoutMonitors.contains(monitor)) {
-            mLockoutMonitors.add(monitor);
-        }
-    }
-
-    private void removeLockoutResetCallback(
-            FingerprintServiceLockoutResetMonitor monitor) {
-        mLockoutMonitors.remove(monitor);
-    }
-
-    private void notifyLockoutResetMonitors() {
-        for (int i = 0; i < mLockoutMonitors.size(); i++) {
-            mLockoutMonitors.get(i).sendLockoutReset();
-        }
-    }
-
-    private void notifyClientActiveCallbacks(boolean isActive) {
-        List<IFingerprintClientActiveCallback> callbacks = mClientActiveCallbacks;
-        for (int i = 0; i < callbacks.size(); i++) {
-            try {
-                callbacks.get(i).onClientActiveChanged(isActive);
-            } catch (RemoteException re) {
-                // If the remote is dead, stop notifying it
-                mClientActiveCallbacks.remove(callbacks.get(i));
-            }
-        }
-    }
-
-    private void startAuthentication(IBinder token, long opId, int callingUserId, int groupId,
-                IFingerprintServiceReceiver receiver, int flags, boolean restricted,
-                String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver) {
-        updateActiveGroup(groupId, opPackageName);
-
-        if (DEBUG) Slog.v(TAG, "startAuthentication(" + opPackageName + ")");
-
-        AuthenticationClient client = new AuthenticationClient(getContext(), mHalDeviceId, token,
-                receiver, mCurrentUserId, groupId, opId, restricted, opPackageName, bundle,
-                dialogReceiver, mStatusBarService) {
-            @Override
-            public void onStart() {
-                try {
-                    mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Could not register task stack listener", e);
-                }
-            }
-
-            @Override
-            public void onStop() {
-                try {
-                    mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Could not unregister task stack listener", e);
-                }
-            }
-
-            @Override
-            public int handleFailedAttempt() {
-                final int currentUser = ActivityManager.getCurrentUser();
-                mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1);
-                mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false);
-                final int lockoutMode = getLockoutMode();
-                if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
-                    mPerformanceStats.permanentLockout++;
-                } else if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
-                    mPerformanceStats.lockout++;
-                }
-
-                // Failing multiple times will continue to push out the lockout time
-                if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
-                    scheduleLockoutResetForUser(currentUser);
-                    return lockoutMode;
-                }
-                return AuthenticationClient.LOCKOUT_NONE;
-            }
-
-            @Override
-            public void resetFailedAttempts() {
-                FingerprintService.this.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
-                        ActivityManager.getCurrentUser());
-            }
-
-            @Override
-            public void notifyUserActivity() {
-                FingerprintService.this.userActivity();
-            }
-
-            @Override
-            public IBiometricsFingerprint getFingerprintDaemon() {
-                return FingerprintService.this.getFingerprintDaemon();
-            }
-        };
-
-        int lockoutMode = getLockoutMode();
-        if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
-            Slog.v(TAG, "In lockout mode(" + lockoutMode +
-                    ") ; disallowing authentication");
-            int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
-                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT :
-                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
-            if (!client.onError(errorCode, 0 /* vendorCode */)) {
-                Slog.w(TAG, "Cannot send permanent lockout message to client");
-            }
-            return;
-        }
-        startClient(client, true /* initiatedByClient */);
-    }
-
-    private void startEnrollment(IBinder token, byte [] cryptoToken, int userId,
-            IFingerprintServiceReceiver receiver, int flags, boolean restricted,
-            String opPackageName) {
-        updateActiveGroup(userId, opPackageName);
-
-        final int groupId = userId; // default group for fingerprint enrollment
-
-        EnrollClient client = new EnrollClient(getContext(), mHalDeviceId, token, receiver,
-                userId, groupId, cryptoToken, restricted, opPackageName) {
-
-            @Override
-            public IBiometricsFingerprint getFingerprintDaemon() {
-                return FingerprintService.this.getFingerprintDaemon();
-            }
-
-            @Override
-            public void notifyUserActivity() {
-                FingerprintService.this.userActivity();
-            }
-        };
-        startClient(client, true /* initiatedByClient */);
-    }
-
-    // attempt counter should only be cleared when Keyguard goes away or when
-    // a fingerprint is successfully authenticated
-    protected void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
-        if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
-            Slog.v(TAG, "Reset fingerprint lockout, clearAttemptCounter=" + clearAttemptCounter);
-        }
-        if (clearAttemptCounter) {
-            mFailedAttempts.put(userId, 0);
-        }
-        mTimedLockoutCleared.put(userId, true);
-        // If we're asked to reset failed attempts externally (i.e. from Keyguard),
-        // the alarm might still be pending; remove it.
-        cancelLockoutResetForUser(userId);
-        notifyLockoutResetMonitors();
-    }
-
-    private class FingerprintServiceLockoutResetMonitor implements IBinder.DeathRecipient {
-
-        private static final long WAKELOCK_TIMEOUT_MS = 2000;
-        private final IFingerprintServiceLockoutResetCallback mCallback;
-        private final WakeLock mWakeLock;
-
-        public FingerprintServiceLockoutResetMonitor(
-                IFingerprintServiceLockoutResetCallback callback) {
-            mCallback = callback;
-            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                    "lockout reset callback");
-            try {
-                mCallback.asBinder().linkToDeath(FingerprintServiceLockoutResetMonitor.this, 0);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "caught remote exception in linkToDeath", e);
-            }
-        }
-
-        public void sendLockoutReset() {
-            if (mCallback != null) {
-                try {
-                    mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
-                    mCallback.onLockoutReset(mHalDeviceId, new IRemoteCallback.Stub() {
-
-                        @Override
-                        public void sendResult(Bundle data) throws RemoteException {
-                            releaseWakelock();
-                        }
-                    });
-                } catch (DeadObjectException e) {
-                    Slog.w(TAG, "Death object while invoking onLockoutReset: ", e);
-                    mHandler.post(mRemoveCallbackRunnable);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to invoke onLockoutReset: ", e);
-                    releaseWakelock();
-                }
-            }
-        }
-
-        private final Runnable mRemoveCallbackRunnable = new Runnable() {
-            @Override
-            public void run() {
-                releaseWakelock();
-                removeLockoutResetCallback(FingerprintServiceLockoutResetMonitor.this);
-            }
-        };
-
-        @Override
-        public void binderDied() {
-            Slog.e(TAG, "Lockout reset callback binder died");
-            mHandler.post(mRemoveCallbackRunnable);
-        }
-
-        private void releaseWakelock() {
-            if (mWakeLock.isHeld()) {
-                mWakeLock.release();
-            }
-        }
-    }
-
-    private IBiometricsFingerprintClientCallback mDaemonCallback =
-            new IBiometricsFingerprintClientCallback.Stub() {
-
-        @Override
-        public void onEnrollResult(final long deviceId, final int fingerId, final int groupId,
-                final int remaining) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    handleEnrollResult(deviceId, fingerId, groupId, remaining);
-                }
-            });
-        }
-
-        @Override
-        public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    handleAcquired(deviceId, acquiredInfo, vendorCode);
-                }
-            });
-        }
-
-        @Override
-        public void onAuthenticated(final long deviceId, final int fingerId, final int groupId,
-                ArrayList<Byte> token) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    handleAuthenticated(deviceId, fingerId, groupId, token);
-                }
-            });
-        }
-
-        @Override
-        public void onError(final long deviceId, final int error, final int vendorCode) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    handleError(deviceId, error, vendorCode);
-                }
-            });
-        }
-
-        @Override
-        public void onRemoved(final long deviceId, final int fingerId, final int groupId, final int remaining) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    handleRemoved(deviceId, fingerId, groupId, remaining);
-                }
-            });
-        }
-
-        @Override
-        public void onEnumerate(final long deviceId, final int fingerId, final int groupId,
-                final int remaining) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    handleEnumerate(deviceId, fingerId, groupId, remaining);
-                }
-            });
-        }
-    };
-
-    private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
-        @Override // Binder call
-        public long preEnroll(IBinder token) {
-            checkPermission(MANAGE_FINGERPRINT);
-            return startPreEnroll(token);
-        }
-
-        @Override // Binder call
-        public int postEnroll(IBinder token) {
-            checkPermission(MANAGE_FINGERPRINT);
-            return startPostEnroll(token);
-        }
-
-        @Override // Binder call
-        public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
-                final IFingerprintServiceReceiver receiver, final int flags,
-                final String opPackageName) {
-            checkPermission(MANAGE_FINGERPRINT);
-            final int limit = mContext.getResources().getInteger(
-                    com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
-
-            final int enrolled = FingerprintService.this.getEnrolledFingerprints(userId).size();
-            if (enrolled >= limit) {
-                Slog.w(TAG, "Too many fingerprints registered");
-                return;
-            }
-
-            // Group ID is arbitrarily set to parent profile user ID. It just represents
-            // the default fingerprints for the user.
-            if (!isCurrentUserOrProfile(userId)) {
-                return;
-            }
-
-            final boolean restricted = isRestricted();
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    startEnrollment(token, cryptoToken, userId, receiver, flags,
-                            restricted, opPackageName);
-                }
-            });
-        }
-
-        private boolean isRestricted() {
-            // Only give privileged apps (like Settings) access to fingerprint info
-            final boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-            return restricted;
-        }
-
-        @Override // Binder call
-        public void cancelEnrollment(final IBinder token) {
-            checkPermission(MANAGE_FINGERPRINT);
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    ClientMonitor client = mCurrentClient;
-                    if (client instanceof EnrollClient && client.getToken() == token) {
-                        client.stop(client.getToken() == token);
-                    }
-                }
-            });
-        }
-
-        @Override // Binder call
-        public void authenticate(final IBinder token, final long opId, final int groupId,
-                final IFingerprintServiceReceiver receiver, final int flags,
-                final String opPackageName, final Bundle bundle,
-                final IBiometricPromptReceiver dialogReceiver) {
-            final int callingUid = Binder.getCallingUid();
-            final int callingPid = Binder.getCallingPid();
-            final int callingUserId = UserHandle.getCallingUserId();
-            final boolean restricted = isRestricted();
-
-            if (!canUseFingerprint(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
-                    callingUserId)) {
-                if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName);
-                return;
-            }
-
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    MetricsLogger.histogram(mContext, "fingerprint_token", opId != 0L ? 1 : 0);
-
-                    // Get performance stats object for this user.
-                    HashMap<Integer, PerformanceStats> pmap
-                            = (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap;
-                    PerformanceStats stats = pmap.get(mCurrentUserId);
-                    if (stats == null) {
-                        stats = new PerformanceStats();
-                        pmap.put(mCurrentUserId, stats);
-                    }
-                    mPerformanceStats = stats;
-
-                    startAuthentication(token, opId, callingUserId, groupId, receiver,
-                            flags, restricted, opPackageName, bundle, dialogReceiver);
-                }
-            });
-        }
-
-        @Override // Binder call
-        public void cancelAuthentication(final IBinder token, final String opPackageName) {
-            final int callingUid = Binder.getCallingUid();
-            final int callingPid = Binder.getCallingPid();
-            final int callingUserId = UserHandle.getCallingUserId();
-
-            if (!canUseFingerprint(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
-                    callingUserId)) {
-                if (DEBUG) Slog.v(TAG, "cancelAuthentication(): reject " + opPackageName);
-                return;
-            }
-
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    ClientMonitor client = mCurrentClient;
-                    if (client instanceof AuthenticationClient) {
-                        if (client.getToken() == token) {
-                            if (DEBUG) Slog.v(TAG, "stop client " + client.getOwnerString());
-                            client.stop(client.getToken() == token);
-                        } else {
-                            if (DEBUG) Slog.v(TAG, "can't stop client "
-                                    + client.getOwnerString() + " since tokens don't match");
-                        }
-                    } else if (client != null) {
-                        if (DEBUG) Slog.v(TAG, "can't cancel non-authenticating client "
-                                + client.getOwnerString());
-                    }
-                }
-            });
-        }
-
-        @Override // Binder call
-        public void setActiveUser(final int userId) {
-            checkPermission(MANAGE_FINGERPRINT);
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    updateActiveGroup(userId, null);
-                }
-            });
-        }
-
-        @Override // Binder call
-        public void remove(final IBinder token, final int fingerId, final int groupId,
-                final int userId, final IFingerprintServiceReceiver receiver) {
-            checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission
-            final boolean restricted = isRestricted();
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    startRemove(token, fingerId, groupId, userId, receiver,
-                            restricted, false /* internal */);
-                }
-            });
-        }
-
-        @Override // Binder call
-        public void enumerate(final IBinder token, final int userId,
-            final IFingerprintServiceReceiver receiver) {
-            checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission
-            final boolean restricted = isRestricted();
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    startEnumerate(token, userId, receiver, restricted, false /* internal */);
-                }
-            });
-        }
-
-        @Override // Binder call
-        public boolean isHardwareDetected(long deviceId, String opPackageName) {
-            if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
-                    Binder.getCallingUid(), Binder.getCallingPid(),
-                    UserHandle.getCallingUserId())) {
-                return false;
-            }
-
-            final long token = Binder.clearCallingIdentity();
-            try {
-                IBiometricsFingerprint daemon = getFingerprintDaemon();
-                return daemon != null && mHalDeviceId != 0;
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override // Binder call
-        public void rename(final int fingerId, final int groupId, final String name) {
-            checkPermission(MANAGE_FINGERPRINT);
-            if (!isCurrentUserOrProfile(groupId)) {
-                return;
-            }
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mFingerprintUtils.renameFingerprintForUser(mContext, fingerId,
-                            groupId, name);
-                }
-            });
-        }
-
-        @Override // Binder call
-        public List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) {
-            if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
-                    Binder.getCallingUid(), Binder.getCallingPid(),
-                    UserHandle.getCallingUserId())) {
-                return Collections.emptyList();
-            }
-
-            return FingerprintService.this.getEnrolledFingerprints(userId);
-        }
-
-        @Override // Binder call
-        public boolean hasEnrolledFingerprints(int userId, String opPackageName) {
-            if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
-                    Binder.getCallingUid(), Binder.getCallingPid(),
-                    UserHandle.getCallingUserId())) {
-                return false;
-            }
-
-            return FingerprintService.this.hasEnrolledFingerprints(userId);
-        }
-
-        @Override // Binder call
-        public long getAuthenticatorId(String opPackageName) {
-            // In this method, we're not checking whether the caller is permitted to use fingerprint
-            // API because current authenticator ID is leaked (in a more contrived way) via Android
-            // Keystore (android.security.keystore package): the user of that API can create a key
-            // which requires fingerprint authentication for its use, and then query the key's
-            // characteristics (hidden API) which returns, among other things, fingerprint
-            // authenticator ID which was active at key creation time.
-            //
-            // Reason: The part of Android Keystore which runs inside an app's process invokes this
-            // method in certain cases. Those cases are not always where the developer demonstrates
-            // explicit intent to use fingerprint functionality. Thus, to avoiding throwing an
-            // unexpected SecurityException this method does not check whether its caller is
-            // permitted to use fingerprint API.
-            //
-            // The permission check should be restored once Android Keystore no longer invokes this
-            // method from inside app processes.
-
-            return FingerprintService.this.getAuthenticatorId(opPackageName);
-        }
-
-        @Override // Binder call
-        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                if (args.length > 0 && "--proto".equals(args[0])) {
-                    dumpProto(fd);
-                } else {
-                    dumpInternal(pw);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        @Override // Binder call
-        public void resetTimeout(byte [] token) {
-            checkPermission(RESET_FINGERPRINT_LOCKOUT);
-            // TODO: confirm security token when we move timeout management into the HAL layer.
-            mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
-        }
-
-        @Override
-        public void addLockoutResetCallback(final IFingerprintServiceLockoutResetCallback callback)
-                throws RemoteException {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    addLockoutResetMonitor(
-                            new FingerprintServiceLockoutResetMonitor(callback));
-                }
-            });
-        }
-
-        @Override
-        public boolean isClientActive() {
-            checkPermission(MANAGE_FINGERPRINT);
-            synchronized(FingerprintService.this) {
-                return (mCurrentClient != null) || (mPendingClient != null);
-            }
-        }
-
-        @Override
-        public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
-            checkPermission(MANAGE_FINGERPRINT);
-            mClientActiveCallbacks.add(callback);
-        }
-
-        @Override
-        public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
-            checkPermission(MANAGE_FINGERPRINT);
-            mClientActiveCallbacks.remove(callback);
-        }
-    }
-
-    private void dumpInternal(PrintWriter pw) {
-        JSONObject dump = new JSONObject();
-        try {
-            dump.put("service", "Fingerprint Manager");
-
-            JSONArray sets = new JSONArray();
-            for (UserInfo user : UserManager.get(getContext()).getUsers()) {
-                final int userId = user.getUserHandle().getIdentifier();
-                final int N = mFingerprintUtils.getFingerprintsForUser(mContext, userId).size();
-                PerformanceStats stats = mPerformanceMap.get(userId);
-                PerformanceStats cryptoStats = mCryptoPerformanceMap.get(userId);
-                JSONObject set = new JSONObject();
-                set.put("id", userId);
-                set.put("count", N);
-                set.put("accept", (stats != null) ? stats.accept : 0);
-                set.put("reject", (stats != null) ? stats.reject : 0);
-                set.put("acquire", (stats != null) ? stats.acquire : 0);
-                set.put("lockout", (stats != null) ? stats.lockout : 0);
-                set.put("permanentLockout", (stats != null) ? stats.permanentLockout : 0);
-                // cryptoStats measures statistics about secure fingerprint transactions
-                // (e.g. to unlock password storage, make secure purchases, etc.)
-                set.put("acceptCrypto", (cryptoStats != null) ? cryptoStats.accept : 0);
-                set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
-                set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
-                set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
-                set.put("permanentLockoutCrypto",
-                    (cryptoStats != null) ? cryptoStats.permanentLockout : 0);
-                sets.put(set);
-            }
-
-            dump.put("prints", sets);
-        } catch (JSONException e) {
-            Slog.e(TAG, "dump formatting failure", e);
-        }
-        pw.println(dump);
-    }
-
-    private void dumpProto(FileDescriptor fd) {
-        final ProtoOutputStream proto = new ProtoOutputStream(fd);
-        for (UserInfo user : UserManager.get(getContext()).getUsers()) {
-            final int userId = user.getUserHandle().getIdentifier();
-
-            final long userToken = proto.start(FingerprintServiceDumpProto.USERS);
-
-            proto.write(FingerprintUserStatsProto.USER_ID, userId);
-            proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS,
-                    mFingerprintUtils.getFingerprintsForUser(mContext, userId).size());
-
-            // Normal fingerprint authentications (e.g. lockscreen)
-            final PerformanceStats normal = mPerformanceMap.get(userId);
-            if (normal != null) {
-                final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
-                proto.write(PerformanceStatsProto.ACCEPT, normal.accept);
-                proto.write(PerformanceStatsProto.REJECT, normal.reject);
-                proto.write(PerformanceStatsProto.ACQUIRE, normal.acquire);
-                proto.write(PerformanceStatsProto.LOCKOUT, normal.lockout);
-                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, normal.permanentLockout);
-                proto.end(countsToken);
-            }
-
-            // Statistics about secure fingerprint transactions (e.g. to unlock password
-            // storage, make secure purchases, etc.)
-            final PerformanceStats crypto = mCryptoPerformanceMap.get(userId);
-            if (crypto != null) {
-                final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
-                proto.write(PerformanceStatsProto.ACCEPT, crypto.accept);
-                proto.write(PerformanceStatsProto.REJECT, crypto.reject);
-                proto.write(PerformanceStatsProto.ACQUIRE, crypto.acquire);
-                proto.write(PerformanceStatsProto.LOCKOUT, crypto.lockout);
-                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, crypto.permanentLockout);
-                proto.end(countsToken);
-            }
-
-            proto.end(userToken);
-        }
-        proto.flush();
-        mPerformanceMap.clear();
-        mCryptoPerformanceMap.clear();
-    }
-
-    @Override
-    public void onStart() {
-        publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
-        SystemServerInitThreadPool.get().submit(this::getFingerprintDaemon, TAG + ".onStart");
-        listenForUserSwitches();
-    }
-
-    private void updateActiveGroup(int userId, String clientPackage) {
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-
-        if (daemon != null) {
-            try {
-                userId = getUserOrWorkProfileId(clientPackage, userId);
-                if (userId != mCurrentUserId) {
-                    int firstSdkInt = Build.VERSION.FIRST_SDK_INT;
-                    if (firstSdkInt < Build.VERSION_CODES.BASE) {
-                        Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be " +
-                                "at least VERSION_CODES.BASE");
-                    }
-                    File baseDir;
-                    if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
-                        baseDir = Environment.getUserSystemDirectory(userId);
-                    } else {
-                        baseDir = Environment.getDataVendorDeDirectory(userId);
-                    }
-
-                    File fpDir = new File(baseDir, FP_DATA_DIR);
-                    if (!fpDir.exists()) {
-                        if (!fpDir.mkdir()) {
-                            Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath());
-                            return;
-                        }
-                        // Calling mkdir() from this process will create a directory with our
-                        // permissions (inherited from the containing dir). This command fixes
-                        // the label.
-                        if (!SELinux.restorecon(fpDir)) {
-                            Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
-                            return;
-                        }
-                    }
-
-                    daemon.setActiveGroup(userId, fpDir.getAbsolutePath());
-                    mCurrentUserId = userId;
-                }
-                mAuthenticatorIds.put(userId,
-                        hasEnrolledFingerprints(userId) ? daemon.getAuthenticatorId() : 0L);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setActiveGroup():", e);
-            }
-        }
-    }
-
-    /**
-     * @param clientPackage the package of the caller
-     * @return the profile id
-     */
-    private int getUserOrWorkProfileId(String clientPackage, int userId) {
-        if (!isKeyguard(clientPackage) && isWorkProfile(userId)) {
-            return userId;
-        }
-        return getEffectiveUserId(userId);
-    }
-
-    /**
-     * @param userId
-     * @return true if this is a work profile
-     */
-    private boolean isWorkProfile(int userId) {
-        UserInfo userInfo = null;
-        final long token = Binder.clearCallingIdentity();
-        try {
-            userInfo = mUserManager.getUserInfo(userId);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-        return userInfo != null && userInfo.isManagedProfile();
-    }
-
-    private void listenForUserSwitches() {
-        try {
-            ActivityManager.getService().registerUserSwitchObserver(
-                new SynchronousUserSwitchObserver() {
-                    @Override
-                    public void onUserSwitching(int newUserId) throws RemoteException {
-                        mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
-                                .sendToTarget();
-                    }
-                }, TAG);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to listen for user switching event" ,e);
-        }
-    }
-
-    /***
-     * @param opPackageName the name of the calling package
-     * @return authenticator id for the calling user
-     */
-    public long getAuthenticatorId(String opPackageName) {
-        final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
-        return mAuthenticatorIds.getOrDefault(userId, 0L);
-    }
-}
diff --git a/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java b/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java
deleted file mode 100644
index 434db98..0000000
--- a/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java
+++ /dev/null
@@ -1,93 +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.fingerprint;
-
-import android.content.Context;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.os.IBinder;
-import android.util.Slog;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An internal class to help clean up unknown fingerprints in the hardware and software
- */
-public abstract class InternalEnumerateClient extends EnumerateClient {
-
-    private List<Fingerprint> mEnrolledList;
-    private List<Fingerprint> mUnknownFingerprints = new ArrayList<>(); // list of fp to delete
-
-    public InternalEnumerateClient(Context context, long halDeviceId, IBinder token,
-            IFingerprintServiceReceiver receiver, int groupId, int userId,
-            boolean restricted, String owner, List<Fingerprint> enrolledList) {
-
-        super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
-        mEnrolledList = enrolledList;
-    }
-
-    private void handleEnumeratedFingerprint(int fingerId, int groupId, int remaining) {
-
-        boolean matched = false;
-        for (int i=0; i<mEnrolledList.size(); i++) {
-            if (mEnrolledList.get(i).getFingerId() == fingerId) {
-                mEnrolledList.remove(i);
-                matched = true;
-                break;
-            }
-        }
-
-        // fingerId 0 means no fingerprints are in hardware
-        if (!matched && fingerId != 0) {
-            Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
-            mUnknownFingerprints.add(fingerprint);
-        }
-    }
-
-    private void doFingerprintCleanup() {
-
-        if (mEnrolledList == null) {
-            return;
-        }
-
-        for (Fingerprint f : mEnrolledList) {
-            Slog.e(TAG, "Internal Enumerate: Removing dangling enrolled fingerprint: "
-                    + f.getName() + " " + f.getFingerId() + " " + f.getGroupId()
-                    + " " + f.getDeviceId());
-
-            FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(),
-                    f.getFingerId(), getTargetUserId());
-        }
-        mEnrolledList.clear();
-    }
-
-    public List<Fingerprint> getUnknownFingerprints() {
-        return mUnknownFingerprints;
-    }
-
-    @Override
-    public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
-
-        handleEnumeratedFingerprint(fingerId, groupId, remaining);
-        if (remaining == 0) {
-            doFingerprintCleanup();
-        }
-
-        return remaining == 0;
-    }
-
-}
diff --git a/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java b/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java
deleted file mode 100644
index 19f61fe..0000000
--- a/services/core/java/com/android/server/fingerprint/InternalRemovalClient.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.fingerprint;
-
-import android.content.Context;
-import android.os.IBinder;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import com.android.server.fingerprint.RemovalClient;
-
-public abstract class InternalRemovalClient extends RemovalClient {
-
-    public InternalRemovalClient(Context context, long halDeviceId, IBinder token,
-            IFingerprintServiceReceiver receiver, int fingerId, int groupId, int userId,
-            boolean restricted, String owner) {
-
-        super(context, halDeviceId, token, receiver, fingerId, groupId, userId, restricted, owner);
-
-    }
-}
diff --git a/services/core/java/com/android/server/fingerprint/RemovalClient.java b/services/core/java/com/android/server/fingerprint/RemovalClient.java
deleted file mode 100644
index ffc8488..0000000
--- a/services/core/java/com/android/server/fingerprint/RemovalClient.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.fingerprint;
-
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import com.android.internal.logging.MetricsLogger;
-
-/**
- * A class to keep track of the remove state for a given client.
- */
-public abstract class RemovalClient extends ClientMonitor {
-    private int mFingerId;
-
-    public RemovalClient(Context context, long halDeviceId, IBinder token,
-            IFingerprintServiceReceiver receiver, int fingerId, int groupId, int userId,
-            boolean restricted, String owner) {
-        super(context, halDeviceId, token, receiver, userId, groupId, restricted, owner);
-        mFingerId = fingerId;
-    }
-
-    @Override
-    public int start() {
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        // The fingerprint template ids will be removed when we get confirmation from the HAL
-        try {
-            final int result = daemon.remove(getGroupId(), mFingerId);
-            if (result != 0) {
-                Slog.w(TAG, "startRemove with id = " + mFingerId + " failed, result=" + result);
-                MetricsLogger.histogram(getContext(), "fingerprintd_remove_start_error", result);
-                onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
-                return result;
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "startRemove failed", e);
-        }
-        return 0;
-    }
-
-    @Override
-    public int stop(boolean initiatedByClient) {
-        if (mAlreadyCancelled) {
-            Slog.w(TAG, "stopRemove: already cancelled!");
-            return 0;
-        }
-        IBiometricsFingerprint daemon = getFingerprintDaemon();
-        if (daemon == null) {
-            Slog.w(TAG, "stopRemoval: no fingerprint HAL!");
-            return ERROR_ESRCH;
-        }
-        try {
-            final int result = daemon.cancel();
-            if (result != 0) {
-                Slog.w(TAG, "stopRemoval failed, result=" + result);
-                return result;
-            }
-            if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer removing");
-        } catch (RemoteException e) {
-            Slog.e(TAG, "stopRemoval failed", e);
-            return ERROR_ESRCH;
-        }
-        mAlreadyCancelled = true;
-        return 0; // success
-    }
-
-    /*
-     * @return true if we're done.
-     */
-    private boolean sendRemoved(int fingerId, int groupId, int remaining) {
-        IFingerprintServiceReceiver receiver = getReceiver();
-        try {
-            if (receiver != null) {
-                receiver.onRemoved(getHalDeviceId(), fingerId, groupId, remaining);
-            }
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to notify Removed:", e);
-        }
-        return remaining == 0;
-    }
-
-    @Override
-    public boolean onRemoved(int fingerId, int groupId, int remaining) {
-        if (fingerId != 0) {
-            FingerprintUtils.getInstance().removeFingerprintIdForUser(getContext(), fingerId,
-                    getTargetUserId());
-        }
-        return sendRemoved(fingerId, getGroupId(), remaining);
-    }
-
-    @Override
-    public boolean onEnrollResult(int fingerId, int groupId, int rem) {
-        if (DEBUG) Slog.w(TAG, "onEnrollResult() called for remove!");
-        return true; // Invalid for Remove
-    }
-
-    @Override
-    public boolean onAuthenticated(int fingerId, int groupId) {
-        if (DEBUG) Slog.w(TAG, "onAuthenticated() called for remove!");
-        return true; // Invalid for Remove.
-    }
-
-    @Override
-    public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for remove!");
-        return true; // Invalid for Remove.
-    }
-
-
-}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index d515ce6..86ecd12 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -254,7 +254,7 @@
     // 1 second, or 1 Hz frequency.
     private static final long LOCATION_UPDATE_MIN_TIME_INTERVAL_MILLIS = 1000;
     // Default update duration in milliseconds for REQUEST_LOCATION.
-    private static final long LOCATION_UPDATE_DURATION_MILLIS = 0;
+    private static final long LOCATION_UPDATE_DURATION_MILLIS = 10 * 1000;
 
     /** simpler wrapper for ProviderRequest + Worksource */
     private static class GpsRequest {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 39d0bf5..75b9f13 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -128,6 +128,11 @@
     // The most recent update time, or the creation time if no updates.
     private long mUpdateTimeMs;
 
+    // The most recent interruption time, or the creation time if no updates. Differs from the
+    // above value because updates are filtered based on whether they actually interrupted the
+    // user
+    private long mInterruptionTimeMs;
+
     // Is this record an update of an old record?
     public boolean isUpdate;
     private int mPackagePriority;
@@ -180,6 +185,7 @@
         mRankingTimeMs = calculateRankingTimeMs(0L);
         mCreationTimeMs = sbn.getPostTime();
         mUpdateTimeMs = mCreationTimeMs;
+        mInterruptionTimeMs = mCreationTimeMs;
         mContext = context;
         stats = new NotificationUsageStats.SingleNotificationStats();
         mChannel = channel;
@@ -525,6 +531,7 @@
         pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs);
         pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs);
         pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs);
+        pw.println(prefix + "mInterruptionTimeMs=" + mInterruptionTimeMs);
         pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects);
         if (mPreChannelsNotification) {
             pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x",
@@ -786,6 +793,10 @@
         return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
     }
 
+    public int getInterruptionMs(long now) {
+        return (int) (now - mInterruptionTimeMs);
+    }
+
     /**
      * Set the visibility of the notification.
      */
@@ -844,7 +855,7 @@
     public void setSeen() {
         mStats.setSeen();
         if (mTextChanged) {
-            mIsInterruptive = true;
+            setInterruptive(true);
         }
     }
 
@@ -940,6 +951,17 @@
 
     public void setInterruptive(boolean interruptive) {
         mIsInterruptive = interruptive;
+        final long now = System.currentTimeMillis();
+        mInterruptionTimeMs = interruptive ? now : mInterruptionTimeMs;
+
+        if (interruptive) {
+            MetricsLogger.action(getLogMaker()
+                    .setCategory(MetricsEvent.NOTIFICATION_INTERRUPTION)
+                    .setType(MetricsEvent.TYPE_OPEN)
+                    .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_INTERRUPTION_MILLIS,
+                            getInterruptionMs(now)));
+            MetricsLogger.histogram(mContext, "note_interruptive", getInterruptionMs(now));
+        }
     }
 
     public void setTextChanged(boolean textChanged) {
@@ -1116,7 +1138,9 @@
                         sbn.getNotification().isGroupSummary() ? 1 : 0)
                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
-                .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now));
+                .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now))
+                .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_INTERRUPTION_MILLIS,
+                        getInterruptionMs(now));
     }
 
     public LogMaker getLogMaker() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 1c9782f..84de6b4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -152,6 +152,11 @@
     }
 
     @Override
+    public long forceNetworkLogs() {
+        return 0;
+    }
+
+    @Override
     public long forceSecurityLogs() {
         return 0;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 414cf47..20147d2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10694,7 +10694,6 @@
                     return false;
                 }
         }
-
         return false;
     }
 
@@ -12282,6 +12281,20 @@
         }
     }
 
+    @Override
+    public long forceNetworkLogs() {
+        enforceShell("forceNetworkLogs");
+        synchronized (getLockObject()) {
+            if (!isNetworkLoggingEnabledInternalLocked()) {
+                throw new IllegalStateException("logging is not available");
+            }
+            if (mNetworkLogger != null) {
+                return mNetworkLogger.forceBatchFinalization();
+            }
+            return 0;
+        }
+    }
+
     /** Pauses security and network logging if there are unaffiliated users on the device */
     private void maybePauseDeviceWideLoggingLocked() {
         if (!areAllUsersAffiliatedWithDeviceLocked()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
index 0967652..4514492 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -187,4 +187,8 @@
     List<NetworkEvent> retrieveLogs(long batchToken) {
         return mNetworkLoggingHandler.retrieveFullLogBatch(batchToken);
     }
+
+    long forceBatchFinalization() {
+        return mNetworkLoggingHandler.forceBatchFinalization();
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index f91f959..0a7070f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -16,6 +16,8 @@
 
 package com.android.server.devicepolicy;
 
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
 import android.app.AlarmManager;
 import android.app.AlarmManager.OnAlarmListener;
 import android.app.admin.DeviceAdminReceiver;
@@ -33,6 +35,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A Handler class for managing network logging on a background thread.
@@ -60,6 +63,12 @@
     /** Delay after which older batches get discarded after a retrieval. */
     private static final long RETRIEVED_BATCH_DISCARD_DELAY_MS = 5 * 60 * 1000; // 5m
 
+    /** Throttle batch finalization to 10 seconds.*/
+    private static final long FORCE_FETCH_THROTTLE_NS = TimeUnit.SECONDS.toNanos(10);
+    /** Timestamp of the last call to finalise a batch. Used for throttling forced finalization.*/
+    @GuardedBy("this")
+    private long mLastFinalizationNanos = -1;
+
     /** Do not call into mDpm with locks held */
     private final DevicePolicyManagerService mDpm;
     private final AlarmManager mAlarmManager;
@@ -155,6 +164,26 @@
                 + "ms from now.");
     }
 
+    /**
+     * Forces batch finalisation. Throttled to 10 seconds per batch finalisation.
+     * @return the number of milliseconds to wait until batch finalisation can be forced.
+     */
+    long forceBatchFinalization() {
+        Bundle notificationExtras;
+        synchronized (this) {
+            final long toWaitNanos =
+                mLastFinalizationNanos + FORCE_FETCH_THROTTLE_NS - System.nanoTime();
+            if (toWaitNanos > 0) {
+                return NANOSECONDS.toMillis(toWaitNanos) + 1; // Round up.
+            }
+            notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+        }
+        if (notificationExtras != null) {
+            notifyDeviceOwner(notificationExtras);
+        }
+        return 0;
+    }
+
     synchronized void pause() {
         Slog.d(TAG, "Paused network logging");
         mPaused = true;
@@ -192,6 +221,7 @@
     @GuardedBy("this")
     /** @returns extras if a message should be sent to the device owner */
     private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() {
+        mLastFinalizationNanos = System.nanoTime();
         Bundle notificationExtras = null;
         if (mNetworkEvents.size() > 0) {
             // Assign ids to the events.
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2811f71..2985fec 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -75,7 +75,7 @@
 import com.android.server.display.DisplayManagerService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.emergency.EmergencyAffordanceService;
-import com.android.server.fingerprint.FingerprintService;
+import com.android.server.biometrics.fingerprint.FingerprintService;
 import com.android.server.hdmi.HdmiControlService;
 import com.android.server.input.InputManagerService;
 import com.android.server.job.JobSchedulerService;
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index 1ce41a6..0674d85 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -174,7 +174,7 @@
 
         // #notifyAll will be called on the ActivityManagerService. we must hold the object lock
         // when this happens.
-        synchronized (mSupervisor.mService) {
+        synchronized (mSupervisor.mService.mGlobalLock) {
             final WaitResult taskToFrontWait = new WaitResult();
             mSupervisor.mWaitingActivityLaunched.add(taskToFrontWait);
             mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity, START_TASK_TO_FRONT);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java
index 7948e4c..a86372a 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java
@@ -59,8 +59,8 @@
         super.setUp();
         mService = createActivityManagerService();
         mFactory = mock(Factory.class);
-        mController = new ActivityStartController(mService, mService.mStackSupervisor, mFactory);
-        mStarter = spy(new ActivityStarter(mController, mService, mService.mStackSupervisor,
+        mController = new ActivityStartController(mService.mActivityTaskManager, mService.mStackSupervisor, mFactory);
+        mStarter = spy(new ActivityStarter(mController, mService.mActivityTaskManager, mService.mStackSupervisor,
                 mock(ActivityStartInterceptor.class)));
         doReturn(mStarter).when(mFactory).obtain();
     }
@@ -96,7 +96,7 @@
     @Test
     public void testRecycling() throws Exception {
         final Intent intent = new Intent();
-        final ActivityStarter optionStarter = new ActivityStarter(mController, mService,
+        final ActivityStarter optionStarter = new ActivityStarter(mController, mService.mActivityTaskManager,
                 mService.mStackSupervisor, mock(ActivityStartInterceptor.class));
         optionStarter
                 .setIntent(intent)
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java
index 9d35ef1..7f55824 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java
@@ -57,7 +57,7 @@
  * Unit tests for {@link ActivityStartInterceptorTest}.
  *
  * Build/Install/Run:
- *  bit FrameworksServicesTests:com.android.server.am.ActivityStartInterceptorTest
+ *  atest FrameworksServicesTests:com.android.server.am.ActivityStartInterceptorTest
  */
 @Presubmit
 @SmallTest
@@ -82,7 +82,9 @@
     @Mock
     private Context mContext;
     @Mock
-    private ActivityManagerService mService;
+    private ActivityManagerService mAm;
+    @Mock
+    private ActivityTaskManagerService mService;
     @Mock
     private ActivityStackSupervisor mSupervisor;
     @Mock
@@ -104,6 +106,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mService.mAm = mAm;
         mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext,
                 mUserController);
         mInterceptor.setStates(TEST_USER_ID, TEST_REAL_CALLING_PID, TEST_REAL_CALLING_UID,
@@ -113,10 +116,9 @@
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
         LocalServices.addService(DevicePolicyManagerInternal.class,
                 mDevicePolicyManager);
-        when(mDevicePolicyManager
-                        .createShowAdminSupportIntent(TEST_USER_ID, true))
+        when(mDevicePolicyManager.createShowAdminSupportIntent(TEST_USER_ID, true))
                 .thenReturn(ADMIN_SUPPORT_INTENT);
-        when(mService.getPackageManagerInternalLocked()).thenReturn(mPackageManagerInternal);
+        when(mAm.getPackageManagerInternalLocked()).thenReturn(mPackageManagerInternal);
 
         // Mock UserManager
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
@@ -129,7 +131,7 @@
                 thenReturn(CONFIRM_CREDENTIALS_INTENT);
 
         // Mock PackageManager
-        when(mService.getPackageManager()).thenReturn(mPackageManager);
+        when(mAm.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
                 .thenReturn(null);
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index 686f571..10d255e 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -109,7 +109,7 @@
         super.setUp();
         mService = createActivityManagerService();
         mController = mock(ActivityStartController.class);
-        mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor,
+        mStarter = new ActivityStarter(mController, mService.mActivityTaskManager, mService.mStackSupervisor,
                 mock(ActivityStartInterceptor.class));
     }
 
@@ -193,7 +193,7 @@
         final IPackageManager packageManager = mock(IPackageManager.class);
         final ActivityStartController controller = mock(ActivityStartController.class);
 
-        final ActivityStarter starter = new ActivityStarter(controller, service,
+        final ActivityStarter starter = new ActivityStarter(controller, service.mActivityTaskManager,
                 service.mStackSupervisor, mock(ActivityStartInterceptor.class));
         final IApplicationThread caller = mock(IApplicationThread.class);
 
@@ -282,7 +282,7 @@
 
         // Ensure that {@link ActivityOptions} are aborted with unsuccessful result.
         if (expectedResult != START_SUCCESS) {
-            final ActivityStarter optionStarter = new ActivityStarter(mController, mService,
+            final ActivityStarter optionStarter = new ActivityStarter(mController, mService.mActivityTaskManager,
                     mService.mStackSupervisor, mock(ActivityStartInterceptor.class));
             final ActivityOptions options = spy(ActivityOptions.makeBasic());
 
@@ -336,7 +336,7 @@
         info.applicationInfo = new ApplicationInfo();
         info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName();
 
-        return new ActivityStarter(mController, mService,
+        return new ActivityStarter(mController, mService.mActivityTaskManager,
                 mService.mStackSupervisor, mock(ActivityStartInterceptor.class))
                 .setIntent(intent)
                 .setActivityInfo(info);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index dda52e1..06ac3b0 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -111,10 +111,11 @@
     protected ActivityManagerService setupActivityManagerService(
             ActivityManagerService service, ActivityTaskManagerService atm) {
         service = spy(service);
+        // Makes sure activity task is created with the spy object.
         atm = spy(atm);
-        // Makes sure the supervisor is using with the spy object.
-        service.mStackSupervisor.setService(service);
         service.setActivityTaskManager(atm);
+        // Makes sure the supervisor is using with the spy object.
+        atm.mStackSupervisor.setService(atm);
         doReturn(mock(IPackageManager.class)).when(service).getPackageManager();
         doNothing().when(service).grantEphemeralAccessLocked(anyInt(), any(), anyInt(), anyInt());
         service.mWindowManager = prepareMockWindowManager();
@@ -311,7 +312,7 @@
             intent.setComponent(mComponent);
             intent.setFlags(mFlags);
 
-            final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService.mActivityTaskManager, mTaskId, aInfo,
+            final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo,
                     intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
             task.userId = mUserId;
 
@@ -360,23 +361,6 @@
 
             return mLockTaskController;
         }
-    }
-
-    /**
-     * An {@link ActivityManagerService} subclass which provides a test
-     * {@link ActivityStackSupervisor}.
-     */
-    protected static class TestActivityManagerService extends ActivityManagerService {
-
-        TestActivityManagerService(Context context) {
-            super(context);
-            mSupportsMultiWindow = true;
-            mSupportsMultiDisplay = true;
-            mSupportsSplitScreenMultiWindow = true;
-            mSupportsFreeformWindowManagement = true;
-            mSupportsPictureInPicture = true;
-            mWindowManager = WindowTestUtils.getMockWindowManagerService();
-        }
 
         @Override
         final protected ActivityStackSupervisor createStackSupervisor() {
@@ -404,12 +388,33 @@
         }
 
         protected ActivityStackSupervisor createTestSupervisor() {
-            return new TestActivityStackSupervisor(this, mHandlerThread.getLooper());
+            return new TestActivityStackSupervisor(this, mH.getLooper());
+        }
+    }
+
+    /**
+     * An {@link ActivityManagerService} subclass which provides a test
+     * {@link ActivityStackSupervisor}.
+     */
+    protected static class TestActivityManagerService extends ActivityManagerService {
+
+        TestActivityManagerService(Context context) {
+            super(context);
+            mSupportsMultiWindow = true;
+            mSupportsMultiDisplay = true;
+            mSupportsSplitScreenMultiWindow = true;
+            mSupportsFreeformWindowManagement = true;
+            mSupportsPictureInPicture = true;
         }
 
         @Override
         void updateUsageStats(ActivityRecord component, boolean resumed) {
         }
+
+        @Override
+        Configuration getGlobalConfiguration() {
+            return mContext.getResources().getConfiguration();
+        }
     }
 
     /**
@@ -420,7 +425,7 @@
         private ActivityDisplay mDisplay;
         private KeyguardController mKeyguardController;
 
-        public TestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
+        public TestActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) {
             super(service, looper);
             mDisplayManager =
                     (DisplayManager) mService.mContext.getSystemService(Context.DISPLAY_SERVICE);
diff --git a/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java b/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java
index 2baf995..e73661b 100644
--- a/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java
@@ -62,7 +62,7 @@
         mService.mHandlerThread.getThreadHandler().runWithScissors(() -> {
             mHandler = new TestHandler(null, mClock);
         }, 0);
-        mRegistry = new PendingRemoteAnimationRegistry(mService, mHandler);
+        mRegistry = new PendingRemoteAnimationRegistry(mService.mActivityTaskManager, mHandler);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index f98c50a..cd70677 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -837,6 +837,12 @@
         protected RecentTasks createRecentTasks() {
             return new TestRecentTasks(this, mTaskPersister, new TestUserController(mAm));
         }
+
+        @Override
+        protected ActivityStackSupervisor createTestSupervisor() {
+            return new MyTestActivityStackSupervisor(this, mH.getLooper());
+        }
+
     }
 
     private class MyTestActivityManagerService extends TestActivityManagerService {
@@ -845,18 +851,13 @@
         }
 
         @Override
-        protected ActivityStackSupervisor createTestSupervisor() {
-            return new MyTestActivityStackSupervisor(this, mHandlerThread.getLooper());
-        }
-
-        @Override
         public boolean isUserRunning(int userId, int flags) {
             return true;
         }
     }
 
     private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor {
-        public MyTestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
+        public MyTestActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) {
             super(service, looper);
         }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
index 942a07a..96ac935 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
@@ -121,7 +121,7 @@
         cal.set(Calendar.MINUTE, 15);
         cal.set(Calendar.SECOND, 0);
         cal.set(Calendar.MILLISECOND, 0);
-        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1};
+        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
         mScheduleInfo.startHour = 1;
         mScheduleInfo.endHour = 3;
         mScheduleInfo.startMinute = 15;
@@ -149,7 +149,7 @@
         cal.set(Calendar.MINUTE, 15);
         cal.set(Calendar.SECOND, 0);
         cal.set(Calendar.MILLISECOND, 0);
-        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1};
+        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
         mScheduleInfo.startHour = 22;
         mScheduleInfo.endHour = 3;
         mScheduleInfo.startMinute = 15;
@@ -250,7 +250,7 @@
         calAlarm.add(Calendar.DATE, 1); // add a day
 
         // ScheduleInfo: day 1, day 2: 9pm-7am
-        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1};
+        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
         mScheduleInfo.startHour = 21;
         mScheduleInfo.endHour = 7;
         mScheduleInfo.startMinute = 0;
@@ -418,7 +418,7 @@
         now.set(Calendar.MILLISECOND, 0);
         now.add(Calendar.DATE, 1); // add a day
 
-        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1};
+        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
         mScheduleInfo.startHour = 22;
         mScheduleInfo.startMinute = 15;
         mScheduleInfo.endHour = 3;
@@ -446,7 +446,7 @@
         now.set(Calendar.MILLISECOND, 0);
         now.add(Calendar.DATE, 1); // add a day
 
-        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1};
+        mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay(1)};
         mScheduleInfo.startHour = 22;
         mScheduleInfo.startMinute = 15;
         mScheduleInfo.endHour = 3;
@@ -464,4 +464,10 @@
     private int getTodayDay() {
         return new GregorianCalendar().get(Calendar.DAY_OF_WEEK);
     }
+
+    private int getTodayDay(int offset) {
+        Calendar cal = new GregorianCalendar();
+        cal.add(Calendar.DATE, offset);
+        return cal.get(Calendar.DAY_OF_WEEK);
+    }
 }
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index f7e6840..4354314 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -36,8 +36,8 @@
 /**
  * Base class of network service. Services that extend NetworkService must register the service in
  * their AndroidManifest to be detected by the framework. They must be protected by the permission
- * "android.permission.BIND_NETWORK_SERVICE". The network service definition in the manifest must
- * follow the following format:
+ * "android.permission.BIND_TELEPHONY_NETWORK_SERVICE". The network service definition in the
+ * manifest must follow the following format:
  * ...
  * <service android:name=".xxxNetworkService"
  *     android:permission="android.permission.BIND_TELEPHONY_NETWORK_SERVICE" >
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d7568b4..0940738 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5432,23 +5432,6 @@
     }
 
     /**
-     * @return true if the IMS resolver is busy resolving a binding and should not be considered
-     * available, false if the IMS resolver is idle.
-     * @hide
-     */
-    public boolean isResolvingImsBinding() {
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                return telephony.isResolvingImsBinding();
-            }
-        } catch (RemoteException e) {
-            Rlog.e(TAG, "isResolvingImsBinding, RemoteException: " + e.getMessage());
-        }
-        return false;
-    }
-
-    /**
      * Set IMS registration state
      *
      * @param Registration state
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 4ca5ce3..1db5850 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -44,11 +44,11 @@
 /**
  * Base class of data service. Services that extend DataService must register the service in
  * their AndroidManifest to be detected by the framework. They must be protected by the permission
- * "android.permission.BIND_DATA_SERVICE". The data service definition in the manifest must follow
- * the following format:
+ * "android.permission.BIND_TELEPHONY_DATA_SERVICE". The data service definition in the manifest
+ * must follow the following format:
  * ...
  * <service android:name=".xxxDataService"
- *     android:permission="android.permission.BIND_DATA_SERVICE" >
+ *     android:permission="android.permission.BIND_TELEPHONY_DATA_SERVICE" >
  *     <intent-filter>
  *         <action android:name="android.telephony.data.DataService" />
  *     </intent-filter>
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 6ea8ccb..84a18b4 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -817,12 +817,6 @@
     IImsConfig getImsConfig(int slotId, int feature);
 
     /**
-     * @return true if the IMS resolver is busy resolving a binding and should not be considered
-     * available, false if the IMS resolver is idle.
-     */
-    boolean isResolvingImsBinding();
-
-    /**
     *  @return true if the ImsService to bind to for the slot id specified was set, false otherwise.
     */
     boolean setImsService(int slotId, boolean isCarrierImsService, String packageName);
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 34ecde8..0d3b8e4 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -71,6 +71,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.net.NetworkRequest;
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
@@ -128,6 +129,10 @@
     private static final String TEST_USB_IFNAME = "test_rndis0";
     private static final String TEST_WLAN_IFNAME = "test_wlan0";
 
+    // Actual contents of the request don't matter for this test. The lack of
+    // any specific TRANSPORT_* is sufficient to identify this request.
+    private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build();
+
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
     @Mock private INetworkManagementService mNMService;
@@ -238,6 +243,11 @@
             isTetheringSupportedCalls++;
             return true;
         }
+
+        @Override
+        public NetworkRequest getDefaultNetworkRequest() {
+            return mDefaultRequest;
+        }
     }
 
     private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6,
@@ -305,6 +315,8 @@
                 .thenReturn(new String[0]);
         when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
                 .thenReturn(new int[0]);
+        when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
+                .thenReturn(false);
         when(mNMService.listInterfaces())
                 .thenReturn(new String[] {
                         TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME});
@@ -458,6 +470,7 @@
     }
 
     private void prepareUsbTethering(NetworkState upstreamState) {
+        when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
         when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
                 .thenReturn(upstreamState);
 
@@ -519,7 +532,7 @@
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
         verifyNoMoreInteractions(mWifiManager);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
-        verify(mUpstreamNetworkMonitor, times(1)).start();
+        verify(mUpstreamNetworkMonitor, times(1)).start(any(NetworkRequest.class));
         // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
         assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls);
 
@@ -656,6 +669,24 @@
     }
 
     @Test
+    public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception {
+        when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
+                .thenReturn(true);
+        sendConfigurationChanged();
+
+        // Setup IPv6
+        final NetworkState upstreamState = buildMobileIPv6UpstreamState();
+        runUsbTethering(upstreamState);
+
+        // UpstreamNetworkMonitor should choose upstream automatically
+        // (in this specific case: choose the default network).
+        verify(mUpstreamNetworkMonitor, times(1)).getCurrentPreferredUpstream();
+        verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any());
+
+        verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
+    }
+
+    @Test
     public void workingLocalOnlyHotspotEnrichedApBroadcastWithIfaceChanged() throws Exception {
         workingLocalOnlyHotspotEnrichedApBroadcast(true);
     }
@@ -718,7 +749,7 @@
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
         verifyNoMoreInteractions(mWifiManager);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER);
-        verify(mUpstreamNetworkMonitor, times(1)).start();
+        verify(mUpstreamNetworkMonitor, times(1)).start(any(NetworkRequest.class));
         // In tethering mode, in the default configuration, an explicit request
         // for a mobile network is also made.
         verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 9661dc2..3e21a2c 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -73,6 +74,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 
@@ -84,6 +86,10 @@
     private static final boolean INCLUDES = true;
     private static final boolean EXCLUDES = false;
 
+    // Actual contents of the request don't matter for this test. The lack of
+    // any specific TRANSPORT_* is sufficient to identify this request.
+    private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build();
+
     @Mock private Context mContext;
     @Mock private IConnectivityManager mCS;
     @Mock private SharedLog mLog;
@@ -113,6 +119,13 @@
     }
 
     @Test
+    public void testStopWithoutStartIsNonFatal() {
+        mUNM.stop();
+        mUNM.stop();
+        mUNM.stop();
+    }
+
+    @Test
     public void testDoesNothingBeforeStarted() {
         assertTrue(mCM.hasNoCallbacks());
         assertFalse(mUNM.mobileNetworkRequested());
@@ -127,7 +140,7 @@
     public void testDefaultNetworkIsTracked() throws Exception {
         assertEquals(0, mCM.trackingDefault.size());
 
-        mUNM.start();
+        mUNM.start(mDefaultRequest);
         assertEquals(1, mCM.trackingDefault.size());
 
         mUNM.stop();
@@ -138,7 +151,7 @@
     public void testListensForAllNetworks() throws Exception {
         assertTrue(mCM.listening.isEmpty());
 
-        mUNM.start();
+        mUNM.start(mDefaultRequest);
         assertFalse(mCM.listening.isEmpty());
         assertTrue(mCM.isListeningForAll());
 
@@ -148,9 +161,11 @@
 
     @Test
     public void testCallbacksRegistered() {
-        mUNM.start();
-        verify(mCM, times(1)).registerNetworkCallback(any(), any(), any());
-        verify(mCM, times(1)).registerDefaultNetworkCallback(any(), any());
+        mUNM.start(mDefaultRequest);
+        verify(mCM, times(1)).registerNetworkCallback(
+                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
+        verify(mCM, times(1)).requestNetwork(
+                eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class));
 
         mUNM.stop();
         verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
@@ -161,7 +176,7 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.start();
+        mUNM.start(mDefaultRequest);
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
@@ -184,17 +199,17 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.start();
-        verify(mCM, Mockito.times(1)).registerNetworkCallback(
+        mUNM.start(mDefaultRequest);
+        verify(mCM, times(1)).registerNetworkCallback(
                 any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
-        verify(mCM, Mockito.times(1)).registerDefaultNetworkCallback(
-                any(NetworkCallback.class), any(Handler.class));
+        verify(mCM, times(1)).requestNetwork(
+                eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class));
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
         mUNM.updateMobileRequiresDun(true);
         mUNM.registerMobileNetworkRequest();
-        verify(mCM, Mockito.times(1)).requestNetwork(
+        verify(mCM, times(1)).requestNetwork(
                 any(NetworkRequest.class), any(NetworkCallback.class), anyInt(), anyInt(),
                 any(Handler.class));
 
@@ -222,7 +237,7 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.start();
+        mUNM.start(mDefaultRequest);
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
@@ -242,7 +257,7 @@
 
     @Test
     public void testUpdateMobileRequiresDun() throws Exception {
-        mUNM.start();
+        mUNM.start(mDefaultRequest);
 
         // Test going from no-DUN to DUN correctly re-registers callbacks.
         mUNM.updateMobileRequiresDun(false);
@@ -270,7 +285,7 @@
         final Collection<Integer> preferredTypes = new ArrayList<>();
         preferredTypes.add(TYPE_WIFI);
 
-        mUNM.start();
+        mUNM.start(mDefaultRequest);
         // There are no networks, so there is nothing to select.
         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
 
@@ -334,8 +349,47 @@
     }
 
     @Test
+    public void testGetCurrentPreferredUpstream() throws Exception {
+        mUNM.start(mDefaultRequest);
+        mUNM.updateMobileRequiresDun(false);
+
+        // [0] Mobile connects, DUN not required -> mobile selected.
+        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        cellAgent.fakeConnect();
+        mCM.makeDefaultNetwork(cellAgent);
+        assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+
+        // [1] WiFi connects but not validated/promoted to default -> mobile selected.
+        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+        wifiAgent.fakeConnect();
+        assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+
+        // [2] WiFi validates and is promoted to the default network -> WiFi selected.
+        mCM.makeDefaultNetwork(wifiAgent);
+        assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+
+        // [3] DUN required, no other changes -> WiFi still selected
+        mUNM.updateMobileRequiresDun(true);
+        assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+
+        // [4] WiFi no longer validated, mobile becomes default, DUN required -> null selected.
+        mCM.makeDefaultNetwork(cellAgent);
+        assertEquals(null, mUNM.getCurrentPreferredUpstream());
+        // TODO: make sure that a DUN request has been filed. This is currently
+        // triggered by code over in Tethering, but once that has been moved
+        // into UNM we should test for this here.
+
+        // [5] DUN network arrives -> DUN selected
+        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
+        dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
+        dunAgent.fakeConnect();
+        assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+    }
+
+    @Test
     public void testLocalPrefixes() throws Exception {
-        mUNM.start();
+        mUNM.start(mDefaultRequest);
 
         // [0] Test minimum set of local prefixes.
         Set<IpPrefix> local = mUNM.getLocalPrefixes();
@@ -345,7 +399,7 @@
 
         // [1] Pretend Wi-Fi connects.
         final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
-        final LinkProperties wifiLp = new LinkProperties();
+        final LinkProperties wifiLp = wifiAgent.linkProperties;
         wifiLp.setInterfaceName("wlan0");
         final String[] WIFI_ADDRS = {
                 "fe80::827a:bfff:fe6f:374d", "100.112.103.18",
@@ -358,7 +412,7 @@
             wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr));
         }
         wifiAgent.fakeConnect();
-        wifiAgent.sendLinkProperties(wifiLp);
+        wifiAgent.sendLinkProperties();
 
         local = mUNM.getLocalPrefixes();
         assertPrefixSet(local, INCLUDES, alreadySeen);
@@ -372,7 +426,7 @@
 
         // [2] Pretend mobile connects.
         final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
-        final LinkProperties cellLp = new LinkProperties();
+        final LinkProperties cellLp = cellAgent.linkProperties;
         cellLp.setInterfaceName("rmnet_data0");
         final String[] CELL_ADDRS = {
                 "10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d",
@@ -382,7 +436,7 @@
             cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
         }
         cellAgent.fakeConnect();
-        cellAgent.sendLinkProperties(cellLp);
+        cellAgent.sendLinkProperties();
 
         local = mUNM.getLocalPrefixes();
         assertPrefixSet(local, INCLUDES, alreadySeen);
@@ -394,17 +448,18 @@
         // [3] Pretend DUN connects.
         final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
         dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
-        final LinkProperties dunLp = new LinkProperties();
+        dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
+        final LinkProperties dunLp = dunAgent.linkProperties;
         dunLp.setInterfaceName("rmnet_data1");
         final String[] DUN_ADDRS = {
                 "192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d",
         };
         for (String addrStr : DUN_ADDRS) {
             final String cidr = addrStr.contains(":") ? "/64" : "/27";
-            cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
+            dunLp.addLinkAddress(new LinkAddress(addrStr + cidr));
         }
         dunAgent.fakeConnect();
-        dunAgent.sendLinkProperties(dunLp);
+        dunAgent.sendLinkProperties();
 
         local = mUNM.getLocalPrefixes();
         assertPrefixSet(local, INCLUDES, alreadySeen);
@@ -442,6 +497,7 @@
     public static class TestConnectivityManager extends ConnectivityManager {
         public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>();
         public Set<NetworkCallback> trackingDefault = new HashSet<>();
+        public TestNetworkAgent defaultNetwork = null;
         public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
         public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
         public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
@@ -483,12 +539,34 @@
 
         int getNetworkId() { return ++mNetworkId; }
 
+        void makeDefaultNetwork(TestNetworkAgent agent) {
+            if (Objects.equals(defaultNetwork, agent)) return;
+
+            final TestNetworkAgent formerDefault = defaultNetwork;
+            defaultNetwork = agent;
+
+            for (NetworkCallback cb : trackingDefault) {
+                if (defaultNetwork != null) {
+                    cb.onAvailable(defaultNetwork.networkId);
+                    cb.onCapabilitiesChanged(
+                            defaultNetwork.networkId, defaultNetwork.networkCapabilities);
+                    cb.onLinkPropertiesChanged(
+                            defaultNetwork.networkId, defaultNetwork.linkProperties);
+                }
+            }
+        }
+
         @Override
         public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
             assertFalse(allCallbacks.containsKey(cb));
             allCallbacks.put(cb, h);
-            assertFalse(requested.containsKey(cb));
-            requested.put(cb, req);
+            if (mDefaultRequest.equals(req)) {
+                assertFalse(trackingDefault.contains(cb));
+                trackingDefault.add(cb);
+            } else {
+                assertFalse(requested.containsKey(cb));
+                requested.put(cb, req);
+            }
         }
 
         @Override
@@ -524,10 +602,7 @@
 
         @Override
         public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) {
-            assertFalse(allCallbacks.containsKey(cb));
-            allCallbacks.put(cb, h);
-            assertFalse(trackingDefault.contains(cb));
-            trackingDefault.add(cb);
+            fail("Should never be called.");
         }
 
         @Override
@@ -561,6 +636,7 @@
         public final Network networkId;
         public final int transportType;
         public final NetworkCapabilities networkCapabilities;
+        public final LinkProperties linkProperties;
 
         public TestNetworkAgent(TestConnectivityManager cm, int transportType) {
             this.cm = cm;
@@ -569,12 +645,14 @@
             networkCapabilities = new NetworkCapabilities();
             networkCapabilities.addTransportType(transportType);
             networkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
+            linkProperties = new LinkProperties();
         }
 
         public void fakeConnect() {
             for (NetworkCallback cb : cm.listening.keySet()) {
                 cb.onAvailable(networkId);
                 cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
+                cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
             }
         }
 
@@ -584,11 +662,16 @@
             }
         }
 
-        public void sendLinkProperties(LinkProperties lp) {
+        public void sendLinkProperties() {
             for (NetworkCallback cb : cm.listening.keySet()) {
-                cb.onLinkPropertiesChanged(networkId, lp);
+                cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
             }
         }
+
+        @Override
+        public String toString() {
+            return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities);
+        }
     }
 
     public static class TestStateMachine extends StateMachine {
@@ -618,6 +701,10 @@
         return new NetworkCapabilities(nc);
     }
 
+    static LinkProperties copy(LinkProperties lp) {
+        return new LinkProperties(lp);
+    }
+
     static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
         final Set<String> expectedSet = new HashSet<>();
         Collections.addAll(expectedSet, expected);
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
index 021185f..c723d90 100644
--- a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
+++ b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
@@ -23,6 +23,7 @@
 LOCAL_SDK_VERSION := current
 LOCAL_MODULE_TAGS := tests
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_MIN_SDK_VERSION := 21
 
 # We need this to retain the R.java generated for this library.
 LOCAL_JAR_EXCLUDE_FILES := none
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
index 39bd481..90a7f62 100644
--- a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
+++ b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
@@ -24,6 +24,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_MIN_SDK_VERSION := 21
 
 # We need this to retain the R.java generated for this library.
 LOCAL_JAR_EXCLUDE_FILES := none