Merge "Introduce RedactingFileDescriptor."
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/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
index dfbabeb..a283e06 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
@@ -47,8 +47,7 @@
public void testCreateRenderNodeNoName() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- RenderNode node = RenderNode.create(null, null);
- node.destroy();
+ RenderNode.create(null, null);
}
}
@@ -56,8 +55,7 @@
public void testCreateRenderNode() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- RenderNode node = RenderNode.create("LinearLayout", null);
- node.destroy();
+ RenderNode.create("LinearLayout", null);
}
}
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 ¶m : 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 ¶meters = 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 ¶meters = 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 ¶meters = 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/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index cc17ea8..1a3260f 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -137,6 +137,7 @@
Landroid/app/ActivityManager$StackInfo;->visible:Z
Landroid/app/ActivityManager$TaskDescription;->getBackgroundColor()I
Landroid/app/ActivityManager$TaskDescription;->getInMemoryIcon()Landroid/graphics/Bitmap;
+Landroid/app/ActivityManager$TaskDescription;->setIcon(Landroid/graphics/Bitmap;)V
Landroid/app/ActivityManager$TaskSnapshot;->getContentInsets()Landroid/graphics/Rect;
Landroid/app/ActivityManager$TaskSnapshot;->getOrientation()I
Landroid/app/ActivityManager$TaskSnapshot;->getScale()F
@@ -1217,6 +1218,7 @@
Landroid/bluetooth/IBluetooth$Stub;-><init>()V
Landroid/bluetooth/IBluetooth$Stub;->asInterface(Landroid/os/IBinder;)Landroid/bluetooth/IBluetooth;
Landroid/bluetooth/IBluetooth$Stub;->TRANSACTION_enable:I
+Landroid/bluetooth/IBluetooth;->fetchRemoteUuids(Landroid/bluetooth/BluetoothDevice;)Z
Landroid/bluetooth/IBluetooth;->getAddress()Ljava/lang/String;
Landroid/bluetooth/IBluetooth;->getRemoteAlias(Landroid/bluetooth/BluetoothDevice;)Ljava/lang/String;
Landroid/bluetooth/IBluetooth;->isEnabled()Z
@@ -1491,6 +1493,7 @@
Landroid/content/pm/IPackageManager$Stub$Proxy;->getAppOpPermissionPackages(Ljava/lang/String;)[Ljava/lang/String;
Landroid/content/pm/IPackageManager$Stub$Proxy;->getInstalledPackages(II)Landroid/content/pm/ParceledListSlice;
Landroid/content/pm/IPackageManager$Stub$Proxy;->getInstallLocation()I
+Landroid/content/pm/IPackageManager$Stub$Proxy;->getLastChosenActivity(Landroid/content/Intent;Ljava/lang/String;I)Landroid/content/pm/ResolveInfo;
Landroid/content/pm/IPackageManager$Stub$Proxy;->getPackageInfo(Ljava/lang/String;II)Landroid/content/pm/PackageInfo;
Landroid/content/pm/IPackageManager$Stub$Proxy;->getPackagesForUid(I)[Ljava/lang/String;
Landroid/content/pm/IPackageManager$Stub$Proxy;->getSystemSharedLibraryNames()[Ljava/lang/String;
@@ -1721,6 +1724,7 @@
Landroid/content/pm/PackageParser;->collectCertificates(Landroid/content/pm/PackageParser$Package;Ljava/io/File;Z)V
Landroid/content/pm/PackageParser;->collectCertificates(Landroid/content/pm/PackageParser$Package;Z)V
Landroid/content/pm/PackageParser;->generateActivityInfo(Landroid/content/pm/PackageParser$Activity;ILandroid/content/pm/PackageUserState;I)Landroid/content/pm/ActivityInfo;
+Landroid/content/pm/PackageParser;->generateApplicationInfo(Landroid/content/pm/PackageParser$Package;ILandroid/content/pm/PackageUserState;)Landroid/content/pm/ApplicationInfo;
Landroid/content/pm/PackageParser;->generateApplicationInfo(Landroid/content/pm/PackageParser$Package;ILandroid/content/pm/PackageUserState;I)Landroid/content/pm/ApplicationInfo;
Landroid/content/pm/PackageParser;->generateInstrumentationInfo(Landroid/content/pm/PackageParser$Instrumentation;I)Landroid/content/pm/InstrumentationInfo;
Landroid/content/pm/PackageParser;->generatePackageInfo(Landroid/content/pm/PackageParser$Package;[IIJJLjava/util/Set;Landroid/content/pm/PackageUserState;)Landroid/content/pm/PackageInfo;
@@ -1732,6 +1736,7 @@
Landroid/content/pm/PackageParser;->mCallback:Landroid/content/pm/PackageParser$Callback;
Landroid/content/pm/PackageParser;->NEW_PERMISSIONS:[Landroid/content/pm/PackageParser$NewPermissionInfo;
Landroid/content/pm/PackageParser;->parseBaseApk(Ljava/lang/String;Landroid/content/res/Resources;Landroid/content/res/XmlResourceParser;I[Ljava/lang/String;)Landroid/content/pm/PackageParser$Package;
+Landroid/content/pm/PackageParser;->parseBaseApplication(Landroid/content/pm/PackageParser$Package;Landroid/content/res/Resources;Landroid/content/res/XmlResourceParser;I[Ljava/lang/String;)Z
Landroid/content/pm/PackageParser;->parseMonolithicPackage(Ljava/io/File;I)Landroid/content/pm/PackageParser$Package;
Landroid/content/pm/PackageParser;->parsePackage(Ljava/io/File;I)Landroid/content/pm/PackageParser$Package;
Landroid/content/pm/PackageParser;->parsePackage(Ljava/io/File;IZ)Landroid/content/pm/PackageParser$Package;
@@ -1815,6 +1820,7 @@
Landroid/content/res/AssetManager;->setConfiguration(IILjava/lang/String;IIIIIIIIIIIIIII)V
Landroid/content/res/AssetManager;->sSystem:Landroid/content/res/AssetManager;
Landroid/content/res/ColorStateList$ColorStateListFactory;-><init>(Landroid/content/res/ColorStateList;)V
+Landroid/content/res/ColorStateList;-><init>()V
Landroid/content/res/ColorStateList;->canApplyTheme()Z
Landroid/content/res/ColorStateList;->getColors()[I
Landroid/content/res/ColorStateList;->getStates()[[I
@@ -1877,6 +1883,7 @@
Landroid/content/res/Resources;->updateSystemConfiguration(Landroid/content/res/Configuration;Landroid/util/DisplayMetrics;Landroid/content/res/CompatibilityInfo;)V
Landroid/content/res/ResourcesImpl;-><init>(Landroid/content/res/AssetManager;Landroid/util/DisplayMetrics;Landroid/content/res/Configuration;Landroid/view/DisplayAdjustments;)V
Landroid/content/res/ResourcesImpl;->getAssets()Landroid/content/res/AssetManager;
+Landroid/content/res/ResourcesImpl;->getDisplayMetrics()Landroid/util/DisplayMetrics;
Landroid/content/res/ResourcesImpl;->getValue(ILandroid/util/TypedValue;Z)V
Landroid/content/res/ResourcesImpl;->mAccessLock:Ljava/lang/Object;
Landroid/content/res/ResourcesImpl;->mAnimatorCache:Landroid/content/res/ConfigurationBoundResourceCache;
@@ -2132,6 +2139,7 @@
Landroid/graphics/CanvasProperty;->createPaint(Landroid/graphics/Paint;)Landroid/graphics/CanvasProperty;
Landroid/graphics/ColorMatrixColorFilter;->mMatrix:Landroid/graphics/ColorMatrix;
Landroid/graphics/ColorMatrixColorFilter;->setColorMatrix(Landroid/graphics/ColorMatrix;)V
+Landroid/graphics/ColorMatrixColorFilter;->setColorMatrixArray([F)V
Landroid/graphics/drawable/AnimatedImageDrawable;->onAnimationEnd()V
Landroid/graphics/drawable/AnimatedRotateDrawable;->setFramesCount(I)V
Landroid/graphics/drawable/AnimatedRotateDrawable;->setFramesDuration(I)V
@@ -4139,6 +4147,7 @@
Landroid/os/SELinux;->restoreconRecursive(Ljava/io/File;)Z
Landroid/os/ServiceManager;-><init>()V
Landroid/os/ServiceManager;->addService(Ljava/lang/String;Landroid/os/IBinder;)V
+Landroid/os/ServiceManager;->addService(Ljava/lang/String;Landroid/os/IBinder;Z)V
Landroid/os/ServiceManager;->addService(Ljava/lang/String;Landroid/os/IBinder;ZI)V
Landroid/os/ServiceManager;->checkService(Ljava/lang/String;)Landroid/os/IBinder;
Landroid/os/ServiceManager;->getIServiceManager()Landroid/os/IServiceManager;
@@ -4223,6 +4232,7 @@
Landroid/os/storage/VolumeInfo;->getDiskId()Ljava/lang/String;
Landroid/os/storage/VolumeInfo;->getEnvironmentForState(I)Ljava/lang/String;
Landroid/os/storage/VolumeInfo;->getFsUuid()Ljava/lang/String;
+Landroid/os/storage/VolumeInfo;->getInternalPath()Ljava/io/File;
Landroid/os/storage/VolumeInfo;->getInternalPathForUser(I)Ljava/io/File;
Landroid/os/storage/VolumeInfo;->getMountUserId()I
Landroid/os/storage/VolumeInfo;->getPath()Ljava/io/File;
@@ -4258,6 +4268,7 @@
Landroid/os/StrictMode;->sLastVmViolationTime:Ljava/util/HashMap;
Landroid/os/StrictMode;->sWindowManager:Landroid/util/Singleton;
Landroid/os/StrictMode;->violationsBeingTimed:Ljava/lang/ThreadLocal;
+Landroid/os/SystemClock;-><init>()V
Landroid/os/SystemClock;->currentThreadTimeMicro()J
Landroid/os/SystemClock;->currentTimeMicro()J
Landroid/os/SystemProperties;-><init>()V
@@ -4342,6 +4353,7 @@
Landroid/os/UserManager;->getUserStartRealtime()J
Landroid/os/UserManager;->getUserUnlockRealtime()J
Landroid/os/UserManager;->hasBaseUserRestriction(Ljava/lang/String;Landroid/os/UserHandle;)Z
+Landroid/os/UserManager;->isDeviceInDemoMode(Landroid/content/Context;)Z
Landroid/os/UserManager;->isGuestUser(I)Z
Landroid/os/UserManager;->isLinkedUser()Z
Landroid/os/UserManager;->isUserAdmin(I)Z
@@ -4547,6 +4559,7 @@
Landroid/provider/Settings$Global;->ZEN_MODE_IMPORTANT_INTERRUPTIONS:I
Landroid/provider/Settings$Global;->ZEN_MODE_NO_INTERRUPTIONS:I
Landroid/provider/Settings$Global;->ZEN_MODE_OFF:I
+Landroid/provider/Settings$NameValueCache;->getStringForUser(Landroid/content/ContentResolver;Ljava/lang/String;I)Ljava/lang/String;
Landroid/provider/Settings$NameValueCache;->mProviderHolder:Landroid/provider/Settings$ContentProviderHolder;
Landroid/provider/Settings$Secure;->ACCESSIBILITY_AUTOCLICK_ENABLED:Ljava/lang/String;
Landroid/provider/Settings$Secure;->ACCESSIBILITY_CAPTIONING_TYPEFACE:Ljava/lang/String;
@@ -4585,10 +4598,12 @@
Landroid/provider/Settings$Secure;->SETTINGS_TO_BACKUP:[Ljava/lang/String;
Landroid/provider/Settings$Secure;->SMS_DEFAULT_APPLICATION:Ljava/lang/String;
Landroid/provider/Settings$Secure;->sNameValueCache:Landroid/provider/Settings$NameValueCache;
+Landroid/provider/Settings$Secure;->sProviderHolder:Landroid/provider/Settings$ContentProviderHolder;
Landroid/provider/Settings$Secure;->VOICE_RECOGNITION_SERVICE:Ljava/lang/String;
Landroid/provider/Settings$System;->AIRPLANE_MODE_TOGGLEABLE_RADIOS:Ljava/lang/String;
Landroid/provider/Settings$System;->CAR_DOCK_SOUND:Ljava/lang/String;
Landroid/provider/Settings$System;->CAR_UNDOCK_SOUND:Ljava/lang/String;
+Landroid/provider/Settings$System;->CLONE_TO_MANAGED_PROFILE:Ljava/util/Set;
Landroid/provider/Settings$System;->DESK_DOCK_SOUND:Ljava/lang/String;
Landroid/provider/Settings$System;->DESK_UNDOCK_SOUND:Ljava/lang/String;
Landroid/provider/Settings$System;->DOCK_SOUNDS_ENABLED:Ljava/lang/String;
@@ -4600,6 +4615,9 @@
Landroid/provider/Settings$System;->LOCKSCREEN_SOUNDS_ENABLED:Ljava/lang/String;
Landroid/provider/Settings$System;->LOCK_SOUND:Ljava/lang/String;
Landroid/provider/Settings$System;->MASTER_MONO:Ljava/lang/String;
+Landroid/provider/Settings$System;->MOVED_TO_GLOBAL:Ljava/util/HashSet;
+Landroid/provider/Settings$System;->MOVED_TO_SECURE:Ljava/util/HashSet;
+Landroid/provider/Settings$System;->MOVED_TO_SECURE_THEN_GLOBAL:Ljava/util/HashSet;
Landroid/provider/Settings$System;->NOTIFICATION_LIGHT_PULSE:Ljava/lang/String;
Landroid/provider/Settings$System;->POINTER_LOCATION:Ljava/lang/String;
Landroid/provider/Settings$System;->POINTER_SPEED:Ljava/lang/String;
@@ -4614,6 +4632,7 @@
Landroid/provider/Settings$System;->sProviderHolder:Landroid/provider/Settings$ContentProviderHolder;
Landroid/provider/Settings$System;->TTY_MODE:Ljava/lang/String;
Landroid/provider/Settings$System;->UNLOCK_SOUND:Ljava/lang/String;
+Landroid/provider/Settings$System;->VALIDATORS:Ljava/util/Map;
Landroid/provider/Settings$System;->VIBRATE_IN_SILENT:Ljava/lang/String;
Landroid/provider/Settings;->ACTION_TRUSTED_CREDENTIALS_USER:Ljava/lang/String;
Landroid/provider/Settings;->ACTION_USER_DICTIONARY_INSERT:Ljava/lang/String;
@@ -5357,6 +5376,7 @@
Landroid/telephony/ServiceState;->mCdmaRoamingIndicator:I
Landroid/telephony/ServiceState;->mCssIndicator:Z
Landroid/telephony/ServiceState;->mIsManualNetworkSelection:Z
+Landroid/telephony/ServiceState;->mIsUsingCarrierAggregation:Z
Landroid/telephony/ServiceState;->mNetworkId:I
Landroid/telephony/ServiceState;->mSystemId:I
Landroid/telephony/ServiceState;->newFromBundle(Landroid/os/Bundle;)Landroid/telephony/ServiceState;
@@ -5720,6 +5740,7 @@
Landroid/transition/Scene;->setCurrentScene(Landroid/view/View;Landroid/transition/Scene;)V
Landroid/transition/Transition;->cancel()V
Landroid/transition/Transition;->end()V
+Landroid/transition/Transition;->getRunningAnimators()Landroid/util/ArrayMap;
Landroid/transition/TransitionManager;->getRunningTransitions()Landroid/util/ArrayMap;
Landroid/transition/TransitionManager;->sPendingTransitions:Ljava/util/ArrayList;
Landroid/transition/TransitionManager;->sRunningTransitions:Ljava/lang/ThreadLocal;
@@ -6166,6 +6187,7 @@
Landroid/view/SurfaceControl;->HIDDEN:I
Landroid/view/SurfaceControl;->hide()V
Landroid/view/SurfaceControl;->openTransaction()V
+Landroid/view/SurfaceControl;->screenshot(Landroid/graphics/Rect;III)Landroid/graphics/Bitmap;
Landroid/view/SurfaceControl;->screenshot(Landroid/graphics/Rect;IIIIZI)Landroid/graphics/Bitmap;
Landroid/view/SurfaceControl;->screenshot(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;IIIIZZ)V
Landroid/view/SurfaceControl;->setDisplayLayerStack(Landroid/os/IBinder;I)V
@@ -6959,6 +6981,7 @@
Landroid/widget/ListView;->fillSpecific(II)Landroid/view/View;
Landroid/widget/ListView;->fillUp(II)Landroid/view/View;
Landroid/widget/ListView;->getHeightForPosition(I)I
+Landroid/widget/ListView;->isDirectChildHeaderOrFooter(Landroid/view/View;)Z
Landroid/widget/ListView;->makeAndAddView(IIZIZ)Landroid/view/View;
Landroid/widget/ListView;->mAreAllItemsSelectable:Z
Landroid/widget/ListView;->mDivider:Landroid/graphics/drawable/Drawable;
@@ -7507,6 +7530,7 @@
Lcom/android/internal/appwidget/IAppWidgetService;->bindAppWidgetId(Ljava/lang/String;IILandroid/content/ComponentName;Landroid/os/Bundle;)Z
Lcom/android/internal/appwidget/IAppWidgetService;->bindRemoteViewsService(Ljava/lang/String;ILandroid/content/Intent;Landroid/app/IApplicationThread;Landroid/os/IBinder;Landroid/app/IServiceConnection;I)Z
Lcom/android/internal/appwidget/IAppWidgetService;->getAppWidgetIds(Landroid/content/ComponentName;)[I
+Lcom/android/internal/appwidget/IAppWidgetService;->getAppWidgetViews(Ljava/lang/String;I)Landroid/widget/RemoteViews;
Lcom/android/internal/backup/IBackupTransport$Stub;-><init>()V
Lcom/android/internal/backup/IBackupTransport$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/backup/IBackupTransport;
Lcom/android/internal/backup/IBackupTransport;->clearBackupData(Landroid/content/pm/PackageInfo;)I
@@ -7723,6 +7747,9 @@
Lcom/android/internal/R$attr;->text:I
Lcom/android/internal/R$attr;->title:I
Lcom/android/internal/R$attr;->webViewStyle:I
+Lcom/android/internal/R$bool;-><init>()V
+Lcom/android/internal/R$bool;->config_automatic_brightness_available:I
+Lcom/android/internal/R$bool;->config_intrusiveNotificationLed:I
Lcom/android/internal/R$bool;->config_mms_content_disposition_support:I
Lcom/android/internal/R$bool;->config_showNavigationBar:I
Lcom/android/internal/R$dimen;-><init>()V
@@ -8146,6 +8173,8 @@
Lcom/android/internal/R$styleable;->Window:[I
Lcom/android/internal/R$styleable;->WindowAnimation:[I
Lcom/android/internal/R$styleable;->Window_windowActionBarFullscreenDecorLayout:I
+Lcom/android/internal/R$styleable;->Window_windowBackground:I
+Lcom/android/internal/R$styleable;->Window_windowFullscreen:I
Lcom/android/internal/R$styleable;->Window_windowIsFloating:I
Lcom/android/internal/R$styleable;->Window_windowIsTranslucent:I
Lcom/android/internal/R$styleable;->Window_windowShowWallpaper:I
@@ -8215,6 +8244,7 @@
Lcom/android/internal/telephony/IPhoneStateListener;->onSignalStrengthChanged(I)V
Lcom/android/internal/telephony/IPhoneStateListener;->onSignalStrengthsChanged(Landroid/telephony/SignalStrength;)V
Lcom/android/internal/telephony/IPhoneSubInfo$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Lcom/android/internal/telephony/IPhoneSubInfo$Stub$Proxy;->getDeviceId(Ljava/lang/String;)Ljava/lang/String;
Lcom/android/internal/telephony/IPhoneSubInfo$Stub;-><init>()V
Lcom/android/internal/telephony/IPhoneSubInfo$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/IPhoneSubInfo;
Lcom/android/internal/telephony/IPhoneSubInfo$Stub;->TRANSACTION_getDeviceId:I
@@ -8400,6 +8430,8 @@
Lcom/android/internal/view/menu/MenuBuilder;->addMenuPresenter(Lcom/android/internal/view/menu/MenuPresenter;Landroid/content/Context;)V
Lcom/android/internal/view/menu/MenuBuilder;->collapseItemActionView(Lcom/android/internal/view/menu/MenuItemImpl;)Z
Lcom/android/internal/view/menu/MenuBuilder;->getContext()Landroid/content/Context;
+Lcom/android/internal/view/menu/MenuBuilder;->getHeaderIcon()Landroid/graphics/drawable/Drawable;
+Lcom/android/internal/view/menu/MenuBuilder;->getHeaderTitle()Ljava/lang/CharSequence;
Lcom/android/internal/view/menu/MenuBuilder;->getNonActionItems()Ljava/util/ArrayList;
Lcom/android/internal/view/menu/MenuBuilder;->getRootMenu()Lcom/android/internal/view/menu/MenuBuilder;
Lcom/android/internal/view/menu/MenuBuilder;->getVisibleItems()Ljava/util/ArrayList;
@@ -8417,11 +8449,14 @@
Lcom/android/internal/view/menu/MenuItemImpl;->requestsActionButton()Z
Lcom/android/internal/view/menu/MenuItemImpl;->requiresActionButton()Z
Lcom/android/internal/view/menu/MenuItemImpl;->setActionViewExpanded(Z)V
+Lcom/android/internal/view/menu/MenuItemImpl;->setExclusiveCheckable(Z)V
Lcom/android/internal/view/menu/MenuItemImpl;->setMenuInfo(Landroid/view/ContextMenu$ContextMenuInfo;)V
Lcom/android/internal/view/menu/MenuPopupHelper;-><init>(Landroid/content/Context;Lcom/android/internal/view/menu/MenuBuilder;)V
Lcom/android/internal/view/menu/MenuPopupHelper;-><init>(Landroid/content/Context;Lcom/android/internal/view/menu/MenuBuilder;Landroid/view/View;)V
Lcom/android/internal/view/menu/MenuPopupHelper;->dismiss()V
+Lcom/android/internal/view/menu/MenuPopupHelper;->getPopup()Lcom/android/internal/view/menu/MenuPopup;
Lcom/android/internal/view/menu/MenuPopupHelper;->mForceShowIcon:Z
+Lcom/android/internal/view/menu/MenuPopupHelper;->setAnchorView(Landroid/view/View;)V
Lcom/android/internal/view/menu/MenuPopupHelper;->setForceShowIcon(Z)V
Lcom/android/internal/view/menu/MenuPopupHelper;->setGravity(I)V
Lcom/android/internal/view/menu/MenuPopupHelper;->show()V
@@ -8751,6 +8786,7 @@
Ljava/lang/reflect/Field;->getOffset()I
Ljava/lang/reflect/Parameter;-><init>(Ljava/lang/String;ILjava/lang/reflect/Executable;I)V
Ljava/lang/reflect/Proxy;->invoke(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
+Ljava/lang/Runtime;-><init>()V
Ljava/lang/Runtime;->load(Ljava/lang/String;Ljava/lang/ClassLoader;)V
Ljava/lang/Runtime;->loadLibrary(Ljava/lang/String;Ljava/lang/ClassLoader;)V
Ljava/lang/Runtime;->loadLibrary0(Ljava/lang/ClassLoader;Ljava/lang/String;)V
@@ -9119,6 +9155,7 @@
Lorg/xml/sax/helpers/NamespaceSupport;->contexts:[Lorg/xml/sax/helpers/NamespaceSupport$Context;
Lorg/xml/sax/helpers/NamespaceSupport;->currentContext:Lorg/xml/sax/helpers/NamespaceSupport$Context;
Lorg/xml/sax/helpers/NamespaceSupport;->EMPTY_ENUMERATION:Ljava/util/Enumeration;
+Lorg/xml/sax/helpers/NamespaceSupport;->namespaceDeclUris:Z
Lorg/xml/sax/helpers/ParserAdapter;->attAdapter:Lorg/xml/sax/helpers/ParserAdapter$AttributeListAdapter;
Lorg/xml/sax/helpers/ParserAdapter;->atts:Lorg/xml/sax/helpers/AttributesImpl;
Lorg/xml/sax/helpers/ParserAdapter;->checkNotParsing(Ljava/lang/String;Ljava/lang/String;)V
@@ -9138,6 +9175,7 @@
Lorg/xml/sax/helpers/ParserAdapter;->reportError(Ljava/lang/String;)V
Lorg/xml/sax/helpers/ParserAdapter;->setup(Lorg/xml/sax/Parser;)V
Lorg/xml/sax/helpers/ParserAdapter;->setupParser()V
+Lorg/xml/sax/helpers/ParserAdapter;->uris:Z
Lorg/xml/sax/helpers/XMLFilterImpl;->contentHandler:Lorg/xml/sax/ContentHandler;
Lorg/xml/sax/helpers/XMLFilterImpl;->dtdHandler:Lorg/xml/sax/DTDHandler;
Lorg/xml/sax/helpers/XMLFilterImpl;->entityResolver:Lorg/xml/sax/EntityResolver;
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 09c7981..7a729f9 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -59,11 +59,6 @@
/** The current windowing mode of the configuration. */
private @WindowingMode int mWindowingMode;
- private int mFlags;
-
- /** Indicates that this window should always be on top of the other windows. */
- private static final int PFLAG_ALWAYS_ON_TOP = 1 << 0;
-
/** Windowing mode is currently not defined. */
public static final int WINDOWING_MODE_UNDEFINED = 0;
/** Occupies the full area of the screen or the parent container. */
@@ -129,6 +124,24 @@
})
public @interface ActivityType {}
+ /** The current always on top status of the configuration. */
+ private @AlwaysOnTop int mAlwaysOnTop;
+
+ /** Always on top is currently not defined. */
+ private static final int ALWAYS_ON_TOP_UNDEFINED = 0;
+ /** Always on top is currently on for this configuration. */
+ private static final int ALWAYS_ON_TOP_ON = 1;
+ /** Always on top is currently off for this configuration. */
+ private static final int ALWAYS_ON_TOP_OFF = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "ALWAYS_ON_TOP_" }, value = {
+ ALWAYS_ON_TOP_UNDEFINED,
+ ALWAYS_ON_TOP_ON,
+ ALWAYS_ON_TOP_OFF,
+ })
+ private @interface AlwaysOnTop {}
+
/** Bit that indicates that the {@link #mBounds} changed.
* @hide */
public static final int WINDOW_CONFIG_BOUNDS = 1 << 0;
@@ -141,16 +154,16 @@
/** Bit that indicates that the {@link #mActivityType} changed.
* @hide */
public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 3;
- /** Bit that indicates that the {@link #mFlags} changed.
+ /** Bit that indicates that the {@link #mAlwaysOnTop} changed.
* @hide */
- public static final int WINDOW_CONFIG_FLAGS = 1 << 4;
+ public static final int WINDOW_CONFIG_ALWAYS_ON_TOP = 1 << 4;
/** @hide */
@IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = {
WINDOW_CONFIG_BOUNDS,
WINDOW_CONFIG_APP_BOUNDS,
WINDOW_CONFIG_WINDOWING_MODE,
WINDOW_CONFIG_ACTIVITY_TYPE,
- WINDOW_CONFIG_FLAGS
+ WINDOW_CONFIG_ALWAYS_ON_TOP,
})
public @interface WindowConfig {}
@@ -176,7 +189,7 @@
dest.writeParcelable(mAppBounds, flags);
dest.writeInt(mWindowingMode);
dest.writeInt(mActivityType);
- dest.writeInt(mFlags);
+ dest.writeInt(mAlwaysOnTop);
}
private void readFromParcel(Parcel source) {
@@ -184,7 +197,7 @@
mAppBounds = source.readParcelable(Rect.class.getClassLoader());
mWindowingMode = source.readInt();
mActivityType = source.readInt();
- mFlags = source.readInt();
+ mAlwaysOnTop = source.readInt();
}
@Override
@@ -232,9 +245,7 @@
setAppBounds(rect.left, rect.top, rect.right, rect.bottom);
}
- private void setFlags(int flags) {
- mFlags = flags;
- }
+
/**
* Sets whether this window should be always on top.
@@ -242,11 +253,11 @@
* @hide
*/
public void setAlwaysOnTop(boolean alwaysOnTop) {
- if (alwaysOnTop) {
- mFlags |= PFLAG_ALWAYS_ON_TOP;
- } else {
- mFlags &= ~PFLAG_ALWAYS_ON_TOP;
- }
+ mAlwaysOnTop = alwaysOnTop ? ALWAYS_ON_TOP_ON : ALWAYS_ON_TOP_OFF;
+ }
+
+ private void setAlwaysOnTop(@AlwaysOnTop int alwaysOnTop) {
+ mAlwaysOnTop = alwaysOnTop;
}
/**
@@ -308,7 +319,7 @@
setAppBounds(other.mAppBounds);
setWindowingMode(other.mWindowingMode);
setActivityType(other.mActivityType);
- setFlags(other.mFlags);
+ setAlwaysOnTop(other.mAlwaysOnTop);
}
/** Set this object to completely undefined.
@@ -323,7 +334,7 @@
setBounds(null);
setWindowingMode(WINDOWING_MODE_UNDEFINED);
setActivityType(ACTIVITY_TYPE_UNDEFINED);
- setFlags(0);
+ setAlwaysOnTop(ALWAYS_ON_TOP_UNDEFINED);
}
/**
@@ -341,10 +352,6 @@
changed |= WINDOW_CONFIG_BOUNDS;
setBounds(delta.mBounds);
}
- if (delta.mFlags != mFlags) {
- changed |= WINDOW_CONFIG_FLAGS;
- setFlags(delta.mFlags);
- }
if (delta.mAppBounds != null && !delta.mAppBounds.equals(mAppBounds)) {
changed |= WINDOW_CONFIG_APP_BOUNDS;
setAppBounds(delta.mAppBounds);
@@ -359,6 +366,11 @@
changed |= WINDOW_CONFIG_ACTIVITY_TYPE;
setActivityType(delta.mActivityType);
}
+ if (delta.mAlwaysOnTop != ALWAYS_ON_TOP_UNDEFINED
+ && mAlwaysOnTop != delta.mAlwaysOnTop) {
+ changed |= WINDOW_CONFIG_ALWAYS_ON_TOP;
+ setAlwaysOnTop(delta.mAlwaysOnTop);
+ }
return changed;
}
@@ -380,10 +392,6 @@
changes |= WINDOW_CONFIG_BOUNDS;
}
- if (mFlags != other.mFlags) {
- changes |= WINDOW_CONFIG_FLAGS;
- }
-
// Make sure that one of the values is not null and that they are not equal.
if ((compareUndefined || other.mAppBounds != null)
&& mAppBounds != other.mAppBounds
@@ -401,6 +409,11 @@
changes |= WINDOW_CONFIG_ACTIVITY_TYPE;
}
+ if ((compareUndefined || other.mAlwaysOnTop != ALWAYS_ON_TOP_UNDEFINED)
+ && mAlwaysOnTop != other.mAlwaysOnTop) {
+ changes |= WINDOW_CONFIG_ALWAYS_ON_TOP;
+ }
+
return changes;
}
@@ -435,8 +448,7 @@
if (n != 0) return n;
n = mActivityType - that.mActivityType;
if (n != 0) return n;
-
- n = mFlags - that.mFlags;
+ n = mAlwaysOnTop - that.mAlwaysOnTop;
if (n != 0) return n;
// if (n != 0) return n;
@@ -465,7 +477,7 @@
result = 31 * result + mWindowingMode;
result = 31 * result + mActivityType;
- result = 31 * result + mFlags;
+ result = 31 * result + mAlwaysOnTop;
return result;
}
@@ -476,7 +488,7 @@
+ " mAppBounds=" + mAppBounds
+ " mWindowingMode=" + windowingModeToString(mWindowingMode)
+ " mActivityType=" + activityTypeToString(mActivityType)
- + " mFlags=0x" + Integer.toHexString(mFlags)
+ + " mAlwaysOnTop=" + activityTypeToString(mAlwaysOnTop)
+ "}";
}
@@ -563,7 +575,7 @@
* @hide
*/
public boolean isAlwaysOnTop() {
- return mWindowingMode == WINDOWING_MODE_PINNED || (mFlags & PFLAG_ALWAYS_ON_TOP) != 0;
+ return mWindowingMode == WINDOWING_MODE_PINNED || mAlwaysOnTop == ALWAYS_ON_TOP_ON;
}
/**
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/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
index 671532c..df4d5c4 100644
--- a/core/java/android/view/DisplayListCanvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -180,13 +180,12 @@
///////////////////////////////////////////////////////////////////////////
/**
- * Draws the specified display list onto this canvas. The display list can only
- * be drawn if {@link android.view.RenderNode#isValid()} returns true.
+ * Draws the specified display list onto this canvas.
*
* @param renderNode The RenderNode to draw.
*/
public void drawRenderNode(RenderNode renderNode) {
- nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList());
+ nDrawRenderNode(mNativeCanvasWrapper, renderNode.mNativeRenderNode);
}
///////////////////////////////////////////////////////////////////////////
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 7c25fac..e10eeb0 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -139,7 +139,9 @@
RenderNode.class.getClassLoader(), nGetNativeFinalizer(), 1024);
}
- // Do not access directly unless you are ThreadedRenderer
+ /** Not for general use; use only if you are ThreadedRenderer or DisplayListCanvas.
+ * @hide
+ */
final long mNativeRenderNode;
private final View mOwningView;
@@ -159,15 +161,6 @@
}
/**
- * Immediately destroys the RenderNode
- * Only suitable for testing/benchmarking where waiting for the GC/finalizer
- * is not feasible.
- */
- public void destroy() {
- // TODO: Removed temporarily
- }
-
- /**
* Creates a new RenderNode that can be used to record batches of
* drawing operations, and store / apply render properties when drawn.
*
@@ -219,6 +212,14 @@
}
/**
+ * Same as {@link #start(int, int)} but with the RenderNode's width & height
+ */
+ public DisplayListCanvas start() {
+ return DisplayListCanvas.obtain(this,
+ nGetWidth(mNativeRenderNode), nGetHeight(mNativeRenderNode));
+ }
+
+ /**
* Ends the recording for this display list. A display list cannot be
* replayed if recording is not finished. Calling this method marks
* the display list valid and {@link #isValid()} will return true.
@@ -251,13 +252,6 @@
return nIsValid(mNativeRenderNode);
}
- long getNativeDisplayList() {
- if (!isValid()) {
- throw new IllegalStateException("The display list is not valid.");
- }
- return mNativeRenderNode;
- }
-
///////////////////////////////////////////////////////////////////////////
// Matrix manipulation
///////////////////////////////////////////////////////////////////////////
@@ -463,7 +457,6 @@
* @see #setHasOverlappingRendering(boolean)
*/
public boolean hasOverlappingRendering() {
- //noinspection SimplifiableIfStatement
return nHasOverlappingRendering(mNativeRenderNode);
}
@@ -1009,4 +1002,8 @@
private static native float nGetPivotX(long renderNode);
@CriticalNative
private static native float nGetPivotY(long renderNode);
+ @CriticalNative
+ private static native int nGetWidth(long renderNode);
+ @CriticalNative
+ private static native int nGetHeight(long renderNode);
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index aa1e407..2f975b6 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -850,7 +850,9 @@
void buildLayer(RenderNode node) {
- nBuildLayer(mNativeProxy, node.getNativeDisplayList());
+ if (node.isValid()) {
+ nBuildLayer(mNativeProxy, node.mNativeRenderNode);
+ }
}
@@ -928,7 +930,7 @@
* not the RenderNode from a View.
**/
public static Bitmap createHardwareBitmap(RenderNode node, int width, int height) {
- return nCreateHardwareBitmap(node.getNativeDisplayList(), width, height);
+ return nCreateHardwareBitmap(node.mNativeRenderNode, width, height);
}
/**
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 276f50a..e4c595b 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -531,7 +531,6 @@
throws IOException {
RenderNode node = RenderNode.create("ViewDebug", null);
profileViewAndChildren(view, node, out, true);
- node.destroy();
}
private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
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/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 929496f..11054c8 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -611,7 +611,6 @@
mRenderer.destroy();
mSurfaceControl.destroy();
mSurfaceSession.kill();
- mBitmapRenderNode.destroy();
mHandler.removeCallbacks(mMagnifierUpdater);
if (mBitmap != null) {
mBitmap.recycle();
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/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 37ea810..22bbc3c 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -431,6 +431,14 @@
return renderNode->stagingProperties().getPivotY();
}
+static jint android_view_RenderNode_getWidth(jlong renderNodePtr) {
+ return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getWidth();
+}
+
+static jint android_view_RenderNode_getHeight(jlong renderNodePtr) {
+ return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getHeight();
+}
+
// ----------------------------------------------------------------------------
// RenderProperties - Animations
// ----------------------------------------------------------------------------
@@ -648,6 +656,8 @@
{ "nGetPivotX", "(J)F", (void*) android_view_RenderNode_getPivotX },
{ "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY },
+ { "nGetWidth", "(J)I", (void*) android_view_RenderNode_getWidth },
+ { "nGetHeight", "(J)I", (void*) android_view_RenderNode_getHeight },
};
int register_android_view_RenderNode(JNIEnv* env) {
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..c601215 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -262,6 +262,7 @@
<protected-broadcast android:name="android.intent.action.HEADSET_PLUG" />
<protected-broadcast android:name="android.media.action.HDMI_AUDIO_PLUG" />
+ <protected-broadcast android:name="android.media.action.MICROPHONE_MUTE_CHANGED" />
<protected-broadcast android:name="android.media.AUDIO_BECOMING_NOISY" />
<protected-broadcast android:name="android.media.RINGER_MODE_CHANGED" />
@@ -598,6 +599,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 +823,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 +836,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 +847,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
@@ -2251,7 +2258,8 @@
android:description="@string/permdesc_install_shortcut"
android:protectionLevel="normal"/>
- <!--This permission is no longer supported.
+ <!-- <p class="caution"><strong>Don't use this permission in your app.</strong><br>This
+ permission is no longer supported.
-->
<permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
android:label="@string/permlab_uninstall_shortcut"
@@ -3989,6 +3997,11 @@
<permission android:name="android.permission.DISABLE_HIDDEN_API_CHECKS"
android:protectionLevel="signature" />
+ <!-- Allows an application to read emergency info name.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="com.android.emergency.permission.READ_EMERGENCY_INFO_NAME"
+ android:protectionLevel="signature" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
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..408a4d0 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 -->
@@ -3471,4 +3479,6 @@
<!-- Whether or not swipe up gesture's opt-in setting is available on this device -->
<bool name="config_swipe_up_gesture_setting_available">false</bool>
+ <!-- Whether or not we should show the option to show battery percentage -->
+ <bool name="config_battery_percentage_setting_available">true</bool>
</resources>
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
<b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></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
+ <b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></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..d9642cb 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" />
@@ -2199,6 +2200,7 @@
<java-symbol type="string" name="ext_media_move_failure_message" />
<java-symbol type="style" name="Animation.RecentApplications" />
<java-symbol type="integer" name="dock_enter_exit_duration" />
+ <java-symbol type="bool" name="config_battery_percentage_setting_available" />
<!-- ImfTest -->
<java-symbol type="layout" name="auto_complete_list" />
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/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 7f8cb2d..3d50d2d 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -127,7 +127,7 @@
return false;
}
void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
- if (!addr) {
+ if (addr == MAP_FAILED) {
int err = errno;
// The file not existing is normal for addToDump(), so only log if
// we get an unexpected error
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/AudioManager.java b/media/java/android/media/AudioManager.java
index 196b886..1a282b2 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -794,6 +794,11 @@
* <p>
* This method should only be used by applications that replace the platform-wide
* management of audio settings or the main telephony application.
+ * <p>This method has no effect if the device implements a fixed volume policy
+ * as indicated by {@link #isVolumeFixed()}.
+ * <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed
+ * unless the app has been granted Do Not Disturb Access.
+ * See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
*
* @param streamType The stream type to adjust. One of {@link #STREAM_VOICE_CALL},
* {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, {@link #STREAM_MUSIC},
@@ -804,6 +809,8 @@
* @param flags One or more flags.
* @see #adjustVolume(int, int)
* @see #setStreamVolume(int, int, int)
+ * @throws SecurityException if the adjustment triggers a Do Not Disturb change
+ * and the caller is not granted notification policy access.
*/
public void adjustStreamVolume(int streamType, int direction, int flags) {
final IAudioService service = getService();
@@ -1121,6 +1128,8 @@
* @see #getStreamMaxVolume(int)
* @see #getStreamVolume(int)
* @see #isVolumeFixed()
+ * @throws SecurityException if the volume change triggers a Do Not Disturb change
+ * and the caller is not granted notification policy access.
*/
public void setStreamVolume(int streamType, int index, int flags) {
final IAudioService service = getService();
diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java
index aae1f51..6bf52bd 100644
--- a/media/java/android/media/MediaHTTPConnection.java
+++ b/media/java/android/media/MediaHTTPConnection.java
@@ -323,8 +323,10 @@
StrictMode.setThreadPolicy(policy);
try {
- if (offset != mCurrentOffset) {
- seekTo(offset);
+ synchronized(this) {
+ if (offset != mCurrentOffset) {
+ seekTo(offset);
+ }
}
int n = mInputStream.read(data, 0, size);
@@ -366,7 +368,7 @@
}
@Override
- public long getSize() {
+ public synchronized long getSize() {
if (mConnection == null) {
try {
seekTo(0);
@@ -379,7 +381,7 @@
}
@Override
- public String getMIMEType() {
+ public synchronized String getMIMEType() {
if (mConnection == null) {
try {
seekTo(0);
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/media/native/midi/include/midi.h b/media/native/midi/include/midi.h
index 780d8a7..755d09f 100644
--- a/media/native/midi/include/midi.h
+++ b/media/native/midi/include/midi.h
@@ -71,7 +71,7 @@
* @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - an unknown error occurred.
*/
media_status_t AMIDI_API AMidiDevice_fromJava(
- JNIEnv *env, jobject midiDeviceObj, AMidiDevice **outDevicePtrPtr);
+ JNIEnv *env, jobject midiDeviceObj, AMidiDevice **outDevicePtrPtr) __INTRODUCED_IN(29);
/**
* Disconnects the native Midi Device Object from the associated Java MidiDevice object.
@@ -88,7 +88,7 @@
* - the JNI interface initialization to the associated java MidiDevice failed.
* @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - couldn't retrieve the device info.
*/
-media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *midiDevice);
+media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *midiDevice) __INTRODUCED_IN(29);
/**
* Gets the MIDI device type.
@@ -104,7 +104,7 @@
* parameter is NULL.
* @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown error.
*/
-int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device);
+int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device) __INTRODUCED_IN(29);
/**
* Gets the number of input (sending) ports available on the specified MIDI device.
@@ -117,7 +117,7 @@
* parameter is NULL.
* @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - couldn't retrieve the device info.
*/
-ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device);
+ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) __INTRODUCED_IN(29);
/**
* Gets the number of output (receiving) ports available on the specified MIDI device.
@@ -130,7 +130,7 @@
* parameter is NULL.
* @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN}- couldn't retrieve the device info.
*/
-ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device);
+ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) __INTRODUCED_IN(29);
/*
* API for receiving data from the Output port of a device.
@@ -149,14 +149,14 @@
* @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown Error.
*/
media_status_t AMIDI_API AMidiOutputPort_open(const AMidiDevice *device, int32_t portNumber,
- AMidiOutputPort **outOutputPortPtr);
+ AMidiOutputPort **outOutputPortPtr) __INTRODUCED_IN(29);
/**
* Closes the output port.
*
* @param outputPort The native API port identifier of the port.
*/
-void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort);
+void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort) __INTRODUCED_IN(29);
/**
* Receives the next pending MIDI message. To retrieve all pending messages, the client should
@@ -177,7 +177,7 @@
* @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown Error.
*/
ssize_t AMIDI_API AMidiOutputPort_receive(const AMidiOutputPort *outputPort, int32_t *opcodePtr,
- uint8_t *buffer, size_t maxBytes, size_t* numBytesReceivedPtr, int64_t *outTimestampPtr);
+ uint8_t *buffer, size_t maxBytes, size_t* numBytesReceivedPtr, int64_t *outTimestampPtr) __INTRODUCED_IN(29);
/*
* API for sending data to the Input port of a device.
@@ -196,7 +196,7 @@
* @see AMEDIA_ERROR_UNKNOWN {@link AMEDIA_ERROR_UNKNOWN} - Unknown Error.
*/
media_status_t AMIDI_API AMidiInputPort_open(const AMidiDevice *device, int32_t portNumber,
- AMidiInputPort **outInputPortPtr);
+ AMidiInputPort **outInputPortPtr) __INTRODUCED_IN(29);
/**
* Sends data to the specified input port.
@@ -210,7 +210,7 @@
* was NULL, the specified buffer was NULL.
*/
ssize_t AMIDI_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uint8_t *buffer,
- size_t numBytes);
+ size_t numBytes) __INTRODUCED_IN(29);
/**
* Sends data to the specified input port with a timestamp.
@@ -225,7 +225,7 @@
* was NULL, the specified buffer was NULL.
*/
ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
- const uint8_t *buffer, size_t numBytes, int64_t timestamp);
+ const uint8_t *buffer, size_t numBytes, int64_t timestamp) __INTRODUCED_IN(29);
/**
* Sends a message with a 'MIDI flush command code' to the specified port. This should cause
@@ -239,14 +239,14 @@
* @see AMEDIA_ERROR_UNSUPPORTED {@link AMEDIA_ERROR_UNSUPPORTED} The FLUSH command couldn't
* be sent.
*/
-media_status_t AMIDI_API AMidiInputPort_sendFlush(const AMidiInputPort *inputPort);
+media_status_t AMIDI_API AMidiInputPort_sendFlush(const AMidiInputPort *inputPort) __INTRODUCED_IN(29);
/**
* Closes the input port.
*
* @param inputPort Identifies the input (sending) port to close.
*/
-void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort);
+void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort) __INTRODUCED_IN(29);
#ifdef __cplusplus
}
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/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
index f7aa297..87f5b4f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
@@ -41,6 +41,7 @@
import android.util.Pair;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.time.ZonedDateTime;
import java.util.Date;
@@ -86,7 +87,8 @@
* mContext.getResources().getInteger(R.integer.default_data_warning_level_mb);
}
- private INetworkStatsSession getSession() {
+ @VisibleForTesting
+ INetworkStatsSession getSession() {
if (mSession == null) {
try {
mSession = mStatsService.openSession();
@@ -176,6 +178,30 @@
}
}
+ /**
+ * Get the total usage level recorded in the network history
+ * @param template the network template to retrieve the network history
+ * @return the total usage level recorded in the network history
+ */
+ public long getHistoriclUsageLevel(NetworkTemplate template) {
+ final INetworkStatsSession session = getSession();
+ if (session != null) {
+ try {
+ final NetworkStatsHistory history = session.getHistoryForNetwork(template, FIELDS);
+ final long now = System.currentTimeMillis();
+ final NetworkStatsHistory.Entry entry =
+ history.getValues(0L /* start */, now /* end */, now, null /* recycle */);
+ if (entry != null) {
+ return entry.rxBytes + entry.txBytes;
+ }
+ Log.w(TAG, "Failed to get data usage, no entry data");
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get data usage, remote call failed");
+ }
+ }
+ return 0L;
+ }
+
private NetworkPolicy findNetworkPolicy(NetworkTemplate template) {
if (mPolicyManager == null || template == null) return null;
final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies();
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/net/DataUsageControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
new file mode 100644
index 0000000..1be856a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.net;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.INetworkStatsSession;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkStatsHistory.Entry;
+import android.net.NetworkTemplate;
+import android.os.RemoteException;
+import android.text.format.DateUtils;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+public class DataUsageControllerTest {
+
+ @Mock
+ private INetworkStatsSession mSession;
+
+ private Context mContext;
+ private DataUsageController mController;
+ private NetworkStatsHistory mNetworkStatsHistory;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mController = spy(new DataUsageController(mContext));
+ mNetworkStatsHistory = spy(
+ new NetworkStatsHistory(DateUtils.DAY_IN_MILLIS /* bucketDuration */));
+ doReturn(mNetworkStatsHistory)
+ .when(mSession).getHistoryForNetwork(any(NetworkTemplate.class), anyInt());
+ }
+
+ @Test
+ public void getHistoriclUsageLevel_noNetworkSession_shouldReturn0() {
+ doReturn(null).when(mController).getSession();
+
+ assertThat(mController.getHistoriclUsageLevel(null /* template */)).isEqualTo(0L);
+
+ }
+
+ @Test
+ public void getHistoriclUsageLevel_noUsageData_shouldReturn0() {
+ doReturn(mSession).when(mController).getSession();
+
+ assertThat(mController.getHistoriclUsageLevel(NetworkTemplate.buildTemplateWifiWildcard()))
+ .isEqualTo(0L);
+
+ }
+
+ @Test
+ public void getHistoriclUsageLevel_hasUsageData_shouldReturnTotalUsage() {
+ doReturn(mSession).when(mController).getSession();
+ final long receivedBytes = 743823454L;
+ final long transmittedBytes = 16574289L;
+ final Entry entry = new Entry();
+ entry.bucketStart = 1521583200000L;
+ entry.rxBytes = receivedBytes;
+ entry.txBytes = transmittedBytes;
+ when(mNetworkStatsHistory.getValues(eq(0L), anyLong(), anyLong(), nullable(Entry.class)))
+ .thenReturn(entry);
+
+ assertThat(mController.getHistoriclUsageLevel(NetworkTemplate.buildTemplateWifiWildcard()))
+ .isEqualTo(receivedBytes + transmittedBytes);
+
+ }
+}
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/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 88d19f4..28466fd 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -161,30 +161,31 @@
style="@style/TextAppearance.NotificationInfo.Button"/>
</LinearLayout>
</LinearLayout>
- <RelativeLayout
+ <com.android.systemui.statusbar.NotificationUndoLayout
android:id="@+id/confirmation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_guts_button_spacing"
- android:layout_marginTop="@dimen/notification_guts_button_spacing"
- android:layout_marginStart="@dimen/notification_guts_button_side_margin"
- android:layout_marginEnd="@dimen/notification_guts_button_side_margin"
android:visibility="gone"
android:orientation="horizontal" >
<TextView
android:id="@+id/confirmation_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
+ android:layout_gravity="start|center_vertical"
+ android:layout_marginStart="@*android:dimen/notification_content_margin_start"
+ android:layout_marginEnd="@*android:dimen/notification_content_margin_start"
android:text="@string/notification_channel_disabled"
style="@style/TextAppearance.NotificationInfo.Confirmation"/>
<TextView
android:id="@+id/undo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
+ android:layout_marginTop="@dimen/notification_guts_button_spacing"
+ android:layout_marginBottom="@dimen/notification_guts_button_spacing"
+ android:layout_marginStart="@dimen/notification_guts_button_side_margin"
+ android:layout_marginEnd="@dimen/notification_guts_button_side_margin"
+ android:layout_gravity="end|center_vertical"
android:text="@string/inline_undo"
style="@style/TextAppearance.NotificationInfo.Button"/>
- </RelativeLayout>
+ </com.android.systemui.statusbar.NotificationUndoLayout>
</com.android.systemui.statusbar.NotificationInfo>
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/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 61784fa..3ac6705 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -55,6 +55,7 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.Utils.DisableStateTracker;
+import com.android.systemui.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -74,6 +75,7 @@
private int mTextColor;
private int mLevel;
private boolean mForceShowPercent;
+ private boolean mShowPercentAvailable;
private int mDarkModeBackgroundColor;
private int mDarkModeFillColor;
@@ -113,6 +115,9 @@
atts.recycle();
mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
+ mShowPercentAvailable = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_battery_percentage_setting_available);
+
addOnAttachStateChangeListener(
new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
@@ -264,8 +269,11 @@
private void updateShowPercent() {
final boolean showing = mBatteryPercentView != null;
- if (0 != Settings.System.getIntForUser(getContext().getContentResolver(),
- SHOW_BATTERY_PERCENT, 0, mUser) || mForceShowPercent) {
+ final boolean systemSetting = 0 != Settings.System
+ .getIntForUser(getContext().getContentResolver(),
+ SHOW_BATTERY_PERCENT, 0, mUser);
+
+ if ((mShowPercentAvailable && systemSetting) || mForceShowPercent) {
if (!showing) {
mBatteryPercentView = loadPercentView();
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
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/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 615b29f..2dc531a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -229,7 +229,9 @@
mSettingsButton = findViewById(R.id.settings);
mSettingsButton.setAlpha(0);
mSettingsButton.setOnClickListener((v) -> {
- showSettings();
+ if (v.getAlpha() != 0) {
+ showSettings();
+ }
});
mDismissButton = findViewById(R.id.dismiss);
mDismissButton.setAlpha(0);
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/NotificationUndoLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUndoLayout.java
new file mode 100644
index 0000000..11a1c9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUndoLayout.java
@@ -0,0 +1,139 @@
+/*
+ * 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.systemui.statusbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+
+/**
+ * Custom view for the NotificationInfo confirmation views so that the confirmation text can
+ * occupy the full width of the notification and push the undo button down to the next line if
+ * necessary.
+ *
+ * @see NotificationInfo
+ */
+public class NotificationUndoLayout extends FrameLayout {
+ /**
+ * View for the prompt/confirmation text to tell the user the previous action was successful.
+ */
+ private View mConfirmationTextView;
+ /** Undo button (actionable text) view. */
+ private View mUndoView;
+
+ /**
+ * Whether {@link #mConfirmationTextView} is multiline and will require the full width of the
+ * parent (which causes the {@link #mUndoView} to push down).
+ */
+ private boolean mIsMultiline = false;
+ private int mMultilineTopMargin;
+
+ public NotificationUndoLayout(Context context) {
+ this(context, null);
+ }
+
+ public NotificationUndoLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationUndoLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mConfirmationTextView = findViewById(R.id.confirmation_text);
+ mUndoView = findViewById(R.id.undo);
+
+ mMultilineTopMargin = getResources().getDimensionPixelOffset(
+ com.android.internal.R.dimen.notification_content_margin_start);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ LayoutParams confirmationLayoutParams =
+ (LayoutParams) mConfirmationTextView.getLayoutParams();
+ LayoutParams undoLayoutParams =(LayoutParams) mUndoView.getLayoutParams();
+
+ int measuredWidth = getMeasuredWidth();
+ // Ignore the left margin on the undo button - no need for additional extra space between
+ // the text and the button.
+ int requiredWidth = mConfirmationTextView.getMeasuredWidth()
+ + confirmationLayoutParams.rightMargin
+ + confirmationLayoutParams.leftMargin
+ + mUndoView.getMeasuredWidth()
+ + undoLayoutParams.rightMargin;
+ // If the measured width isn't enough to accommodate both the undo button and the text in
+ // the same line, we'll need to adjust the view to be multi-line. Otherwise, we're done.
+ if (requiredWidth > measuredWidth) {
+ mIsMultiline = true;
+
+ // Update height requirement to the text height and the button's height (along with
+ // additional spacing for the top of the text).
+ int updatedHeight = mMultilineTopMargin
+ + mConfirmationTextView.getMeasuredHeight()
+ + mUndoView.getMeasuredHeight()
+ + undoLayoutParams.topMargin
+ + undoLayoutParams.bottomMargin;
+
+ setMeasuredDimension(measuredWidth, updatedHeight);
+ } else {
+ mIsMultiline = false;
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ // If the text view and undo view don't fit on the same line, we'll need to manually lay
+ // out the content.
+ if (mIsMultiline) {
+ // Re-align parent right/bottom values. Left and top are considered to be 0.
+ int parentBottom = getMeasuredHeight();
+ int parentRight = getMeasuredWidth();
+
+ LayoutParams confirmationLayoutParams =
+ (LayoutParams) mConfirmationTextView.getLayoutParams();
+ LayoutParams undoLayoutParams = (LayoutParams) mUndoView.getLayoutParams();
+
+ // The confirmation text occupies the full width as computed earlier. Both side margins
+ // are equivalent, so we only need to grab the left one here.
+ mConfirmationTextView.layout(
+ confirmationLayoutParams.leftMargin,
+ mMultilineTopMargin,
+ confirmationLayoutParams.leftMargin + mConfirmationTextView.getMeasuredWidth(),
+ mMultilineTopMargin + mConfirmationTextView.getMeasuredHeight());
+
+ // The undo button is aligned bottom|end with the parent in the case of multiline text.
+ int undoViewLeft = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? undoLayoutParams.rightMargin
+ : parentRight - mUndoView.getMeasuredWidth() - undoLayoutParams.rightMargin;
+ mUndoView.layout(
+ undoViewLeft,
+ parentBottom - mUndoView.getMeasuredHeight() - undoLayoutParams.bottomMargin,
+ undoViewLeft + mUndoView.getMeasuredWidth(),
+ parentBottom - undoLayoutParams.bottomMargin);
+ } else {
+ super.onLayout(changed, left, top, right, bottom);
+ }
+ }
+}
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/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index e47dcea..20ea27a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -70,6 +70,7 @@
private static final int LAYOUT_CUTOUT = 1;
private static final int LAYOUT_NO_CUTOUT = 2;
+ private boolean mShowPercentAvailable;
private boolean mBatteryCharging;
private boolean mKeyguardUserSwitcherShowing;
private boolean mBatteryListening;
@@ -168,6 +169,8 @@
R.dimen.system_icons_super_container_avatarless_margin_end);
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
+ mShowPercentAvailable = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_battery_percentage_setting_available);
}
private void updateVisibilities() {
@@ -189,7 +192,7 @@
mMultiUserSwitch.setVisibility(View.GONE);
}
}
- mBatteryView.setForceShowPercent(mBatteryCharging);
+ mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
}
private void updateSystemIconsLayoutParams() {
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/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 2dd3d4e..ffd5494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -49,10 +49,14 @@
*/
public class NotificationChildrenContainer extends ViewGroup {
- private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
- private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
- private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
- private static final int NUMBER_OF_CHILDREN_WHEN_AMBIENT = 1;
+ @VisibleForTesting
+ static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
+ @VisibleForTesting
+ static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
+ @VisibleForTesting
+ static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
+ @VisibleForTesting
+ static final int NUMBER_OF_CHILDREN_WHEN_AMBIENT = 1;
private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() {
private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
@@ -699,15 +703,18 @@
return childState.height != intrinsicHeight && !childState.hidden;
}
- private int getMaxAllowedVisibleChildren() {
+ @VisibleForTesting
+ int getMaxAllowedVisibleChildren() {
return getMaxAllowedVisibleChildren(false /* likeCollapsed */);
}
- private int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
+ @VisibleForTesting
+ int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
if (mContainingNotification.isShowingAmbient()) {
return NUMBER_OF_CHILDREN_WHEN_AMBIENT;
}
- if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())) {
+ if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())
+ && !showingAsLowPriority()) {
return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
}
if (mIsLowPriority || !mContainingNotification.isOnKeyguard()
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 8458575..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);
@@ -2053,11 +2054,9 @@
}
private int getScrollRange() {
- int contentHeight = getContentHeight();
- int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
+ int scrollRange = Math.max(0, mContentHeight - mMaxLayoutHeight);
int imeInset = getImeInset();
- scrollRange += Math.min(imeInset, Math.max(0,
- getContentHeight() - (getHeight() - imeInset)));
+ scrollRange += Math.min(imeInset, Math.max(0, mContentHeight - (getHeight() - imeInset)));
return scrollRange;
}
@@ -2158,10 +2157,6 @@
return count;
}
- public int getContentHeight() {
- return mContentHeight;
- }
-
private void updateContentHeight() {
int height = 0;
float previousPaddingRequest = mPaddingBetweenElements;
@@ -2225,7 +2220,11 @@
}
}
mIntrinsicContentHeight = height;
- mContentHeight = height + mTopPadding + mBottomMargin;
+
+ // We don't want to use the toppadding since that might be interpolated and we want
+ // to take the final value of the animation.
+ int topPadding = mAmbientState.isFullyDark() ? mDarkTopPadding : mRegularTopPadding;
+ mContentHeight = height + topPadding + mBottomMargin;
updateScrollability();
clampScrollPosition();
mAmbientState.setLayoutMaxHeight(mContentHeight);
@@ -4005,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();
}
/**
@@ -4036,7 +4040,7 @@
}
updateAlgorithmHeightAndPadding();
updateBackgroundDimming();
- updateAntiBurnInTranslation();
+ updatePanelTranslation();
requestChildrenUpdate();
}
@@ -4604,7 +4608,7 @@
public void setAntiBurnInOffsetX(int antiBurnInOffsetX) {
mAntiBurnInOffsetX = antiBurnInOffsetX;
- updateAntiBurnInTranslation();
+ updatePanelTranslation();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -4717,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/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
index ff12c53..f363cf0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
@@ -308,4 +308,12 @@
mGroupRow.resetTranslation();
assertEquals(0, mGroupRow.getEntry().expandedIcon.getScrollX());
}
+
+ @Test
+ public void testIsExpanded_userExpanded() {
+ mGroupRow.setExpandable(true);
+ Assert.assertFalse(mGroupRow.isExpanded());
+ mGroupRow.setUserExpanded(true);
+ Assert.assertTrue(mGroupRow.isExpanded());
+ }
}
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
index 8a74019..cfacf0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
@@ -40,30 +40,117 @@
private ExpandableNotificationRow mGroup;
private int mId;
private NotificationTestHelper mNotificationTestHelper;
+ private NotificationChildrenContainer mChildrenContainer;
@Before
public void setUp() throws Exception {
mNotificationTestHelper = new NotificationTestHelper(mContext);
mGroup = mNotificationTestHelper.createGroup();
+ mChildrenContainer = mGroup.getChildrenContainer();
+ }
+
+ @Test
+ public void testGetMaxAllowedVisibleChildren_ambient() {
+ mGroup.setShowAmbient(true);
+ Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(),
+ NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_AMBIENT);
+ }
+
+ @Test
+ public void testGetMaxAllowedVisibleChildren_lowPriority() {
+ mChildrenContainer.setIsLowPriority(true);
+ Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(),
+ NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED);
+ }
+
+ @Test
+ public void testGetMaxAllowedVisibleChildren_headsUp() {
+ mGroup.setHeadsUp(true);
+ Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(),
+ NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED);
+ }
+
+ @Test
+ public void testGetMaxAllowedVisibleChildren_lowPriority_expandedChildren() {
+ mChildrenContainer.setIsLowPriority(true);
+ mChildrenContainer.setChildrenExpanded(true);
+ Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(),
+ NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED);
+ }
+
+ @Test
+ public void testGetMaxAllowedVisibleChildren_lowPriority_userLocked() {
+ mChildrenContainer.setIsLowPriority(true);
+ mChildrenContainer.setUserLocked(true);
+ Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(),
+ NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED);
+ }
+
+ @Test
+ public void testGetMaxAllowedVisibleChildren_likeCollapsed() {
+ Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(true),
+ NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_COLLAPSED);
+ }
+
+
+ @Test
+ public void testGetMaxAllowedVisibleChildren_expandedChildren() {
+ mChildrenContainer.setChildrenExpanded(true);
+ Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(),
+ NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
+ }
+
+ @Test
+ public void testGetMaxAllowedVisibleChildren_userLocked() {
+ mGroup.setUserLocked(true);
+ Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(),
+ NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
+ }
+
+ @Test
+ public void testShowingAsLowPriority_lowPriority() {
+ mChildrenContainer.setIsLowPriority(true);
+ Assert.assertTrue(mChildrenContainer.showingAsLowPriority());
+ }
+
+ @Test
+ public void testShowingAsLowPriority_notLowPriority() {
+ Assert.assertFalse(mChildrenContainer.showingAsLowPriority());
+ }
+
+ @Test
+ public void testShowingAsLowPriority_lowPriority_expanded() {
+ mChildrenContainer.setIsLowPriority(true);
+ mGroup.setExpandable(true);
+ mGroup.setUserExpanded(true, false);
+ Assert.assertFalse(mChildrenContainer.showingAsLowPriority());
+ }
+
+ @Test
+ public void testGetMaxAllowedVisibleChildren_userLocked_expandedChildren_lowPriority() {
+ mGroup.setUserLocked(true);
+ mGroup.setExpandable(true);
+ mGroup.setUserExpanded(true);
+ mChildrenContainer.setIsLowPriority(true);
+ Assert.assertEquals(mChildrenContainer.getMaxAllowedVisibleChildren(),
+ NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
}
@Test
public void testLowPriorityHeaderCleared() {
mGroup.setIsLowPriority(true);
- NotificationChildrenContainer childrenContainer = mGroup.getChildrenContainer();
- NotificationHeaderView lowPriorityHeaderView = childrenContainer.getLowPriorityHeaderView();
+ NotificationHeaderView lowPriorityHeaderView = mChildrenContainer.getLowPriorityHeaderView();
Assert.assertTrue(lowPriorityHeaderView.getVisibility() == View.VISIBLE);
- Assert.assertTrue(lowPriorityHeaderView.getParent() == childrenContainer);
+ Assert.assertTrue(lowPriorityHeaderView.getParent() == mChildrenContainer);
mGroup.setIsLowPriority(false);
Assert.assertTrue(lowPriorityHeaderView.getParent() == null);
- Assert.assertTrue(childrenContainer.getLowPriorityHeaderView() == null);
+ Assert.assertTrue(mChildrenContainer.getLowPriorityHeaderView() == null);
}
@Test
public void testRecreateNotificationHeader_hasHeader() {
- NotificationChildrenContainer childrenContainer = mGroup.getChildrenContainer();
- childrenContainer.recreateNotificationHeader(null);
+ mChildrenContainer.recreateNotificationHeader(null);
Assert.assertNotNull("Children container must have a header after recreation",
- childrenContainer.getCurrentHeaderView());
+ mChildrenContainer.getCurrentHeaderView());
}
}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d2990de..7108fae 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -6118,8 +6118,18 @@
// ---- 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;
+
+ // OPEN: Settings
+ // CATEGORY: SETTINGS
+ // OS: Q
+ SETTINGS_HOMEPAGE = 1502;
// ---- 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/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 6ca81c2..3261209 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -172,7 +172,8 @@
long mNativeData;
private long mNextWakeup;
private long mNextNonWakeup;
- private long mLastWakeupSet;
+ private long mNextWakeUpSetAt;
+ private long mNextNonWakeUpSetAt;
private long mLastWakeup;
private long mLastTrigger;
private long mLastTickSet;
@@ -1939,7 +1940,7 @@
TimeUtils.formatDuration(nowELAPSED - mLastAlarmDeliveryTime, pw);
pw.println();
pw.print(" Next non-wakeup delivery time: ");
- TimeUtils.formatDuration(nowELAPSED - mNextNonWakeupDeliveryTime, pw);
+ TimeUtils.formatDuration(mNextNonWakeupDeliveryTime, nowELAPSED, pw);
pw.println();
long nextWakeupRTC = mNextWakeup + (nowRTC - nowELAPSED);
@@ -1948,10 +1949,12 @@
TimeUtils.formatDuration(mNextNonWakeup, nowELAPSED, pw);
pw.print(" = "); pw.print(mNextNonWakeup);
pw.print(" = "); pw.println(sdf.format(new Date(nextNonWakeupRTC)));
+ pw.print(" set at "); TimeUtils.formatDuration(mNextNonWakeUpSetAt, nowELAPSED, pw);
+ pw.println();
pw.print(" Next wakeup alarm: "); TimeUtils.formatDuration(mNextWakeup, nowELAPSED, pw);
pw.print(" = "); pw.print(mNextWakeup);
pw.print(" = "); pw.println(sdf.format(new Date(nextWakeupRTC)));
- pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
+ pw.print(" set at "); TimeUtils.formatDuration(mNextWakeUpSetAt, nowELAPSED, pw);
pw.println();
pw.print(" Next kernel non-wakeup alarm: ");
@@ -2290,7 +2293,7 @@
proto.write(AlarmManagerServiceDumpProto.TIME_SINCE_LAST_WAKEUP_MS,
nowElapsed - mLastWakeup);
proto.write(AlarmManagerServiceDumpProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
- nowElapsed - mLastWakeupSet);
+ nowElapsed - mNextWakeUpSetAt);
proto.write(AlarmManagerServiceDumpProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
final TreeSet<Integer> users = new TreeSet<>();
@@ -2675,16 +2678,49 @@
DateFormat.format(pattern, info.getTriggerTime()).toString();
}
+ /**
+ * If the last time AlarmThread woke up precedes any due wakeup or non-wakeup alarm that we set
+ * by more than half a minute, log a wtf.
+ */
+ private void validateLastAlarmExpiredLocked(long nowElapsed) {
+ final StringBuilder errorMsg = new StringBuilder();
+ boolean stuck = false;
+ if (mNextNonWakeup < nowElapsed && mLastWakeup < (mNextNonWakeup - 30_000)) {
+ stuck = true;
+ errorMsg.append("[mNextNonWakeup=");
+ TimeUtils.formatDuration(mNextNonWakeup - nowElapsed, errorMsg);
+ errorMsg.append(", mLastWakeup=");
+ TimeUtils.formatDuration(mLastWakeup - nowElapsed, errorMsg);
+ errorMsg.append(", timerfd_gettime=" + getNextAlarm(mNativeData, ELAPSED_REALTIME));
+ errorMsg.append("];");
+ }
+ if (mNextWakeup < nowElapsed && mLastWakeup < (mNextWakeup - 30_000)) {
+ stuck = true;
+ errorMsg.append("[mNextWakeup=");
+ TimeUtils.formatDuration(mNextWakeup - nowElapsed, errorMsg);
+ errorMsg.append(", mLastWakeup=");
+ TimeUtils.formatDuration(mLastWakeup - nowElapsed, errorMsg);
+ errorMsg.append(", timerfd_gettime="
+ + getNextAlarm(mNativeData, ELAPSED_REALTIME_WAKEUP));
+ errorMsg.append("];");
+ }
+ if (stuck) {
+ Slog.wtf(TAG, "Alarm delivery stuck: " + errorMsg.toString());
+ }
+ }
+
void rescheduleKernelAlarmsLocked() {
// Schedule the next upcoming wakeup alarm. If there is a deliverable batch
// prior to that which contains no wakeups, we schedule that as well.
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ validateLastAlarmExpiredLocked(nowElapsed);
long nextNonWakeup = 0;
if (mAlarmBatches.size() > 0) {
final Batch firstWakeup = findFirstWakeupBatchLocked();
final Batch firstBatch = mAlarmBatches.get(0);
if (firstWakeup != null) {
mNextWakeup = firstWakeup.start;
- mLastWakeupSet = SystemClock.elapsedRealtime();
+ mNextWakeUpSetAt = nowElapsed;
setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
}
if (firstBatch != firstWakeup) {
@@ -2698,6 +2734,7 @@
}
if (nextNonWakeup != 0) {
mNextNonWakeup = nextNonWakeup;
+ mNextNonWakeUpSetAt = nowElapsed;
setLocked(ELAPSED_REALTIME, nextNonWakeup);
}
}
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..3c94a34 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) {
@@ -1020,7 +1024,8 @@
}
}
- private NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) {
+ @VisibleForTesting
+ protected NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) {
if (network == null) {
return null;
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 607db4e..ad9fa40 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1588,10 +1588,10 @@
// Wakeup apps for the (SUBSCRIPTION_)PHONE_STATE broadcast.
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ // Create a version of the intent with the number always populated.
Intent intentWithPhoneNumber = new Intent(intent);
- if (!TextUtils.isEmpty(incomingNumber)) {
- intentWithPhoneNumber.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber);
- }
+ intentWithPhoneNumber.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber);
+
// Send broadcast twice, once for apps that have PRIVILEGED permission and once for those
// that have the runtime one
mContext.sendBroadcastAsUser(intentWithPhoneNumber, UserHandle.ALL,
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index 8ddf09a..c043e18 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -63,12 +63,14 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
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 +537,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
@@ -1022,19 +1025,46 @@
private static final class ISpellCheckerServiceCallbackBinder
extends ISpellCheckerServiceCallback.Stub {
@NonNull
- private final SpellCheckerBindGroup mBindGroup;
- @NonNull
- private final SessionRequest mRequest;
+ private final Object mCallbackLock = new Object();
- ISpellCheckerServiceCallbackBinder(@NonNull final SpellCheckerBindGroup bindGroup,
- @NonNull final SessionRequest request) {
- mBindGroup = bindGroup;
- mRequest = request;
+ @GuardedBy("mCallbackLock")
+ @Nullable
+ private WeakReference<SpellCheckerBindGroup> mBindGroup;
+
+ /**
+ * Original {@link SessionRequest} that is associated with this callback.
+ *
+ * <p>Note that {@link SpellCheckerBindGroup#mOnGoingSessionRequests} guarantees that this
+ * {@link SessionRequest} object is kept alive until the request is canceled.</p>
+ */
+ @GuardedBy("mCallbackLock")
+ @Nullable
+ private WeakReference<SessionRequest> mRequest;
+
+ ISpellCheckerServiceCallbackBinder(@NonNull SpellCheckerBindGroup bindGroup,
+ @NonNull SessionRequest request) {
+ synchronized (mCallbackLock) {
+ mBindGroup = new WeakReference<>(bindGroup);
+ mRequest = new WeakReference<>(request);
+ }
}
@Override
public void onSessionCreated(@Nullable ISpellCheckerSession newSession) {
- mBindGroup.onSessionCreated(newSession, mRequest);
+ final SpellCheckerBindGroup group;
+ final SessionRequest request;
+ synchronized (mCallbackLock) {
+ if (mBindGroup == null || mRequest == null) {
+ return;
+ }
+ group = mBindGroup.get();
+ request = mRequest.get();
+ mBindGroup = null;
+ mRequest = null;
+ }
+ if (group != null && request != null) {
+ group.onSessionCreated(newSession, request);
+ }
}
}
}
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/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 5681367..2949b92 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -516,8 +516,7 @@
static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
byte[] params = message.getParams();
return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
- && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
- || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION
+ && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
}
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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 32f1744..c45d861 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4532,6 +4532,15 @@
@GuardedBy("mNotificationLock")
@VisibleForTesting
protected boolean isVisuallyInterruptive(NotificationRecord old, NotificationRecord r) {
+ // Ignore summary updates because we don't display most of the information.
+ if (r.sbn.isGroup() && r.sbn.getNotification().isGroupSummary()) {
+ if (DEBUG_INTERRUPTIVENESS) {
+ Log.v(TAG, "INTERRUPTIVENESS: "
+ + r.getKey() + " is not interruptive: summary");
+ }
+ return false;
+ }
+
if (old == null) {
if (DEBUG_INTERRUPTIVENESS) {
Log.v(TAG, "INTERRUPTIVENESS: "
@@ -4569,15 +4578,6 @@
return false;
}
- // Ignore summary updates because we don't display most of the information.
- if (r.sbn.isGroup() && r.sbn.getNotification().isGroupSummary()) {
- if (DEBUG_INTERRUPTIVENESS) {
- Log.v(TAG, "INTERRUPTIVENESS: "
- + r.getKey() + " is not interruptive: summary");
- }
- return false;
- }
-
final String oldTitle = String.valueOf(oldN.extras.get(Notification.EXTRA_TITLE));
final String newTitle = String.valueOf(newN.extras.get(Notification.EXTRA_TITLE));
if (!Objects.equals(oldTitle, newTitle)) {
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/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index c3b2f87..bb9b1c4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -385,9 +385,12 @@
@Test
public void testAlwaysOnTopStackLocation() {
final TaskStack alwaysOnTopStack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(alwaysOnTopStack, 0 /* userId */);
alwaysOnTopStack.setAlwaysOnTop(true);
mDisplayContent.positionStackAt(POSITION_TOP, alwaysOnTopStack);
assertTrue(alwaysOnTopStack.isAlwaysOnTop());
+ // Ensure always on top state is synced to the children of the stack.
+ assertTrue(alwaysOnTopStack.getTopChild().isAlwaysOnTop());
assertEquals(alwaysOnTopStack, mDisplayContent.getTopStack());
final TaskStack pinnedStack = createStackControllerOnStackOnDisplay(
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
index 513c1ec..10d7aad 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
@@ -31,6 +31,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOW_CONFIG_ALWAYS_ON_TOP;
import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
import static android.app.WindowConfiguration.WINDOW_CONFIG_WINDOWING_MODE;
import static android.content.pm.ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
@@ -81,6 +82,11 @@
assertEquals(WINDOW_CONFIG_APP_BOUNDS | WINDOW_CONFIG_WINDOWING_MODE,
winConfig1.diff(winConfig2, false /* compareUndefined */));
+ winConfig2.setAlwaysOnTop(true);
+ assertEquals(WINDOW_CONFIG_APP_BOUNDS | WINDOW_CONFIG_WINDOWING_MODE
+ | WINDOW_CONFIG_ALWAYS_ON_TOP,
+ winConfig1.diff(winConfig2, false /* compareUndefined */));
+
assertEquals(0, config1.diff(config3));
assertEquals(0, config1.diffPublicOnly(config3));
assertEquals(0, winConfig1.diff(winConfig3, false /* compareUndefined */));
@@ -108,6 +114,12 @@
assertNotEquals(winConfig1.compareTo(winConfig2), 0);
winConfig2.setWindowingMode(winConfig1.getWindowingMode());
+ // Different always on top state
+ winConfig2.setAlwaysOnTop(true);
+ assertNotEquals(config1.compareTo(config2), 0);
+ assertNotEquals(winConfig1.compareTo(winConfig2), 0);
+ winConfig2.setAlwaysOnTop(winConfig1.isAlwaysOnTop());
+
// Different bounds
winConfig2.setAppBounds(0, 2, 3, 4);
assertNotEquals(config1.compareTo(config2), 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 96c948f..45a3c41 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3003,6 +3003,20 @@
}
@Test
+ public void testVisualDifference_summaryNewNotification() {
+ Notification.Builder nb2 = new Notification.Builder(mContext, "")
+ .setGroup("bananas")
+ .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
+ .setContentText("bar");
+ StatusBarNotification sbn2 = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0,
+ nb2.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord r2 =
+ new NotificationRecord(mContext, sbn2, mock(NotificationChannel.class));
+
+ assertFalse(mService.isVisuallyInterruptive(null, r2));
+ }
+
+ @Test
public void testHideAndUnhideNotificationsOnSuspendedPackageBroadcast() {
// post 2 notification from this package
final NotificationRecord notif1 = generateNotificationRecord(
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..102d5a0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -335,11 +335,18 @@
* <p>
* The {@link #EXTRA_STATE} extra indicates the new call state.
* If a receiving app has {@link android.Manifest.permission#READ_CALL_LOG} permission, a second
- * extra {@link #EXTRA_INCOMING_NUMBER} provides the phone number for incoming and outoing calls
- * as a String. Note: If the receiving app has
+ * extra {@link #EXTRA_INCOMING_NUMBER} provides the phone number for incoming and outgoing
+ * calls as a String.
+ * <p>
+ * If the receiving app has
* {@link android.Manifest.permission#READ_CALL_LOG} and
* {@link android.Manifest.permission#READ_PHONE_STATE} permission, it will receive the
- * broadcast twice; one with the phone number and another without it.
+ * broadcast twice; one with the {@link #EXTRA_INCOMING_NUMBER} populated with the phone number,
+ * and another with it blank. Due to the nature of broadcasts, you cannot assume the order
+ * in which these broadcasts will arrive, however you are guaranteed to receive two in this
+ * case. Apps which are interested in the {@link #EXTRA_INCOMING_NUMBER} can ignore the
+ * broadcasts where {@link #EXTRA_INCOMING_NUMBER} is not present in the extras (e.g. where
+ * {@link Intent#hasExtra(String)} returns {@code false}).
* <p class="note">
* This was a {@link android.content.Context#sendStickyBroadcast sticky}
* broadcast in version 1.0, but it is no longer sticky.
@@ -488,10 +495,19 @@
public static final String EXTRA_STATE_OFFHOOK = PhoneConstants.State.OFFHOOK.toString();
/**
- * The lookup key used with the {@link #ACTION_PHONE_STATE_CHANGED} broadcast
- * for a String containing the incoming phone number.
- * Only valid when the new call state is RINGING.
- *
+ * Extra key used with the {@link #ACTION_PHONE_STATE_CHANGED} broadcast
+ * for a String containing the incoming or outgoing phone number.
+ * <p>
+ * This extra is only populated for receivers of the {@link #ACTION_PHONE_STATE_CHANGED}
+ * broadcast which have been granted the {@link android.Manifest.permission#READ_CALL_LOG} and
+ * {@link android.Manifest.permission#READ_PHONE_STATE} permissions.
+ * <p>
+ * For incoming calls, the phone number is only guaranteed to be populated when the
+ * {@link #EXTRA_STATE} changes from {@link #EXTRA_STATE_IDLE} to {@link #EXTRA_STATE_RINGING}.
+ * If the incoming caller is from an unknown number, the extra will be populated with an empty
+ * string.
+ * For outgoing calls, the phone number is only guaranteed to be populated when the
+ * {@link #EXTRA_STATE} changes from {@link #EXTRA_STATE_IDLE} to {@link #EXTRA_STATE_OFFHOOK}.
* <p class="note">
* Retrieve with
* {@link android.content.Intent#getStringExtra(String)}.
@@ -5432,23 +5448,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/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2208580..dbf81d6 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -57,6 +57,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -95,6 +96,7 @@
import android.net.ConnectivityThread;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -125,6 +127,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -132,6 +135,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.test.mock.MockContentResolver;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -145,6 +149,7 @@
import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
+import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.Vpn;
@@ -161,10 +166,13 @@
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import java.net.Inet4Address;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -190,6 +198,7 @@
private static final int TIMEOUT_MS = 500;
private static final int TEST_LINGER_DELAY_MS = 120;
+ private static final String CLAT_PREFIX = "v4-";
private static final String MOBILE_IFNAME = "test_rmnet_data0";
private static final String WIFI_IFNAME = "test_wlan0";
@@ -950,6 +959,10 @@
return monitor;
}
+ public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
+ return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
+ }
+
@Override
public MultinetworkPolicyTracker createMultinetworkPolicyTracker(
Context c, Handler h, Runnable r) {
@@ -4422,4 +4435,97 @@
mMockVpn.disconnect();
}
+
+ /**
+ * Make simulated InterfaceConfig for Nat464Xlat to query clat lower layer info.
+ */
+ private InterfaceConfiguration getClatInterfaceConfig(LinkAddress la) {
+ InterfaceConfiguration cfg = new InterfaceConfiguration();
+ cfg.setHardwareAddress("11:22:33:44:55:66");
+ cfg.setLinkAddress(la);
+ return cfg;
+ }
+
+ /**
+ * Make expected stack link properties, copied from Nat464Xlat.
+ */
+ private LinkProperties makeClatLinkProperties(LinkAddress la) {
+ LinkAddress clatAddress = la;
+ LinkProperties stacked = new LinkProperties();
+ stacked.setInterfaceName(CLAT_PREFIX + MOBILE_IFNAME);
+ RouteInfo ipv4Default = new RouteInfo(
+ new LinkAddress(Inet4Address.ANY, 0),
+ clatAddress.getAddress(), CLAT_PREFIX + MOBILE_IFNAME);
+ stacked.addRoute(ipv4Default);
+ stacked.addLinkAddress(clatAddress);
+ return stacked;
+ }
+
+ @Test
+ public void testStackedLinkProperties() throws UnknownHostException, RemoteException {
+ final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24");
+ final LinkAddress myIpv6 = new LinkAddress("2001:db8:1::1/64");
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(networkRequest, networkCallback);
+
+ // Prepare ipv6 only link properties and connect.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ final LinkProperties cellLp = new LinkProperties();
+ cellLp.setInterfaceName(MOBILE_IFNAME);
+ cellLp.addLinkAddress(myIpv6);
+ cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME));
+ cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME));
+ reset(mNetworkManagementService);
+ when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME))
+ .thenReturn(getClatInterfaceConfig(myIpv4));
+
+ // Connect with ipv6 link properties, then expect clat setup ipv4 and update link
+ // properties properly.
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ mCellNetworkAgent.connect(true);
+ networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ verify(mNetworkManagementService, times(1)).startClatd(MOBILE_IFNAME);
+ Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent);
+
+ // Clat iface up, expect stack link updated.
+ clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
+ waitForIdle();
+ List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork())
+ .getStackedLinks();
+ assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0));
+
+ // Change trivial linkproperties and see if stacked link is preserved.
+ cellLp.addDnsServer(InetAddress.getByName("8.8.8.8"));
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ waitForIdle();
+ networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+
+ List<LinkProperties> stackedLpsAfterChange =
+ mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getStackedLinks();
+ assertNotEquals(stackedLpsAfterChange, Collections.EMPTY_LIST);
+ assertEquals(makeClatLinkProperties(myIpv4), stackedLpsAfterChange.get(0));
+
+ // Add ipv4 address, expect stacked linkproperties be cleaned up
+ cellLp.addLinkAddress(myIpv4);
+ cellLp.addRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ waitForIdle();
+ networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ verify(mNetworkManagementService, times(1)).stopClatd(MOBILE_IFNAME);
+
+ // Clat iface removed, expect linkproperties revert to original one
+ clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME);
+ waitForIdle();
+ networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork());
+ assertEquals(cellLp, actualLpAfterIpv4);
+
+ // Clean up
+ mCellNetworkAgent.disconnect();
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
}
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