Merge "Changed BenchmarkRule imports with PerfStatusReporter"
diff --git a/.clang-format b/.clang-format
index 03af56d..d60d33c 100644
--- a/.clang-format
+++ b/.clang-format
@@ -9,5 +9,17 @@
ConstructorInitializerIndentWidth: 6
ContinuationIndentWidth: 8
IndentWidth: 4
+JavaImportGroups:
+- android
+- androidx
+- com.android
+- dalvik
+- libcore
+- com
+- junit
+- net
+- org
+- java
+- javax
PenaltyBreakBeforeFirstCallParameter: 100000
SpacesBeforeTrailingComments: 1
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 98e4f45..9366ff2d 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -62,4 +62,10 @@
test_suites: ["device-tests"],
certificate: "platform",
+
+ errorprone: {
+ javacflags: [
+ "-Xep:ReturnValueIgnored:WARN",
+ ],
+ },
}
diff --git a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
index 2ef68ca..05a3e12 100644
--- a/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ReferencePerfTest.java
@@ -118,7 +118,7 @@
int got = count.get();
if (n != got) {
throw new IllegalStateException(
- String.format("Only %i of %i objects finalized?", got, n));
+ String.format("Only %d of %d objects finalized?", got, n));
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d9fe30d..e0d1a30 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -569,7 +569,7 @@
public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
@VisibleForTesting
static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS;
- static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = false;
+ static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
private static final boolean DEFAULT_USE_TARE_POLICY = false;
/**
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 4f8faca..7a08cbd 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -222,6 +222,7 @@
},
data: [
"tests/data/**/*.apk",
+ "tests/data/**/*.png",
],
compile_multilib: "first",
test_options: {
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 4431164..10947dc 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -39,6 +39,7 @@
#include "idmap2/PrettyPrintVisitor.h"
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
+#include <fcntl.h>
using android::base::StringPrintf;
using android::binder::Status;
@@ -238,6 +239,9 @@
if (res.dataType == Res_value::TYPE_STRING) {
builder.SetResourceValue(res.resourceName, res.dataType, res.stringData.value(),
res.configuration.value_or(std::string()));
+ } else if (res.binaryData.has_value()) {
+ builder.SetResourceValue(res.resourceName, res.binaryData->get(),
+ res.configuration.value_or(std::string()));
} else {
builder.SetResourceValue(res.resourceName, res.dataType, res.data,
res.configuration.value_or(std::string()));
@@ -264,6 +268,7 @@
file_name.c_str(), kMaxFileNameLength));
}
} while (std::filesystem::exists(path));
+ builder.setFrroPath(path);
const uid_t uid = IPCThreadState::self()->getCallingUid();
if (!UidHasWriteAccessToPath(uid, path)) {
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index c773e11..3ad6d58 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -24,5 +24,6 @@
int dataType;
int data;
@nullable @utf8InCpp String stringData;
+ @nullable ParcelFileDescriptor binaryData;
@nullable @utf8InCpp String configuration;
}
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 05b0618..9f57710 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -28,6 +28,7 @@
#include "idmap2/ResourceContainer.h"
#include "idmap2/Result.h"
+#include <binder/ParcelFileDescriptor.h>
namespace android::idmap2 {
@@ -45,6 +46,15 @@
const std::string& data_string_value,
const std::string& configuration);
+ Builder& SetResourceValue(const std::string& resource_name,
+ std::optional<android::base::borrowed_fd>&& binary_value,
+ const std::string& configuration);
+
+ inline Builder& setFrroPath(std::string frro_path) {
+ frro_path_ = std::move(frro_path);
+ return *this;
+ }
+
WARN_UNUSED Result<FabricatedOverlay> Build();
private:
@@ -53,6 +63,7 @@
DataType data_type;
DataValue data_value;
std::string data_string_value;
+ std::optional<android::base::borrowed_fd> data_binary_value;
std::string configuration;
};
@@ -60,6 +71,7 @@
std::string name_;
std::string target_package_name_;
std::string target_overlayable_;
+ std::string frro_path_;
std::vector<Entry> entries_;
};
@@ -79,10 +91,14 @@
explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay,
std::string&& string_pool_data_,
+ std::vector<android::base::borrowed_fd> binary_files_,
+ off_t total_binary_bytes_,
std::optional<uint32_t> crc_from_disk = {});
pb::FabricatedOverlay overlay_pb_;
std::string string_pool_data_;
+ std::vector<android::base::borrowed_fd> binary_files_;
+ uint32_t total_binary_bytes_;
std::optional<uint32_t> crc_from_disk_;
mutable std::optional<SerializedData> data_;
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index af4dd89..2214a83 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -19,6 +19,7 @@
#include <optional>
#include <string>
+#include <android-base/unique_fd.h>
#include "androidfw/AssetManager2.h"
#include "idmap2/Result.h"
@@ -41,6 +42,7 @@
DataType data_type;
DataValue data_value;
std::string data_string_value;
+ std::optional<android::base::borrowed_fd> data_binary_value;
};
struct TargetValueWithConfig {
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index bde9b0b..d517e29 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -16,6 +16,10 @@
#include "idmap2/FabricatedOverlay.h"
+#include <sys/stat.h> // umask
+#include <sys/types.h> // umask
+
+#include <android-base/file.h>
#include <androidfw/ResourceUtils.h>
#include <androidfw/StringPool.h>
#include <google/protobuf/io/coded_stream.h>
@@ -51,9 +55,13 @@
FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
std::string&& string_pool_data,
+ std::vector<android::base::borrowed_fd> binary_files,
+ off_t total_binary_bytes,
std::optional<uint32_t> crc_from_disk)
: overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)),
string_pool_data_(std::move(string_pool_data)),
+ binary_files_(std::move(binary_files)),
+ total_binary_bytes_(total_binary_bytes),
crc_from_disk_(crc_from_disk) {
}
@@ -72,14 +80,23 @@
FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
const std::string& resource_name, uint8_t data_type, uint32_t data_value,
const std::string& configuration) {
- entries_.emplace_back(Entry{resource_name, data_type, data_value, "", configuration});
+ entries_.emplace_back(
+ Entry{resource_name, data_type, data_value, "", std::nullopt, configuration});
return *this;
}
FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
const std::string& resource_name, uint8_t data_type, const std::string& data_string_value,
const std::string& configuration) {
- entries_.emplace_back(Entry{resource_name, data_type, 0, data_string_value, configuration});
+ entries_.emplace_back(
+ Entry{resource_name, data_type, 0, data_string_value, std::nullopt, configuration});
+ return *this;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
+ const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value,
+ const std::string& configuration) {
+ entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value, configuration});
return *this;
}
@@ -135,7 +152,7 @@
}
value->second = TargetValue{res_entry.data_type, res_entry.data_value,
- res_entry.data_string_value};
+ res_entry.data_string_value, res_entry.data_binary_value};
}
pb::FabricatedOverlay overlay_pb;
@@ -144,6 +161,11 @@
overlay_pb.set_target_package_name(target_package_name_);
overlay_pb.set_target_overlayable(target_overlayable_);
+ std::vector<android::base::borrowed_fd> binary_files;
+ size_t total_binary_bytes = 0;
+ // 16 for the number of bytes in the frro file before the binary data
+ const size_t FRRO_HEADER_SIZE = 16;
+
for (auto& package : package_map) {
auto package_pb = overlay_pb.add_packages();
package_pb->set_name(package.first);
@@ -162,6 +184,20 @@
if (value.second.data_type == Res_value::TYPE_STRING) {
auto ref = string_pool.MakeRef(value.second.data_string_value);
pb_value->set_data_value(ref.index());
+ } else if (value.second.data_binary_value.has_value()) {
+ pb_value->set_data_type(Res_value::TYPE_STRING);
+ struct stat s;
+ if (fstat(value.second.data_binary_value->get(), &s) == -1) {
+ return Error("unable to get size of binary file: %d", errno);
+ }
+ std::string uri
+ = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(),
+ static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes),
+ static_cast<int> (s.st_size));
+ total_binary_bytes += s.st_size;
+ binary_files.emplace_back(value.second.data_binary_value->get());
+ auto ref = string_pool.MakeRef(std::move(uri));
+ pb_value->set_data_value(ref.index());
} else {
pb_value->set_data_value(value.second.data_value);
}
@@ -169,10 +205,10 @@
}
}
}
-
android::BigBuffer string_buffer(kBufferSize);
android::StringPool::FlattenUtf8(&string_buffer, string_pool, nullptr);
- return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string());
+ return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string(),
+ std::move(binary_files), total_binary_bytes);
}
Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
@@ -190,7 +226,7 @@
return Error("Failed to read fabricated overlay version.");
}
- if (version != 1 && version != 2) {
+ if (version < 1 || version > 3) {
return Error("Invalid fabricated overlay version '%u'.", version);
}
@@ -201,7 +237,14 @@
pb::FabricatedOverlay overlay{};
std::string sp_data;
- if (version == 2) {
+ uint32_t total_binary_bytes;
+ if (version == 3) {
+ if (!Read32(stream, &total_binary_bytes)) {
+ return Error("Failed read total binary bytes.");
+ }
+ stream.seekg(total_binary_bytes, std::istream::cur);
+ }
+ if (version >= 2) {
uint32_t sp_size;
if (!Read32(stream, &sp_size)) {
return Error("Failed read string pool size.");
@@ -211,20 +254,15 @@
return Error("Failed to read string pool.");
}
sp_data = buf;
-
- if (!overlay.ParseFromIstream(&stream)) {
- return Error("Failed read fabricated overlay proto.");
- }
- } else {
- if (!overlay.ParseFromIstream(&stream)) {
- return Error("Failed read fabricated overlay proto.");
- }
+ }
+ if (!overlay.ParseFromIstream(&stream)) {
+ return Error("Failed read fabricated overlay proto.");
}
// If the proto version is the latest version, then the contents of the proto must be the same
// when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
// proto to the latest version will likely change the contents of the fabricated overlay.
- return FabricatedOverlay(std::move(overlay), std::move(sp_data),
+ return FabricatedOverlay(std::move(overlay), std::move(sp_data), {}, total_binary_bytes,
version == kFabricatedOverlayCurrentVersion
? std::optional<uint32_t>(crc)
: std::nullopt);
@@ -274,6 +312,14 @@
Write32(stream, kFabricatedOverlayMagic);
Write32(stream, kFabricatedOverlayCurrentVersion);
Write32(stream, (*data)->pb_crc);
+ Write32(stream, total_binary_bytes_);
+ std::string file_contents;
+ for (const android::base::borrowed_fd fd : binary_files_) {
+ if (!ReadFdToString(fd, &file_contents)) {
+ return Error("Failed to read binary file data.");
+ }
+ stream.write(file_contents.data(), file_contents.length());
+ }
Write32(stream, (*data)->sp_data.length());
stream.write((*data)->sp_data.data(), (*data)->sp_data.length());
if (stream.bad()) {
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index e804c87..e13a0eb 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -17,6 +17,7 @@
#include <android-base/file.h>
#include <gtest/gtest.h>
#include <idmap2/FabricatedOverlay.h>
+#include "TestHelpers.h"
#include <fstream>
#include <utility>
@@ -41,6 +42,10 @@
}
TEST(FabricatedOverlayTests, SetResourceValue) {
+ auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+ auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
auto overlay =
FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
.SetResourceValue(
@@ -54,6 +59,8 @@
Res_value::TYPE_STRING,
"foobar",
"en-rUS-normal-xxhdpi-v21")
+ .SetResourceValue("com.example.target:drawable/dr1", fd, "port-xxhdpi-v7")
+ .setFrroPath("/foo/bar/biz.frro")
.Build();
ASSERT_TRUE(overlay);
auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
@@ -67,19 +74,28 @@
auto pairs = container->GetOverlayData(*info);
ASSERT_TRUE(pairs);
- ASSERT_EQ(4U, pairs->pairs.size());
+ ASSERT_EQ(5U, pairs->pairs.size());
auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
pairs->string_pool_data->data_length, false);
auto& it = pairs->pairs[0];
- ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+ ASSERT_EQ("com.example.target:drawable/dr1", it.resource_name);
auto entry = std::get_if<TargetValueWithConfig>(&it.value);
ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(std::string("frro://foo/bar/biz.frro?offset=16&size=8341"),
+ string_pool.string8At(entry->value.data_value).value_or(""));
+ ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
+ ASSERT_EQ("port-xxhdpi-v7", entry->config);
+
+ it = pairs->pairs[1];
+ ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+ entry = std::get_if<TargetValueWithConfig>(&it.value);
+ ASSERT_NE(nullptr, entry);
ASSERT_EQ(1U, entry->value.data_value);
ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
ASSERT_EQ("port", entry->config);
- it = pairs->pairs[1];
+ it = pairs->pairs[2];
ASSERT_EQ("com.example.target:string/int3", it.resource_name);
entry = std::get_if<TargetValueWithConfig>(&it.value);
ASSERT_NE(nullptr, entry);
@@ -87,7 +103,7 @@
ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->value.data_type);
ASSERT_EQ("xxhdpi-v7", entry->config);
- it = pairs->pairs[2];
+ it = pairs->pairs[3];
ASSERT_EQ("com.example.target:string/string1", it.resource_name);
entry = std::get_if<TargetValueWithConfig>(&it.value);
ASSERT_NE(nullptr, entry);
@@ -95,7 +111,7 @@
ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->value.data_value).value_or(""));
ASSERT_EQ("en-rUS-normal-xxhdpi-v21", entry->config);
- it = pairs->pairs[3];
+ it = pairs->pairs[4];
ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
entry = std::get_if<TargetValueWithConfig>(&it.value);
ASSERT_NE(nullptr, entry);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 7b7dc17..b473f26 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -260,11 +260,17 @@
auto target = TargetResourceContainer::FromPath(target_apk_path);
ASSERT_TRUE(target);
+ auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+ auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
+
auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7")
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land")
.SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7")
+ .SetResourceValue("drawable/dr1", fd, "port-xxhdpi-v7")
+ .setFrroPath("/foo/bar/biz.frro")
.Build();
ASSERT_TRUE(frro);
@@ -293,14 +299,19 @@
auto string_pool_data = data->GetStringPoolData();
auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
+ std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+ uint32_t uri_index
+ = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
const auto& target_inline_entries = data->GetTargetInlineEntries();
- ASSERT_EQ(target_inline_entries.size(), 3U);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, "land-xxhdpi-v7",
+ ASSERT_EQ(target_inline_entries.size(), 4U);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::drawable::dr1, "port-xxhdpi-v7",
+ Res_value::TYPE_STRING, uri_index);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::integer::int1, "land-xxhdpi-v7",
Res_value::TYPE_INT_DEC, 2U);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, "land",
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str1, "land",
Res_value::TYPE_REFERENCE, 0x7f010000);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2, "xxhdpi-v7",
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[3], R::target::string::str2, "xxhdpi-v7",
Res_value::TYPE_STRING,
(uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1));
}
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index ad998b9..80c062d 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -26,24 +26,27 @@
// clang-format off
namespace R::target {
namespace integer { // NOLINT(runtime/indentation_namespace)
- constexpr ResourceId int1 = 0x7f010000;
+ constexpr ResourceId int1 = 0x7f020000;
+ }
+ namespace drawable {
+ constexpr ResourceId dr1 = 0x7f010000;
}
namespace string { // NOLINT(runtime/indentation_namespace)
- constexpr ResourceId not_overlayable = 0x7f020003;
- constexpr ResourceId other = 0x7f020004;
- constexpr ResourceId policy_actor = 0x7f020005;
- constexpr ResourceId policy_config_signature = 0x7f020006;
- constexpr ResourceId policy_odm = 0x7f020007;
- constexpr ResourceId policy_oem = 0x7f020008;
- constexpr ResourceId policy_product = 0x7f020009;
- constexpr ResourceId policy_public = 0x7f02000a;
- constexpr ResourceId policy_signature = 0x7f02000b;
- constexpr ResourceId policy_system = 0x7f02000c;
- constexpr ResourceId policy_system_vendor = 0x7f02000d;
- constexpr ResourceId str1 = 0x7f02000e;
- constexpr ResourceId str2 = 0x7f02000f;
- constexpr ResourceId str3 = 0x7f020010;
- constexpr ResourceId str4 = 0x7f020011;
+ constexpr ResourceId not_overlayable = 0x7f030003;
+ constexpr ResourceId other = 0x7f030004;
+ constexpr ResourceId policy_actor = 0x7f030005;
+ constexpr ResourceId policy_config_signature = 0x7f030006;
+ constexpr ResourceId policy_odm = 0x7f030007;
+ constexpr ResourceId policy_oem = 0x7f030008;
+ constexpr ResourceId policy_product = 0x7f030009;
+ constexpr ResourceId policy_public = 0x7f03000a;
+ constexpr ResourceId policy_signature = 0x7f03000b;
+ constexpr ResourceId policy_system = 0x7f03000c;
+ constexpr ResourceId policy_system_vendor = 0x7f03000d;
+ constexpr ResourceId str1 = 0x7f03000e;
+ constexpr ResourceId str2 = 0x7f03000f;
+ constexpr ResourceId str3 = 0x7f030010;
+ constexpr ResourceId str4 = 0x7f030011;
} // namespace string
} // namespace R::target
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 7112eeb..68164e2 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -79,22 +79,22 @@
ASSERT_CONTAINS_REGEX(ADDRESS "00000000 config count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "0000000a string pool index offset", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id: integer/int1", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e target id: string/str1", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e target id: string/str1", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b overlay id: string/str1", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f020010 target id: string/str3", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030010 target id: string/str3", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c overlay id: string/str3", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f020011 target id: string/str4", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030011 target id: string/str4", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d overlay id: string/str4", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id: integer/int1", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000b overlay id: string/str1", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f02000e target id: string/str1", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f03000e target id: string/str1", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000c overlay id: string/str3", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f020010 target id: string/str3", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030010 target id: string/str3", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f02000d overlay id: string/str4", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f020011 target id: string/str4", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030011 target id: string/str4", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "000000b4 string pool size", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "........ string pool", stream.str());
}
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 016d427..380e462 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -23,6 +23,7 @@
#include <memory>
#include <string>
+#include <fcntl.h>
#include "R.h"
#include "TestConstants.h"
#include "TestHelpers.h"
@@ -76,7 +77,12 @@
auto target_map = mapping.GetTargetToOverlayMap();
auto entry_map = target_map.find(target_resource);
if (entry_map == target_map.end()) {
- return Error("Failed to find mapping for target resource");
+ std::string keys;
+ for (const auto &pair : target_map) {
+ keys.append(fmt::format("0x{:x}", pair.first)).append(" ");
+ }
+ return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+ target_resource, keys.c_str());
}
auto actual_overlay_resource = std::get_if<ResourceId>(&entry_map->second);
@@ -108,7 +114,12 @@
auto target_map = mapping.GetTargetToOverlayMap();
auto entry_map = target_map.find(target_resource);
if (entry_map == target_map.end()) {
- return Error("Failed to find mapping for target resource");
+ std::string keys;
+ for (const auto &pair : target_map) {
+ keys.append(fmt::format("{:x}", pair.first)).append(" ");
+ }
+ return Error(R"(Failed to find mapping for target resource "0x%02x": "%s")",
+ target_resource, keys.c_str());
}
auto config_map = std::get_if<ConfigMap>(&entry_map->second);
@@ -193,11 +204,16 @@
}
TEST(ResourceMappingTests, FabricatedOverlay) {
+ auto path = GetTestDataPath() + "/overlay/res/drawable/android.png";
+ auto fd = android::base::unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_TRUE(fd > 0) << "errno " << errno << " for path " << path;
auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
.SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
+ .SetResourceValue("drawable/dr1", fd, "")
+ .setFrroPath("/foo/bar/biz.frro")
.Build();
ASSERT_TRUE(frro);
@@ -214,11 +230,16 @@
auto string_pool_data = res.GetStringPoolData();
auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
- ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+ std::u16string expected_uri = u"frro://foo/bar/biz.frro?offset=16&size=8341";
+ uint32_t uri_index
+ = string_pool.indexOfString(expected_uri.data(), expected_uri.length()).value_or(-1);
+
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U);
ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
ASSERT_RESULT(MappingExists(res, R::target::string::str2, Res_value::TYPE_STRING,
(uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1)));
+ ASSERT_RESULT(MappingExists(res, R::target::drawable::dr1, Res_value::TYPE_STRING, uri_index));
ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
}
diff --git a/cmds/idmap2/tests/TestConstants.h b/cmds/idmap2/tests/TestConstants.h
index d5799ad..794d622 100644
--- a/cmds/idmap2/tests/TestConstants.h
+++ b/cmds/idmap2/tests/TestConstants.h
@@ -19,8 +19,8 @@
namespace android::idmap2::TestConstants {
-constexpr const auto TARGET_CRC = 0x7c2d4719;
-constexpr const auto TARGET_CRC_STRING = "7c2d4719";
+constexpr const auto TARGET_CRC = 0xa960a69;
+constexpr const auto TARGET_CRC_STRING = "0a960a69";
constexpr const auto OVERLAY_CRC = 0xb71095cf;
constexpr const auto OVERLAY_CRC_STRING = "b71095cf";
diff --git a/cmds/idmap2/tests/data/overlay/res/drawable/android.png b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
new file mode 100644
index 0000000..b7317b0
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/drawable/android.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/build b/cmds/idmap2/tests/data/target/build
index e6df742..cd13a7e 100755
--- a/cmds/idmap2/tests/data/target/build
+++ b/cmds/idmap2/tests/data/target/build
@@ -17,5 +17,7 @@
rm compiled.flata
aapt2 compile res/values/values.xml -o .
-aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat
-rm values_values.arsc.flat
\ No newline at end of file
+aapt2 compile res/drawable/dr1.png -o .
+aapt2 link --manifest AndroidManifest.xml -A assets -o target-no-overlayable.apk values_values.arsc.flat drawable_dr1.png.flat
+rm values_values.arsc.flat
+rm drawable_dr1.png.flat
diff --git a/cmds/idmap2/tests/data/target/res/drawable/dr1.png b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
new file mode 100644
index 0000000..1a56e68
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/res/drawable/dr1.png
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/res/values/overlayable.xml b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
index 57e6c43..aac9081 100644
--- a/cmds/idmap2/tests/data/target/res/values/overlayable.xml
+++ b/cmds/idmap2/tests/data/target/res/values/overlayable.xml
@@ -63,6 +63,7 @@
<item type="string" name="y" />
<item type="string" name="z" />
<item type="integer" name="int1" />
+ <item type="drawable" name="dr1" />
</policy>
</overlayable>
diff --git a/cmds/idmap2/tests/data/target/target-no-overlayable.apk b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
index cc3491d..680eeb6 100644
--- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk
+++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
index 4a58c5e..145e737 100644
--- a/cmds/idmap2/tests/data/target/target.apk
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ
diff --git a/core/api/current.txt b/core/api/current.txt
index 2b965cf..6a875b8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -91,6 +91,18 @@
field public static final String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
field public static final String FACTORY_TEST = "android.permission.FACTORY_TEST";
field public static final String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
+ field public static final String FOREGROUND_SERVICE_CAMERA = "android.permission.FOREGROUND_SERVICE_CAMERA";
+ field public static final String FOREGROUND_SERVICE_CONNECTED_DEVICE = "android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE";
+ field public static final String FOREGROUND_SERVICE_DATA_SYNC = "android.permission.FOREGROUND_SERVICE_DATA_SYNC";
+ field public static final String FOREGROUND_SERVICE_HEALTH = "android.permission.FOREGROUND_SERVICE_HEALTH";
+ field public static final String FOREGROUND_SERVICE_LOCATION = "android.permission.FOREGROUND_SERVICE_LOCATION";
+ field public static final String FOREGROUND_SERVICE_MEDIA_PLAYBACK = "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK";
+ field public static final String FOREGROUND_SERVICE_MEDIA_PROJECTION = "android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION";
+ field public static final String FOREGROUND_SERVICE_MICROPHONE = "android.permission.FOREGROUND_SERVICE_MICROPHONE";
+ field public static final String FOREGROUND_SERVICE_PHONE_CALL = "android.permission.FOREGROUND_SERVICE_PHONE_CALL";
+ field public static final String FOREGROUND_SERVICE_REMOTE_MESSAGING = "android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING";
+ field public static final String FOREGROUND_SERVICE_SPECIAL_USE = "android.permission.FOREGROUND_SERVICE_SPECIAL_USE";
+ field public static final String FOREGROUND_SERVICE_SYSTEM_EXEMPTED = "android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED";
field public static final String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
@@ -3112,10 +3124,13 @@
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
method public void takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
+ method public void takeScreenshotOfWindow(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 1; // 0x1
field public static final int ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT = 3; // 0x3
field public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4; // 0x4
+ field public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5; // 0x5
field public static final int ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS = 2; // 0x2
+ field public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6; // 0x6
field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
@@ -5275,6 +5290,13 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceStartNotAllowedException> CREATOR;
}
+ public final class ForegroundServiceTypeNotAllowedException extends android.app.ServiceStartNotAllowedException implements android.os.Parcelable {
+ ctor public ForegroundServiceTypeNotAllowedException(@NonNull String);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ForegroundServiceTypeNotAllowedException> CREATOR;
+ }
+
@Deprecated public class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
ctor @Deprecated public Fragment();
method @Deprecated public void dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]);
@@ -6936,7 +6958,7 @@
method public void onTrimMemory(int);
method public boolean onUnbind(android.content.Intent);
method public final void startForeground(int, android.app.Notification);
- method public final void startForeground(int, @NonNull android.app.Notification, int);
+ method public final void startForeground(int, @NonNull android.app.Notification, @RequiresPermission int);
method @Deprecated public final void stopForeground(boolean);
method public final void stopForeground(int);
method public final void stopSelf();
@@ -7669,6 +7691,7 @@
method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting);
method public void wipeData(int);
method public void wipeData(int, @NonNull CharSequence);
+ method public void wipeDevice(int);
field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
@@ -12122,6 +12145,7 @@
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
field public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
field public static final String FEATURE_USB_HOST = "android.hardware.usb.host";
+ field public static final String FEATURE_UWB = "android.hardware.uwb";
field public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
field public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
field @Deprecated public static final String FEATURE_VR_MODE = "android.software.vr.mode";
@@ -12183,6 +12207,7 @@
field public static final int PERMISSION_DENIED = -1; // 0xffffffff
field public static final int PERMISSION_GRANTED = 0; // 0x0
field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
+ field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
field public static final int SIGNATURE_MATCH = 0; // 0x0
field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -12396,16 +12421,20 @@
field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1
field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8
- field public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
- field public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
- field public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
- field public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
+ field @Deprecated @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
- field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
- field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
- field public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
- field public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
- field public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_MICROPHONE}, anyOf={android.Manifest.permission.CAPTURE_AUDIO_OUTPUT, android.Manifest.permission.RECORD_AUDIO}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
+ field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
+ field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
field public int flags;
field public String permission;
}
@@ -16597,6 +16626,7 @@
field public static final int FONT_WEIGHT_NORMAL = 400; // 0x190
field public static final int FONT_WEIGHT_SEMI_BOLD = 600; // 0x258
field public static final int FONT_WEIGHT_THIN = 100; // 0x64
+ field public static final int FONT_WEIGHT_UNSPECIFIED = -1; // 0xffffffff
}
public final class FontVariationAxis {
@@ -32636,6 +32666,7 @@
field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
field public static final String DISALLOW_SMS = "no_sms";
field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+ field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
field public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
@@ -39925,7 +39956,7 @@
public abstract class WallpaperService extends android.app.Service {
ctor public WallpaperService();
method public final android.os.IBinder onBind(android.content.Intent);
- method public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
+ method @MainThread public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
field public static final String SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService";
field public static final String SERVICE_META_DATA = "android.service.wallpaper";
}
@@ -39940,20 +39971,20 @@
method public boolean isPreview();
method public boolean isVisible();
method public void notifyColorsChanged();
- method public void onApplyWindowInsets(android.view.WindowInsets);
- method public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
- method @Nullable public android.app.WallpaperColors onComputeColors();
- method public void onCreate(android.view.SurfaceHolder);
- method public void onDesiredSizeChanged(int, int);
- method public void onDestroy();
- method public void onOffsetsChanged(float, float, float, float, int, int);
- method public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
- method public void onSurfaceCreated(android.view.SurfaceHolder);
- method public void onSurfaceDestroyed(android.view.SurfaceHolder);
- method public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
- method public void onTouchEvent(android.view.MotionEvent);
- method public void onVisibilityChanged(boolean);
- method public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
+ method @MainThread public void onApplyWindowInsets(android.view.WindowInsets);
+ method @MainThread public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
+ method @MainThread @Nullable public android.app.WallpaperColors onComputeColors();
+ method @MainThread public void onCreate(android.view.SurfaceHolder);
+ method @MainThread public void onDesiredSizeChanged(int, int);
+ method @MainThread public void onDestroy();
+ method @MainThread public void onOffsetsChanged(float, float, float, float, int, int);
+ method @MainThread public void onSurfaceChanged(android.view.SurfaceHolder, int, int, int);
+ method @MainThread public void onSurfaceCreated(android.view.SurfaceHolder);
+ method @MainThread public void onSurfaceDestroyed(android.view.SurfaceHolder);
+ method @MainThread public void onSurfaceRedrawNeeded(android.view.SurfaceHolder);
+ method @MainThread public void onTouchEvent(android.view.MotionEvent);
+ method @MainThread public void onVisibilityChanged(boolean);
+ method @MainThread public void onZoomChanged(@FloatRange(from=0.0f, to=1.0f) float);
method public void setOffsetNotificationsEnabled(boolean);
method public void setTouchEventsEnabled(boolean);
}
@@ -40388,6 +40419,7 @@
field public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE";
field public static final String EXTRA_DIAGNOSTIC_MESSAGE = "android.telecom.extra.DIAGNOSTIC_MESSAGE";
field public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID";
+ field public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB = "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED";
field public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS";
@@ -41881,6 +41913,7 @@
field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
+ field public static final String KEY_VONR_ON_BY_DEFAULT_BOOL = "vonr_on_by_default_bool";
field public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
field public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = "vt_upgrade_supported_for_downgraded_rtt_call";
field public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
@@ -44116,7 +44149,7 @@
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13; // 0xd
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
- field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14; // 0xe
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION = 14; // 0xe
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
@@ -51924,6 +51957,7 @@
method public static int statusBars();
method public static int systemBars();
method public static int systemGestures();
+ method public static int systemOverlays();
method public static int tappableElement();
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index ce18745..e928eb5 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -349,6 +349,7 @@
}
public final class ServiceManager {
+ method @NonNull public static String[] getDeclaredInstances(@NonNull String);
method public static boolean isDeclared(@NonNull String);
method @Nullable public static android.os.IBinder waitForDeclaredService(@NonNull String);
method @Nullable public static android.os.IBinder waitForService(@NonNull String);
@@ -384,6 +385,7 @@
method public static void traceBegin(long, @NonNull String);
method public static void traceCounter(long, @NonNull String, int);
method public static void traceEnd(long);
+ field public static final long TRACE_TAG_AIDL = 16777216L; // 0x1000000L
field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ae6e58c..ce1eff1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -588,6 +588,7 @@
field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
+ field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
@@ -7155,6 +7156,7 @@
public class Tuner implements java.lang.AutoCloseable {
ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @Nullable String, int);
+ method public int applyFrontend(@NonNull android.media.tv.tuner.frontend.FrontendInfo);
method public int cancelScanning();
method public int cancelTuning();
method public void clearOnTuneEventListener();
@@ -7185,7 +7187,6 @@
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER) public static android.media.tv.tuner.filter.SharedFilter openSharedFilter(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.filter.SharedFilterCallback);
method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();
method public int removeOutputPid(@IntRange(from=0) int);
- method public int requestFrontendById(int);
method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);
method public int setLnaEnabled(boolean);
method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
@@ -9968,9 +9969,10 @@
method public boolean isCloneProfile();
method public boolean isCredentialSharableWithParent();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isGuestUser();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isMainUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
method public boolean isMediaSharedWithParent();
- method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
method public static boolean isRemoveResultSuccessful(int);
method public boolean isRestrictedProfile();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
@@ -11924,11 +11926,39 @@
method public abstract void onStopUpdates();
method public final void reportPermanentFailure(@NonNull Throwable);
method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion);
+ method public final void reportSuggestion(@NonNull android.service.timezone.TimeZoneProviderSuggestion, @NonNull android.service.timezone.TimeZoneProviderStatus);
method public final void reportUncertain();
+ method public final void reportUncertain(@NonNull android.service.timezone.TimeZoneProviderStatus);
field public static final String PRIMARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.PrimaryLocationTimeZoneProviderService";
field public static final String SECONDARY_LOCATION_TIME_ZONE_PROVIDER_SERVICE_INTERFACE = "android.service.timezone.SecondaryLocationTimeZoneProviderService";
}
+ public final class TimeZoneProviderStatus implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getConnectivityDependencyStatus();
+ method public int getLocationDetectionDependencyStatus();
+ method public int getTimeZoneResolutionOperationStatus();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.timezone.TimeZoneProviderStatus> CREATOR;
+ field public static final int DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT = 4; // 0x4
+ field public static final int DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS = 6; // 0x6
+ field public static final int DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS = 5; // 0x5
+ field public static final int DEPENDENCY_STATUS_NOT_APPLICABLE = 1; // 0x1
+ field public static final int DEPENDENCY_STATUS_OK = 2; // 0x2
+ field public static final int DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE = 3; // 0x3
+ field public static final int OPERATION_STATUS_FAILED = 3; // 0x3
+ field public static final int OPERATION_STATUS_NOT_APPLICABLE = 1; // 0x1
+ field public static final int OPERATION_STATUS_OK = 2; // 0x2
+ }
+
+ public static final class TimeZoneProviderStatus.Builder {
+ ctor public TimeZoneProviderStatus.Builder();
+ method @NonNull public android.service.timezone.TimeZoneProviderStatus build();
+ method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setConnectivityDependencyStatus(int);
+ method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setLocationDetectionDependencyStatus(int);
+ method @NonNull public android.service.timezone.TimeZoneProviderStatus.Builder setTimeZoneResolutionOperationStatus(int);
+ }
+
public final class TimeZoneProviderSuggestion implements android.os.Parcelable {
method public int describeContents();
method public long getElapsedRealtimeMillis();
@@ -12120,6 +12150,7 @@
method public static int getMaxScore();
method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent();
method public int getPersonalizedScore();
+ method public int getProximity();
method public int getScore();
method public boolean isHotwordDetectionPersonalized();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -12133,6 +12164,9 @@
field public static final int CONFIDENCE_LEVEL_VERY_HIGH = 6; // 0x6
field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordDetectedResult> CREATOR;
field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
+ field public static final int PROXIMITY_FAR = 2; // 0x2
+ field public static final int PROXIMITY_NEAR = 1; // 0x1
+ field public static final int PROXIMITY_UNKNOWN = -1; // 0xffffffff
}
public static final class HotwordDetectedResult.Builder {
@@ -12221,7 +12255,7 @@
public class WallpaperService.Engine {
method public boolean isInAmbientMode();
- method public void onAmbientModeChanged(boolean, long);
+ method @MainThread public void onAmbientModeChanged(boolean, long);
}
}
@@ -12469,6 +12503,8 @@
field public static final int CS_BOUND = 6; // 0x6
field public static final int DIRECT_TO_VM_FINISHED = 103; // 0x67
field public static final int DIRECT_TO_VM_INITIATED = 102; // 0x66
+ field public static final int DND_CHECK_COMPLETED = 110; // 0x6e
+ field public static final int DND_CHECK_INITIATED = 109; // 0x6d
field public static final int FILTERING_COMPLETED = 107; // 0x6b
field public static final int FILTERING_INITIATED = 106; // 0x6a
field public static final int FILTERING_TIMED_OUT = 108; // 0x6c
@@ -12508,6 +12544,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics.EventTiming> CREATOR;
field public static final int DIRECT_TO_VM_FINISHED_TIMING = 8; // 0x8
field public static final int DISCONNECT_TIMING = 2; // 0x2
+ field public static final int DND_PRE_CALL_PRE_CHECK_TIMING = 12; // 0xc
field public static final int FILTERING_COMPLETED_TIMING = 10; // 0xa
field public static final int FILTERING_TIMED_OUT_TIMING = 11; // 0xb
field public static final int HOLD_TIMING = 3; // 0x3
@@ -14155,6 +14192,7 @@
ctor public QualifiedNetworksService.NetworkAvailabilityProvider(int);
method public abstract void close();
method public final int getSlotIndex();
+ method public void reportEmergencyDataNetworkPreferredTransportChanged(int);
method public void reportThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ThrottleStatus>);
method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d8419a8..3fee610 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -758,6 +758,7 @@
method public int getUserId();
method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
+ field public static final String ATTENTION_SERVICE = "attention";
field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
field public static final String DREAM_SERVICE = "dream";
@@ -904,6 +905,7 @@
method public boolean isFull();
method public boolean isGuest();
method public boolean isInitialized();
+ method public boolean isMain();
method public boolean isManagedProfile();
method public boolean isPrimary();
method public boolean isProfile();
@@ -922,6 +924,7 @@
field public static final int FLAG_FULL = 1024; // 0x400
field @Deprecated public static final int FLAG_GUEST = 4; // 0x4
field public static final int FLAG_INITIALIZED = 16; // 0x10
+ field public static final int FLAG_MAIN = 16384; // 0x4000
field @Deprecated public static final int FLAG_MANAGED_PROFILE = 32; // 0x20
field public static final int FLAG_PRIMARY = 1; // 0x1
field public static final int FLAG_PROFILE = 4096; // 0x1000
@@ -1900,6 +1903,7 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
method public static boolean isSplitSystemUser();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
method public boolean isUsersOnSecondaryDisplaysSupported();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
@@ -2499,6 +2503,10 @@
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
}
+ public abstract class HotwordDetectionService extends android.app.Service {
+ field public static final boolean ENABLE_PROXIMITY_RESULT = true;
+ }
+
public final class VisibleActivityInfo implements android.os.Parcelable {
ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
}
@@ -2622,13 +2630,23 @@
method public int getCarrierIdListVersion();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<java.lang.String> getCertsFromCarrierPrivilegeAccessRules();
method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
+ method @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getHalVersion(int);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
- method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
+ method @Deprecated public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
method @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
+ field public static final int HAL_SERVICE_DATA = 1; // 0x1
+ field public static final int HAL_SERVICE_IMS = 7; // 0x7
+ field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
+ field public static final int HAL_SERVICE_MODEM = 3; // 0x3
+ field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
+ field public static final int HAL_SERVICE_SIM = 5; // 0x5
+ field public static final int HAL_SERVICE_VOICE = 6; // 0x6
+ field public static final android.util.Pair HAL_VERSION_UNKNOWN;
+ field public static final android.util.Pair HAL_VERSION_UNSUPPORTED;
field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index f9b8a30..8e21d8c 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -567,6 +567,10 @@
Missing nullability on parameter `destAddress` in method `checkSmsShortCodeDestination`
MissingNullability: android.telephony.SmsManager#checkSmsShortCodeDestination(String, String) parameter #1:
Missing nullability on parameter `countryIso` in method `checkSmsShortCodeDestination`
+MissingNullability: android.telephony.TelephonyManager#HAL_VERSION_UNKNOWN:
+ Missing nullability on field `HAL_VERSION_UNKNOWN` in class `class android.telephony.TelephonyManager`
+MissingNullability: android.telephony.TelephonyManager#HAL_VERSION_UNSUPPORTED:
+ Missing nullability on field `HAL_VERSION_UNSUPPORTED` in class `class android.telephony.TelephonyManager`
MissingNullability: android.telephony.TelephonyManager#getLine1AlphaTag():
Missing nullability on method `getLine1AlphaTag` return
MissingNullability: android.telephony.TelephonyManager#getRadioHalVersion():
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index e86d2f3..2fe5d51 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -696,7 +696,8 @@
ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
- ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY
+ ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+ ERROR_TAKE_SCREENSHOT_INVALID_WINDOW
})
public @interface ScreenshotErrorCode {}
@@ -728,6 +729,18 @@
public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4;
/**
+ * The status of taking screenshot is failure and the reason is invalid accessibility window Id.
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_INVALID_WINDOW = 5;
+
+ /**
+ * The status of taking screenshot is failure and the reason is the window contains secure
+ * content.
+ * @see WindowManager.LayoutParams#FLAG_SECURE
+ */
+ public static final int ERROR_TAKE_SCREENSHOT_SECURE_WINDOW = 6;
+
+ /**
* The interval time of calling
* {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
* @hide
@@ -2568,6 +2581,7 @@
* @param executor Executor on which to run the callback.
* @param callback The callback invoked when taking screenshot has succeeded or failed.
* See {@link TakeScreenshotCallback} for details.
+ * @see #takeScreenshotOfWindow
*/
public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
@NonNull TakeScreenshotCallback callback) {
@@ -2589,7 +2603,8 @@
final HardwareBuffer hardwareBuffer =
result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, android.hardware.HardwareBuffer.class);
final ParcelableColorSpace colorSpace =
- result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, android.graphics.ParcelableColorSpace.class);
+ result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE,
+ android.graphics.ParcelableColorSpace.class);
final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
colorSpace.getColorSpace(),
result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
@@ -2601,6 +2616,37 @@
}
/**
+ * Takes a screenshot of the specified window and returns it via an
+ * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+ * to construct the bitmap from the ScreenshotResult's payload.
+ * <p>
+ * <strong>Note:</strong> In order to take screenshots your service has
+ * to declare the capability to take screenshot by setting the
+ * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * </p>
+ * <p>
+ * Both this method and {@link #takeScreenshot} can be used for machine learning-based visual
+ * screen understanding. Use <code>takeScreenshotOfWindow</code> if your target window might be
+ * visually underneath an accessibility overlay (from your or another accessibility service) in
+ * order to capture the window contents without the screenshot being covered by the overlay
+ * contents drawn on the screen.
+ * </p>
+ *
+ * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when taking screenshot has succeeded or failed.
+ * See {@link TakeScreenshotCallback} for details.
+ * @see #takeScreenshot
+ */
+ public void takeScreenshotOfWindow(int accessibilityWindowId,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull TakeScreenshotCallback callback) {
+ AccessibilityInteractionClient.getInstance(this).takeScreenshotOfWindow(
+ mConnectionId, accessibilityWindowId, executor, callback);
+ }
+
+ /**
* Sets the strokeWidth and color of the accessibility focus rectangle.
* <p>
* <strong>Note:</strong> This setting persists until this or another active
@@ -3113,7 +3159,8 @@
private final @NonNull ColorSpace mColorSpace;
private final long mTimestamp;
- private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
+ /** @hide */
+ public ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
@NonNull ColorSpace colorSpace, long timestamp) {
Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 9abce3a..da14b50 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -30,6 +30,7 @@
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.accessibility.AccessibilityWindowInfo;
import java.util.List;
+import android.window.ScreenCapture;
/**
* Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
@@ -122,6 +123,10 @@
void takeScreenshot(int displayId, in RemoteCallback callback);
+ void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+ in ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback);
+
void setGestureDetectionPassthroughRegion(int displayId, in Region region);
void setTouchExplorationPassthroughRegion(int displayId, in Region region);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index dc325ff..1b3282e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -42,6 +42,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.database.DatabaseUtils;
+import android.healthconnect.HealthConnectManager;
import android.media.AudioAttributes.AttributeUsage;
import android.os.Binder;
import android.os.Build;
@@ -1390,9 +1391,25 @@
public static final int OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY;
+ /**
+ * An app op for reading/writing health connect data.
+ *
+ * @hide
+ */
+ public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA;
+
+ /**
+ * Use foreground service with the type
+ * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+ *
+ * @hide
+ */
+ public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
+ AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 126;
+ public static final int _NUM_OP = 128;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1873,6 +1890,15 @@
"android:read_media_visual_user_selected";
/**
+ * An app op for reading/writing health connect data.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String OPSTR_READ_WRITE_HEALTH_DATA =
+ "android:read_write_health_data";
+
+ /**
* Record audio from near-field microphone (ie. TV remote)
* Allows audio recording regardless of sensor privacy state,
* as it is an intentional user interaction: hold-to-talk
@@ -1913,6 +1939,14 @@
public static final String OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
"android:system_exempt_from_forced_app_standby";
+ /**
+ * Start a foreground service with the type "specialUse".
+ *
+ * @hide
+ */
+ public static final String OPSTR_FOREGROUND_SERVICE_SPECIAL_USE =
+ "android:foreground_service_special_use";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2009,6 +2043,7 @@
OP_TURN_SCREEN_ON,
OP_RUN_LONG_JOBS,
OP_READ_MEDIA_VISUAL_USER_SELECTED,
+ OP_FOREGROUND_SERVICE_SPECIAL_USE,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2400,9 +2435,17 @@
"SYSTEM_EXEMPT_FROM_APP_STANDBY").build(),
new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
- "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build()
+ "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build(),
+ new AppOpInfo.Builder(OP_READ_WRITE_HEALTH_DATA, OPSTR_READ_WRITE_HEALTH_DATA,
+ "READ_WRITE_HEALTH_DATA").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_FOREGROUND_SERVICE_SPECIAL_USE,
+ OPSTR_FOREGROUND_SERVICE_SPECIAL_USE, "FOREGROUND_SERVICE_SPECIAL_USE")
+ .setPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE).build(),
};
+ // The number of longs needed to form a full bitmask of app ops
+ private static final int BITMASK_LEN = ((_NUM_OP - 1) / Long.SIZE) + 1;
+
/**
* @hide
*/
@@ -2437,8 +2480,8 @@
* @see #getNotedOpCollectionMode
* @see #collectNotedOpSync
*/
- private static final ThreadLocal<ArrayMap<String, long[]>> sAppOpsNotedInThisBinderTransaction =
- new ThreadLocal<>();
+ private static final ThreadLocal<ArrayMap<String, BitSet>>
+ sAppOpsNotedInThisBinderTransaction = new ThreadLocal<>();
static {
if (sAppOpInfos.length != _NUM_OP) {
@@ -2557,7 +2600,14 @@
@TestApi
public static int permissionToOpCode(String permission) {
Integer boxedOpCode = sPermToOp.get(permission);
- return boxedOpCode != null ? boxedOpCode : OP_NONE;
+ if (boxedOpCode != null) {
+ return boxedOpCode;
+ }
+ if (HealthConnectManager.isHealthPermission(ActivityThread.currentApplication(),
+ permission)) {
+ return OP_READ_WRITE_HEALTH_DATA;
+ }
+ return OP_NONE;
}
/**
@@ -7221,10 +7271,14 @@
*/
public static @Nullable String permissionToOp(@NonNull String permission) {
final Integer opCode = sPermToOp.get(permission);
- if (opCode == null) {
- return null;
+ if (opCode != null) {
+ return sAppOpInfos[opCode].name;
}
- return sAppOpInfos[opCode].name;
+ if (HealthConnectManager.isHealthPermission(ActivityThread.currentApplication(),
+ permission)) {
+ return sAppOpInfos[OP_READ_WRITE_HEALTH_DATA].name;
+ }
+ return null;
}
/**
@@ -8453,8 +8507,9 @@
*/
public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean skipProxyOperation) {
- return startProxyOpNoThrow(op, attributionSource, message, skipProxyOperation,
- ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_CHAIN_ID_NONE);
+ return startProxyOpNoThrow(attributionSource.getToken(), op, attributionSource, message,
+ skipProxyOperation, ATTRIBUTION_FLAGS_NONE, ATTRIBUTION_FLAGS_NONE,
+ ATTRIBUTION_CHAIN_ID_NONE);
}
/**
@@ -8466,7 +8521,8 @@
*
* @hide
*/
- public int startProxyOpNoThrow(int op, @NonNull AttributionSource attributionSource,
+ public int startProxyOpNoThrow(@NonNull IBinder clientId, int op,
+ @NonNull AttributionSource attributionSource,
@Nullable String message, boolean skipProxyOperation, @AttributionFlags
int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
int attributionChainId) {
@@ -8484,7 +8540,7 @@
}
}
- SyncNotedAppOp syncOp = mService.startProxyOperation(op,
+ SyncNotedAppOp syncOp = mService.startProxyOperation(clientId, op,
attributionSource, false, collectionMode == COLLECT_ASYNC, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId);
@@ -8582,9 +8638,10 @@
*/
public void finishProxyOp(@NonNull String op, int proxiedUid,
@NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
- finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
+ IBinder token = mContext.getAttributionSource().getToken();
+ finishProxyOp(token, op, new AttributionSource(mContext.getAttributionSource(),
new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
+ token)), /*skipProxyOperation*/ false);
}
/**
@@ -8599,10 +8656,11 @@
*
* @hide
*/
- public void finishProxyOp(@NonNull String op, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation) {
+ public void finishProxyOp(@NonNull IBinder clientId, @NonNull String op,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
try {
- mService.finishProxyOperation(strOpToOp(op), attributionSource, skipProxyOperation);
+ mService.finishProxyOperation(clientId, strOpToOp(op), attributionSource,
+ skipProxyOperation);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8684,10 +8742,10 @@
*/
public static class PausedNotedAppOpsCollection {
final int mUid;
- final @Nullable ArrayMap<String, long[]> mCollectedNotedAppOps;
+ final @Nullable ArrayMap<String, BitSet> mCollectedNotedAppOps;
PausedNotedAppOpsCollection(int uid, @Nullable ArrayMap<String,
- long[]> collectedNotedAppOps) {
+ BitSet> collectedNotedAppOps) {
mUid = uid;
mCollectedNotedAppOps = collectedNotedAppOps;
}
@@ -8705,7 +8763,7 @@
public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() {
Integer previousUid = sBinderThreadCallingUid.get();
if (previousUid != null) {
- ArrayMap<String, long[]> previousCollectedNotedAppOps =
+ ArrayMap<String, BitSet> previousCollectedNotedAppOps =
sAppOpsNotedInThisBinderTransaction.get();
sBinderThreadCallingUid.remove();
@@ -8779,23 +8837,19 @@
// We are inside of a two-way binder call. Delivered to caller via
// {@link #prefixParcelWithAppOpsIfNeeded}
int op = sOpStrToOp.get(syncOp.getOp());
- ArrayMap<String, long[]> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
+ ArrayMap<String, BitSet> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
if (appOpsNoted == null) {
appOpsNoted = new ArrayMap<>(1);
sAppOpsNotedInThisBinderTransaction.set(appOpsNoted);
}
- long[] appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
+ BitSet appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
if (appOpsNotedForAttribution == null) {
- appOpsNotedForAttribution = new long[2];
+ appOpsNotedForAttribution = new BitSet(_NUM_OP);
appOpsNoted.put(syncOp.getAttributionTag(), appOpsNotedForAttribution);
}
- if (op < 64) {
- appOpsNotedForAttribution[0] |= 1L << op;
- } else {
- appOpsNotedForAttribution[1] |= 1L << (op - 64);
- }
+ appOpsNotedForAttribution.set(op);
}
/** @hide */
@@ -8869,7 +8923,7 @@
*/
// TODO (b/186872903) Refactor how sync noted ops are propagated.
public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
- ArrayMap<String, long[]> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+ ArrayMap<String, BitSet> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
if (notedAppOps == null) {
return;
}
@@ -8881,8 +8935,15 @@
for (int i = 0; i < numAttributionWithNotesAppOps; i++) {
p.writeString(notedAppOps.keyAt(i));
- p.writeLong(notedAppOps.valueAt(i)[0]);
- p.writeLong(notedAppOps.valueAt(i)[1]);
+ // Bitmask's toLongArray will truncate the array, if upper bits arent used
+ long[] notedOpsMask = notedAppOps.valueAt(i).toLongArray();
+ for (int j = 0; j < BITMASK_LEN; j++) {
+ if (j < notedOpsMask.length) {
+ p.writeLong(notedOpsMask[j]);
+ } else {
+ p.writeLong(0);
+ }
+ }
}
}
@@ -8901,12 +8962,13 @@
for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
String attributionTag = p.readString();
- long[] rawNotedAppOps = new long[2];
- rawNotedAppOps[0] = p.readLong();
- rawNotedAppOps[1] = p.readLong();
+ long[] rawNotedAppOps = new long[BITMASK_LEN];
+ for (int j = 0; j < rawNotedAppOps.length; j++) {
+ rawNotedAppOps[j] = p.readLong();
+ }
+ BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
- if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
- BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+ if (!notedAppOps.isEmpty()) {
synchronized (sLock) {
for (int code = notedAppOps.nextSetBit(0); code != -1;
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 4d6e4ae..43023fe 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -26,13 +26,11 @@
import android.util.SparseIntArray;
import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintConsumer;
import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.UndecFunction;
/**
@@ -135,6 +133,7 @@
/**
* Allows overriding start proxy operation behavior.
*
+ * @param clientId The client calling start, represented by an IBinder
* @param code The op code to start.
* @param attributionSource The permission identity of the caller.
* @param startIfModeDefault Whether to start the op of the mode is default.
@@ -148,11 +147,12 @@
* @param superImpl The super implementation.
* @return The app op note result.
*/
- SyncNotedAppOp startProxyOperation(int code, @NonNull AttributionSource attributionSource,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
- int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
- int attributionChainId, @NonNull DecFunction<Integer, AttributionSource, Boolean,
+ SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+ @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
+ @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean,
Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
SyncNotedAppOp> superImpl);
@@ -176,10 +176,15 @@
*
* @param code The op code to finish.
* @param attributionSource The permission identity of the caller.
+ * @param skipProxyOperation Whether to skip the proxy in the proxy/proxied operation
+ * @param clientId The client calling finishProxyOperation
+ * @param superImpl The "standard" implementation to potentially call
*/
- void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
+ void finishProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource,
boolean skipProxyOperation,
- @NonNull TriFunction<Integer, AttributionSource, Boolean, Void> superImpl);
+ @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
+ Void> superImpl);
}
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 10cdf53..042bdd7 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1814,12 +1814,6 @@
}
}
try {
- ActivityThread thread = ActivityThread.currentActivityThread();
- Instrumentation instrumentation = thread.getInstrumentation();
- if (instrumentation.isInstrumenting()
- && ((flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
- flags = flags | Context.RECEIVER_EXPORTED;
- }
final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
diff --git a/core/java/android/app/ForegroundServiceTypeNotAllowedException.java b/core/java/android/app/ForegroundServiceTypeNotAllowedException.java
new file mode 100644
index 0000000..c258242
--- /dev/null
+++ b/core/java/android/app/ForegroundServiceTypeNotAllowedException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.app;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Exception thrown when an app tries to start a foreground {@link Service} without a valid type.
+ */
+public final class ForegroundServiceTypeNotAllowedException
+ extends ServiceStartNotAllowedException implements Parcelable {
+ /**
+ * Constructor.
+ */
+ public ForegroundServiceTypeNotAllowedException(@NonNull String message) {
+ super(message);
+ }
+
+ ForegroundServiceTypeNotAllowedException(@NonNull Parcel source) {
+ super(source.readString());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(getMessage());
+ }
+
+ public static final @NonNull Creator<android.app.ForegroundServiceTypeNotAllowedException>
+ CREATOR = new Creator<android.app.ForegroundServiceTypeNotAllowedException>() {
+ @NonNull
+ public android.app.ForegroundServiceTypeNotAllowedException createFromParcel(
+ Parcel source) {
+ return new android.app.ForegroundServiceTypeNotAllowedException(source);
+ }
+
+ @NonNull
+ public android.app.ForegroundServiceTypeNotAllowedException[] newArray(int size) {
+ return new android.app.ForegroundServiceTypeNotAllowedException[size];
+ }
+ };
+}
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
new file mode 100644
index 0000000..eccc563
--- /dev/null
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -0,0 +1,1033 @@
+/*
+ * Copyright (C) 2022 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.app;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.compat.CompatChanges;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Overridable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.CompatibilityChangeConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Optional;
+
+/**
+ * This class enforces the policies around the foreground service types.
+ *
+ * @hide
+ */
+public abstract class ForegroundServiceTypePolicy {
+ static final String TAG = "ForegroundServiceTypePolicy";
+ static final boolean DEBUG_FOREGROUND_SERVICE_TYPE_POLICY = false;
+
+ /**
+ * The FGS type enforcement:
+ * deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ *
+ * <p>Starting a FGS with this type (equivalent of no type) from apps with
+ * targetSdkVersion {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+ * result in a warning in the log.</p>
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+ @Overridable
+ public static final long FGS_TYPE_NONE_DEPRECATION_CHANGE_ID = 255042465L;
+
+ /**
+ * The FGS type enforcement:
+ * disabling the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ *
+ * <p>Starting a FGS with this type (equivalent of no type) from apps with
+ * targetSdkVersion {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+ * result in an exception.</p>
+ *
+ * @hide
+ */
+ // TODO (b/254661666): Change to @EnabledAfter(T)
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long FGS_TYPE_NONE_DISABLED_CHANGE_ID = 255038118L;
+
+ /**
+ * The FGS type enforcement:
+ * deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+ *
+ * <p>Starting a FGS with this type from apps with targetSdkVersion
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+ * result in a warning in the log.</p>
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+ @Overridable
+ public static final long FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID = 255039210L;
+
+ /**
+ * The FGS type enforcement:
+ * disabling the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+ *
+ * <p>Starting a FGS with this type from apps with targetSdkVersion
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later will
+ * result in an exception.</p>
+ *
+ * @hide
+ */
+ // TODO (b/254661666): Change to @EnabledSince(U) in next OS release
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long FGS_TYPE_DATA_SYNC_DISABLED_CHANGE_ID = 255659651L;
+
+ /**
+ * The FGS type enforcement: Starting a FGS from apps with targetSdkVersion
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later but without the required
+ * permissions associated with the FGS type will result in a SecurityException.
+ *
+ * @hide
+ */
+ // TODO (b/254661666): Change to @EnabledAfter(T)
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long FGS_TYPE_PERMISSION_CHANGE_ID = 254662522L;
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_NONE =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_NONE,
+ FGS_TYPE_NONE_DEPRECATION_CHANGE_ID,
+ FGS_TYPE_NONE_DISABLED_CHANGE_ID,
+ null,
+ null
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_DATA_SYNC =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+ FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID,
+ FGS_TYPE_DATA_SYNC_DISABLED_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC)
+ }, true),
+ null
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PLAYBACK =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK)
+ }, true),
+ null
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_PHONE_CALL}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_PHONE_CALL =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_PHONE_CALL,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.MANAGE_OWN_CALLS)
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_LOCATION =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_LOCATION,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_LOCATION)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.ACCESS_COARSE_LOCATION),
+ new RegularPermission(Manifest.permission.ACCESS_FINE_LOCATION),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_CONNECTED_DEVICE =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.BLUETOOTH_CONNECT),
+ new RegularPermission(Manifest.permission.CHANGE_NETWORK_STATE),
+ new RegularPermission(Manifest.permission.CHANGE_WIFI_STATE),
+ new RegularPermission(Manifest.permission.CHANGE_WIFI_MULTICAST_STATE),
+ new RegularPermission(Manifest.permission.NFC),
+ new RegularPermission(Manifest.permission.TRANSMIT_IR),
+ new UsbDevicePermission(),
+ new UsbAccessoryPermission(),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PROJECTION =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT),
+ new AppOpPermission(AppOpsManager.OP_PROJECT_MEDIA)
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_CAMERA}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_CAMERA =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_CAMERA,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_CAMERA)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.CAMERA),
+ new RegularPermission(Manifest.permission.SYSTEM_CAMERA),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MICROPHONE =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_MICROPHONE,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD),
+ new RegularPermission(Manifest.permission.CAPTURE_AUDIO_OUTPUT),
+ new RegularPermission(Manifest.permission.CAPTURE_MEDIA_OUTPUT),
+ new RegularPermission(Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT),
+ new RegularPermission(Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT),
+ new RegularPermission(Manifest.permission.RECORD_AUDIO),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_HEALTH =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_HEALTH,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_HEALTH)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
+ new RegularPermission(Manifest.permission.BODY_SENSORS),
+ new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_REMOTE_MESSAGING =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING)
+ }, true),
+ null
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_SYSTEM_EXEMPTED =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED)
+ }, true),
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.SCHEDULE_EXACT_ALARM),
+ new RegularPermission(Manifest.permission.USE_EXACT_ALARM),
+ new AppOpPermission(AppOpsManager.OP_ACTIVATE_VPN),
+ new AppOpPermission(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN),
+ }, false)
+ );
+
+ /**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_SPECIAL_USE =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE)
+ }, true),
+ null
+ );
+
+ /**
+ * Foreground service policy check result code: this one is not actually being used.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_UNKNOWN =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_UNKNOWN;
+
+ /**
+ * Foreground service policy check result code: okay to go.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_OK =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_OK;
+
+ /**
+ * Foreground service policy check result code: this foreground service type is deprecated.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_DEPRECATED =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_DEPRECATED;
+
+ /**
+ * Foreground service policy check result code: this foreground service type is disabled.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_DISABLED =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_DISABLED;
+
+ /**
+ * Foreground service policy check result code: the caller doesn't have permission to start
+ * foreground service with this type, but the policy is permissive.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+
+ /**
+ * Foreground service policy check result code: the caller doesn't have permission to start
+ * foreground service with this type, and the policy is enforced.
+ *
+ * @hide
+ */
+ public static final int FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED =
+ AppProtoEnums.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED;
+
+ /**
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "FGS_TYPE_POLICY_CHECK_" }, value = {
+ FGS_TYPE_POLICY_CHECK_UNKNOWN,
+ FGS_TYPE_POLICY_CHECK_OK,
+ FGS_TYPE_POLICY_CHECK_DEPRECATED,
+ FGS_TYPE_POLICY_CHECK_DISABLED,
+ FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE,
+ FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ForegroundServicePolicyCheckCode{}
+
+ /**
+ * @return The policy info for the given type.
+ */
+ @NonNull
+ public abstract ForegroundServiceTypePolicyInfo getForegroundServiceTypePolicyInfo(
+ @ForegroundServiceType int type, @ForegroundServiceType int defaultToType);
+
+ /**
+ * Run check on the foreground service type policy for the given uid/pid
+ *
+ * @hide
+ */
+ @ForegroundServicePolicyCheckCode
+ public abstract int checkForegroundServiceTypePolicy(@NonNull Context context,
+ @NonNull String packageName, int callerUid, int callerPid, boolean allowWhileInUse,
+ @NonNull ForegroundServiceTypePolicyInfo policy);
+
+ @GuardedBy("sLock")
+ private static ForegroundServiceTypePolicy sDefaultForegroundServiceTypePolicy = null;
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Return the default policy for FGS type.
+ */
+ public static @NonNull ForegroundServiceTypePolicy getDefaultPolicy() {
+ synchronized (sLock) {
+ if (sDefaultForegroundServiceTypePolicy == null) {
+ sDefaultForegroundServiceTypePolicy = new DefaultForegroundServiceTypePolicy();
+ }
+ return sDefaultForegroundServiceTypePolicy;
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @hide
+ */
+ public ForegroundServiceTypePolicy() {
+ }
+
+ /**
+ * This class represents the policy for a specific FGS service type.
+ *
+ * @hide
+ */
+ public static final class ForegroundServiceTypePolicyInfo {
+ /**
+ * The foreground service type.
+ */
+ final @ForegroundServiceType int mType;
+
+ /**
+ * The change id to tell if this FGS type is deprecated.
+ *
+ * <p>A 0 indicates it's not deprecated.</p>
+ */
+ final long mDeprecationChangeId;
+
+ /**
+ * The change id to tell if this FGS type is disabled.
+ *
+ * <p>A 0 indicates it's not disabled.</p>
+ */
+ final long mDisabledChangeId;
+
+ /**
+ * The required permissions to start a foreground with this type, all of them
+ * MUST have been granted.
+ */
+ final @Nullable ForegroundServiceTypePermissions mAllOfPermissions;
+
+ /**
+ * The required permissions to start a foreground with this type, any one of them
+ * being granted is sufficient.
+ */
+ final @Nullable ForegroundServiceTypePermissions mAnyOfPermissions;
+
+ /**
+ * A customized check for the permissions.
+ */
+ @Nullable ForegroundServiceTypePermission mCustomPermission;
+
+ /**
+ * Not a real change id, but a place holder.
+ */
+ private static final long INVALID_CHANGE_ID = 0L;
+
+ /**
+ * @return {@code true} if the given change id is valid.
+ */
+ private static boolean isValidChangeId(long changeId) {
+ return changeId != INVALID_CHANGE_ID;
+ }
+
+ /**
+ * Construct a new instance.
+ *
+ * @hide
+ */
+ public ForegroundServiceTypePolicyInfo(@ForegroundServiceType int type,
+ long deprecationChangeId, long disabledChangeId,
+ @Nullable ForegroundServiceTypePermissions allOfPermissions,
+ @Nullable ForegroundServiceTypePermissions anyOfPermissions) {
+ mType = type;
+ mDeprecationChangeId = deprecationChangeId;
+ mDisabledChangeId = disabledChangeId;
+ mAllOfPermissions = allOfPermissions;
+ mAnyOfPermissions = anyOfPermissions;
+ }
+
+ /**
+ * @return The foreground service type.
+ */
+ @ForegroundServiceType
+ public int getForegroundServiceType() {
+ return mType;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = toPermissionString(new StringBuilder());
+ sb.append("type=0x");
+ sb.append(Integer.toHexString(mType));
+ sb.append(" deprecationChangeId=");
+ sb.append(mDeprecationChangeId);
+ sb.append(" disabledChangeId=");
+ sb.append(mDisabledChangeId);
+ sb.append(" customPermission=");
+ sb.append(mCustomPermission);
+ return sb.toString();
+ }
+
+ /**
+ * @return The required permissions.
+ */
+ public String toPermissionString() {
+ return toPermissionString(new StringBuilder()).toString();
+ }
+
+ private StringBuilder toPermissionString(StringBuilder sb) {
+ if (mAllOfPermissions != null) {
+ sb.append("all of the permissions ");
+ sb.append(mAllOfPermissions.toString());
+ sb.append(' ');
+ }
+ if (mAnyOfPermissions != null) {
+ sb.append("any of the permissions ");
+ sb.append(mAnyOfPermissions.toString());
+ sb.append(' ');
+ }
+ return sb;
+ }
+
+ /**
+ * @hide
+ */
+ public void setCustomPermission(
+ @Nullable ForegroundServiceTypePermission customPermission) {
+ mCustomPermission = customPermission;
+ }
+
+ /**
+ * @return The name of the permissions which are all required.
+ * It may contain app op names.
+ *
+ * For test only.
+ */
+ public @NonNull Optional<String[]> getRequiredAllOfPermissionsForTest() {
+ if (mAllOfPermissions == null) {
+ return Optional.empty();
+ }
+ return Optional.of(mAllOfPermissions.toStringArray());
+ }
+
+ /**
+ * @return The name of the permissions where any of the is granted is sufficient.
+ * It may contain app op names.
+ *
+ * For test only.
+ */
+ public @NonNull Optional<String[]> getRequiredAnyOfPermissionsForTest() {
+ if (mAnyOfPermissions == null) {
+ return Optional.empty();
+ }
+ return Optional.of(mAnyOfPermissions.toStringArray());
+ }
+
+ /**
+ * Whether or not this type is disabled.
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public boolean isTypeDisabled(int callerUid) {
+ return isValidChangeId(mDisabledChangeId)
+ && CompatChanges.isChangeEnabled(mDisabledChangeId, callerUid);
+ }
+
+ /**
+ * Override the type disabling change Id.
+ *
+ * For test only.
+ */
+ public void setTypeDisabledForTest(boolean disabled, @NonNull String packageName)
+ throws RemoteException {
+ overrideChangeIdForTest(mDisabledChangeId, disabled, packageName);
+ }
+
+ /**
+ * clear the type disabling change Id.
+ *
+ * For test only.
+ */
+ public void clearTypeDisabledForTest(@NonNull String packageName) throws RemoteException {
+ clearOverrideForTest(mDisabledChangeId, packageName);
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ boolean isTypeDeprecated(int callerUid) {
+ return isValidChangeId(mDeprecationChangeId)
+ && CompatChanges.isChangeEnabled(mDeprecationChangeId, callerUid);
+ }
+
+ private void overrideChangeIdForTest(long changeId, boolean enable, String packageName)
+ throws RemoteException {
+ if (!isValidChangeId(changeId)) {
+ return;
+ }
+ final ArraySet<Long> enabled = new ArraySet<>();
+ final ArraySet<Long> disabled = new ArraySet<>();
+ if (enable) {
+ enabled.add(changeId);
+ } else {
+ disabled.add(changeId);
+ }
+ final CompatibilityChangeConfig overrides = new CompatibilityChangeConfig(
+ new Compatibility.ChangeConfig(enabled, disabled));
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ platformCompat.setOverridesForTest(overrides, packageName);
+ }
+
+ private void clearOverrideForTest(long changeId, @NonNull String packageName)
+ throws RemoteException {
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ platformCompat.clearOverrideForTest(changeId, packageName);
+ }
+ }
+
+ /**
+ * This represents the set of permissions that's going to be required
+ * for a specific service type.
+ *
+ * @hide
+ */
+ public static class ForegroundServiceTypePermissions {
+ /**
+ * The set of the permissions to be required.
+ */
+ final @NonNull ForegroundServiceTypePermission[] mPermissions;
+
+ /**
+ * Are we requiring all of the permissions to be granted or any of them.
+ */
+ final boolean mAllOf;
+
+ /**
+ * Constructor.
+ */
+ public ForegroundServiceTypePermissions(
+ @NonNull ForegroundServiceTypePermission[] permissions, boolean allOf) {
+ mPermissions = permissions;
+ mAllOf = allOf;
+ }
+
+ /**
+ * Check the permissions.
+ */
+ @PackageManager.PermissionResult
+ public int checkPermissions(@NonNull Context context, int callerUid, int callerPid,
+ @NonNull String packageName, boolean allowWhileInUse) {
+ if (mAllOf) {
+ for (ForegroundServiceTypePermission perm : mPermissions) {
+ final int result = perm.checkPermission(context, callerUid, callerPid,
+ packageName, allowWhileInUse);
+ if (result != PERMISSION_GRANTED) {
+ return PERMISSION_DENIED;
+ }
+ }
+ return PERMISSION_GRANTED;
+ } else {
+ boolean anyOfGranted = false;
+ for (ForegroundServiceTypePermission perm : mPermissions) {
+ final int result = perm.checkPermission(context, callerUid, callerPid,
+ packageName, allowWhileInUse);
+ if (result == PERMISSION_GRANTED) {
+ anyOfGranted = true;
+ break;
+ }
+ }
+ return anyOfGranted ? PERMISSION_GRANTED : PERMISSION_DENIED;
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("allOf=");
+ sb.append(mAllOf);
+ sb.append(' ');
+ sb.append('[');
+ for (int i = 0; i < mPermissions.length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(mPermissions[i].toString());
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ @NonNull String[] toStringArray() {
+ final String[] names = new String[mPermissions.length];
+ for (int i = 0; i < mPermissions.length; i++) {
+ names[i] = mPermissions[i].mName;
+ }
+ return names;
+ }
+ }
+
+ /**
+ * This represents a permission that's going to be required for a specific service type.
+ *
+ * @hide
+ */
+ public abstract static class ForegroundServiceTypePermission {
+ /**
+ * The name of this permission.
+ */
+ final @NonNull String mName;
+
+ /**
+ * Constructor.
+ */
+ public ForegroundServiceTypePermission(@NonNull String name) {
+ mName = name;
+ }
+
+ /**
+ * Check if the given uid/pid/package has the access to the permission.
+ */
+ @PackageManager.PermissionResult
+ public abstract int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+ @NonNull String packageName, boolean allowWhileInUse);
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+ }
+
+ /**
+ * This represents a regular Android permission to be required for a specific service type.
+ */
+ static class RegularPermission extends ForegroundServiceTypePermission {
+ RegularPermission(@NonNull String name) {
+ super(name);
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ public int checkPermission(Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ // Simple case, check if it's already granted.
+ if (context.checkPermission(mName, callerPid, callerUid) == PERMISSION_GRANTED) {
+ return PERMISSION_GRANTED;
+ }
+ if (allowWhileInUse) {
+ // Check its appops
+ final int opCode = AppOpsManager.permissionToOpCode(mName);
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ if (opCode != AppOpsManager.OP_NONE) {
+ final int currentMode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid,
+ packageName);
+ if (currentMode == MODE_FOREGROUND) {
+ // It's in foreground only mode and we're allowing while-in-use.
+ return PERMISSION_GRANTED;
+ }
+ }
+ }
+ return PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * This represents an app op permission to be required for a specific service type.
+ */
+ static class AppOpPermission extends ForegroundServiceTypePermission {
+ final int mOpCode;
+
+ AppOpPermission(int opCode) {
+ super(AppOpsManager.opToPublicName(opCode));
+ mOpCode = opCode;
+ }
+
+ @Override
+ @PackageManager.PermissionResult
+ public int checkPermission(Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ final int mode = appOpsManager.unsafeCheckOpRawNoThrow(mOpCode, callerUid, packageName);
+ return (mode == MODE_ALLOWED || (allowWhileInUse && mode == MODE_FOREGROUND))
+ ? PERMISSION_GRANTED : PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * This represents a special Android permission to be required for accessing usb devices.
+ */
+ static class UsbDevicePermission extends ForegroundServiceTypePermission {
+ UsbDevicePermission() {
+ super("USB Device");
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ public int checkPermission(Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ final UsbManager usbManager = context.getSystemService(UsbManager.class);
+ final HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
+ if (!ArrayUtils.isEmpty(devices)) {
+ for (UsbDevice device : devices.values()) {
+ if (usbManager.hasPermission(device, packageName, callerPid, callerUid)) {
+ return PERMISSION_GRANTED;
+ }
+ }
+ }
+ return PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * This represents a special Android permission to be required for accessing usb accessories.
+ */
+ static class UsbAccessoryPermission extends ForegroundServiceTypePermission {
+ UsbAccessoryPermission() {
+ super("USB Accessory");
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @PackageManager.PermissionResult
+ public int checkPermission(Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ final UsbManager usbManager = context.getSystemService(UsbManager.class);
+ final UsbAccessory[] accessories = usbManager.getAccessoryList();
+ if (!ArrayUtils.isEmpty(accessories)) {
+ for (UsbAccessory accessory: accessories) {
+ if (usbManager.hasPermission(accessory, callerPid, callerUid)) {
+ return PERMISSION_GRANTED;
+ }
+ }
+ }
+ return PERMISSION_DENIED;
+ }
+ }
+
+ /**
+ * The default policy for the foreground service types.
+ *
+ * @hide
+ */
+ public static class DefaultForegroundServiceTypePolicy extends ForegroundServiceTypePolicy {
+ private final SparseArray<ForegroundServiceTypePolicyInfo> mForegroundServiceTypePolicies =
+ new SparseArray<>();
+
+ /**
+ * Constructor
+ */
+ public DefaultForegroundServiceTypePolicy() {
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_NONE,
+ FGS_TYPE_POLICY_NONE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_DATA_SYNC,
+ FGS_TYPE_POLICY_DATA_SYNC);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ FGS_TYPE_POLICY_MEDIA_PLAYBACK);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_PHONE_CALL,
+ FGS_TYPE_POLICY_PHONE_CALL);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_LOCATION,
+ FGS_TYPE_POLICY_LOCATION);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+ FGS_TYPE_POLICY_CONNECTED_DEVICE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
+ FGS_TYPE_POLICY_MEDIA_PROJECTION);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_CAMERA,
+ FGS_TYPE_POLICY_CAMERA);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MICROPHONE,
+ FGS_TYPE_POLICY_MICROPHONE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_HEALTH,
+ FGS_TYPE_POLICY_HEALTH);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+ FGS_TYPE_POLICY_REMOTE_MESSAGING);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+ FGS_TYPE_POLICY_SYSTEM_EXEMPTED);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
+ FGS_TYPE_POLICY_SPECIAL_USE);
+ }
+
+ @Override
+ public ForegroundServiceTypePolicyInfo getForegroundServiceTypePolicyInfo(
+ @ForegroundServiceType int type, @ForegroundServiceType int defaultToType) {
+ ForegroundServiceTypePolicyInfo info = mForegroundServiceTypePolicies.get(type);
+ if (info == null) {
+ // Unknown type, fallback to the defaultToType
+ info = mForegroundServiceTypePolicies.get(defaultToType);
+ if (info == null) {
+ // It shouldn't happen.
+ throw new IllegalArgumentException("Invalid default fgs type " + defaultToType);
+ }
+ }
+ return info;
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @ForegroundServicePolicyCheckCode
+ public int checkForegroundServiceTypePolicy(Context context, String packageName,
+ int callerUid, int callerPid, boolean allowWhileInUse,
+ @NonNull ForegroundServiceTypePolicyInfo policy) {
+ // Has this FGS type been disabled and not allowed to use anymore?
+ if (policy.isTypeDisabled(callerUid)) {
+ return FGS_TYPE_POLICY_CHECK_DISABLED;
+ }
+ int permissionResult = PERMISSION_DENIED;
+ // Do we have the permission to start FGS with this type.
+ if (policy.mAllOfPermissions != null) {
+ permissionResult = policy.mAllOfPermissions.checkPermissions(context,
+ callerUid, callerPid, packageName, allowWhileInUse);
+ }
+ // If it has the "all of" permissions granted, check the "any of" ones.
+ if (permissionResult == PERMISSION_GRANTED) {
+ boolean checkCustomPermission = true;
+ // Check the "any of" permissions.
+ if (policy.mAnyOfPermissions != null) {
+ permissionResult = policy.mAnyOfPermissions.checkPermissions(context,
+ callerUid, callerPid, packageName, allowWhileInUse);
+ if (permissionResult == PERMISSION_GRANTED) {
+ // We have one of them granted, no need to check custom permissions.
+ checkCustomPermission = false;
+ }
+ }
+ // If we have a customized permission checker, also call it now.
+ if (checkCustomPermission && policy.mCustomPermission != null) {
+ permissionResult = policy.mCustomPermission.checkPermission(context,
+ callerUid, callerPid, packageName, allowWhileInUse);
+ }
+ }
+ if (permissionResult != PERMISSION_GRANTED) {
+ return (CompatChanges.isChangeEnabled(
+ FGS_TYPE_PERMISSION_CHANGE_ID, callerUid))
+ ? FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED
+ : FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+ }
+ // Has this FGS type been deprecated?
+ if (policy.isTypeDeprecated(callerUid)) {
+ return FGS_TYPE_POLICY_CHECK_DEPRECATED;
+ }
+ return FGS_TYPE_POLICY_CHECK_OK;
+ }
+ }
+}
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index 5388282..bd5d105 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -396,6 +396,17 @@
private final String mSuggestActionMsg;
private final String mSuggestActionMsgColumn;
+ public static final Parcelable.Creator<ActionKeyInfo> CREATOR =
+ new Parcelable.Creator<ActionKeyInfo>() {
+ public ActionKeyInfo createFromParcel(Parcel in) {
+ return new ActionKeyInfo(in);
+ }
+
+ public ActionKeyInfo[] newArray(int size) {
+ return new ActionKeyInfo[size];
+ }
+ };
+
/**
* Create one object using attributeset as input data.
* @param activityContext runtime context of the activity that the action key information
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 7635138..01e4b15 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
@@ -726,10 +727,32 @@
* for more details.
* </div>
*
+ * <div class="caution">
+ * <p><strong>Note:</strong>
+ * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ * or higher are not allowed to start foreground services without specifying a valid
+ * foreground service type in the manifest attribute
+ * {@link android.R.attr#foregroundServiceType}.
+ * See
+ * <a href="{@docRoot}/about/versions/14/behavior-changes-14">
+ * Behavior changes: Apps targeting Android 14
+ * </a>
+ * for more details.
+ * </div>
+ *
* @throws ForegroundServiceStartNotAllowedException
* If the app targeting API is
* {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
* becoming foreground service due to background restriction.
+ * @throws ForegroundServiceTypeNotAllowedException
+ * If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+ * {@link android.R.attr#foregroundServiceType} is not set.
+ * @throws SecurityException If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later and doesn't have the
+ * permission to start the foreground service with the specified type in the manifest attribute
+ * {@link android.R.attr#foregroundServiceType}.
*
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
@@ -748,51 +771,77 @@
}
}
- /**
- * An overloaded version of {@link #startForeground(int, Notification)} with additional
- * foregroundServiceType parameter.
- *
- * <p>Apps built with SDK version {@link android.os.Build.VERSION_CODES#Q} or later can specify
- * the foreground service types using attribute {@link android.R.attr#foregroundServiceType} in
- * service element of manifest file. The value of attribute
- * {@link android.R.attr#foregroundServiceType} can be multiple flags ORed together.</p>
- *
- * <p>The foregroundServiceType parameter must be a subset flags of what is specified in manifest
- * attribute {@link android.R.attr#foregroundServiceType}, if not, an IllegalArgumentException is
- * thrown. Specify foregroundServiceType parameter as
- * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST} to use all flags that
- * is specified in manifest attribute foregroundServiceType.</p>
- *
- * <div class="caution">
- * <p><strong>Note:</strong>
- * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
- * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
- * or higher are not allowed to start foreground services from the background.
- * See
- * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
- * Behavior changes: Apps targeting Android 12
- * </a>
- * for more details.
- * </div>
- *
- * @param id The identifier for this notification as per
- * {@link NotificationManager#notify(int, Notification)
- * NotificationManager.notify(int, Notification)}; must not be 0.
- * @param notification The Notification to be displayed.
- * @param foregroundServiceType must be a subset flags of manifest attribute
- * {@link android.R.attr#foregroundServiceType} flags.
- *
- * @throws IllegalArgumentException if param foregroundServiceType is not subset of manifest
- * attribute {@link android.R.attr#foregroundServiceType}.
- * @throws ForegroundServiceStartNotAllowedException
- * If the app targeting API is
- * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
- * becoming foreground service due to background restriction.
- *
- * @see android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST
- */
+ /**
+ * An overloaded version of {@link #startForeground(int, Notification)} with additional
+ * foregroundServiceType parameter.
+ *
+ * <p>Apps built with SDK version {@link android.os.Build.VERSION_CODES#Q} or later can specify
+ * the foreground service types using attribute {@link android.R.attr#foregroundServiceType} in
+ * service element of manifest file. The value of attribute
+ * {@link android.R.attr#foregroundServiceType} can be multiple flags ORed together.</p>
+ *
+ * <p>The foregroundServiceType parameter must be a subset flags of what is specified in
+ * manifest attribute {@link android.R.attr#foregroundServiceType}, if not, an
+ * IllegalArgumentException is thrown. Specify foregroundServiceType parameter as
+ * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST} to use all flags that
+ * is specified in manifest attribute foregroundServiceType.</p>
+ *
+ * <div class="caution">
+ * <p><strong>Note:</strong>
+ * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#S},
+ * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
+ * or higher are not allowed to start foreground services from the background.
+ * See
+ * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+ * Behavior changes: Apps targeting Android 12
+ * </a>
+ * for more details.
+ * </div>
+ *
+ * <div class="caution">
+ * <p><strong>Note:</strong>
+ * Beginning with SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ * or higher are not allowed to start foreground services without specifying a valid
+ * foreground service type in the manifest attribute
+ * {@link android.R.attr#foregroundServiceType}, and the parameter {@code foregroundServiceType}
+ * here must not be the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ * See
+ * <a href="{@docRoot}/about/versions/14/behavior-changes-14">
+ * Behavior changes: Apps targeting Android 14
+ * </a>
+ * for more details.
+ * </div>
+ *
+ * @param id The identifier for this notification as per
+ * {@link NotificationManager#notify(int, Notification)
+ * NotificationManager.notify(int, Notification)}; must not be 0.
+ * @param notification The Notification to be displayed.
+ * @param foregroundServiceType must be a subset flags of manifest attribute
+ * {@link android.R.attr#foregroundServiceType} flags; must not be
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ *
+ * @throws IllegalArgumentException if param foregroundServiceType is not subset of manifest
+ * attribute {@link android.R.attr#foregroundServiceType}.
+ * @throws ForegroundServiceStartNotAllowedException
+ * If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#S} or later, and the service is restricted from
+ * becoming foreground service due to background restriction.
+ * @throws ForegroundServiceTypeNotAllowedException
+ * If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later, and the manifest attribute
+ * {@link android.R.attr#foregroundServiceType} is not set, or the param
+ * {@code foregroundServiceType} is {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ * @throws SecurityException If the app targeting API is
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or later and doesn't have the
+ * permission to start the foreground service with the specified type in
+ * {@code foregroundServiceType}.
+ * {@link android.R.attr#foregroundServiceType}.
+ *
+ * @see android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MANIFEST
+ */
public final void startForeground(int id, @NonNull Notification notification,
- @ForegroundServiceType int foregroundServiceType) {
+ @RequiresPermission @ForegroundServiceType int foregroundServiceType) {
try {
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, id,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f4cee5a..6fedb41 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -171,6 +171,7 @@
private final boolean mParentInstance;
private final DevicePolicyResourcesManager mResourcesManager;
+
/** @hide */
public DevicePolicyManager(Context context, IDevicePolicyManager service) {
this(context, service, false);
@@ -6207,46 +6208,46 @@
public static final int WIPE_SILENTLY = 0x0008;
/**
- * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
- * other users will remain unaffected. Calling from the primary user will cause the device to
- * reboot, erasing all device data - including all the secondary users and their data - while
- * booting up.
- * <p>
- * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
- * be able to call this method; if it has not, a security exception will be thrown.
- *
- * If the caller is a profile owner of an organization-owned managed profile, it may
- * additionally call this method on the parent instance.
- * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
- * entire device, while calling it on the current profile instance would relinquish the device
- * for personal use, removing the managed profile and all policies set by the profile owner.
+ * See {@link #wipeData(int, CharSequence)}
*
* @param flags Bit mask of additional options: currently supported flags are
- * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
- * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
- * @throws SecurityException if the calling application does not own an active administrator
- * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
- * {@link android.Manifest.permission#MASTER_CLEAR} permission.
+ * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+ * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
+ * @throws SecurityException if the calling application does not own an active
+ * administrator
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is
+ * not granted the
+ * {@link android.Manifest.permission#MASTER_CLEAR} permission.
+ * @throws IllegalStateException if called on last full-user or system-user
+ * @see #wipeDevice(int)
+ * @see #wipeData(int, CharSequence)
*/
public void wipeData(int flags) {
- wipeDataInternal(flags, "");
+ wipeDataInternal(flags,
+ /* wipeReasonForUser= */ "",
+ /* factoryReset= */ false);
}
/**
- * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
- * other users will remain unaffected, the provided reason for wiping data can be shown to
- * user. Calling from the primary user will cause the device to reboot, erasing all device data
- * - including all the secondary users and their data - while booting up. In this case, we don't
- * show the reason to the user since the device would be factory reset.
- * <p>
- * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
- * be able to call this method; if it has not, a security exception will be thrown.
+ * Ask that all user data be wiped.
*
- * If the caller is a profile owner of an organization-owned managed profile, it may
- * additionally call this method on the parent instance.
- * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
- * entire device, while calling it on the current profile instance would relinquish the device
- * for personal use, removing the managed profile and all policies set by the profile owner.
+ * <p>
+ * If called as a secondary user or managed profile, the user itself and its associated user
+ * data will be wiped. In particular, If the caller is a profile owner of an
+ * organization-owned managed profile, calling this method will relinquish the device for
+ * personal use, removing the managed profile and all policies set by the profile owner.
+ * </p>
+ *
+ * <p>
+ * Calling this method from the primary user will only work if the calling app is targeting
+ * Android 13 or below, in which case it will cause the device to reboot, erasing all device
+ * data - including all the secondary users and their data - while booting up. If an app
+ * targeting Android 13+ is calling this method from the primary user or last full user,
+ * {@link IllegalStateException} will be thrown.
+ * </p>
+ *
+ * If an app wants to wipe the entire device irrespective of which user they are from, they
+ * should use {@link #wipeDevice} instead.
*
* @param flags Bit mask of additional options: currently supported flags are
* {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and
@@ -6254,30 +6255,61 @@
* @param reason a string that contains the reason for wiping data, which can be
* presented to the user.
* @throws SecurityException if the calling application does not own an active administrator
- * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not granted the
* {@link android.Manifest.permission#MASTER_CLEAR} permission.
* @throws IllegalArgumentException if the input reason string is null or empty, or if
* {@link #WIPE_SILENTLY} is set.
+ * @throws IllegalStateException if called on last full-user or system-user
+ * @see #wipeDevice(int)
+ * @see #wipeData(int)
*/
public void wipeData(int flags, @NonNull CharSequence reason) {
Objects.requireNonNull(reason, "reason string is null");
Preconditions.checkStringNotEmpty(reason, "reason string is empty");
Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set");
- wipeDataInternal(flags, reason.toString());
+ wipeDataInternal(flags, reason.toString(), /* factoryReset= */ false);
}
/**
- * Internal function for both {@link #wipeData(int)} and
- * {@link #wipeData(int, CharSequence)} to call.
+ * Ask that the device be wiped and factory reset.
*
+ * <p>
+ * The calling Device Owner or Organization Owned Profile Owner must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call this method; if it has
+ * not, a security exception will be thrown.
+ *
+ * @param flags Bit mask of additional options: currently supported flags are
+ * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+ * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
+ * @throws SecurityException if the calling application does not own an active administrator
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not
+ * granted the {@link android.Manifest.permission#MASTER_CLEAR}
+ * permission.
* @see #wipeData(int)
* @see #wipeData(int, CharSequence)
- * @hide
*/
- private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
+ // TODO(b/255323293) Add host-side tests
+ public void wipeDevice(int flags) {
+ wipeDataInternal(flags,
+ /* wipeReasonForUser= */ "",
+ /* factoryReset= */ true);
+ }
+
+ /**
+ * Internal function for {@link #wipeData(int)}, {@link #wipeData(int, CharSequence)}
+ * and {@link #wipeDevice(int)} to call.
+ *
+ * @hide
+ * @see #wipeData(int)
+ * @see #wipeData(int, CharSequence)
+ * @see #wipeDevice(int)
+ */
+ private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser,
+ boolean factoryReset) {
if (mService != null) {
try {
- mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance);
+ mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance,
+ factoryReset);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8642,7 +8674,7 @@
public void reportFailedPasswordAttempt(int userHandle) {
if (mService != null) {
try {
- mService.reportFailedPasswordAttempt(userHandle);
+ mService.reportFailedPasswordAttempt(userHandle, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 75bfc25..6c27dd7 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -117,7 +117,10 @@
void lockNow(int flags, boolean parent);
- void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
+ /**
+ * @param factoryReset only applicable when `targetSdk >= U`, either tries to factoryReset/fail or removeUser/fail otherwise
+ **/
+ void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent, boolean factoryReset);
void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
@@ -161,7 +164,7 @@
boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
void reportPasswordChanged(in PasswordMetrics metrics, int userId);
- void reportFailedPasswordAttempt(int userHandle);
+ void reportFailedPasswordAttempt(int userHandle, boolean parent);
void reportSuccessfulPasswordAttempt(int userHandle);
void reportFailedBiometricAttempt(int userHandle);
void reportSuccessfulBiometricAttempt(int userHandle);
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 760c6f0..6f62c8a 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -53,6 +53,8 @@
/**
* Operation types for which this logger can be used.
+ *
+ * @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -87,6 +89,8 @@
* {@link OperationType}. Attempts to use logging methods that don't match
* the specified operation type will be rejected (e.g. use backup methods
* for a restore logger and vice versa).
+ *
+ * @hide
*/
public BackupRestoreEventLogger(@OperationType int operationType) {
mOperationType = operationType;
@@ -111,11 +115,9 @@
*
* @param dataType the type of data being backed.
* @param count number of items of the given type that have been successfully backed up.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
- return logSuccess(OperationType.BACKUP, dataType, count);
+ public void logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
+ logSuccess(OperationType.BACKUP, dataType, count);
}
/**
@@ -130,12 +132,10 @@
* @param dataType the type of data being backed.
* @param count number of items of the given type that have failed to back up.
* @param error optional, the error that has caused the failure.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+ public void logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return logFailure(OperationType.BACKUP, dataType, count, error);
+ logFailure(OperationType.BACKUP, dataType, count, error);
}
/**
@@ -151,12 +151,10 @@
*
* @param dataType the type of data being backed up.
* @param metaData the metadata associated with the data type.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
+ public void logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metaData) {
- return logMetaData(OperationType.BACKUP, dataType, metaData);
+ logMetaData(OperationType.BACKUP, dataType, metaData);
}
/**
@@ -172,11 +170,9 @@
*
* @param dataType the type of data being restored.
* @param count number of items of the given type that have been successfully restored.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
- return logSuccess(OperationType.RESTORE, dataType, count);
+ public void logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
+ logSuccess(OperationType.RESTORE, dataType, count);
}
/**
@@ -193,12 +189,10 @@
* @param dataType the type of data being restored.
* @param count number of items of the given type that have failed to restore.
* @param error optional, the error that has caused the failure.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
+ public void logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return logFailure(OperationType.RESTORE, dataType, count, error);
+ logFailure(OperationType.RESTORE, dataType, count, error);
}
/**
@@ -216,12 +210,10 @@
*
* @param dataType the type of data being restored.
* @param metadata the metadata associated with the data type.
- *
- * @return boolean, indicating whether the log has been accepted.
*/
- public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
+ public void logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metadata) {
- return logMetaData(OperationType.RESTORE, dataType, metadata);
+ logMetaData(OperationType.RESTORE, dataType, metadata);
}
/**
@@ -240,52 +232,47 @@
*
* @hide
*/
- public @OperationType int getOperationType() {
+ @OperationType
+ public int getOperationType() {
return mOperationType;
}
- private boolean logSuccess(@OperationType int operationType,
+ private void logSuccess(@OperationType int operationType,
@BackupRestoreDataType String dataType, int count) {
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
if (dataTypeResult == null) {
- return false;
+ return;
}
dataTypeResult.mSuccessCount += count;
mResults.put(dataType, dataTypeResult);
-
- return true;
}
- private boolean logFailure(@OperationType int operationType,
+ private void logFailure(@OperationType int operationType,
@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
if (dataTypeResult == null) {
- return false;
+ return;
}
dataTypeResult.mFailCount += count;
if (error != null) {
dataTypeResult.mErrors.merge(error, count, Integer::sum);
}
-
- return true;
}
- private boolean logMetaData(@OperationType int operationType,
+ private void logMetaData(@OperationType int operationType,
@NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
if (mHashDigest == null) {
- return false;
+ return;
}
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
if (dataTypeResult == null) {
- return false;
+ return;
}
dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
-
- return true;
}
/**
diff --git a/core/java/android/content/ActivityNotFoundException.java b/core/java/android/content/ActivityNotFoundException.java
index 16149bb..5b50189 100644
--- a/core/java/android/content/ActivityNotFoundException.java
+++ b/core/java/android/content/ActivityNotFoundException.java
@@ -31,5 +31,4 @@
{
super(name);
}
-};
-
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1df0fa8..ae1f689 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4917,6 +4917,7 @@
* @see android.server.attention.AttentionManagerService
* @hide
*/
+ @TestApi
public static final String ATTENTION_SERVICE = "attention";
/**
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index b3435b1..8b6c4dd 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -28,6 +28,7 @@
import android.os.PatternMatcher;
import android.text.TextUtils;
import android.util.AndroidException;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Printer;
import android.util.proto.ProtoOutputStream;
@@ -302,7 +303,7 @@
@UnsupportedAppUsage
private int mOrder;
@UnsupportedAppUsage
- private final ArrayList<String> mActions;
+ private final ArraySet<String> mActions;
private ArrayList<String> mCategories = null;
private ArrayList<String> mDataSchemes = null;
private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null;
@@ -433,7 +434,7 @@
*/
public IntentFilter() {
mPriority = 0;
- mActions = new ArrayList<String>();
+ mActions = new ArraySet<>();
}
/**
@@ -445,7 +446,7 @@
*/
public IntentFilter(String action) {
mPriority = 0;
- mActions = new ArrayList<String>();
+ mActions = new ArraySet<>();
addAction(action);
}
@@ -468,7 +469,7 @@
public IntentFilter(String action, String dataType)
throws MalformedMimeTypeException {
mPriority = 0;
- mActions = new ArrayList<String>();
+ mActions = new ArraySet<>();
addAction(action);
addDataType(dataType);
}
@@ -481,7 +482,7 @@
public IntentFilter(IntentFilter o) {
mPriority = o.mPriority;
mOrder = o.mOrder;
- mActions = new ArrayList<String>(o.mActions);
+ mActions = new ArraySet<>(o.mActions);
if (o.mCategories != null) {
mCategories = new ArrayList<String>(o.mCategories);
}
@@ -742,9 +743,7 @@
* @param action Name of the action to match, such as Intent.ACTION_VIEW.
*/
public final void addAction(String action) {
- if (!mActions.contains(action)) {
- mActions.add(action.intern());
- }
+ mActions.add(action.intern());
}
/**
@@ -758,7 +757,7 @@
* Return an action in the filter.
*/
public final String getAction(int index) {
- return mActions.get(index);
+ return mActions.valueAt(index);
}
/**
@@ -797,8 +796,11 @@
if (ignoreActions == null) {
return !mActions.isEmpty();
}
+ if (mActions.size() > ignoreActions.size()) {
+ return true; // some actions are definitely not ignored
+ }
for (int i = mActions.size() - 1; i >= 0; i--) {
- if (!ignoreActions.contains(mActions.get(i))) {
+ if (!ignoreActions.contains(mActions.valueAt(i))) {
return true;
}
}
@@ -1918,7 +1920,7 @@
int N = countActions();
for (int i=0; i<N; i++) {
serializer.startTag(null, ACTION_STR);
- serializer.attribute(null, NAME_STR, mActions.get(i));
+ serializer.attribute(null, NAME_STR, mActions.valueAt(i));
serializer.endTag(null, ACTION_STR);
}
N = countCategories();
@@ -2313,7 +2315,7 @@
}
public final void writeToParcel(Parcel dest, int flags) {
- dest.writeStringList(mActions);
+ dest.writeStringArray(mActions.toArray(new String[mActions.size()]));
if (mCategories != null) {
dest.writeInt(1);
dest.writeStringList(mCategories);
@@ -2407,8 +2409,9 @@
/** @hide */
public IntentFilter(Parcel source) {
- mActions = new ArrayList<String>();
- source.readStringList(mActions);
+ List<String> actions = new ArrayList<>();
+ source.readStringList(actions);
+ mActions = new ArraySet<>(actions);
if (source.readInt() != 0) {
mCategories = new ArrayList<String>();
source.readStringList(mCategories);
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index f888813..1b5f64c 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -261,8 +261,8 @@
}
LongAtomicFormula that = (LongAtomicFormula) o;
return getKey() == that.getKey()
- && mValue == that.mValue
- && mOperator == that.mOperator;
+ && Objects.equals(mValue, that.mValue)
+ && Objects.equals(mOperator, that.mOperator);
}
@Override
@@ -628,7 +628,7 @@
return false;
}
BooleanAtomicFormula that = (BooleanAtomicFormula) o;
- return getKey() == that.getKey() && mValue == that.mValue;
+ return getKey() == that.getKey() && Objects.equals(mValue, that.mValue);
}
@Override
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 3ca0560..cc7977a 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -20,11 +20,13 @@
import android.annotation.Nullable;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
+import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
+import java.util.Objects;
/**
* Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
@@ -88,6 +90,27 @@
}
/**
+ * Ensure the resource name is in the form [package]:type/entry.
+ *
+ * @param name name of the target resource to overlay (in the form [package]:type/entry)
+ * @return the valid name
+ */
+ private static String ensureValidResourceName(@NonNull String name) {
+ Objects.requireNonNull(name);
+ final int slashIndex = name.indexOf('/'); /* must contain '/' */
+ final int colonIndex = name.indexOf(':'); /* ':' should before '/' if ':' exist */
+
+ // The minimum length of resource type is "id".
+ Preconditions.checkArgument(
+ slashIndex >= 0 /* It must contain the type name */
+ && colonIndex != 0 /* 0 means the package name is empty */
+ && (slashIndex - colonIndex) > 2 /* The shortest length of type is "id" */,
+ "\"%s\" is invalid resource name",
+ name);
+ return name;
+ }
+
+ /**
* Sets the value of the fabricated overlay
*
* @param resourceName name of the target resource to overlay (in the form
@@ -98,6 +121,8 @@
* @see android.util.TypedValue#type
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -119,6 +144,8 @@
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
String configuration) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -139,6 +166,8 @@
* @see android.util.TypedValue#type
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -160,6 +189,8 @@
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
String configuration) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -169,6 +200,26 @@
return this;
}
+ /**
+ * Sets the value of the fabricated overlay
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param value the file descriptor whose contents are the value of the frro
+ * @param configuration The string representation of the config this overlay is enabled for
+ */
+ public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
+ String configuration) {
+ ensureValidResourceName(resourceName);
+
+ final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+ entry.resourceName = resourceName;
+ entry.binaryData = value;
+ entry.configuration = configuration;
+ mEntries.add(entry);
+ return this;
+ }
+
/** Builds an immutable fabricated overlay. */
public FabricatedOverlay build() {
final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index aa86af9..6c1d84b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -165,6 +165,21 @@
"android.internal.PROPERTY_NO_APP_DATA_STORAGE";
/**
+ * <service> level {@link android.content.pm.PackageManager.Property} tag specifying
+ * the actual use case of the service if it's foreground service with the type
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
+ *
+ * <p>
+ * For example:
+ * <service>
+ * <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+ * android:value="foo"/>
+ * </service>
+ */
+ public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE =
+ "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
+
+ /**
* A property value set within the manifest.
* <p>
* The value of a property will only have a single type, as defined by
@@ -189,12 +204,12 @@
@VisibleForTesting
public Property(@NonNull String name, int type,
@NonNull String packageName, @Nullable String className) {
- assert name != null;
- assert type >= TYPE_BOOLEAN && type <= TYPE_STRING;
- assert packageName != null;
- this.mName = name;
+ if (type < TYPE_BOOLEAN || type > TYPE_STRING) {
+ throw new IllegalArgumentException("Invalid type");
+ }
+ this.mName = Objects.requireNonNull(name);
this.mType = type;
- this.mPackageName = packageName;
+ this.mPackageName = Objects.requireNonNull(packageName);
this.mClassName = className;
}
/** @hide */
@@ -442,9 +457,8 @@
*/
public ComponentEnabledSetting(@NonNull ComponentName componentName,
@EnabledState int newState, @EnabledFlags int flags) {
- Objects.nonNull(componentName);
mPackageName = null;
- mComponentName = componentName;
+ mComponentName = Objects.requireNonNull(componentName);
mEnabledState = newState;
mEnabledFlags = flags;
}
@@ -460,8 +474,7 @@
*/
public ComponentEnabledSetting(@NonNull String packageName,
@EnabledState int newState, @EnabledFlags int flags) {
- Objects.nonNull(packageName);
- mPackageName = packageName;
+ mPackageName = Objects.requireNonNull(packageName);
mComponentName = null;
mEnabledState = newState;
mEnabledFlags = flags;
@@ -3407,7 +3420,6 @@
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device is capable of communicating with
* other devices via ultra wideband.
- * @hide
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_UWB = "android.hardware.uwb";
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 88d7004..ab20b6f 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -16,7 +16,9 @@
package android.content.pm;
+import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Printer;
@@ -100,7 +102,15 @@
/**
* The default foreground service type if not been set in manifest file.
+ *
+ * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
+ * later should NOT use this type,
+ * calling {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+ * this type will get a {@link android.app.ForegroundServiceTypeNotAllowedException}.</p>
+ *
+ * @deprecated Do not use.
*/
+ @Deprecated
public static final int FOREGROUND_SERVICE_TYPE_NONE = 0;
/**
@@ -108,14 +118,36 @@
* the {@link android.R.attr#foregroundServiceType} attribute.
* Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
* transfer over network between device and cloud.
+ *
+ * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
+ * later should NOT use this type:
+ * calling {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+ * this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} is still
+ * allowed, but calling it with this type on devices running future platform releases may get a
+ * {@link android.app.ForegroundServiceTypeNotAllowedException}.</p>
+ *
+ * @deprecated Use {@link android.app.job.JobInfo.Builder} data transfer APIs instead.
*/
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
+ conditional = true
+ )
+ @Deprecated
public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1 << 0;
/**
* Constant corresponding to <code>mediaPlayback</code> in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Music, video, news or other media playback.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PLAYBACK}.
*/
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK,
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 1 << 1;
/**
@@ -123,28 +155,94 @@
* the {@link android.R.attr#foregroundServiceType} attribute.
* Ongoing operations related to phone calls, video conferencing,
* or similar interactive communication.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL,
+ },
+ anyOf = {
+ Manifest.permission.MANAGE_OWN_CALLS,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 1 << 2;
/**
* Constant corresponding to <code>location</code> in
* the {@link android.R.attr#foregroundServiceType} attribute.
* GPS, map, navigation location update.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_LOCATION} and one of the
+ * following permissions:
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_LOCATION,
+ },
+ anyOf = {
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 1 << 3;
/**
* Constant corresponding to <code>connectedDevice</code> in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Auto, bluetooth, TV or other devices connection, monitoring and interaction.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_CONNECTED_DEVICE} and one of the
+ * following permissions:
+ * {@link android.Manifest.permission#BLUETOOTH_CONNECT},
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE},
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE},
+ * {@link android.Manifest.permission#CHANGE_WIFI_MULTICAST_STATE},
+ * {@link android.Manifest.permission#NFC},
+ * {@link android.Manifest.permission#TRANSMIT_IR},
+ * or has been granted the access to one of the attached USB devices/accessories.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE,
+ },
+ anyOf = {
+ Manifest.permission.BLUETOOTH_CONNECT,
+ Manifest.permission.CHANGE_NETWORK_STATE,
+ Manifest.permission.CHANGE_WIFI_STATE,
+ Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
+ Manifest.permission.NFC,
+ Manifest.permission.TRANSMIT_IR,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 1 << 4;
/**
* Constant corresponding to {@code mediaProjection} in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Managing a media projection session, e.g for screen recording or taking screenshots.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user must
+ * have allowed the screen capture request from this app.
*/
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION,
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 1 << 5;
/**
@@ -155,7 +253,21 @@
* above, a foreground service will not be able to access the camera if this type is not
* specified in the manifest and in
* {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_CAMERA} and
+ * {@link android.Manifest.permission#CAMERA}.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_CAMERA,
+ },
+ anyOf = {
+ Manifest.permission.CAMERA,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 1 << 6;
/**
@@ -166,17 +278,148 @@
* above, a foreground service will not be able to access the microphone if this type is not
* specified in the manifest and in
* {@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+ *
+ * <p>Starting foreground service with this type from apps targeting API level
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_MICROPHONE} and one of the following
+ * permissions:
+ * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT},
+ * {@link android.Manifest.permission#RECORD_AUDIO}.
*/
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_MICROPHONE,
+ },
+ anyOf = {
+ Manifest.permission.CAPTURE_AUDIO_OUTPUT,
+ Manifest.permission.RECORD_AUDIO,
+ },
+ conditional = true
+ )
public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 1 << 7;
/**
- * The number of foreground service types, this doesn't include
- * the {@link #FOREGROUND_SERVICE_TYPE_MANIFEST} and {@link #FOREGROUND_SERVICE_TYPE_NONE}
- * as they're not real service types.
+ * Constant corresponding to {@code health} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Health, wellness and fitness.
+ *
+ * <p>The caller app is required to have the permissions
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following
+ * permissions:
+ * {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
+ * {@link android.Manifest.permission#BODY_SENSORS},
+ * {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+ */
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.FOREGROUND_SERVICE_HEALTH,
+ },
+ anyOf = {
+ Manifest.permission.ACTIVITY_RECOGNITION,
+ Manifest.permission.BODY_SENSORS,
+ Manifest.permission.HIGH_SAMPLING_RATE_SENSORS,
+ },
+ conditional = true
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 1 << 8;
+
+ /**
+ * Constant corresponding to {@code remoteMessaging} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Messaging use cases which host local server to relay messages across devices.
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING,
+ conditional = true
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 1 << 9;
+
+ /**
+ * Constant corresponding to {@code systemExempted} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * The system exmpted foreground service use cases.
+ *
+ * <p class="note">Note, apps are allowed to use this type only in the following cases:
+ * <ul>
+ * <li>App has a UID < {@link android.os.Process#FIRST_APPLICATION_UID}</li>
+ * <li>App is on Doze allowlist</li>
+ * <li>Device is running in <a href="https://android.googlesource.com/platform/frameworks/base/+/master/packages/SystemUI/docs/demo_mode.md">Demo Mode</a></li>
+ * <li><a href="https://source.android.com/devices/tech/admin/provision">Device owner app</a><li>
+ * <li><a href="https://source.android.com/devices/tech/admin/managed-profiles">Profile owner apps</a><li>
+ * <li>Persistent apps</li>
+ * <li><a href="https://source.android.com/docs/core/connect/carrier">Carrier privileged apps</a></li>
+ * <li>Apps that have the {@code android.app.role.RoleManager#ROLE_EMERGENCY} role</li>
+ * <li>Headless system apps</li>
+ * <li><a href="{@docRoot}guide/topics/admin/device-admin">Device admin apps</a></li>
+ * <li>Active VPN apps</li>
+ * <li>Apps holding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or
+ * {@link Manifest.permission#USE_EXACT_ALARM} permission.</li>
+ * </ul>
+ * </p>
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED,
+ conditional = true
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
+
+ /**
+ * Constant corresponding to {@code specialUse} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Use cases that can't be categorized into any other foreground service types, but also
+ * can't use {@link android.app.job.JobInfo.Builder} APIs.
+ *
+ * <p>The use of this foreground service type may be restricted. Additionally, apps must declare
+ * a service-level {@link PackageManager#PROPERTY_SPECIAL_USE_FGS_SUBTYPE <property>} in
+ * {@code AndroidManifest.xml} as a hint of what the exact use case here is.
+ * Here is an example:
+ * <pre>
+ * <uses-permission
+ * android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+ * />
+ * <service
+ * android:name=".MySpecialForegroundService"
+ * android:foregroundServiceType="specialUse">
+ * <property
+ * android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+ * android:value="foo"
+ * />
+ * </service>
+ * </pre>
+ *
+ * In a future release of Android, if the above foreground service type {@code foo} is supported
+ * by the platform, to offer the backward compatibility, the app could specify
+ * the {@code android:maxSdkVersion} attribute in the <uses-permission> section,
+ * and also add the foreground service type {@code foo} into
+ * the {@code android:foregroundServiceType}, therefore the same app could be installed
+ * in both platforms.
+ * <pre>
+ * <uses-permission
+ * android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+ * android:maxSdkVersion="last_sdk_version_without_type_foo"
+ * />
+ * <service
+ * android:name=".MySpecialForegroundService"
+ * android:foregroundServiceType="specialUse|foo">
+ * <property
+ * android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE""
+ * android:value="foo"
+ * />
+ * </service>
+ * </pre>
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE,
+ conditional = true
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1 << 30;
+
+ /**
+ * The max index being used in the definition of foreground service types.
*
* @hide
*/
- public static final int NUM_OF_FOREGROUND_SERVICE_TYPES = 8;
+ public static final int FOREGROUND_SERVICE_TYPES_MAX_INDEX = 30;
/**
* A special value indicates to use all types set in manifest file.
@@ -199,7 +442,11 @@
FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION,
FOREGROUND_SERVICE_TYPE_CAMERA,
- FOREGROUND_SERVICE_TYPE_MICROPHONE
+ FOREGROUND_SERVICE_TYPE_MICROPHONE,
+ FOREGROUND_SERVICE_TYPE_HEALTH,
+ FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
+ FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE
})
@Retention(RetentionPolicy.SOURCE)
public @interface ForegroundServiceType {}
@@ -275,6 +522,14 @@
return "camera";
case FOREGROUND_SERVICE_TYPE_MICROPHONE:
return "microphone";
+ case FOREGROUND_SERVICE_TYPE_HEALTH:
+ return "health";
+ case FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING:
+ return "remoteMessaging";
+ case FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED:
+ return "systemExempted";
+ case FOREGROUND_SERVICE_TYPE_SPECIAL_USE:
+ return "specialUse";
default:
return "unknown";
}
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 1f83d75..295df5c 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -50,6 +50,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import java.lang.IllegalArgumentException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -1360,7 +1361,9 @@
@NonNull
public Builder setIntents(@NonNull Intent[] intents) {
Objects.requireNonNull(intents, "intents cannot be null");
- Objects.requireNonNull(intents.length, "intents cannot be empty");
+ if (intents.length == 0) {
+ throw new IllegalArgumentException("intents cannot be empty");
+ }
for (Intent intent : intents) {
Objects.requireNonNull(intent, "intents cannot contain null");
Objects.requireNonNull(intent.getAction(), "intent's action must be set");
@@ -1398,7 +1401,9 @@
@NonNull
public Builder setPersons(@NonNull Person[] persons) {
Objects.requireNonNull(persons, "persons cannot be null");
- Objects.requireNonNull(persons.length, "persons cannot be empty");
+ if (persons.length == 0) {
+ throw new IllegalArgumentException("persons cannot be empty");
+ }
for (Person person : persons) {
Objects.requireNonNull(person, "persons cannot contain null");
}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 9baa6ba..2be0323 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -159,6 +159,18 @@
public static final int FLAG_EPHEMERAL_ON_CREATE = 0x00002000;
/**
+ * Indicates that this user is the designated main user on the device. This user may have access
+ * to certain features which are limited to at most one user.
+ *
+ * <p>Currently, this will be the first user to go through setup on the device, but in future
+ * releases this status may be transferable or may even not be given to any users.
+ *
+ * <p>This is not necessarily the system user. For example, it will not be the system user on
+ * devices for which {@link UserManager#isHeadlessSystemUserMode()} returns true.
+ */
+ public static final int FLAG_MAIN = 0x00004000;
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = "FLAG_", value = {
@@ -175,7 +187,8 @@
FLAG_FULL,
FLAG_SYSTEM,
FLAG_PROFILE,
- FLAG_EPHEMERAL_ON_CREATE
+ FLAG_EPHEMERAL_ON_CREATE,
+ FLAG_MAIN
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserInfoFlag {
@@ -369,6 +382,13 @@
}
/**
+ * @see #FLAG_MAIN
+ */
+ public boolean isMain() {
+ return (flags & FLAG_MAIN) == FLAG_MAIN;
+ }
+
+ /**
* Returns true if the user is a split system user.
* <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
* the method always returns false.
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 0d3c8db..fd35378 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -45,6 +45,7 @@
private static final String ATTR_START_WITH_PARENT = "startWithParent";
private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy";
+ private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts";
/** Index values of each property (to indicate whether they are present in this object). */
@IntDef(prefix = "INDEX_", value = {
@@ -52,6 +53,7 @@
INDEX_START_WITH_PARENT,
INDEX_SHOW_IN_SETTINGS,
INDEX_INHERIT_DEVICE_POLICY,
+ INDEX_USE_PARENTS_CONTACTS,
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
@@ -60,6 +62,7 @@
private static final int INDEX_START_WITH_PARENT = 1;
private static final int INDEX_SHOW_IN_SETTINGS = 2;
private static final int INDEX_INHERIT_DEVICE_POLICY = 3;
+ private static final int INDEX_USE_PARENTS_CONTACTS = 4;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -200,6 +203,7 @@
if (hasManagePermission) {
// Add items that require MANAGE_USERS or stronger.
setShowInSettings(orig.getShowInSettings());
+ setUseParentsContacts(orig.getUseParentsContacts());
}
if (hasQueryOrManagePermission) {
// Add items that require QUERY_USERS or stronger.
@@ -317,6 +321,39 @@
}
private @InheritDevicePolicy int mInheritDevicePolicy;
+ /**
+ * Returns whether the current user must use parent user's contacts. If true, writes to the
+ * ContactsProvider corresponding to the current user will be disabled and reads will be
+ * redirected to the parent.
+ *
+ * This only applies to users that have parents (i.e. profiles) and is used to ensure
+ * they can access contacts from the parent profile. This will be generally inapplicable for
+ * non-profile users.
+ *
+ * Please note that in case of the clone profiles, only the allow-listed apps would be allowed
+ * to access contacts across profiles and other apps will not see any contacts.
+ * TODO(b/256126819) Add link to the method returning apps allow-listed for app-cloning
+ *
+ * @return whether contacts access from an associated profile is enabled for the user
+ * @hide
+ */
+ public boolean getUseParentsContacts() {
+ if (isPresent(INDEX_USE_PARENTS_CONTACTS)) return mUseParentsContacts;
+ if (mDefaultProperties != null) return mDefaultProperties.mUseParentsContacts;
+ throw new SecurityException("You don't have permission to query useParentsContacts");
+ }
+ /** @hide */
+ public void setUseParentsContacts(boolean val) {
+ this.mUseParentsContacts = val;
+ setPresent(INDEX_USE_PARENTS_CONTACTS);
+ }
+ /**
+ * Indicates whether the current user should use parent user's contacts.
+ * If this property is set true, the user will be blocked from storing any contacts in its
+ * own contacts database and will serve all read contacts calls through the parent's contacts.
+ */
+ private boolean mUseParentsContacts;
+
@Override
public String toString() {
// Please print in increasing order of PropertyIndex.
@@ -326,6 +363,7 @@
+ ", mStartWithParent=" + getStartWithParent()
+ ", mShowInSettings=" + getShowInSettings()
+ ", mInheritDevicePolicy=" + getInheritDevicePolicy()
+ + ", mUseParentsContacts=" + getUseParentsContacts()
+ "}";
}
@@ -341,6 +379,7 @@
pw.println(prefix + " mStartWithParent=" + getStartWithParent());
pw.println(prefix + " mShowInSettings=" + getShowInSettings());
pw.println(prefix + " mInheritDevicePolicy=" + getInheritDevicePolicy());
+ pw.println(prefix + " mUseParentsContacts=" + getUseParentsContacts());
}
/**
@@ -386,6 +425,9 @@
case ATTR_INHERIT_DEVICE_POLICY:
setInheritDevicePolicy(parser.getAttributeInt(i));
break;
+ case ATTR_USE_PARENTS_CONTACTS:
+ setUseParentsContacts(parser.getAttributeBoolean(i));
+ break;
default:
Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
}
@@ -416,6 +458,10 @@
serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY,
mInheritDevicePolicy);
}
+ if (isPresent(INDEX_USE_PARENTS_CONTACTS)) {
+ serializer.attributeBoolean(null, ATTR_USE_PARENTS_CONTACTS,
+ mUseParentsContacts);
+ }
}
// For use only with an object that has already had any permission-lacking fields stripped out.
@@ -426,6 +472,7 @@
dest.writeBoolean(mStartWithParent);
dest.writeInt(mShowInSettings);
dest.writeInt(mInheritDevicePolicy);
+ dest.writeBoolean(mUseParentsContacts);
}
/**
@@ -440,6 +487,7 @@
mStartWithParent = source.readBoolean();
mShowInSettings = source.readInt();
mInheritDevicePolicy = source.readInt();
+ mUseParentsContacts = source.readBoolean();
}
@Override
@@ -468,6 +516,7 @@
private boolean mStartWithParent = false;
private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO;
+ private boolean mUseParentsContacts = false;
public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
mShowInLauncher = showInLauncher;
@@ -492,13 +541,19 @@
return this;
}
+ public Builder setUseParentsContacts(boolean useParentsContacts) {
+ mUseParentsContacts = useParentsContacts;
+ return this;
+ }
+
/** Builds a UserProperties object with *all* values populated. */
public UserProperties build() {
return new UserProperties(
mShowInLauncher,
mStartWithParent,
mShowInSettings,
- mInheritDevicePolicy);
+ mInheritDevicePolicy,
+ mUseParentsContacts);
}
} // end Builder
@@ -507,12 +562,14 @@
@ShowInLauncher int showInLauncher,
boolean startWithParent,
@ShowInSettings int showInSettings,
- @InheritDevicePolicy int inheritDevicePolicy) {
+ @InheritDevicePolicy int inheritDevicePolicy,
+ boolean useParentsContacts) {
mDefaultProperties = null;
setShowInLauncher(showInLauncher);
setStartWithParent(startWithParent);
setShowInSettings(showInSettings);
setInheritDevicePolicy(inheritDevicePolicy);
+ setUseParentsContacts(useParentsContacts);
}
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index ff07291..09d24d4 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -40,8 +40,10 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableContainer;
import android.icu.text.PluralRules;
+import android.net.Uri;
import android.os.Build;
import android.os.LocaleList;
+import android.os.ParcelFileDescriptor;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -59,6 +61,8 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@@ -799,7 +803,21 @@
private Drawable decodeImageDrawable(@NonNull AssetInputStream ais,
@NonNull Resources wrapper, @NonNull TypedValue value) {
ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
- wrapper, value);
+ wrapper, value);
+ try {
+ return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ });
+ } catch (IOException ioe) {
+ // This is okay. This may be something that ImageDecoder does not
+ // support, like SVG.
+ return null;
+ }
+ }
+
+ @Nullable
+ private Drawable decodeImageDrawable(@NonNull FileInputStream fis, @NonNull Resources wrapper) {
+ ImageDecoder.Source src = ImageDecoder.createSource(wrapper, fis);
try {
return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
@@ -860,6 +878,17 @@
} else {
dr = loadXmlDrawable(wrapper, value, id, density, file);
}
+ } else if (file.startsWith("frro://")) {
+ Uri uri = Uri.parse(file);
+ File f = new File('/' + uri.getHost() + uri.getPath());
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(f,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ AssetFileDescriptor afd = new AssetFileDescriptor(
+ pfd,
+ Long.parseLong(uri.getQueryParameter("offset")),
+ Long.parseLong(uri.getQueryParameter("size")));
+ FileInputStream is = afd.createInputStream();
+ dr = decodeImageDrawable(is, wrapper);
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
diff --git a/core/java/android/hardware/CameraInfo.java b/core/java/android/hardware/CameraInfo.java
index 072be50..41ef6aa 100644
--- a/core/java/android/hardware/CameraInfo.java
+++ b/core/java/android/hardware/CameraInfo.java
@@ -60,4 +60,4 @@
return new CameraInfo[size];
}
};
-};
+}
diff --git a/core/java/android/hardware/CameraStatus.java b/core/java/android/hardware/CameraStatus.java
index 874af29..fa35efb 100644
--- a/core/java/android/hardware/CameraStatus.java
+++ b/core/java/android/hardware/CameraStatus.java
@@ -68,4 +68,4 @@
return new CameraStatus[size];
}
};
-};
+}
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index d415706..267ef36 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -118,4 +118,4 @@
}
return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto);
}
-};
+}
diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
index 7b6a457..8304796 100644
--- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
+++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
@@ -26,6 +26,7 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.TreeMap;
/**
@@ -63,11 +64,11 @@
}
private void update() {
- Iterator iter = mFutureErrorMap.entrySet().iterator();
+ Iterator<Map.Entry<Long, Integer>> iter = mFutureErrorMap.entrySet().iterator();
while (iter.hasNext()) {
- TreeMap.Entry pair = (TreeMap.Entry)iter.next();
- Long errorFrameNumber = (Long)pair.getKey();
- int requestType = (int) pair.getValue();
+ Map.Entry<Long, Integer> pair = iter.next();
+ long errorFrameNumber = pair.getKey();
+ int requestType = pair.getValue();
Boolean removeError = false;
if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) {
removeError = true;
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
index 621a418..92a2fb6 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableEnum.java
@@ -103,6 +103,7 @@
return new MarshalerEnum(managedType, nativeType);
}
+ @SuppressWarnings("ReturnValueIgnored")
@Override
public boolean isTypeMappingSupported(TypeReference<T> managedType, int nativeType) {
if (nativeType == TYPE_INT32 || nativeType == TYPE_BYTE) {
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index a0d8f8d..f4b87b9 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1344,7 +1344,8 @@
return false;
}
for (int j = 0; j < mSensorPixelModesUsed.size(); j++) {
- if (mSensorPixelModesUsed.get(j) != other.mSensorPixelModesUsed.get(j)) {
+ if (!Objects.equals(
+ mSensorPixelModesUsed.get(j), other.mSensorPixelModesUsed.get(j))) {
return false;
}
}
diff --git a/core/java/android/hardware/fingerprint/Fingerprint.java b/core/java/android/hardware/fingerprint/Fingerprint.java
index 9ce834ca..c01c94c 100644
--- a/core/java/android/hardware/fingerprint/Fingerprint.java
+++ b/core/java/android/hardware/fingerprint/Fingerprint.java
@@ -69,4 +69,4 @@
return new Fingerprint[size];
}
};
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 5a44244..51236fe3 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -79,9 +79,20 @@
/* Returns true if the caller has permission to access the device. */
boolean hasDevicePermission(in UsbDevice device, String packageName);
+ /* Returns true if the given package/pid/uid has permission to access the device. */
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+ boolean hasDevicePermissionWithIdentity(in UsbDevice device, String packageName,
+ int pid, int uid);
+
/* Returns true if the caller has permission to access the accessory. */
boolean hasAccessoryPermission(in UsbAccessory accessory);
+ /* Returns true if the given pid/uid has permission to access the accessory. */
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_USB)")
+ boolean hasAccessoryPermissionWithIdentity(in UsbAccessory accessory, int pid, int uid);
+
/* Requests permission for the given package to access the device.
* Will display a system dialog to query the user if permission
* had not already been given.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 2c38f70..50dd0064 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -838,6 +838,28 @@
}
/**
+ * Returns true if the caller has permission to access the device. It's similar to the
+ * {@link #hasPermission(UsbDevice)} but allows to specify a different package/uid/pid.
+ *
+ * <p>Not for third-party apps.</p>
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ @RequiresFeature(PackageManager.FEATURE_USB_HOST)
+ public boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName,
+ int pid, int uid) {
+ if (mService == null) {
+ return false;
+ }
+ try {
+ return mService.hasDevicePermissionWithIdentity(device, packageName, pid, uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns true if the caller has permission to access the accessory.
* Permission might have been granted temporarily via
* {@link #requestPermission(UsbAccessory, PendingIntent)} or
@@ -859,6 +881,27 @@
}
/**
+ * Returns true if the caller has permission to access the accessory. It's similar to the
+ * {@link #hasPermission(UsbAccessory)} but allows to specify a different uid/pid.
+ *
+ * <p>Not for third-party apps.</p>
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
+ @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
+ public boolean hasPermission(@NonNull UsbAccessory accessory, int pid, int uid) {
+ if (mService == null) {
+ return false;
+ }
+ try {
+ return mService.hasAccessoryPermissionWithIdentity(accessory, pid, uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Requests temporary permission for the given package to access the device.
* This may result in a system dialog being displayed to the user
* if permission had not already been granted.
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index d23fb36..d55367f 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -32,6 +32,7 @@
import android.util.Log;
import android.view.InputChannel;
import android.view.MotionEvent;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
@@ -93,9 +94,10 @@
final int mTargetSdkVersion;
/**
- * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
- * so that {@link RemoteInputConnection} can query if {@link #unbindInput()} has already been
- * called or not, mainly to avoid unnecessary blocking operations.
+ * This is not {@code null} only between {@link #bindInput(InputBinding)} and
+ * {@link #unbindInput()} so that {@link RemoteInputConnection} can query if
+ * {@link #unbindInput()} has already been called or not, mainly to avoid unnecessary
+ * blocking operations.
*
* <p>This field must be set and cleared only from the binder thread(s), where the system
* guarantees that {@link #bindInput(InputBinding)},
@@ -219,18 +221,26 @@
return;
case DO_SHOW_SOFT_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
+ final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
inputMethod.showSoftInputWithToken(
- msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+ msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
}
args.recycle();
return;
}
case DO_HIDE_SOFT_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
+ final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
- (IBinder) args.arg1);
+ (IBinder) args.arg1, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
}
args.recycle();
return;
@@ -416,16 +426,20 @@
@BinderThread
@Override
- public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT,
- flags, showInputToken, resultReceiver));
+ public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+ int flags, ResultReceiver resultReceiver) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
+ flags, showInputToken, resultReceiver, statsToken));
}
@BinderThread
@Override
- public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT,
- flags, hideInputToken, resultReceiver));
+ public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
+ int flags, ResultReceiver resultReceiver) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
+ flags, hideInputToken, resultReceiver, statsToken));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d902486..bf4fc4a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -124,6 +124,7 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;
import android.view.inputmethod.InputBinding;
@@ -669,6 +670,10 @@
*/
private IBinder mCurHideInputToken;
+ /** The token tracking the current IME request or {@code null} otherwise. */
+ @Nullable
+ private ImeTracker.Token mCurStatsToken;
+
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
onComputeInsets(mTmpInsets);
if (!mViewsCreated) {
@@ -870,10 +875,12 @@
@MainThread
@Override
public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
- IBinder hideInputToken) {
+ IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
mSystemCallingHideSoftInput = true;
mCurHideInputToken = hideInputToken;
+ mCurStatsToken = statsToken;
hideSoftInput(flags, resultReceiver);
+ mCurStatsToken = null;
mCurHideInputToken = null;
mSystemCallingHideSoftInput = false;
}
@@ -884,6 +891,7 @@
@MainThread
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
+ ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
if (DEBUG) Log.v(TAG, "hideSoftInput()");
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
&& !mSystemCallingHideSoftInput) {
@@ -918,12 +926,17 @@
@MainThread
@Override
public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
- IBinder showInputToken) {
+ IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
mSystemCallingShowSoftInput = true;
mCurShowInputToken = showInputToken;
- showSoftInput(flags, resultReceiver);
- mCurShowInputToken = null;
- mSystemCallingShowSoftInput = false;
+ mCurStatsToken = statsToken;
+ try {
+ showSoftInput(flags, resultReceiver);
+ } finally {
+ mCurStatsToken = null;
+ mCurShowInputToken = null;
+ mSystemCallingShowSoftInput = false;
+ }
}
/**
@@ -932,6 +945,7 @@
@MainThread
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+ ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
if (DEBUG) Log.v(TAG, "showSoftInput()");
// TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
@@ -947,7 +961,12 @@
null /* icProto */);
final boolean wasVisible = isInputViewShown();
if (dispatchOnShowInputRequested(flags, false)) {
+ ImeTracker.get().onProgress(mCurStatsToken,
+ ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
showWindow(true);
+ } else {
+ ImeTracker.get().onFailed(mCurStatsToken,
+ ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
}
setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
@@ -2923,8 +2942,10 @@
ImeTracing.getInstance().triggerServiceDump(
"InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
null /* icProto */);
+ ImeTracker.get().onProgress(mCurStatsToken,
+ ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
mPrivOps.applyImeVisibilityAsync(setVisible
- ? mCurShowInputToken : mCurHideInputToken, setVisible);
+ ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
}
private void finishViews(boolean finishingInput) {
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
index f8e2558..d4b76c8 100644
--- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -53,8 +53,8 @@
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -89,12 +89,10 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Prefix {}
- private static final HashMap<String, String> sPrefixLegacyFileNameMap =
- new HashMap<String, String>() {{
- put(PREFIX_XT, "netstats_xt.bin");
- put(PREFIX_UID, "netstats_uid.bin");
- put(PREFIX_UID_TAG, "netstats_uid.bin");
- }};
+ private static final Map<String, String> sPrefixLegacyFileNameMap = Map.of(
+ PREFIX_XT, "netstats_xt.bin",
+ PREFIX_UID, "netstats_uid.bin",
+ PREFIX_UID_TAG, "netstats_uid.bin");
// These version constants are copied from NetworkStatsCollection/History, which is okay for
// OEMs to modify to adapt their own logic.
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
index 4bc5b49..0427742 100644
--- a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
@@ -53,7 +53,7 @@
if (in.keySet().size() != EXPECTED_BUNDLE_KEY_CNT) {
throw new IllegalArgumentException(
String.format(
- "Expect PersistableBundle to have %d element but found: %d",
+ "Expect PersistableBundle to have %d element but found: %s",
EXPECTED_BUNDLE_KEY_CNT, in.keySet()));
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index d5c3de1..b478a379 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -84,7 +84,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
-import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -1314,31 +1313,31 @@
private static long toBytes(long value, String unit) {
unit = unit.toUpperCase();
- if (List.of("B").contains(unit)) {
+ if ("B".equals(unit)) {
return value;
}
- if (List.of("K", "KB").contains(unit)) {
+ if ("K".equals(unit) || "KB".equals(unit)) {
return DataUnit.KILOBYTES.toBytes(value);
}
- if (List.of("M", "MB").contains(unit)) {
+ if ("M".equals(unit) || "MB".equals(unit)) {
return DataUnit.MEGABYTES.toBytes(value);
}
- if (List.of("G", "GB").contains(unit)) {
+ if ("G".equals(unit) || "GB".equals(unit)) {
return DataUnit.GIGABYTES.toBytes(value);
}
- if (List.of("KI", "KIB").contains(unit)) {
+ if ("KI".equals(unit) || "KIB".equals(unit)) {
return DataUnit.KIBIBYTES.toBytes(value);
}
- if (List.of("MI", "MIB").contains(unit)) {
+ if ("MI".equals(unit) || "MIB".equals(unit)) {
return DataUnit.MEBIBYTES.toBytes(value);
}
- if (List.of("GI", "GIB").contains(unit)) {
+ if ("GI".equals(unit) || "GIB".equals(unit)) {
return DataUnit.GIBIBYTES.toBytes(value);
}
@@ -1370,7 +1369,7 @@
sign = -1;
}
- fmtSize = fmtSize.replace(first + "", "");
+ fmtSize = fmtSize.substring(1);
}
int index = 0;
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index e321a66..b6ff102 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -258,12 +258,14 @@
* waitForService should always be able to return the service.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
public static String[] getDeclaredInstances(@NonNull String iface) {
try {
return getIServiceManager().getDeclaredInstances(iface);
} catch (RemoteException e) {
Log.e(TAG, "error in getDeclaredInstances", e);
- return null;
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 6091bf9..bf72b1d 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.vibrator.IVibrator;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Range;
@@ -313,8 +314,14 @@
private static final float EPSILON = 1e-5f;
public MultiVibratorInfo(VibratorInfo[] vibrators) {
+ // Need to use an extra constructor to share the computation in super initialization.
+ this(vibrators, frequencyProfileIntersection(vibrators));
+ }
+
+ private MultiVibratorInfo(VibratorInfo[] vibrators,
+ VibratorInfo.FrequencyProfile mergedProfile) {
super(/* id= */ -1,
- capabilitiesIntersection(vibrators),
+ capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
supportedEffectsIntersection(vibrators),
supportedBrakingIntersection(vibrators),
supportedPrimitivesAndDurationsIntersection(vibrators),
@@ -323,14 +330,19 @@
integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
- frequencyProfileIntersection(vibrators));
+ mergedProfile);
}
- private static int capabilitiesIntersection(VibratorInfo[] infos) {
+ private static int capabilitiesIntersection(VibratorInfo[] infos,
+ boolean frequencyProfileIsEmpty) {
int intersection = ~0;
for (VibratorInfo info : infos) {
intersection &= info.getCapabilities();
}
+ if (frequencyProfileIsEmpty) {
+ // Revoke frequency control if the merged frequency profile ended up empty.
+ intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
+ }
return intersection;
}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 8bfa0e9..fb197f5 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -100,6 +100,7 @@
/** @hide */
public static final long TRACE_TAG_VIBRATOR = 1L << 23;
/** @hide */
+ @SystemApi(client = MODULE_LIBRARIES)
public static final long TRACE_TAG_AIDL = 1L << 24;
/** @hide */
public static final long TRACE_TAG_NNAPI = 1L << 25;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a38d9da..1f21bfe 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1506,6 +1506,30 @@
public static final String DISALLOW_CELLULAR_2G = "no_cellular_2g";
/**
+ * This user restriction specifies if Ultra-wideband is disallowed on the device. If
+ * Ultra-wideband is disallowed it cannot be turned on via Settings.
+ *
+ * <p>This restriction can only be set by a device owner or a profile owner of an
+ * organization-owned managed profile on the parent profile.
+ * In both cases, the restriction applies globally on the device and will turn off the
+ * ultra-wideband radio if it's currently on and prevent the radio from being turned on in
+ * the future.
+ *
+ * <p>
+ * Ultra-wideband (UWB) is a radio technology that can use a very low energy level
+ * for short-range, high-bandwidth communications over a large portion of the radio spectrum.
+ *
+ * <p>Default is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
+
+ /**
* List of key values that can be passed into the various user restriction related methods
* in {@link UserManager} & {@link DevicePolicyManager}.
* Note: This is slightly different from the real set of user restrictions listed in {@link
@@ -1587,6 +1611,7 @@
DISALLOW_WIFI_DIRECT,
DISALLOW_ADD_WIFI_CONFIG,
DISALLOW_CELLULAR_2G,
+ DISALLOW_ULTRA_WIDEBAND_RADIO,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserRestrictionKey {}
@@ -1612,6 +1637,16 @@
/** @hide */
public static final String SYSTEM_USER_MODE_EMULATION_HEADLESS = "headless";
+ /**
+ * System Property used to override whether users can be created even if their type is disabled
+ * or their limit is reached. Set value to 1 to enable.
+ *
+ * <p>Only used on non-user builds.
+ *
+ * @hide
+ */
+ public static final String DEV_CREATE_OVERRIDE_PROPERTY = "debug.user.creation_override";
+
private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
/**
@@ -2306,12 +2341,18 @@
}
/**
- * Used to check if the context user is the primary user. The primary user
- * is the first human user on a device. This is not supported in headless system user mode.
+ * Used to check if the context user is the primary user. The primary user is the first human
+ * user on a device. This is not supported in headless system user mode.
*
* @return whether the context user is the primary user.
+ *
+ * @deprecated This method always returns true for the system user, who may not be a full user
+ * if {@link #isHeadlessSystemUserMode} is true. Use {@link #isSystemUser}, {@link #isAdminUser}
+ * or {@link #isMainUser} instead.
+ *
* @hide
*/
+ @Deprecated
@SystemApi
@RequiresPermission(anyOf = {
Manifest.permission.MANAGE_USERS,
@@ -2336,6 +2377,29 @@
}
/**
+ * Returns true if the context user is the designated "main user" of the device. This user may
+ * have access to certain features which are limited to at most one user.
+ *
+ * <p>Currently, the first human user on the device will be the main user; in the future, the
+ * concept may be transferable, so a different user (or even no user at all) may be designated
+ * the main user instead.
+ *
+ * <p>Note that this will be the not be the system user on devices for which
+ * {@link #isHeadlessSystemUserMode()} returns true.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ @UserHandleAware
+ public boolean isMainUser() {
+ final UserInfo user = getUserInfo(mUserId);
+ return user != null && user.isMain();
+ }
+
+ /**
* Used to check if the context user is an admin user. An admin user is allowed to
* modify or configure certain settings that aren't available to non-admin users,
* create and delete additional users, etc. There can be more than one admin users.
@@ -4357,6 +4421,7 @@
* @return true if the creation of users of the given user type is enabled on this device.
* @hide
*/
+ @TestApi
@RequiresPermission(anyOf = {
android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5865437..52b1adb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -192,6 +192,21 @@
"android.settings.LOCATION_SCANNING_SETTINGS";
/**
+ * Activity Action: Show settings to manage creation/deletion of cloned apps.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_CLONED_APPS_SETTINGS =
+ "android.settings.MANAGE_CLONED_APPS_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of users.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -10416,11 +10431,11 @@
public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
/**
- * The duration of timeout, in milliseconds, to switch from a non-primary user to the
- * primary user when the device is docked.
+ * The duration of timeout, in milliseconds, to switch from a non-Dock User to the
+ * Dock User when the device is docked.
* @hide
*/
- public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero";
+ public static final String TIMEOUT_TO_DOCK_USER = "timeout_to_dock_user";
/**
* Backup manager behavioral parameters.
@@ -18659,6 +18674,9 @@
/**
* Activity Action: For system or preinstalled apps to show their {@link Activity} embedded
* in Settings app on large screen devices.
+ *
+ * Developers should resolve the Intent action before using it.
+ *
* <p>
* Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to
* specify the intent for the activity which will be embedded in Settings app.
diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java
index 2c382ef..c78fb1a 100644
--- a/core/java/android/security/keymaster/ExportResult.java
+++ b/core/java/android/security/keymaster/ExportResult.java
@@ -61,4 +61,4 @@
out.writeInt(resultCode);
out.writeByteArray(exportData);
}
-};
+}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
index e330d1e..f69dca8 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -33,13 +33,11 @@
* @hide
*/
public final class CreateCredentialResponse implements Parcelable {
- private final @Nullable CharSequence mHeader;
private final @NonNull List<SaveEntry> mSaveEntries;
private final @Nullable Action mRemoteSaveEntry;
//TODO : Add actions if needed
private CreateCredentialResponse(@NonNull Parcel in) {
- mHeader = in.readCharSequence();
List<SaveEntry> saveEntries = new ArrayList<>();
in.readTypedList(saveEntries, SaveEntry.CREATOR);
mSaveEntries = saveEntries;
@@ -48,7 +46,6 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeCharSequence(mHeader);
dest.writeTypedList(mSaveEntries);
dest.writeTypedObject(mRemoteSaveEntry, flags);
}
@@ -72,21 +69,14 @@
};
/* package-private */ CreateCredentialResponse(
- @Nullable CharSequence header,
@NonNull List<SaveEntry> saveEntries,
@Nullable Action remoteSaveEntry) {
- this.mHeader = header;
this.mSaveEntries = saveEntries;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mSaveEntries);
this.mRemoteSaveEntry = remoteSaveEntry;
}
- /** Returns the header to be displayed on the UI. */
- public @Nullable CharSequence getHeader() {
- return mHeader;
- }
-
/** Returns the list of save entries to be displayed on the UI. */
public @NonNull List<SaveEntry> getSaveEntries() {
return mSaveEntries;
@@ -102,17 +92,9 @@
*/
@SuppressWarnings("WeakerAccess")
public static final class Builder {
-
- private @Nullable CharSequence mHeader;
private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
private @Nullable Action mRemoteSaveEntry;
- /** Sets the header to be displayed on the UI. */
- public @NonNull Builder setHeader(@Nullable CharSequence header) {
- mHeader = header;
- return this;
- }
-
/**
* Sets the list of save entries to be shown on the UI.
*
@@ -154,7 +136,6 @@
Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
+ "not be empty");
return new CreateCredentialResponse(
- mHeader,
mSaveEntries,
mRemoteSaveEntry);
}
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 1d4ac25..98c537a 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -173,7 +173,7 @@
*/
public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
if (pendingIntent != null) {
- Preconditions.checkState(mCredential != null,
+ Preconditions.checkState(mCredential == null,
"credential is already set. Cannot set both the pendingIntent "
+ "and the credential");
}
@@ -189,7 +189,7 @@
*/
public @NonNull Builder setCredential(@Nullable Credential credential) {
if (credential != null) {
- Preconditions.checkState(mPendingIntent != null,
+ Preconditions.checkState(mPendingIntent == null,
"pendingIntent is already set. Cannot set both the "
+ "pendingIntent and the credential");
}
@@ -215,10 +215,10 @@
* is set, or if both are set.
*/
public @NonNull CredentialEntry build() {
- Preconditions.checkState(mPendingIntent == null && mCredential == null,
- "Either pendingIntent or credential must be set");
- Preconditions.checkState(mPendingIntent != null && mCredential != null,
- "Cannot set both the pendingIntent and credential");
+ Preconditions.checkState(((mPendingIntent != null && mCredential == null)
+ || (mPendingIntent == null && mCredential != null)),
+ "Either pendingIntent or credential must be set, and both cannot"
+ + "be set at the same time");
return new CredentialEntry(mType, mSlice, mPendingIntent,
mCredential, mAutoSelectAllowed);
}
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java
index 2c7a983..f89ad8e 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfo.java
@@ -24,7 +24,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -96,6 +95,8 @@
mLabel = mServiceInfo.loadSafeLabel(
mContext.getPackageManager(), 0 /* do not ellipsize */,
TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
+ Log.i(TAG, "mLabel is : " + mLabel + ", for: " + mServiceInfo.getComponentName()
+ .flattenToString());
populateProviderCapabilities(context, serviceInfo);
}
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index b1b08f4..6f3e786 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -41,6 +41,18 @@
* @hide
*/
public abstract class CredentialProviderService extends Service {
+ /** Extra to be used by provider to populate the credential when ending the activity started
+ * through the {@code pendingIntent} on the selected {@link SaveEntry}. **/
+ public static final String EXTRA_SAVE_CREDENTIAL =
+ "android.service.credentials.extra.SAVE_CREDENTIAL";
+
+ /**
+ * Provider must read the value against this extra to receive the complete create credential
+ * request parameters, when a pending intent is launched.
+ */
+ public static final String EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS =
+ "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST_PARAMS";
+
private static final String TAG = "CredProviderService";
public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
@@ -64,7 +76,7 @@
}
@Override
- public final @NonNull IBinder onBind(@NonNull Intent intent) {
+ @NonNull public final IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
return mInterface.asBinder();
}
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
index ab5b524..4b23800 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -34,9 +34,6 @@
* @hide
*/
public final class CredentialsDisplayContent implements Parcelable {
- /** Header to be displayed on the UI. */
- private final @Nullable CharSequence mHeader;
-
/** List of credential entries to be displayed on the UI. */
private final @NonNull List<CredentialEntry> mCredentialEntries;
@@ -46,18 +43,15 @@
/** Remote credential entry to get the response from a different device. */
private final @Nullable Action mRemoteCredentialEntry;
- private CredentialsDisplayContent(@Nullable CharSequence header,
- @NonNull List<CredentialEntry> credentialEntries,
+ private CredentialsDisplayContent(@NonNull List<CredentialEntry> credentialEntries,
@NonNull List<Action> actions,
@Nullable Action remoteCredentialEntry) {
- mHeader = header;
mCredentialEntries = credentialEntries;
mActions = actions;
mRemoteCredentialEntry = remoteCredentialEntry;
}
private CredentialsDisplayContent(@NonNull Parcel in) {
- mHeader = in.readCharSequence();
List<CredentialEntry> credentialEntries = new ArrayList<>();
in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
mCredentialEntries = credentialEntries;
@@ -87,20 +81,12 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeCharSequence(mHeader);
dest.writeTypedList(mCredentialEntries, flags);
dest.writeTypedList(mActions, flags);
dest.writeTypedObject(mRemoteCredentialEntry, flags);
}
/**
- * Returns the header to be displayed on the UI.
- */
- public @Nullable CharSequence getHeader() {
- return mHeader;
- }
-
- /**
* Returns the list of credential entries to be displayed on the UI.
*/
public @NonNull List<CredentialEntry> getCredentialEntries() {
@@ -125,20 +111,11 @@
* Builds an instance of {@link CredentialsDisplayContent}.
*/
public static final class Builder {
- private CharSequence mHeader;
private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
private List<Action> mActions = new ArrayList<>();
private Action mRemoteCredentialEntry;
/**
- * Sets the header to be displayed on the UI.
- */
- public @NonNull Builder setHeader(@Nullable CharSequence header) {
- mHeader = header;
- return this;
- }
-
- /**
* Sets the remote credential entry to be displayed on the UI.
*/
public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
@@ -208,7 +185,7 @@
throw new IllegalStateException("credentialEntries and actions must not both "
+ "be empty");
}
- return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions,
+ return new CredentialsDisplayContent(mCredentialEntries, mActions,
mRemoteCredentialEntry);
}
}
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
index e06be44..03ba20e 100644
--- a/core/java/android/service/credentials/GetCredentialsRequest.java
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -119,9 +119,9 @@
*/
public @NonNull Builder setGetCredentialOptions(
@NonNull List<GetCredentialOption> getCredentialOptions) {
- Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
+ Preconditions.checkCollectionNotEmpty(getCredentialOptions,
"getCredentialOptions");
- Preconditions.checkCollectionElementsNotNull(mGetCredentialOptions,
+ Preconditions.checkCollectionElementsNotNull(getCredentialOptions,
"getCredentialOptions");
mGetCredentialOptions = getCredentialOptions;
return this;
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index aa45c20..6e8198b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -49,6 +49,17 @@
mShowComplications = shouldShowComplications;
onStartDream(layoutParams);
}
+
+ @Override
+ public void wakeUp() {
+ onWakeUp(() -> {
+ try {
+ mDreamOverlayCallback.onWakeUpComplete();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not notify dream of wakeUp:" + e);
+ }
+ });
+ }
};
IDreamOverlayCallback mDreamOverlayCallback;
@@ -71,6 +82,17 @@
public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams);
/**
+ * This method is overridden by implementations to handle when the dream has been requested
+ * to wakeup. This allows any overlay animations to run.
+ *
+ * @param onCompleteCallback The callback to trigger to notify the dream service that the
+ * overlay has completed waking up.
+ * @hide
+ */
+ public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+ }
+
+ /**
* This method is invoked to request the dream exit.
*/
public final void requestExit() {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 32bdf79..8b9852a 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -312,7 +312,14 @@
@Override
public void onExitRequested() {
// Simply finish dream when exit is requested.
- finish();
+ mHandler.post(() -> finish());
+ }
+
+ @Override
+ public void onWakeUpComplete() {
+ // Finish the dream once overlay animations are complete. Execute on handler since
+ // this is coming in on the overlay binder.
+ mHandler.post(() -> finish());
}
};
@@ -975,7 +982,18 @@
* </p>
*/
public void onWakeUp() {
- finish();
+ if (mOverlayConnection != null) {
+ mOverlayConnection.addConsumer(overlay -> {
+ try {
+ overlay.wakeUp();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error waking the overlay service", e);
+ finish();
+ }
+ });
+ } else {
+ finish();
+ }
}
/** {@inheritDoc} */
@@ -1294,7 +1312,7 @@
if (!mWindowless) {
Intent i = new Intent(this, DreamActivity.class);
i.setPackage(getApplicationContext().getPackageName());
- i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
final ServiceInfo serviceInfo = fetchServiceInfo(this,
new ComponentName(this, getClass()));
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 05ebbfe..7aeceb2c 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -38,4 +38,7 @@
*/
void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
in String dreamComponent, in boolean shouldShowComplications);
+
+ /** Called when the dream is waking, to do any exit animations */
+ void wakeUp();
}
diff --git a/core/java/android/service/dreams/IDreamOverlayCallback.aidl b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
index ec76a33..4ad63f1 100644
--- a/core/java/android/service/dreams/IDreamOverlayCallback.aidl
+++ b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
@@ -28,4 +28,7 @@
* Invoked to request the dream exit.
*/
void onExitRequested();
+
+ /** Invoked when the dream overlay wakeUp animation is complete. */
+ void onWakeUpComplete();
}
\ No newline at end of file
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index 2cea95a..41ca94b 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -44,8 +44,8 @@
*
* <p>Once started, providers are expected to detect the time zone if possible, and report the
* result via {@link #reportSuggestion(TimeZoneProviderSuggestion)} or {@link
- * #reportUncertain()}. Providers may also report that they have permanently failed
- * by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
+ * #reportUncertain(TimeZoneProviderStatus)}. Providers may also report that they have permanently
+ * failed by calling {@link #reportPermanentFailure(Throwable)}. See the javadocs for each
* method for details.
*
* <p>After starting, providers are expected to issue their first callback within the timeout
@@ -213,8 +213,6 @@
*
* @param providerStatus provider status information that can influence detector service
* behavior and/or be reported via the device UI
- *
- * @hide
*/
public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion,
@NonNull TimeZoneProviderStatus providerStatus) {
@@ -248,8 +246,9 @@
/**
* Indicates the time zone is not known because of an expected runtime state or error, e.g. when
- * the provider is unable to detect location, or there was a problem when resolving the location
- * to a time zone.
+ * the provider is unable to detect location, or there was connectivity issue.
+ *
+ * <p>See {@link #reportUncertain(TimeZoneProviderStatus)} for a more expressive version
*/
public final void reportUncertain() {
TimeZoneProviderStatus providerStatus = null;
@@ -264,8 +263,6 @@
*
* @param providerStatus provider status information that can influence detector service
* behavior and/or be reported via the device UI
- *
- * @hide
*/
public final void reportUncertain(@NonNull TimeZoneProviderStatus providerStatus) {
Objects.requireNonNull(providerStatus);
@@ -362,8 +359,8 @@
* <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android
* system server holds the latest report from the provider in memory. After an initial report,
* provider implementations are only required to send a report via {@link
- * #reportSuggestion(TimeZoneProviderSuggestion)} or via {@link #reportUncertain()} when it
- * differs from the previous report.
+ * #reportSuggestion(TimeZoneProviderSuggestion, TimeZoneProviderStatus)} or via {@link
+ * #reportUncertain(TimeZoneProviderStatus)} when it differs from the previous report.
*
* <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations
* in rare cases, after which the provider should consider itself stopped and not make any
@@ -375,7 +372,8 @@
* Android system server may move on to use other providers or detection methods. Providers
* should therefore make best efforts during this time to generate a report, which could involve
* increased power usage. Providers should preferably report an explicit {@link
- * #reportUncertain()} if the time zone(s) cannot be detected within the initialization timeout.
+ * #reportUncertain(TimeZoneProviderStatus)} if the time zone(s) cannot be detected within the
+ * initialization timeout.
*
* @see #onStopUpdates() for the signal from the system server to stop sending reports
*/
diff --git a/core/java/android/service/timezone/TimeZoneProviderStatus.java b/core/java/android/service/timezone/TimeZoneProviderStatus.java
index 513068f..e0b78e9 100644
--- a/core/java/android/service/timezone/TimeZoneProviderStatus.java
+++ b/core/java/android/service/timezone/TimeZoneProviderStatus.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -65,6 +66,7 @@
*
* @hide
*/
+@SystemApi
public final class TimeZoneProviderStatus implements Parcelable {
/**
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index b4f20c9..dee560b 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -31,6 +31,8 @@
import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -99,14 +101,30 @@
private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;
/**
- * The bundle key for proximity value
+ * The bundle key for proximity
*
* TODO(b/238896013): Move the proximity logic out of bundle to proper API.
- *
- * @hide
*/
- public static final String EXTRA_PROXIMITY_METERS =
- "android.service.voice.extra.PROXIMITY_METERS";
+ private static final String EXTRA_PROXIMITY =
+ "android.service.voice.extra.PROXIMITY";
+
+ /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */
+ public static final int PROXIMITY_UNKNOWN = -1;
+
+ /** Proximity value that represents that the object is near. */
+ public static final int PROXIMITY_NEAR = 1;
+
+ /** Proximity value that represents that the object is far. */
+ public static final int PROXIMITY_FAR = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"PROXIMITY"}, value = {
+ PROXIMITY_UNKNOWN,
+ PROXIMITY_NEAR,
+ PROXIMITY_FAR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProximityValue {}
/** Confidence level in the trigger outcome. */
@HotwordConfidenceLevelValue
@@ -220,12 +238,14 @@
* versions of Android.
*
* <p>After the trigger happens, a special case of proximity-related extra, with the key of
- * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
- * will be stored to enable proximity logic. The proximity meters is provided by the system,
- * on devices that support detecting proximity of nearby users, to help disambiguate which
- * nearby device should respond. When the proximity is unknown, the proximity value will not
- * be stored. This mapping will be excluded from the max bundle size calculation because this
- * mapping is included after the result is returned from the hotword detector service.
+ * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+ * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+ * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+ * proximity. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+ * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+ * from the max bundle size calculation because this mapping is included after the result is
+ * returned from the hotword detector service.
*
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
@@ -348,16 +368,16 @@
// Remove the proximity key from the bundle before checking the bundle size. The
// proximity value is added after the privileged module and can avoid the
// maxBundleSize limitation.
- if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
- double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
- mExtras.remove(EXTRA_PROXIMITY_METERS);
+ if (mExtras.containsKey(EXTRA_PROXIMITY)) {
+ int proximityValue = mExtras.getInt(EXTRA_PROXIMITY);
+ mExtras.remove(EXTRA_PROXIMITY);
// Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
// has parcelable size of 4, but the default bundle has parcelable size of 0.
if (mExtras.size() > 0) {
Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
getMaxBundleSize(), "extras");
}
- mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
+ mExtras.putInt(EXTRA_PROXIMITY, proximityValue);
} else {
Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
getMaxBundleSize(), "extras");
@@ -372,6 +392,52 @@
return List.copyOf(mAudioStreams);
}
+ /**
+ * Adds proximity level, either near or far, that is mapped for the given distance into
+ * the bundle. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond.
+ * This mapping will be excluded from the max bundle size calculation because this mapping is
+ * included after the result is returned from the hotword detector service. The value will not
+ * be included if the proximity was unknown.
+ *
+ * @hide
+ */
+ public void setProximity(double distance) {
+ int proximityLevel = convertToProximityLevel(distance);
+ if (proximityLevel != PROXIMITY_UNKNOWN) {
+ mExtras.putInt(EXTRA_PROXIMITY, proximityLevel);
+ }
+ }
+
+ /**
+ * Returns proximity level, which can be either of {@link HotwordDetectedResult#PROXIMITY_NEAR}
+ * or {@link HotwordDetectedResult#PROXIMITY_FAR}. If the proximity is unknown, it will
+ * return {@link HotwordDetectedResult#PROXIMITY_UNKNOWN}.
+ */
+ @ProximityValue
+ public int getProximity() {
+ return mExtras.getInt(EXTRA_PROXIMITY, PROXIMITY_UNKNOWN);
+ }
+
+ /**
+ * Mapping of the proximity distance (meters) to proximity values, unknown, near, and far.
+ * Currently, this mapping is handled by HotwordDetectedResult because it handles just
+ * HotwordDetectionConnection which we know the mapping of. However, the mapping will need to
+ * move to a more centralized place once there are more clients.
+ *
+ * TODO(b/258531144): Move the proximity mapping to a central location
+ */
+ @ProximityValue
+ private int convertToProximityLevel(double distance) {
+ if (distance < 0) {
+ return PROXIMITY_UNKNOWN;
+ } else if (distance <= 3) {
+ return PROXIMITY_NEAR;
+ } else {
+ return PROXIMITY_FAR;
+ }
+ }
+
@DataClass.Suppress("addAudioStreams")
abstract static class BaseBuilder {
/**
@@ -432,7 +498,7 @@
CONFIDENCE_LEVEL_HIGH,
CONFIDENCE_LEVEL_VERY_HIGH
})
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
public @interface ConfidenceLevel {}
@@ -463,7 +529,7 @@
LIMIT_HOTWORD_OFFSET_MAX_VALUE,
LIMIT_AUDIO_CHANNEL_MAX_VALUE
})
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
/* package-private */ @interface Limit {}
@@ -479,6 +545,30 @@
}
}
+ /** @hide */
+ @IntDef(prefix = "PROXIMITY_", value = {
+ PROXIMITY_UNKNOWN,
+ PROXIMITY_NEAR,
+ PROXIMITY_FAR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Proximity {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String proximityToString(@Proximity int value) {
+ switch (value) {
+ case PROXIMITY_UNKNOWN:
+ return "PROXIMITY_UNKNOWN";
+ case PROXIMITY_NEAR:
+ return "PROXIMITY_NEAR";
+ case PROXIMITY_FAR:
+ return "PROXIMITY_FAR";
+ default: return Integer.toHexString(value);
+ }
+ }
+
@DataClass.Generated.Member
/* package-private */ HotwordDetectedResult(
@HotwordConfidenceLevelValue int confidenceLevel,
@@ -605,12 +695,14 @@
* versions of Android.
*
* <p>After the trigger happens, a special case of proximity-related extra, with the key of
- * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
- * will be stored to enable proximity logic. The proximity meters is provided by the system,
- * on devices that support detecting proximity of nearby users, to help disambiguate which
- * nearby device should respond. When the proximity is unknown, the proximity value will not
- * be stored. This mapping will be excluded from the max bundle size calculation because this
- * mapping is included after the result is returned from the hotword detector service.
+ * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+ * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+ * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+ * proximity. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+ * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+ * from the max bundle size calculation because this mapping is included after the result is
+ * returned from the hotword detector service.
*
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
@@ -923,12 +1015,14 @@
* versions of Android.
*
* <p>After the trigger happens, a special case of proximity-related extra, with the key of
- * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
- * will be stored to enable proximity logic. The proximity meters is provided by the system,
- * on devices that support detecting proximity of nearby users, to help disambiguate which
- * nearby device should respond. When the proximity is unknown, the proximity value will not
- * be stored. This mapping will be excluded from the max bundle size calculation because this
- * mapping is included after the result is returned from the hotword detector service.
+ * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+ * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+ * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+ * proximity. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+ * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+ * from the max bundle size calculation because this mapping is included after the result is
+ * returned from the hotword detector service.
*
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
@@ -1003,10 +1097,10 @@
}
@DataClass.Generated(
- time = 1666342044844L,
+ time = 1668385264834L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
- inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index df69cc0..552a793 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -25,6 +25,7 @@
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.Service;
import android.content.ContentCaptureOptions;
import android.content.Context;
@@ -90,10 +91,10 @@
/**
* Feature flag for Attention Service.
*
- * TODO(b/247920386): Add TestApi annotation
* @hide
*/
- public static final boolean ENABLE_PROXIMITY_RESULT = false;
+ @TestApi
+ public static final boolean ENABLE_PROXIMITY_RESULT = true;
/**
* Indicates that the updated status is successful.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 104570d0..007478a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -31,6 +31,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.FloatRange;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -657,6 +658,7 @@
* Called once to initialize the engine. After returning, the
* engine's surface will be created by the framework.
*/
+ @MainThread
public void onCreate(SurfaceHolder surfaceHolder) {
}
@@ -665,6 +667,7 @@
* surface will be destroyed and this Engine object is no longer
* valid.
*/
+ @MainThread
public void onDestroy() {
}
@@ -673,6 +676,7 @@
* hidden. <em>It is very important that a wallpaper only use
* CPU while it is visible.</em>.
*/
+ @MainThread
public void onVisibilityChanged(boolean visible) {
}
@@ -683,6 +687,7 @@
*
* @param insets Insets to apply.
*/
+ @MainThread
public void onApplyWindowInsets(WindowInsets insets) {
}
@@ -693,6 +698,7 @@
* user is interacting with, so if it is slow you will get fewer
* move events.
*/
+ @MainThread
public void onTouchEvent(MotionEvent event) {
}
@@ -702,6 +708,7 @@
* call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float)
* WallpaperManager.setWallpaperOffsets()}.
*/
+ @MainThread
public void onOffsetsChanged(float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep,
int xPixelOffset, int yPixelOffset) {
@@ -724,6 +731,7 @@
* @return If returning a result, create a Bundle and place the
* result data in to it. Otherwise return null.
*/
+ @MainThread
public Bundle onCommand(String action, int x, int y, int z,
Bundle extras, boolean resultRequested) {
return null;
@@ -742,6 +750,7 @@
* @hide
*/
@SystemApi
+ @MainThread
public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
}
@@ -749,6 +758,7 @@
* Called when an application has changed the desired virtual size of
* the wallpaper.
*/
+ @MainThread
public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
}
@@ -756,6 +766,7 @@
* Convenience for {@link SurfaceHolder.Callback#surfaceChanged
* SurfaceHolder.Callback.surfaceChanged()}.
*/
+ @MainThread
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@@ -763,6 +774,7 @@
* Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded
* SurfaceHolder.Callback.surfaceRedrawNeeded()}.
*/
+ @MainThread
public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
}
@@ -770,6 +782,7 @@
* Convenience for {@link SurfaceHolder.Callback#surfaceCreated
* SurfaceHolder.Callback.surfaceCreated()}.
*/
+ @MainThread
public void onSurfaceCreated(SurfaceHolder holder) {
}
@@ -777,6 +790,7 @@
* Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed
* SurfaceHolder.Callback.surfaceDestroyed()}.
*/
+ @MainThread
public void onSurfaceDestroyed(SurfaceHolder holder) {
}
@@ -787,6 +801,7 @@
* @param zoom the zoom level, between 0 indicating fully zoomed in and 1 indicating fully
* zoomed out.
*/
+ @MainThread
public void onZoomChanged(@FloatRange(from = 0f, to = 1f) float zoom) {
}
@@ -836,6 +851,7 @@
*
* @return Wallpaper colors.
*/
+ @MainThread
public @Nullable WallpaperColors onComputeColors() {
return null;
}
@@ -2510,6 +2526,7 @@
* when the wallpaper is currently set as the active wallpaper and the user
* is in the wallpaper picker viewing a preview of it as well.
*/
+ @MainThread
public abstract Engine onCreateEngine();
@Override
diff --git a/core/java/android/text/style/AccessibilityURLSpan.java b/core/java/android/text/style/AccessibilityURLSpan.java
index bd81623..e280bdf 100644
--- a/core/java/android/text/style/AccessibilityURLSpan.java
+++ b/core/java/android/text/style/AccessibilityURLSpan.java
@@ -26,6 +26,7 @@
* It is used to replace URLSpans in {@link AccessibilityNodeInfo#setText(CharSequence)}
* @hide
*/
+@SuppressWarnings("ParcelableCreator")
public class AccessibilityURLSpan extends URLSpan implements Parcelable {
final AccessibilityClickableSpan mAccessibilityClickableSpan;
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index 85b7ae9..d61228b 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -149,7 +149,7 @@
}
mTextFontWeight = a.getInt(com.android.internal.R.styleable
- .TextAppearance_textFontWeight, -1);
+ .TextAppearance_textFontWeight, /*defValue*/ FontStyle.FONT_WEIGHT_UNSPECIFIED);
final String localeString = a.getString(com.android.internal.R.styleable
.TextAppearance_textLocale);
@@ -215,7 +215,7 @@
mTextColorLink = linkColor;
mTypeface = null;
- mTextFontWeight = -1;
+ mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
mTextLocales = null;
mShadowRadius = 0.0f;
@@ -359,8 +359,8 @@
}
/**
- * Returns the text font weight specified by this span, or <code>-1</code>
- * if it does not specify one.
+ * Returns the text font weight specified by this span, or
+ * <code>FontStyle.FONT_WEIGHT_UNSPECIFIED</code> if it does not specify one.
*/
public int getTextFontWeight() {
return mTextFontWeight;
diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java
index 1345ddf..d1b9d9f 100644
--- a/core/java/android/util/AndroidException.java
+++ b/core/java/android/util/AndroidException.java
@@ -40,5 +40,5 @@
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
-};
+}
diff --git a/core/java/android/util/AndroidRuntimeException.java b/core/java/android/util/AndroidRuntimeException.java
index 2b824bf..72c34d8b 100644
--- a/core/java/android/util/AndroidRuntimeException.java
+++ b/core/java/android/util/AndroidRuntimeException.java
@@ -34,5 +34,4 @@
public AndroidRuntimeException(Exception cause) {
super(cause);
}
-};
-
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 7b6a6d2..4afd268 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -132,6 +132,12 @@
*/
public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer";
+ /**
+ * Flag to enable/disable biometrics enrollment v2
+ * @hide
+ */
+ public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
+
private static final Map<String, String> DEFAULT_FLAGS;
static {
@@ -167,6 +173,7 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
+ DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS
index d4cf6e6..3772006 100644
--- a/core/java/android/util/OWNERS
+++ b/core/java/android/util/OWNERS
@@ -1,6 +1,6 @@
per-file Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
per-file FeatureFlagUtils.java = sbasi@google.com
-per-file FeatureFlagUtils.java = tmfang@google.com
+per-file FeatureFlagUtils.java = edgarwang@google.com
per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS
per-file TypedValue.java = file:/core/java/android/content/res/OWNERS
diff --git a/core/java/android/util/Range.java b/core/java/android/util/Range.java
index 9fd0ab9..41c171a 100644
--- a/core/java/android/util/Range.java
+++ b/core/java/android/util/Range.java
@@ -356,4 +356,4 @@
private final T mLower;
private final T mUpper;
-};
+}
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 19de396..44318bb 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -696,5 +696,5 @@
sb.append("}");
return sb.toString();
}
-};
+}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 510bde1..44168ca 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -21,6 +21,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+import android.accessibilityservice.AccessibilityService;
import android.annotation.NonNull;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -46,11 +47,13 @@
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityRequestPreparer;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.function.pooled.PooledLambda;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -588,6 +591,43 @@
}
}
+ /**
+ * Take a screenshot using {@link ScreenCapture} of this {@link ViewRootImpl}'s {@link
+ * SurfaceControl}.
+ */
+ public void takeScreenshotOfWindowClientThread(int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) {
+ Message message = PooledLambda.obtainMessage(
+ AccessibilityInteractionController::takeScreenshotOfWindowUiThread,
+ this, interactionId, listener, callback);
+
+ // Screenshot results are returned to the service asynchronously, so the same-thread
+ // message wait logic from #scheduleMessage() is not needed.
+ mHandler.sendMessage(message);
+ }
+
+ private void takeScreenshotOfWindowUiThread(int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) {
+ try {
+ if ((mViewRootImpl.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_SECURE_WINDOW, interactionId);
+ return;
+ }
+ final ScreenCapture.LayerCaptureArgs captureArgs =
+ new ScreenCapture.LayerCaptureArgs.Builder(mViewRootImpl.getSurfaceControl())
+ .setChildrenOnly(false).setUid(Process.myUid()).build();
+ if (ScreenCapture.captureLayers(captureArgs, listener) != 0) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+ }
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+
public void findFocusClientThread(long accessibilityNodeId, int focusType,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
diff --git a/core/java/android/view/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java
index f8aa934..3fc3b6a 100644
--- a/core/java/android/view/CutoutSpecification.java
+++ b/core/java/android/view/CutoutSpecification.java
@@ -394,7 +394,6 @@
Log.e(TAG, "According to SVG definition, it shouldn't happen");
return;
}
- spec.trim();
translateMatrix();
final Path newPath = PathParser.createPathFromPathData(spec);
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 0769f12..91270d4 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -19,6 +19,7 @@
import android.content.ComponentName;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.inputmethod.ImeTracker;
/**
* Singular controller of insets to use when there isn't another obvious controller available.
@@ -48,10 +49,10 @@
/**
* @see IWindow#showInsets
*/
- void showInsets(int types, boolean fromIme);
+ void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
/**
* @see IWindow#hideInsets
*/
- void hideInsets(int types, boolean fromIme);
+ void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index a856474..8e16f24 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -29,6 +29,7 @@
import android.view.IScrollCaptureResponseListener;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -68,16 +69,18 @@
*
* @param types internal insets types (WindowInsets.Type.InsetsType) to show
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*/
- void showInsets(int types, boolean fromIme);
+ void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
/**
* Called when a set of insets source window should be hidden by policy.
*
* @param types internal insets types (WindowInsets.Type.InsetsType) to hide
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
*/
- void hideInsets(int types, boolean fromIme);
+ void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
void moved(int newX, int newY);
void dispatchAppVisibility(boolean visible);
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 27b4d87..e775969 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -58,6 +58,7 @@
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowManager.LayoutParams;
import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.VisibleForTesting;
@@ -68,7 +69,7 @@
* Implements {@link WindowInsetsAnimationController}
* @hide
*/
-@VisibleForTesting
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class InsetsAnimationControlImpl implements InternalInsetsAnimationController,
InsetsAnimationControlRunner {
@@ -96,6 +97,8 @@
/** @see WindowInsetsAnimationController#hasZeroInsetsIme */
private final boolean mHasZeroInsetsIme;
private final CompatibilityInfo.Translator mTranslator;
+ @Nullable
+ private final ImeTracker.Token mStatsToken;
private Insets mCurrentInsets;
private Insets mPendingInsets;
private float mPendingFraction;
@@ -114,7 +117,7 @@
@InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
Interpolator interpolator, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
- CompatibilityInfo.Translator translator) {
+ CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
mControls = controls;
mListener = listener;
mTypes = types;
@@ -152,6 +155,7 @@
mAnimationType = animationType;
mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
mTranslator = translator;
+ mStatsToken = statsToken;
mController.startAnimation(this, listener, types, mAnimation,
new Bounds(mHiddenInsets, mShownInsets));
}
@@ -228,6 +232,11 @@
}
@Override
+ public ImeTracker.Token getStatsToken() {
+ return mStatsToken;
+ }
+
+ @Override
public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */);
}
@@ -253,10 +262,10 @@
}
}
- @VisibleForTesting
/**
* @return Whether the finish callback of this animation should be invoked.
*/
+ @VisibleForTesting
public boolean applyChangeInsets(@Nullable InsetsState outState) {
if (mCancelled) {
if (DEBUG) Log.d(TAG, "applyChangeInsets canceled");
diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java
index 291351e..cf40e7e 100644
--- a/core/java/android/view/InsetsAnimationControlRunner.java
+++ b/core/java/android/view/InsetsAnimationControlRunner.java
@@ -16,10 +16,12 @@
package android.view;
+import android.annotation.Nullable;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsController.AnimationType;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
/**
* Interface representing a runner for an insets animation.
@@ -74,6 +76,12 @@
@AnimationType int getAnimationType();
/**
+ * @return The token tracking the current IME request or {@code null} otherwise.
+ */
+ @Nullable
+ ImeTracker.Token getStatsToken();
+
+ /**
*
* Export the state of classes that implement this interface into a protocol buffer
* output stream.
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index fc97541..f7b9aa2 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -34,6 +34,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
/**
* Insets animation runner that uses {@link InsetsAnimationThread} to run the animation off from the
@@ -112,12 +113,13 @@
@InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
Interpolator interpolator, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
- CompatibilityInfo.Translator translator, Handler mainThreadHandler) {
+ CompatibilityInfo.Translator translator, Handler mainThreadHandler,
+ @Nullable ImeTracker.Token statsToken) {
mMainThreadHandler = mainThreadHandler;
mOuterCallbacks = controller;
mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
mCallbacks, durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
- translator);
+ translator, statsToken);
InsetsAnimationThread.getHandler().post(() -> {
if (mControl.isCancelled()) {
return;
@@ -141,6 +143,11 @@
}
@Override
+ public ImeTracker.Token getStatsToken() {
+ return mControl.getStatsToken();
+ }
+
+ @Override
@UiThread
public int getTypes() {
return mControl.getTypes();
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 35838a3..fbd8226 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -44,6 +44,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Trace;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -60,6 +61,7 @@
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -929,10 +931,12 @@
hideTypes[0] &= ~animatingTypes;
if (showTypes[0] != 0) {
- applyAnimation(showTypes[0], true /* show */, false /* fromIme */);
+ applyAnimation(showTypes[0], true /* show */, false /* fromIme */,
+ null /* statsToken */);
}
if (hideTypes[0] != 0) {
- applyAnimation(hideTypes[0], false /* show */, false /* fromIme */);
+ applyAnimation(hideTypes[0], false /* show */, false /* fromIme */,
+ null /* statsToken */);
}
if (mControllableTypes != controllableTypes) {
@@ -948,11 +952,12 @@
@Override
public void show(@InsetsType int types) {
- show(types, false /* fromIme */);
+ show(types, false /* fromIme */, null /* statsToken */);
}
- @VisibleForTesting
- public void show(@InsetsType int types, boolean fromIme) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void show(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if ((types & ime()) != 0) {
Log.d(TAG, "show(ime(), fromIme=" + fromIme + ")");
}
@@ -979,7 +984,7 @@
true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
pendingRequest.animationType,
pendingRequest.layoutInsetsDuringAnimation,
- pendingRequest.useInsetsAnimationThread);
+ pendingRequest.useInsetsAnimationThread, statsToken);
return;
}
@@ -990,8 +995,9 @@
if ((types & type) == 0) {
continue;
}
- final @AnimationType int animationType = getAnimationType(type);
+ @AnimationType final int animationType = getAnimationType(type);
final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+ final boolean isImeAnimation = type == ime();
if (requestedVisible && animationType == ANIMATION_TYPE_NONE
|| animationType == ANIMATION_TYPE_SHOW) {
// no-op: already shown or animating in (because window visibility is
@@ -999,25 +1005,36 @@
if (DEBUG) Log.d(TAG, String.format(
"show ignored for type: %d animType: %d requestedVisible: %s",
type, animationType, requestedVisible));
+ if (isImeAnimation) {
+ ImeTracker.get().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
continue;
}
if (fromIme && animationType == ANIMATION_TYPE_USER) {
// App is already controlling the IME, don't cancel it.
+ if (isImeAnimation) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
continue;
}
+ if (isImeAnimation) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
typesReady |= type;
}
if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
- applyAnimation(typesReady, true /* show */, fromIme);
+ applyAnimation(typesReady, true /* show */, fromIme, statsToken);
}
@Override
public void hide(@InsetsType int types) {
- hide(types, false /* fromIme */);
+ hide(types, false /* fromIme */, null /* statsToken */);
}
@VisibleForTesting
- public void hide(@InsetsType int types, boolean fromIme) {
+ public void hide(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if (fromIme) {
ImeTracing.getInstance().triggerClientDump("InsetsController#hide",
mHost.getInputMethodManager(), null /* icProto */);
@@ -1030,16 +1047,25 @@
if ((types & type) == 0) {
continue;
}
- final @AnimationType int animationType = getAnimationType(type);
+ @AnimationType final int animationType = getAnimationType(type);
final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+ final boolean isImeAnimation = type == ime();
if (!requestedVisible && animationType == ANIMATION_TYPE_NONE
|| animationType == ANIMATION_TYPE_HIDE) {
- // no-op: already hidden or animating out.
+ // no-op: already hidden or animating out (because window visibility is
+ // applied before starting animation).
+ if (isImeAnimation) {
+ ImeTracker.get().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
continue;
}
+ if (isImeAnimation) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ }
typesReady |= type;
}
- applyAnimation(typesReady, false /* show */, fromIme /* fromIme */);
+ applyAnimation(typesReady, false /* show */, fromIme, statsToken);
}
@Override
@@ -1068,7 +1094,7 @@
controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
interpolator, animationType, getLayoutInsetsDuringAnimationMode(types),
- false /* useInsetsAnimationThread */);
+ false /* useInsetsAnimationThread */, null /* statsToken */);
}
private void controlAnimationUnchecked(@InsetsType int types,
@@ -1077,7 +1103,8 @@
long durationMs, Interpolator interpolator,
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
- boolean useInsetsAnimationThread) {
+ boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if ((types & mTypesBeingCancelled) != 0) {
throw new IllegalStateException("Cannot start a new insets animation of "
+ Type.toString(types)
@@ -1152,14 +1179,16 @@
? new InsetsAnimationThreadControlRunner(controls,
frame, mState, listener, typesReady, this, durationMs, interpolator,
animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
- mHost.getHandler())
+ mHost.getHandler(), statsToken)
: new InsetsAnimationControlImpl(controls,
frame, mState, listener, typesReady, this, durationMs, interpolator,
- animationType, layoutInsetsDuringAnimation, mHost.getTranslator());
+ animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
+ statsToken);
if ((typesReady & WindowInsets.Type.ime()) != 0) {
ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
mHost.getInputMethodManager(), null /* icProto */);
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
mRunningAnimations.add(new RunningAnimation(runner, animationType));
if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
+ useInsetsAnimationThread);
@@ -1311,11 +1340,18 @@
// requested visibility.
return;
}
+ final ImeTracker.Token statsToken = runner.getStatsToken();
if (shown) {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
showDirectly(runner.getTypes(), true /* fromIme */);
+ ImeTracker.get().onShown(statsToken);
} else {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
hideDirectly(runner.getTypes(), true /* animationFinished */,
runner.getAnimationType(), true /* fromIme */);
+ ImeTracker.get().onHidden(statsToken);
}
}
@@ -1339,10 +1375,19 @@
}
private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
- if (DEBUG) Log.d(TAG, String.format("cancelAnimation of types: %d, animType: %d, host: %s",
- control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
if (invokeCallback) {
+ ImeTracker.get().onCancelled(control.getStatsToken(),
+ ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
control.cancel();
+ } else {
+ // Succeeds if invokeCallback is false (i.e. when called from notifyFinished).
+ ImeTracker.get().onProgress(control.getStatsToken(),
+ ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
+ }
+ if (DEBUG) {
+ Log.d(TAG, TextUtils.formatSimple(
+ "cancelAnimation of types: %d, animType: %d, host: %s",
+ control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
}
boolean stateChanged = false;
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
@@ -1452,7 +1497,8 @@
}
@VisibleForTesting
- public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme) {
+ public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
// TODO(b/166736352): We should only skip the animation of specific types, not all types.
boolean skipAnim = false;
if ((types & ime()) != 0) {
@@ -1465,12 +1511,12 @@
&& consumer.hasViewFocusWhenWindowFocusGain();
}
}
- applyAnimation(types, show, fromIme, skipAnim);
+ applyAnimation(types, show, fromIme, skipAnim, statsToken);
}
@VisibleForTesting
public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme,
- boolean skipAnim) {
+ boolean skipAnim, @Nullable ImeTracker.Token statsToken) {
if (types == 0) {
// nothing to animate.
if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate");
@@ -1490,12 +1536,11 @@
listener.getDurationMs(), listener.getInsetsInterpolator(),
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
- !hasAnimationCallbacks /* useInsetsAnimationThread */);
+ !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
}
- private void hideDirectly(
- @InsetsType int types, boolean animationFinished, @AnimationType int animationType,
- boolean fromIme) {
+ private void hideDirectly(@InsetsType int types, boolean animationFinished,
+ @AnimationType int animationType, boolean fromIme) {
if ((types & ime()) != 0) {
ImeTracing.getInstance().triggerClientDump("InsetsController#hideDirectly",
mHost.getInputMethodManager(), null /* icProto */);
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index edcfc95..778c677 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -37,6 +37,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
/**
* Runs a fake animation of resizing insets to produce insets animation callbacks.
@@ -92,6 +93,12 @@
}
@Override
+ public ImeTracker.Token getStatsToken() {
+ // Return null as resizing the IME view is not explicitly tracked.
+ return null;
+ }
+
+ @Override
public void cancel() {
if (mCancelled || mFinished) {
return;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index e91839b..a8cc9b6 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -702,7 +702,7 @@
result.add(ITYPE_NAVIGATION_BAR);
result.add(ITYPE_EXTRA_NAVIGATION_BAR);
}
- if ((types & Type.GENERIC_OVERLAYS) != 0) {
+ if ((types & Type.SYSTEM_OVERLAYS) != 0) {
result.add(ITYPE_LEFT_GENERIC_OVERLAY);
result.add(ITYPE_TOP_GENERIC_OVERLAY);
result.add(ITYPE_RIGHT_GENERIC_OVERLAY);
@@ -752,7 +752,7 @@
case ITYPE_TOP_GENERIC_OVERLAY:
case ITYPE_RIGHT_GENERIC_OVERLAY:
case ITYPE_BOTTOM_GENERIC_OVERLAY:
- return Type.GENERIC_OVERLAYS;
+ return Type.SYSTEM_OVERLAYS;
case ITYPE_CAPTION_BAR:
return Type.CAPTION_BAR;
case ITYPE_IME:
diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java
index ea97995..ff282ba 100644
--- a/core/java/android/view/RemoteAnimationDefinition.java
+++ b/core/java/android/view/RemoteAnimationDefinition.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.app.WindowConfiguration.ActivityType;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.IBinder;
@@ -157,7 +158,7 @@
}
}
- public static final @android.annotation.NonNull Creator<RemoteAnimationDefinition> CREATOR =
+ public static final @NonNull Creator<RemoteAnimationDefinition> CREATOR =
new Creator<RemoteAnimationDefinition>() {
public RemoteAnimationDefinition createFromParcel(Parcel in) {
return new RemoteAnimationDefinition(in);
@@ -199,18 +200,17 @@
return 0;
}
- private static final @android.annotation.NonNull Creator<RemoteAnimationAdapterEntry> CREATOR
- = new Creator<RemoteAnimationAdapterEntry>() {
+ public static final @NonNull Parcelable.Creator<RemoteAnimationAdapterEntry> CREATOR =
+ new Parcelable.Creator<RemoteAnimationAdapterEntry>() {
+ @Override
+ public RemoteAnimationAdapterEntry createFromParcel(Parcel in) {
+ return new RemoteAnimationAdapterEntry(in);
+ }
- @Override
- public RemoteAnimationAdapterEntry createFromParcel(Parcel in) {
- return new RemoteAnimationAdapterEntry(in);
- }
-
- @Override
- public RemoteAnimationAdapterEntry[] newArray(int size) {
- return new RemoteAnimationAdapterEntry[size];
- }
- };
+ @Override
+ public RemoteAnimationAdapterEntry[] newArray(int size) {
+ return new RemoteAnimationAdapterEntry[size];
+ }
+ };
}
}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 6d25523..00170cb 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -28,7 +28,7 @@
import java.util.Map;
/**
- * Helper for tracking the velocity of touch events, for implementing
+ * Helper for tracking the velocity of motion events, for implementing
* flinging and other such gestures.
*
* Use {@link #obtain} to retrieve a new instance of the class when you are going
@@ -43,6 +43,15 @@
private static final int ACTIVE_POINTER_ID = -1;
+ /** @hide */
+ @IntDef(value = {
+ MotionEvent.AXIS_X,
+ MotionEvent.AXIS_Y,
+ MotionEvent.AXIS_SCROLL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VelocityTrackableMotionEventAxis {}
+
/**
* Velocity Tracker Strategy: Invalid.
*
@@ -306,10 +315,12 @@
}
/**
- * Checks whether a given motion axis is supported for velocity tracking.
+ * Checks whether a given velocity-trackable {@link MotionEvent} axis is supported for velocity
+ * tracking by this {@link VelocityTracker} instance (refer to
+ * {@link #getAxisVelocity(int, int)} for a list of potentially velocity-trackable axes).
*
- * <p>The axis values that would make sense to use for this method are the ones defined in the
- * {@link MotionEvent} class.
+ * <p>Note that the value returned from this method will stay the same for a given instance, so
+ * a single check for axis support is enough per a {@link VelocityTracker} instance.
*
* @param axis The axis to check for velocity support.
* @return {@code true} if {@code axis} is supported for velocity tracking, or {@code false}
@@ -317,7 +328,7 @@
* @see #getAxisVelocity(int, int)
* @see #getAxisVelocity(int)
*/
- public boolean isAxisSupported(int axis) {
+ public boolean isAxisSupported(@VelocityTrackableMotionEventAxis int axis) {
return nativeIsAxisSupported(axis);
}
@@ -421,13 +432,16 @@
* calling this function.
*
* <p>In addition to {@link MotionEvent#AXIS_X} and {@link MotionEvent#AXIS_Y} which have been
- * supported since the introduction of this class, the following axes are supported for this
+ * supported since the introduction of this class, the following axes can be candidates for this
* method:
* <ul>
* <li> {@link MotionEvent#AXIS_SCROLL}: supported starting
* {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
* </ul>
*
+ * <p>Before accessing velocities of an axis using this method, check that your
+ * {@link VelocityTracker} instance supports the axis by using {@link #isAxisSupported(int)}.
+ *
* @param axis Which axis' velocity to return.
* @param id Which pointer's velocity to return.
* @return The previously computed velocity for {@code axis} for pointer ID of {@code id} if
@@ -435,7 +449,7 @@
* for the axis.
* @see #isAxisSupported(int)
*/
- public float getAxisVelocity(int axis, int id) {
+ public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis, int id) {
return nativeGetVelocity(mPtr, axis, id);
}
@@ -450,7 +464,7 @@
* @see #isAxisSupported(int)
* @see #getAxisVelocity(int, int)
*/
- public float getAxisVelocity(int axis) {
+ public float getAxisVelocity(@VelocityTrackableMotionEventAxis int axis) {
return nativeGetVelocity(mPtr, axis, ACTIVE_POINTER_ID);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e664ebf..7e8ebd7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -88,6 +88,7 @@
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
import android.Manifest;
+import android.accessibilityservice.AccessibilityService;
import android.animation.AnimationHandler;
import android.animation.LayoutTransition;
import android.annotation.AnyThread;
@@ -192,12 +193,14 @@
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureSession;
import android.view.contentcapture.MainContentCaptureSession;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
import android.window.ClientWindowFrames;
import android.window.CompatOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
+import android.window.ScreenCapture;
import android.window.SurfaceSyncGroup;
import android.window.WindowOnBackInvokedDispatcher;
@@ -711,7 +714,7 @@
private final InsetsState mTempInsets = new InsetsState();
private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
- private float mInvSizeCompatScale = 1f;
+ private float mInvCompatScale = 1f;
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
= new ViewTreeObserver.InternalInsetsInfo();
@@ -1105,11 +1108,11 @@
private WindowConfiguration getCompatWindowConfiguration() {
final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
- if (mInvSizeCompatScale == 1f) {
+ if (mInvCompatScale == 1f) {
return winConfig;
}
mTempWinConfig.setTo(winConfig);
- mTempWinConfig.scale(mInvSizeCompatScale);
+ mTempWinConfig.scale(mInvCompatScale);
return mTempWinConfig;
}
@@ -1241,11 +1244,11 @@
controlInsetsForCompatibility(mWindowAttributes);
Rect attachedFrame = new Rect();
- final float[] sizeCompatScale = { 1f };
+ final float[] compatScale = { 1f };
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
- mTempControls, attachedFrame, sizeCompatScale);
+ mTempControls, attachedFrame, compatScale);
if (!attachedFrame.isValid()) {
attachedFrame = null;
}
@@ -1255,8 +1258,8 @@
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
mTmpFrames.attachedFrame = attachedFrame;
- mTmpFrames.sizeCompatScale = sizeCompatScale[0];
- mInvSizeCompatScale = 1f / sizeCompatScale[0];
+ mTmpFrames.compatScale = compatScale[0];
+ mInvCompatScale = 1f / compatScale[0];
} catch (RemoteException | RuntimeException e) {
mAdded = false;
mView = null;
@@ -1787,24 +1790,24 @@
mTranslator.translateRectInScreenToAppWindow(displayFrame);
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
- final float sizeCompatScale = frames.sizeCompatScale;
+ final float compatScale = frames.compatScale;
final boolean frameChanged = !mWinFrame.equals(frame);
final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
final boolean attachedFrameChanged = LOCAL_LAYOUT
&& !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
final boolean resizeModeChanged = mResizeMode != resizeMode;
- final boolean sizeCompatScaleChanged = mTmpFrames.sizeCompatScale != sizeCompatScale;
+ final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
&& !displayChanged && !resizeModeChanged && !forceNextWindowRelayout
- && !sizeCompatScaleChanged) {
+ && !compatScaleChanged) {
return;
}
mPendingDragResizing = resizeMode != RESIZE_MODE_INVALID;
mResizeMode = resizeMode;
- mTmpFrames.sizeCompatScale = sizeCompatScale;
- mInvSizeCompatScale = 1f / sizeCompatScale;
+ mTmpFrames.compatScale = compatScale;
+ mInvCompatScale = 1f / compatScale;
if (configChanged) {
// If configuration changed - notify about that and, maybe, about move to display.
@@ -5649,17 +5652,23 @@
break;
}
case MSG_SHOW_INSETS: {
+ final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_HANDLE_SHOW_INSETS);
if (mView == null) {
Log.e(TAG,
String.format("Calling showInsets(%d,%b) on window that no longer"
+ " has views.", msg.arg1, msg.arg2 == 1));
}
clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1);
- mInsetsController.show(msg.arg1, msg.arg2 == 1);
+ mInsetsController.show(msg.arg1, msg.arg2 == 1, statsToken);
break;
}
case MSG_HIDE_INSETS: {
- mInsetsController.hide(msg.arg1, msg.arg2 == 1);
+ final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_HANDLE_HIDE_INSETS);
+ mInsetsController.hide(msg.arg1, msg.arg2 == 1, statsToken);
break;
}
case MSG_WINDOW_MOVED:
@@ -8225,7 +8234,7 @@
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
}
- mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
+ mInvCompatScale = 1f / mTmpFrames.compatScale;
CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls);
@@ -8811,12 +8820,14 @@
mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget();
}
- private void showInsets(@InsetsType int types, boolean fromIme) {
- mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+ private void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
+ mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget();
}
- private void hideInsets(@InsetsType int types, boolean fromIme) {
- mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+ private void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
+ mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget();
}
public void dispatchMoved(int newX, int newY) {
@@ -10180,7 +10191,8 @@
}
@Override
- public void showInsets(@InsetsType int types, boolean fromIme) {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (fromIme) {
ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#showInsets",
@@ -10188,13 +10200,16 @@
null /* icProto */);
}
if (viewAncestor != null) {
- viewAncestor.showInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+ viewAncestor.showInsets(types, fromIme, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
}
}
@Override
- public void hideInsets(@InsetsType int types, boolean fromIme) {
-
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (fromIme) {
ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#hideInsets",
@@ -10202,7 +10217,10 @@
null /* icProto */);
}
if (viewAncestor != null) {
- viewAncestor.hideInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+ viewAncestor.hideInsets(types, fromIme, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
}
}
@@ -10691,6 +10709,25 @@
.notifyOutsideTouchClientThread();
}
}
+
+ @Override
+ public void takeScreenshotOfWindow(int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .takeScreenshotOfWindowClientThread(interactionId, listener, callback);
+ } else {
+ try {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+ interactionId);
+ } catch (RemoteException re) {
+ /* best effort - ignore */
+ }
+ }
+ }
}
/**
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 2a76c4e..d77e499 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1425,8 +1425,8 @@
static final int WINDOW_DECOR = 1 << 8;
- static final int GENERIC_OVERLAYS = 1 << 9;
- static final int LAST = GENERIC_OVERLAYS;
+ static final int SYSTEM_OVERLAYS = 1 << 9;
+ static final int LAST = SYSTEM_OVERLAYS;
static final int SIZE = 10;
static final int DEFAULT_VISIBLE = ~IME;
@@ -1451,7 +1451,7 @@
return 7;
case WINDOW_DECOR:
return 8;
- case GENERIC_OVERLAYS:
+ case SYSTEM_OVERLAYS:
return 9;
default:
throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
@@ -1489,8 +1489,8 @@
if ((types & WINDOW_DECOR) != 0) {
result.append("windowDecor |");
}
- if ((types & GENERIC_OVERLAYS) != 0) {
- result.append("genericOverlays |");
+ if ((types & SYSTEM_OVERLAYS) != 0) {
+ result.append("systemOverlays |");
}
if (result.length() > 0) {
result.delete(result.length() - 2, result.length());
@@ -1505,7 +1505,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {STATUS_BARS, NAVIGATION_BARS, CAPTION_BAR, IME, WINDOW_DECOR,
SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT,
- GENERIC_OVERLAYS})
+ SYSTEM_OVERLAYS})
public @interface InsetsType {
}
@@ -1593,11 +1593,27 @@
}
/**
+ * System overlays represent the insets caused by the system visible elements. Unlike
+ * {@link #navigationBars()} or {@link #statusBars()}, system overlays might not be
+ * hidden by the client.
+ *
+ * For compatibility reasons, this type is included in {@link #systemBars()}. In this
+ * way, views which fit {@link #systemBars()} fit {@link #systemOverlays()}.
+ *
+ * Examples include climate controls, multi-tasking affordances, etc.
+ *
+ * @return An insets type representing the system overlays.
+ */
+ public static @InsetsType int systemOverlays() {
+ return SYSTEM_OVERLAYS;
+ }
+
+ /**
* @return All system bars. Includes {@link #statusBars()}, {@link #captionBar()} as well as
- * {@link #navigationBars()}, but not {@link #ime()}.
+ * {@link #navigationBars()}, {@link #systemOverlays()}, but not {@link #ime()}.
*/
public static @InsetsType int systemBars() {
- return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | GENERIC_OVERLAYS;
+ return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | SYSTEM_OVERLAYS;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cc85181..16f6cea 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3408,6 +3408,11 @@
* alt="Screenshot of an activity on a display with a cutout on the long edge in portrait,
* letterbox is applied."/>
*
+ * <p>
+ * Note: Android might not allow the content view to overlap the system bars in view level.
+ * To override this behavior and allow content to be able to extend into the cutout area,
+ * call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+ *
* @see DisplayCutout
* @see WindowInsets#getDisplayCutout()
* @see #layoutInDisplayCutoutMode
@@ -3443,6 +3448,11 @@
* In this mode, the window extends under cutouts on the all edges of the display in both
* portrait and landscape, regardless of whether the window is hiding the system bars.
*
+ * <p>
+ * Note: Android might not allow the content view to overlap the system bars in view level.
+ * To override this behavior and allow content to be able to extend into the cutout area,
+ * call {@link Window#setDecorFitsSystemWindows(boolean)} with {@code false}.
+ *
* @see DisplayCutout
* @see WindowInsets#getDisplayCutout()
* @see #layoutInDisplayCutoutMode
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index e3ffc9d..7030ab5 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -22,7 +22,9 @@
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
+import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -31,17 +33,21 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseLongArray;
import android.view.Display;
import android.view.ViewConfiguration;
+import android.window.ScreenCapture;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -53,6 +59,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -146,6 +153,10 @@
private boolean mPerformAccessibilityActionResult;
+ // SparseArray of interaction ID -> screenshot executor+callback.
+ private final SparseArray<Pair<Executor, AccessibilityService.TakeScreenshotCallback>>
+ mTakeScreenshotOfWindowCallbacks = new SparseArray<>();
+
private Message mSameThreadMessage;
private int mInteractionIdWaitingForPrefetchResult = -1;
@@ -779,6 +790,59 @@
}
/**
+ * Takes a screenshot of the window with the provided {@code accessibilityWindowId} and
+ * returns the answer asynchronously. This async behavior is similar to {@link
+ * AccessibilityService#takeScreenshot} but unlike other methods in this class which perform
+ * synchronous waiting in the AccessibilityService client.
+ *
+ * @see AccessibilityService#takeScreenshotOfWindow
+ */
+ public void takeScreenshotOfWindow(int connectionId, int accessibilityWindowId,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AccessibilityService.TakeScreenshotCallback callback) {
+ synchronized (mInstanceLock) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection == null) {
+ executor.execute(() -> callback.onFailure(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+ return;
+ }
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ mTakeScreenshotOfWindowCallbacks.put(interactionId,
+ Pair.create(executor, callback));
+ // Create a ScreenCaptureListener to receive the screenshot directly from
+ // SurfaceFlinger instead of requiring an extra IPC from the app:
+ // A11yService -> App -> SurfaceFlinger -> A11yService
+ ScreenCapture.ScreenCaptureListener listener =
+ new ScreenCapture.ScreenCaptureListener(
+ screenshot -> sendWindowScreenshotSuccess(screenshot,
+ interactionId));
+ connection.takeScreenshotOfWindow(accessibilityWindowId, interactionId,
+ listener, this);
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ synchronized (mInstanceLock) {
+ // Notify failure if we still haven't sent a response after timeout.
+ if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+ sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+ interactionId);
+ }
+ }
+ }, TIMEOUT_INTERACTION_MILLIS);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ } catch (RemoteException re) {
+ executor.execute(() -> callback.onFailure(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR));
+ }
+ }
+ }
+
+ /**
* Finds {@link AccessibilityNodeInfo}s by View text. The match is case
* insensitive containment. The search is performed in the window whose
* id is specified and starts from the node whose accessibility id is
@@ -1254,6 +1318,55 @@
}
/**
+ * Sends the result of a window screenshot request to the requesting client.
+ *
+ * {@link #takeScreenshotOfWindow} does not perform synchronous waiting, so this method
+ * does not notify any wait lock.
+ */
+ private void sendWindowScreenshotSuccess(ScreenCapture.ScreenshotHardwareBuffer screenshot,
+ int interactionId) {
+ if (screenshot == null) {
+ sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+ return;
+ }
+ synchronized (mInstanceLock) {
+ if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+ final AccessibilityService.ScreenshotResult result =
+ new AccessibilityService.ScreenshotResult(screenshot.getHardwareBuffer(),
+ screenshot.getColorSpace(), SystemClock.uptimeMillis());
+ final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+ mTakeScreenshotOfWindowCallbacks.get(interactionId);
+ final Executor executor = pair.first;
+ final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+ executor.execute(() -> callback.onSuccess(result));
+ mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+ }
+ }
+ }
+
+ /**
+ * Sends an error code for a window screenshot request to the requesting client.
+ *
+ * @param errorCode The error code from {@link AccessibilityService.ScreenshotErrorCode}.
+ * @param interactionId The interaction id of the request.
+ */
+ @Override
+ public void sendTakeScreenshotOfWindowError(
+ @AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) {
+ synchronized (mInstanceLock) {
+ if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) {
+ final Pair<Executor, AccessibilityService.TakeScreenshotCallback> pair =
+ mTakeScreenshotOfWindowCallbacks.get(interactionId);
+ final Executor executor = pair.first;
+ final AccessibilityService.TakeScreenshotCallback callback = pair.second;
+ executor.execute(() -> callback.onFailure(errorCode));
+ mTakeScreenshotOfWindowCallbacks.remove(interactionId);
+ }
+ }
+ }
+
+ /**
* Clears the result state.
*/
private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 472a363..fb01921 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -22,6 +22,7 @@
import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
/**
* Interface for interaction between the AccessibilityManagerService
@@ -60,4 +61,8 @@
void clearAccessibilityFocus();
void notifyOutsideTouch();
+
+ void takeScreenshotOfWindow(int interactionId,
+ in ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index 231e75a..456bf58 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -63,4 +63,9 @@
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
+
+ /**
+ * Sends an error code for a window screenshot request to the requesting client.
+ */
+ void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index a66c67b..6eae63a 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -275,15 +275,16 @@
@AnyThread
static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
- int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver,
+ @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
+ @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
final IInputMethodManager service = getService();
if (service == null) {
return false;
}
try {
- return service.showSoftInput(
- client, windowToken, flags, lastClickToolType, resultReceiver, reason);
+ return service.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
+ resultReceiver, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -291,14 +292,15 @@
@AnyThread
static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
- int flags, @Nullable ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
+ @Nullable ImeTracker.Token statsToken, int flags,
+ @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
final IInputMethodManager service = getService();
if (service == null) {
return false;
}
try {
- return service.hideSoftInput(client, windowToken, flags, resultReceiver, reason);
+ return service.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+ reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/ImeTracker.aidl b/core/java/android/view/inputmethod/ImeTracker.aidl
new file mode 100644
index 0000000..1988f48
--- /dev/null
+++ b/core/java/android/view/inputmethod/ImeTracker.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 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.inputmethod;
+
+parcelable ImeTracker.Token;
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
new file mode 100644
index 0000000..f4ecdff
--- /dev/null
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2022 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.inputmethod;
+
+import static android.view.inputmethod.ImeTracker.Debug.originToString;
+import static android.view.inputmethod.ImeTracker.Debug.phaseToString;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+/** @hide */
+public interface ImeTracker {
+
+ String TAG = "ImeTracker";
+
+ /**
+ * The origin of the IME request
+ *
+ * The name follows the format {@code PHASE_x_...} where {@code x} denotes
+ * where the origin is (i.e. {@code PHASE_SERVER_...} occurs in the server).
+ */
+ @IntDef(prefix = { "ORIGIN_" }, value = {
+ ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ ORIGIN_SERVER_START_INPUT,
+ ORIGIN_SERVER_HIDE_INPUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Origin {}
+
+ /**
+ * The IME show request originated in the client.
+ */
+ int ORIGIN_CLIENT_SHOW_SOFT_INPUT = 0;
+
+ /**
+ * The IME hide request originated in the client.
+ */
+ int ORIGIN_CLIENT_HIDE_SOFT_INPUT = 1;
+
+ /**
+ * The IME show request originated in the server.
+ */
+ int ORIGIN_SERVER_START_INPUT = 2;
+
+ /**
+ * The IME hide request originated in the server.
+ */
+ int ORIGIN_SERVER_HIDE_INPUT = 3;
+
+ /**
+ * The current phase of the IME request.
+ *
+ * The name follows the format {@code PHASE_x_...} where {@code x} denotes
+ * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server).
+ */
+ @IntDef(prefix = { "PHASE_" }, value = {
+ PHASE_CLIENT_VIEW_SERVED,
+ PHASE_SERVER_CLIENT_KNOWN,
+ PHASE_SERVER_CLIENT_FOCUSED,
+ PHASE_SERVER_ACCESSIBILITY,
+ PHASE_SERVER_SYSTEM_READY,
+ PHASE_SERVER_HIDE_IMPLICIT,
+ PHASE_SERVER_HIDE_NOT_ALWAYS,
+ PHASE_SERVER_WAIT_IME,
+ PHASE_SERVER_HAS_IME,
+ PHASE_SERVER_SHOULD_HIDE,
+ PHASE_IME_WRAPPER,
+ PHASE_IME_WRAPPER_DISPATCH,
+ PHASE_IME_SHOW_SOFT_INPUT,
+ PHASE_IME_HIDE_SOFT_INPUT,
+ PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE,
+ PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER,
+ PHASE_SERVER_APPLY_IME_VISIBILITY,
+ PHASE_WM_SHOW_IME_RUNNER,
+ PHASE_WM_SHOW_IME_READY,
+ PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET,
+ PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS,
+ PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS,
+ PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS,
+ PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS,
+ PHASE_WM_REMOTE_INSETS_CONTROLLER,
+ PHASE_WM_ANIMATION_CREATE,
+ PHASE_WM_ANIMATION_RUNNING,
+ PHASE_CLIENT_SHOW_INSETS,
+ PHASE_CLIENT_HIDE_INSETS,
+ PHASE_CLIENT_HANDLE_SHOW_INSETS,
+ PHASE_CLIENT_HANDLE_HIDE_INSETS,
+ PHASE_CLIENT_APPLY_ANIMATION,
+ PHASE_CLIENT_CONTROL_ANIMATION,
+ PHASE_CLIENT_ANIMATION_RUNNING,
+ PHASE_CLIENT_ANIMATION_CANCEL,
+ PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
+ PHASE_CLIENT_ANIMATION_FINISHED_HIDE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Phase {}
+
+ /** The view that requested the IME has been served by the IMM. */
+ int PHASE_CLIENT_VIEW_SERVED = 0;
+
+ /** The IME client that requested the IME has window manager focus. */
+ int PHASE_SERVER_CLIENT_KNOWN = 1;
+
+ /** The IME client that requested the IME has IME focus. */
+ int PHASE_SERVER_CLIENT_FOCUSED = 2;
+
+ /** The IME request complies with the current accessibility settings. */
+ int PHASE_SERVER_ACCESSIBILITY = 3;
+
+ /** The server is ready to run third party code. */
+ int PHASE_SERVER_SYSTEM_READY = 4;
+
+ /** Checked the implicit hide request against any explicit show requests. */
+ int PHASE_SERVER_HIDE_IMPLICIT = 5;
+
+ /** Checked the not-always hide request against any forced show requests. */
+ int PHASE_SERVER_HIDE_NOT_ALWAYS = 6;
+
+ /** The server is waiting for a connection to the IME. */
+ int PHASE_SERVER_WAIT_IME = 7;
+
+ /** The server has a connection to the IME. */
+ int PHASE_SERVER_HAS_IME = 8;
+
+ /** The server decided the IME should be hidden. */
+ int PHASE_SERVER_SHOULD_HIDE = 9;
+
+ /** Reached the IME wrapper. */
+ int PHASE_IME_WRAPPER = 10;
+
+ /** Dispatched from the IME wrapper to the IME. */
+ int PHASE_IME_WRAPPER_DISPATCH = 11;
+
+ /** Reached the IME' showSoftInput method. */
+ int PHASE_IME_SHOW_SOFT_INPUT = 12;
+
+ /** Reached the IME' hideSoftInput method. */
+ int PHASE_IME_HIDE_SOFT_INPUT = 13;
+
+ /** The server decided the IME should be shown. */
+ int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = 14;
+
+ /** Requested applying the IME visibility in the insets source consumer. */
+ int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = 15;
+
+ /** Applied the IME visibility. */
+ int PHASE_SERVER_APPLY_IME_VISIBILITY = 16;
+
+ /** Created the show IME runner. */
+ int PHASE_WM_SHOW_IME_RUNNER = 17;
+
+ /** Ready to show IME. */
+ int PHASE_WM_SHOW_IME_READY = 18;
+
+ /** The Window Manager has a connection to the IME insets control target. */
+ int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET = 19;
+
+ /** Reached the window insets control target's show insets method. */
+ int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS = 20;
+
+ /** Reached the window insets control target's hide insets method. */
+ int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS = 21;
+
+ /** Reached the remote insets control target's show insets method. */
+ int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS = 22;
+
+ /** Reached the remote insets control target's hide insets method. */
+ int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS = 23;
+
+ /** Reached the remote insets controller. */
+ int PHASE_WM_REMOTE_INSETS_CONTROLLER = 24;
+
+ /** Created the IME window insets show animation. */
+ int PHASE_WM_ANIMATION_CREATE = 25;
+
+ /** Started the IME window insets show animation. */
+ int PHASE_WM_ANIMATION_RUNNING = 26;
+
+ /** Reached the client's show insets method. */
+ int PHASE_CLIENT_SHOW_INSETS = 27;
+
+ /** Reached the client's hide insets method. */
+ int PHASE_CLIENT_HIDE_INSETS = 28;
+
+ /** Handling the IME window insets show request. */
+ int PHASE_CLIENT_HANDLE_SHOW_INSETS = 29;
+
+ /** Handling the IME window insets hide request. */
+ int PHASE_CLIENT_HANDLE_HIDE_INSETS = 30;
+
+ /** Applied the IME window insets show animation. */
+ int PHASE_CLIENT_APPLY_ANIMATION = 31;
+
+ /** Started the IME window insets show animation. */
+ int PHASE_CLIENT_CONTROL_ANIMATION = 32;
+
+ /** Queued the IME window insets show animation. */
+ int PHASE_CLIENT_ANIMATION_RUNNING = 33;
+
+ /** Cancelled the IME window insets show animation. */
+ int PHASE_CLIENT_ANIMATION_CANCEL = 34;
+
+ /** Finished the IME window insets show animation. */
+ int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = 35;
+
+ /** Finished the IME window insets hide animation. */
+ int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = 36;
+
+ /**
+ * Called when an IME show request is created.
+ *
+ * @param token the token tracking the current IME show request or {@code null} otherwise.
+ * @param origin the origin of the IME show request.
+ * @param reason the reason why the IME show request was created.
+ */
+ void onRequestShow(@Nullable Token token, @Origin int origin,
+ @SoftInputShowHideReason int reason);
+
+ /**
+ * Called when an IME hide request is created.
+ *
+ * @param token the token tracking the current IME hide request or {@code null} otherwise.
+ * @param origin the origin of the IME hide request.
+ * @param reason the reason why the IME hide request was created.
+ */
+ void onRequestHide(@Nullable Token token, @Origin int origin,
+ @SoftInputShowHideReason int reason);
+
+ /**
+ * Called when an IME request progresses to a further phase.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param phase the new phase the IME request reached.
+ */
+ void onProgress(@Nullable Token token, @Phase int phase);
+
+ /**
+ * Called when an IME request fails.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param phase the phase the IME request failed at.
+ */
+ void onFailed(@Nullable Token token, @Phase int phase);
+
+ /**
+ * Called when an IME request reached a flow that is not yet implemented.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param phase the phase the IME request was currently at.
+ */
+ void onTodo(@Nullable Token token, @Phase int phase);
+
+ /**
+ * Called when an IME request is cancelled.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param phase the phase the IME request was cancelled at.
+ */
+ void onCancelled(@Nullable Token token, @Phase int phase);
+
+ /**
+ * Called when the IME show request is successful.
+ *
+ * @param token the token tracking the current IME show request or {@code null} otherwise.
+ */
+ void onShown(@Nullable Token token);
+
+ /**
+ * Called when the IME hide request is successful.
+ *
+ * @param token the token tracking the current IME hide request or {@code null} otherwise.
+ */
+ void onHidden(@Nullable Token token);
+
+ /**
+ * Get the singleton instance of this class.
+ *
+ * @return the singleton instance of this class
+ */
+ @NonNull
+ static ImeTracker get() {
+ return SystemProperties.getBoolean("persist.debug.imetracker", false)
+ ? LOGGER
+ : NOOP_LOGGER;
+ }
+
+ /** The singleton IME tracker instance. */
+ ImeTracker LOGGER = new ImeTracker() {
+
+ @Override
+ public void onRequestShow(@Nullable Token token, int origin,
+ @SoftInputShowHideReason int reason) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onRequestShow at " + originToString(origin)
+ + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+ }
+
+ @Override
+ public void onRequestHide(@Nullable Token token, int origin,
+ @SoftInputShowHideReason int reason) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onRequestHide at " + originToString(origin)
+ + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+ }
+
+ @Override
+ public void onProgress(@Nullable Token token, int phase) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onProgress at " + phaseToString(phase));
+ }
+
+ @Override
+ public void onFailed(@Nullable Token token, int phase) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onFailed at " + phaseToString(phase));
+ }
+
+ @Override
+ public void onTodo(@Nullable Token token, int phase) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onTodo at " + phaseToString(phase));
+ }
+
+ @Override
+ public void onCancelled(@Nullable Token token, int phase) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onCancelled at " + phaseToString(phase));
+ }
+
+ @Override
+ public void onShown(@Nullable Token token) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onShown");
+ }
+
+ @Override
+ public void onHidden(@Nullable Token token) {
+ if (token == null) return;
+ Log.i(TAG, token.mTag + ": onHidden");
+ }
+ };
+
+ /** The singleton no-op IME tracker instance. */
+ ImeTracker NOOP_LOGGER = new ImeTracker() {
+
+ @Override
+ public void onRequestShow(@Nullable Token token, int origin,
+ @SoftInputShowHideReason int reason) {}
+
+ @Override
+ public void onRequestHide(@Nullable Token token, int origin,
+ @SoftInputShowHideReason int reason) {}
+
+ @Override
+ public void onProgress(@Nullable Token token, int phase) {}
+
+ @Override
+ public void onFailed(@Nullable Token token, int phase) {}
+
+ @Override
+ public void onTodo(@Nullable Token token, int phase) {}
+
+ @Override
+ public void onCancelled(@Nullable Token token, int phase) {}
+
+ @Override
+ public void onShown(@Nullable Token token) {}
+
+ @Override
+ public void onHidden(@Nullable Token token) {}
+ };
+
+ /** A token that tracks the progress of an IME request. */
+ class Token implements Parcelable {
+
+ private final IBinder mBinder;
+ private final String mTag;
+
+ public Token() {
+ this(ActivityThread.currentProcessName());
+ }
+
+ public Token(String component) {
+ this(new Binder(), component + ":" + Integer.toHexString((new Random().nextInt())));
+ }
+
+ private Token(IBinder binder, String tag) {
+ mBinder = binder;
+ mTag = tag;
+ }
+
+ /** For Parcelable, no special marshalled objects. */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mBinder);
+ dest.writeString8(mTag);
+ }
+
+ @NonNull
+ public static final Creator<Token> CREATOR = new Creator<>() {
+ @Override
+ public Token createFromParcel(Parcel source) {
+ IBinder binder = source.readStrongBinder();
+ String tag = source.readString8();
+ return new Token(binder, tag);
+ }
+
+ @Override
+ public Token[] newArray(int size) {
+ return new Token[size];
+ }
+ };
+ }
+
+ /**
+ * Utilities for mapping phases and origins IntDef values to their names.
+ *
+ * Note: This is held in a separate class so that it only gets initialized when actually needed.
+ */
+ class Debug {
+
+ private static final Map<Integer, String> sOrigins =
+ getFieldMapping(ImeTracker.class, "ORIGIN_");
+ private static final Map<Integer, String> sPhases =
+ getFieldMapping(ImeTracker.class, "PHASE_");
+
+ public static String originToString(int origin) {
+ return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
+ }
+
+ public static String phaseToString(int phase) {
+ return sPhases.getOrDefault(phase, "PHASE_" + phase);
+ }
+
+ private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) {
+ return Arrays.stream(cls.getDeclaredFields())
+ .filter(field -> field.getName().startsWith(fieldPrefix))
+ .collect(Collectors.toMap(Debug::getFieldValue, Field::getName));
+ }
+
+ private static int getFieldValue(Field field) {
+ try {
+ return field.getInt(null);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 4d5a17d..92380ed 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -300,11 +300,12 @@
* @param showInputToken an opaque {@link android.os.Binder} token to identify which API call
* of {@link InputMethodManager#showSoftInput(View, int)} is associated with
* this callback.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
* @hide
*/
@MainThread
- default public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
- IBinder showInputToken) {
+ public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+ IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
showSoftInput(flags, resultReceiver);
}
@@ -338,11 +339,14 @@
* @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call
* of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated
* with this callback.
+ * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
* @hide
*/
@MainThread
- public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
- IBinder hideInputToken);
+ public default void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+ IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
+ hideSoftInput(flags, resultReceiver);
+ }
/**
* Request that any soft input part of the input method be hidden from the user.
@@ -369,7 +373,7 @@
/**
* Checks if IME is ready to start stylus handwriting session.
- * If yes, {@link #startStylusHandwriting(InputChannel, List)} is called.
+ * If yes, {@link #startStylusHandwriting(int, InputChannel, List)} is called.
* @param requestId
* @hide
*/
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 74afced..ee31fd5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -632,6 +632,8 @@
private final DelegateImpl mDelegate = new DelegateImpl();
+ private static boolean sPreventImeStartupUnlessTextEditor;
+
// -----------------------------------------------------------
private static final int MSG_DUMP = 1;
@@ -1435,6 +1437,10 @@
// display case.
final Looper looper = displayId == Display.DEFAULT_DISPLAY
? Looper.getMainLooper() : context.getMainLooper();
+ // Keep track of whether to expect the IME to be unavailable so as to avoid log spam in
+ // sendInputEventOnMainLooperLocked() by not logging a verbose message on every DPAD event
+ sPreventImeStartupUnlessTextEditor = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
return forContextInternal(displayId, looper);
}
@@ -2001,6 +2007,10 @@
private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ reason);
+
ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
null /* icProto */);
// Re-dispatch if there is a context mismatch.
@@ -2012,10 +2022,13 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view)) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
@@ -2024,6 +2037,7 @@
return IInputMethodManagerGlobalInvoker.showSoftInput(
mClient,
view.getWindowToken(),
+ statsToken,
flags,
mCurRootView.getLastClickToolType(),
resultReceiver,
@@ -2043,19 +2057,28 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
synchronized (mH) {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ SoftInputShowHideReason.SHOW_SOFT_INPUT);
+
Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
+ " removed soon. If you are using androidx.appcompat.widget.SearchView,"
+ " please update to version 26.0 or newer version.");
if (mCurRootView == null || mCurRootView.getView() == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
return;
}
+
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
IInputMethodManagerGlobalInvoker.showSoftInput(
mClient,
mCurRootView.getView().getWindowToken(),
+ statsToken,
flags,
mCurRootView.getLastClickToolType(),
resultReceiver,
@@ -2125,17 +2148,24 @@
private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ reason);
+
ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
this, null /* icProto */);
checkFocus();
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null || servedView.getWindowToken() != windowToken) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
return false;
}
- return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, flags,
- resultReceiver, reason);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
+ return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
+ flags, resultReceiver, reason);
}
}
@@ -2763,14 +2793,23 @@
@UnsupportedAppUsage
void closeCurrentInput() {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT);
+
synchronized (mH) {
if (mCurRootView == null || mCurRootView.getView() == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
return;
}
+
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
IInputMethodManagerGlobalInvoker.hideSoftInput(
mClient,
mCurRootView.getView().getWindowToken(),
+ statsToken,
HIDE_NOT_ALWAYS,
null,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
@@ -2839,15 +2878,24 @@
* @hide
*/
public void notifyImeHidden(IBinder windowToken) {
+ final ImeTracker.Token statsToken = new ImeTracker.Token();
+ ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+
ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
null /* icProto */);
synchronized (mH) {
- if (isImeSessionAvailableLocked() && mCurRootView != null
- && mCurRootView.getWindowToken() == windowToken) {
- IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, 0 /* flags */,
- null /* resultReceiver */,
- SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+ if (!isImeSessionAvailableLocked() || mCurRootView == null
+ || mCurRootView.getWindowToken() != windowToken) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ return;
}
+
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
+ IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
+ 0 /* flags */, null /* resultReceiver */,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
}
@@ -3364,8 +3412,12 @@
return DISPATCH_IN_PROGRESS;
}
- Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
- + " dropping: " + event);
+ if (sPreventImeStartupUnlessTextEditor) {
+ Log.d(TAG, "Dropping event because IME is evicted: " + event);
+ } else {
+ Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
+ + " dropping: " + event);
+ }
}
return DISPATCH_NOT_HANDLED;
}
@@ -3967,7 +4019,7 @@
/**
* As reported by {@link InputBindResult}. This value is determined by
- * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
+ * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecker}.
*/
final boolean mIsInputMethodSuppressingSpellChecker;
diff --git a/core/java/android/webkit/ConsoleMessage.java b/core/java/android/webkit/ConsoleMessage.java
index 5474557..89cb6b2 100644
--- a/core/java/android/webkit/ConsoleMessage.java
+++ b/core/java/android/webkit/ConsoleMessage.java
@@ -68,4 +68,4 @@
public int lineNumber() {
return mLineNumber;
}
-};
+}
diff --git a/core/java/android/webkit/ValueCallback.java b/core/java/android/webkit/ValueCallback.java
index 5c7d97f..3d5bb49 100644
--- a/core/java/android/webkit/ValueCallback.java
+++ b/core/java/android/webkit/ValueCallback.java
@@ -25,4 +25,4 @@
* @param value The value.
*/
public void onReceiveValue(T value);
-};
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 810fde2..bf1a2bd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2289,11 +2289,13 @@
* @param familyName family name string, e.g. "serif"
* @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
* @param style a typeface style
- * @param weight a weight value for the Typeface or -1 if not specified.
+ * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED}
+ * if not specified.
*/
private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
@XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
- @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ int weight) {
if (typeface == null && familyName != null) {
// Lookup normal Typeface from system font map.
final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
@@ -2320,7 +2322,8 @@
}
private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
- @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ int weight) {
if (weight >= 0) {
weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
final boolean italic = (style & Typeface.ITALIC) != 0;
@@ -4021,7 +4024,7 @@
boolean mFontFamilyExplicit = false;
int mTypefaceIndex = -1;
int mTextStyle = 0;
- int mFontWeight = -1;
+ int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
boolean mAllCaps = false;
int mShadowColor = 0;
float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
@@ -6946,18 +6949,18 @@
if (isPassword) {
setTransformationMethod(PasswordTransformationMethod.getInstance());
setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
- Typeface.NORMAL, -1 /* weight, not specifeid */);
+ Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
} else if (isVisiblePassword) {
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}
setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
- Typeface.NORMAL, -1 /* weight, not specified */);
+ Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
} else if (wasPassword || wasVisiblePassword) {
// not in password mode, clean up typeface and transformation
setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
- -1 /* weight, not specified */);
+ FontStyle.FONT_WEIGHT_UNSPECIFIED);
if (mTransformation == PasswordTransformationMethod.getInstance()) {
forceUpdate = true;
}
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index f274d1a..0ce076b6 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -49,7 +49,7 @@
public boolean isParentFrameClippedByDisplayCutout;
- public float sizeCompatScale = 1f;
+ public float compatScale = 1f;
public ClientWindowFrames() {
}
@@ -62,7 +62,7 @@
attachedFrame = new Rect(other.attachedFrame);
}
isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout;
- sizeCompatScale = other.sizeCompatScale;
+ compatScale = other.compatScale;
}
private ClientWindowFrames(Parcel in) {
@@ -76,7 +76,7 @@
parentFrame.readFromParcel(in);
attachedFrame = in.readTypedObject(Rect.CREATOR);
isParentFrameClippedByDisplayCutout = in.readBoolean();
- sizeCompatScale = in.readFloat();
+ compatScale = in.readFloat();
}
@Override
@@ -86,7 +86,7 @@
parentFrame.writeToParcel(dest, flags);
dest.writeTypedObject(attachedFrame, flags);
dest.writeBoolean(isParentFrameClippedByDisplayCutout);
- dest.writeFloat(sizeCompatScale);
+ dest.writeFloat(compatScale);
}
@Override
@@ -97,7 +97,7 @@
+ " parentFrame=" + parentFrame.toShortString(sb)
+ (attachedFrame != null ? " attachedFrame=" + attachedFrame.toShortString() : "")
+ (isParentFrameClippedByDisplayCutout ? " parentClippedByDisplayCutout" : "")
- + (sizeCompatScale != 1f ? " sizeCompatScale=" + sizeCompatScale : "") + "}";
+ + (compatScale != 1f ? " sizeCompatScale=" + compatScale : "") + "}";
}
@Override
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index a5aefd5..f55932e 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -16,6 +16,8 @@
package android.window;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
import android.annotation.NonNull;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -142,6 +144,14 @@
*/
public void onRunningAppsChanged(ArraySet<Integer> runningUids) {}
+ /**
+ * This is called when an Activity is entering PIP.
+ * Returns {@code true} if the Activity is allowed to enter PIP.
+ */
+ public boolean isEnteringPipAllowed(int uid) {
+ return isWindowingModeSupported(WINDOWING_MODE_PINNED);
+ }
+
/** Dump debug data */
public void dump(String prefix, final PrintWriter pw) {
pw.println(prefix + "DisplayWindowPolicyController{" + super.toString() + "}");
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index db15145..e62d5c9 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -296,7 +296,7 @@
out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i]));
}
}
- out.append("]").toString();
+ out.append("]");
out.append(" flags=" + TransitionInfo.flagsToString(mFlags));
out.append(" mustBeTask=" + mMustBeTask);
out.append(" order=" + containerOrderToString(mOrder));
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 2d29c59..c7a2d24 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -454,6 +454,23 @@
}
/**
+ * Sets whether a container is being drag-resized.
+ * When {@code true}, the client will reuse a single (larger) surface size to avoid
+ * continuous allocations on every size change.
+ *
+ * @param container WindowContainerToken of the task that changed its drag resizing state
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container,
+ boolean dragResizing) {
+ final Change change = getOrCreateChange(container.asBinder());
+ change.mChangeMask |= Change.CHANGE_DRAG_RESIZING;
+ change.mDragResizing = dragResizing;
+ return this;
+ }
+
+ /**
* Sends a pending intent in sync.
* @param sender The PendingIntent sender.
* @param intent The fillIn intent to patch over the sender's base intent.
@@ -894,12 +911,14 @@
public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
public static final int CHANGE_FORCE_NO_PIP = 1 << 6;
public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;
+ public static final int CHANGE_DRAG_RESIZING = 1 << 8;
private final Configuration mConfiguration = new Configuration();
private boolean mFocusable = true;
private boolean mHidden = false;
private boolean mIgnoreOrientationRequest = false;
private boolean mForceTranslucent = false;
+ private boolean mDragResizing = false;
private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
@@ -920,6 +939,7 @@
mHidden = in.readBoolean();
mIgnoreOrientationRequest = in.readBoolean();
mForceTranslucent = in.readBoolean();
+ mDragResizing = in.readBoolean();
mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
@@ -968,6 +988,9 @@
if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
mForceTranslucent = other.mForceTranslucent;
}
+ if ((other.mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+ mDragResizing = other.mDragResizing;
+ }
mChangeMask |= other.mChangeMask;
if (other.mActivityWindowingMode >= 0) {
mActivityWindowingMode = other.mActivityWindowingMode;
@@ -1027,6 +1050,15 @@
return mForceTranslucent;
}
+ /** Gets the requested drag resizing state. */
+ public boolean getDragResizing() {
+ if ((mChangeMask & CHANGE_DRAG_RESIZING) == 0) {
+ throw new RuntimeException("Drag resizing not set. "
+ + "Check CHANGE_DRAG_RESIZING first");
+ }
+ return mDragResizing;
+ }
+
public int getChangeMask() {
return mChangeMask;
}
@@ -1088,6 +1120,9 @@
if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
sb.append("focusable:" + mFocusable + ",");
}
+ if ((mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+ sb.append("dragResizing:" + mDragResizing + ",");
+ }
if (mBoundsChangeTransaction != null) {
sb.append("hasBoundsTransaction,");
}
@@ -1105,6 +1140,7 @@
dest.writeBoolean(mHidden);
dest.writeBoolean(mIgnoreOrientationRequest);
dest.writeBoolean(mForceTranslucent);
+ dest.writeBoolean(mDragResizing);
dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 30da4b4..88447da 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -58,11 +58,12 @@
SyncNotedAppOp noteProxyOperation(int code, in AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation);
- SyncNotedAppOp startProxyOperation(int code, in AttributionSource attributionSource,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
- boolean shouldCollectMessage, boolean skipProxyOperation, int proxyAttributionFlags,
- int proxiedAttributionFlags, int attributionChainId);
- void finishProxyOperation(int code, in AttributionSource attributionSource,
+ SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+ in AttributionSource attributionSource, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+ boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags,
+ int attributionChainId);
+ void finishProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource,
boolean skipProxyOperation);
// Remaining methods are only used in Java.
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index 97f4b0f..a21a842 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -59,6 +59,7 @@
/**
* The state of the source process of an association.
*/
+ @SuppressWarnings("ParcelableCreator")
public static final class SourceState implements Parcelable {
private @NonNull final ProcessStats mProcessStats;
private @Nullable final AssociationState mAssociationState;
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index c62fba9..1e3714e 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -21,6 +21,7 @@
import android.view.InputChannel;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
import android.window.ImeOnBackInvokedDispatcher;
@@ -69,9 +70,11 @@
void setSessionEnabled(IInputMethodSession session, boolean enabled);
- void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver);
+ void showSoftInput(in IBinder showInputToken, in @nullable ImeTracker.Token statsToken,
+ int flags, in ResultReceiver resultReceiver);
- void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver);
+ void hideSoftInput(in IBinder hideInputToken, in @nullable ImeTracker.Token statsToken,
+ int flags, in ResultReceiver resultReceiver);
void updateEditorToolType(int toolType);
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 4babb70..f77e962 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -17,6 +17,7 @@
package com.android.internal.inputmethod;
import android.net.Uri;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.infra.AndroidFuture;
@@ -41,7 +42,8 @@
void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */);
void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
void notifyUserActionAsync();
- void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible);
+ void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
+ in @nullable ImeTracker.Token statsToken);
void onStylusHandwritingReady(int requestId, int pid);
void resetStylusHandwriting(int requestId);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 67c2103..66e3333 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -25,6 +25,7 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.GuardedBy;
@@ -100,7 +101,7 @@
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int}.
+ * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int)}.
*
* @param vis visibility flags
* @param backDisposition disposition flags
@@ -250,7 +251,7 @@
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, IVoidResultCallback)}
+ * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)}
*
* @param flags additional operating flags
* @param reason the reason to hide soft input
@@ -316,7 +317,7 @@
/**
* Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean,
- * IBooleanResultCallback)}
+ * AndroidFuture)}
*
* @param onlyCurrentIme {@code true} to switch to a {@link InputMethodSubtype} within the same
* IME
@@ -375,22 +376,25 @@
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean)}.
+ * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean,
+ * ImeTracker.Token)}.
*
* @param showOrHideInputToken placeholder token that maps to window requesting
* {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} or
- * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow
- * (IBinder, int)}
+ * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow(IBinder,
+ * int)}
* @param setVisible {@code true} to set IME visible, else hidden.
+ * @param statsToken the token tracking the current IME request or {@code null} otherwise.
*/
@AnyThread
- public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible) {
+ public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
+ @Nullable ImeTracker.Token statsToken) {
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
if (ops == null) {
return;
}
try {
- ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible);
+ ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 0a29fc52..eb62cb0 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -735,7 +735,7 @@
}
protected boolean shouldRecordDetailedData() {
- return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
+ return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
}
/**
diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java
index e9d55db..1276fb9 100644
--- a/core/java/com/android/internal/os/BinderLatencyObserver.java
+++ b/core/java/com/android/internal/os/BinderLatencyObserver.java
@@ -236,7 +236,7 @@
}
protected boolean shouldKeepSample() {
- return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
+ return mRandom.nextInt(mPeriodicSamplingInterval) == 0;
}
/** Updates the sampling interval. */
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 2805dcc..0645eb7 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -290,7 +290,7 @@
}
protected boolean shouldCollectDetailedData() {
- return ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+ return ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0;
}
private static class DispatchSession {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 3e988e6..145aeaf 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -2402,7 +2402,7 @@
return;
}
final ThreadedRenderer renderer = getThreadedRenderer();
- if (renderer != null) {
+ if (renderer != null && !CAPTION_ON_SHELL) {
loadBackgroundDrawablesIfNeeded();
WindowInsets rootInsets = getRootWindowInsets();
mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer,
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 8fcb6d5..4b7b91c 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -468,7 +468,7 @@
boolean shouldSample;
int traceThreshold;
synchronized (mLock) {
- shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+ shouldSample = ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0;
traceThreshold = mTraceThresholdPerAction[action];
}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index fe77236..2ac4309 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,6 +16,7 @@
package com.android.internal.view;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.input.InputManager;
import android.os.Bundle;
@@ -31,6 +32,7 @@
import android.view.PointerIcon;
import android.view.ScrollCaptureResponse;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -66,11 +68,13 @@
}
@Override
- public void showInsets(@InsetsType int types, boolean fromIme) {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
}
@Override
- public void hideInsets(@InsetsType int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
}
@Override
diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java
index 32ce0fe..1ae1307 100644
--- a/core/java/com/android/internal/view/BaseSurfaceHolder.java
+++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java
@@ -241,4 +241,4 @@
mSurfaceFrame.right = width;
mSurfaceFrame.bottom = height;
}
-};
+}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 40c6a05..00bc3f2 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -17,6 +17,7 @@
package com.android.internal.view;
import android.os.ResultReceiver;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
@@ -54,11 +55,12 @@
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
InputMethodSubtype getLastInputMethodSubtype(int userId);
- boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
- int lastClickToolType, in @nullable ResultReceiver resultReceiver, int reason);
- boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
+ boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
+ in @nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
in @nullable ResultReceiver resultReceiver, int reason);
-
+ boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
+ in @nullable ImeTracker.Token statsToken, int flags,
+ in @nullable ResultReceiver resultReceiver, int reason);
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'editorInfo' is non-null then also does startInput.
// @NonNull
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 5b08bb1..6063c90 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -54,6 +54,12 @@
// TODO(b/183140900) split store escrow key errors into detailed ones.
/**
+ * This is called when Weaver is guaranteed to be available (if the device supports Weaver).
+ * It does any synthetic password related work that was delayed from earlier in the boot.
+ */
+ public abstract void onThirdPartyAppsStarted();
+
+ /**
* Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
* doesn't have an LSKF.
* <p>
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7ada548..196ea59 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6184,6 +6184,116 @@
android:label="@string/permlab_foregroundService"
android:protectionLevel="normal|instant" />
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "camera".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"
+ android:description="@string/permdesc_foregroundServiceCamera"
+ android:label="@string/permlab_foregroundServiceCamera"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "connectedDevice".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"
+ android:description="@string/permdesc_foregroundServiceConnectedDevice"
+ android:label="@string/permlab_foregroundServiceConnectedDevice"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "dataSync".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"
+ android:description="@string/permdesc_foregroundServiceDataSync"
+ android:label="@string/permlab_foregroundServiceDataSync"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "location".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"
+ android:description="@string/permdesc_foregroundServiceLocation"
+ android:label="@string/permlab_foregroundServiceLocation"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "mediaPlayback".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"
+ android:description="@string/permdesc_foregroundServiceMediaPlayback"
+ android:label="@string/permlab_foregroundServiceMediaPlayback"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "mediaProjection".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"
+ android:description="@string/permdesc_foregroundServiceMediaProjection"
+ android:label="@string/permlab_foregroundServiceMediaProjection"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "microphone".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"
+ android:description="@string/permdesc_foregroundServiceMicrophone"
+ android:label="@string/permlab_foregroundServiceMicrophone"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "phoneCall".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"
+ android:description="@string/permdesc_foregroundServicePhoneCall"
+ android:label="@string/permlab_foregroundServicePhoneCall"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "health".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH"
+ android:description="@string/permdesc_foregroundServiceHealth"
+ android:label="@string/permlab_foregroundServiceHealth"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "remoteMessaging".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING"
+ android:description="@string/permdesc_foregroundServiceRemoteMessaging"
+ android:label="@string/permlab_foregroundServiceRemoteMessaging"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "systemExempted".
+ Apps are allowed to use this type only in the use cases listed in
+ {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"
+ android:description="@string/permdesc_foregroundServiceSystemExempted"
+ android:label="@string/permlab_foregroundServiceSystemExempted"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "specialUse".
+ <p>Protection level: signature|appop|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
+ android:description="@string/permdesc_foregroundServiceSpecialUse"
+ android:label="@string/permlab_foregroundServiceSpecialUse"
+ android:protectionLevel="signature|appop|instant" />
+
<!-- @SystemApi Allows to access all app shortcuts.
@hide -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index eac2b94..607467a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1586,19 +1586,71 @@
together. -->
<attr name="foregroundServiceType">
<!-- Data (photo, file, account) upload/download, backup/restore, import/export, fetch,
- transfer over network between device and cloud. -->
+ transfer over network between device and cloud.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, this type should NOT
+ be used: calling
+ {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+ this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ is still allowed, but calling it with this type on devices running future platform
+ releases may get a {@link android.app.ForegroundServiceTypeNotAllowedException}.
+ -->
<flag name="dataSync" value="0x01" />
- <!-- Music, video, news or other media play. -->
+ <!-- Music, video, news or other media play.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PLAYBACK}.
+ -->
<flag name="mediaPlayback" value="0x02" />
<!-- Ongoing operations related to phone calls, video conferencing,
- or similar interactive communication. -->
+ or similar interactive communication.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
+ {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+ -->
<flag name="phoneCall" value="0x04" />
- <!-- GPS, map, navigation location update. -->
+ <!-- GPS, map, navigation location update.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_LOCATION} and one of the
+ following permissions:
+ {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+ {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+ -->
<flag name="location" value="0x08" />
- <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction. -->
+ <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_CONNECTED_DEVICE} and one of the
+ following permissions:
+ {@link android.Manifest.permission#BLUETOOTH_CONNECT},
+ {@link android.Manifest.permission#CHANGE_NETWORK_STATE},
+ {@link android.Manifest.permission#CHANGE_WIFI_STATE},
+ {@link android.Manifest.permission#CHANGE_WIFI_MULTICAST_STATE},
+ {@link android.Manifest.permission#NFC},
+ {@link android.Manifest.permission#TRANSMIT_IR},
+ or has been granted the access to one of the attached USB devices/accessories.
+ -->
<flag name="connectedDevice" value="0x10" />
<!-- Managing a media projection session, e.g, for screen recording or taking
- screenshots.-->
+ screenshots.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROJECTION}, and the user
+ must have allowed the screen capture request from this app.
+ -->
<flag name="mediaProjection" value="0x20" />
<!-- Use the camera device or record video.
@@ -1606,6 +1658,12 @@
and above, a foreground service will not be able to access the camera if this type is
not specified in the manifest and in
{@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_CAMERA} and
+ {@link android.Manifest.permission#CAMERA}.
-->
<flag name="camera" value="0x40" />
<!--Use the microphone device or record audio.
@@ -1614,8 +1672,48 @@
and above, a foreground service will not be able to access the microphone if this type
is not specified in the manifest and in
{@link android.app.Service#startForeground(int, android.app.Notification, int)}.
+
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
+ service with this type will require permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_MICROPHONE} and one of the
+ following permissions:
+ {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT},
+ {@link android.Manifest.permission#RECORD_AUDIO}.
-->
<flag name="microphone" value="0x80" />
+ <!--Health, wellness and fitness.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following
+ permissions
+ {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
+ {@link android.Manifest.permission#BODY_SENSORS},
+ {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+ -->
+ <flag name="health" value="0x100" />
+ <!-- Messaging use cases which host local server to relay messages across devices.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_REMOTE_MESSAGING} in order to use
+ this type.
+ -->
+ <flag name="remoteMessaging" value="0x200" />
+ <!-- The system exmpted foreground service use cases.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_SYSTEM_EXEMPTED} in order to use
+ this type. Apps are allowed to use this type only in the use cases listed in
+ {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+ -->
+ <flag name="systemExempted" value="0x400" />
+ <!-- Use cases that can't be categorized into any other foreground service types, but also
+ can't use @link android.app.job.JobInfo.Builder} APIs.
+ See {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE} for the
+ best practice of the use of this type.
+
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_SPECIAL_USE} in order to use
+ this type.
+ -->
+ <flag name="specialUse" value="0x40000000" />
</attr>
<!-- Enable sampled memory bug detection in this process.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0218215..932b24e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1855,6 +1855,10 @@
-->
<string name="config_defaultCaptivePortalLoginPackageName" translatable="false">com.android.captiveportallogin</string>
+ <!-- The package name of the dock manager app. Must be granted the
+ POST_NOTIFICATIONS permission. -->
+ <string name="config_defaultDockManagerPackageName" translatable="false"></string>
+
<!-- Whether to enable geocoder overlay which allows geocoder to be replaced
by an app at run-time. When disabled, only the
config_geocoderProviderPackageName package will be searched for
@@ -2422,15 +2426,15 @@
<integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
<!-- Limit of how long the device can remain unlocked due to attention checking. -->
<integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. -->
- <!-- Is the system user the only user allowed to dream. -->
- <bool name="config_dreamsOnlyEnabledForSystemUser">false</bool>
+ <!-- Whether there is to be a chosen Dock User who is the only user allowed to dream. -->
+ <bool name="config_dreamsOnlyEnabledForDockUser">false</bool>
<!-- Whether dreams are disabled when ambient mode is suppressed. -->
<bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
<!-- The duration in milliseconds of the dream opening animation. -->
<integer name="config_dreamOpenAnimationDuration">250</integer>
<!-- The duration in milliseconds of the dream closing animation. -->
- <integer name="config_dreamCloseAnimationDuration">100</integer>
+ <integer name="config_dreamCloseAnimationDuration">300</integer>
<!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
@@ -2672,9 +2676,9 @@
Should be false for most devices, except automotive vehicle with passenger displays. -->
<bool name="config_multiuserUsersOnSecondaryDisplays">false</bool>
- <!-- Whether to automatically switch a non-primary user back to the primary user after a
- timeout when the device is docked. -->
- <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool>
+ <!-- Whether to automatically switch to the designated Dock User (the user chosen for
+ displaying dreams, etc.) after a timeout when the device is docked. -->
+ <bool name="config_enableTimeoutToDockUserWhenDocked">false</bool>
<!-- Whether to only install system packages on a user if they're allowlisted for that user
type. These are flags and can be freely combined.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 48484c7..7714082 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1143,6 +1143,66 @@
<string name="permdesc_foregroundService">Allows the app to make use of foreground services.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceCamera">run foreground service with the type \"camera\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceCamera">Allows the app to make use of foreground services with the type \"camera\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceConnectedDevice">run foreground service with the type \"connectedDevice\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceConnectedDevice">Allows the app to make use of foreground services with the type \"connectedDevice\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceDataSync">run foreground service with the type \"dataSync\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceDataSync">Allows the app to make use of foreground services with the type \"dataSync\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceLocation">run foreground service with the type \"location\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceLocation">Allows the app to make use of foreground services with the type \"location\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceMediaPlayback">run foreground service with the type \"mediaPlayback\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceMediaPlayback">Allows the app to make use of foreground services with the type \"mediaPlayback\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceMediaProjection">run foreground service with the type \"mediaProjection\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceMediaProjection">Allows the app to make use of foreground services with the type \"mediaProjection\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceMicrophone">run foreground service with the type \"microphone\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceMicrophone">Allows the app to make use of foreground services with the type \"microphone\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServicePhoneCall">run foreground service with the type \"phoneCall\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServicePhoneCall">Allows the app to make use of foreground services with the type \"phoneCall\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceHealth">run foreground service with the type \"health\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceHealth">Allows the app to make use of foreground services with the type \"health\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceRemoteMessaging">run foreground service with the type \"remoteMessaging\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceRemoteMessaging">Allows the app to make use of foreground services with the type \"remoteMessaging\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceSystemExempted">run foreground service with the type \"systemExempted\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceSystemExempted">Allows the app to make use of foreground services with the type \"systemExempted\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceSpecialUse">run foreground service with the type \"specialUse\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceSpecialUse">Allows the app to make use of foreground services with the type \"specialUse\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_getPackageSize">measure app storage space</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_getPackageSize">Allows the app to retrieve its code, data, and cache sizes</string>
@@ -6331,6 +6391,8 @@
<string name="vdm_camera_access_denied" product="tablet">Can’t access the tablet’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
<!-- Error message indicating the user cannot access secure content when running on a virtual device. [CHAR LIMIT=NONE] -->
<string name="vdm_secure_window">This can’t be accessed while streaming. Try on your phone instead.</string>
+ <!-- Error message indicating the user cannot view picture-in-picture when running on a virtual device. [CHAR LIMIT=NONE] -->
+ <string name="vdm_pip_blocked">Can’t view picture-in-picture while streaming</string>
<!-- Title for preference of the system default locale. [CHAR LIMIT=50]-->
<string name="system_locale_title">System default</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b5d534f..89ec5ba 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -466,7 +466,7 @@
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
<java-symbol type="bool" name="config_multiuserUsersOnSecondaryDisplays" />
- <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" />
+ <java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
<java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
<java-symbol type="xml" name="config_user_types" />
<java-symbol type="integer" name="config_safe_media_volume_index" />
@@ -2236,7 +2236,7 @@
<java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
<java-symbol type="string" name="config_dreamsDefaultComponent" />
<java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" />
- <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
+ <java-symbol type="bool" name="config_dreamsOnlyEnabledForDockUser" />
<java-symbol type="integer" name="config_dreamOpenAnimationDuration" />
<java-symbol type="integer" name="config_dreamCloseAnimationDuration" />
<java-symbol type="array" name="config_supportedDreamComplications" />
@@ -3480,6 +3480,9 @@
<!-- Captive Portal Login -->
<java-symbol type="string" name="config_defaultCaptivePortalLoginPackageName" />
+ <!-- Dock Manager -->
+ <java-symbol type="string" name="config_defaultDockManagerPackageName" />
+
<!-- Optional IPsec algorithms -->
<java-symbol type="array" name="config_optionalIpSecAlgorithms" />
@@ -4858,6 +4861,7 @@
<!-- For VirtualDeviceManager -->
<java-symbol type="string" name="vdm_camera_access_denied" />
<java-symbol type="string" name="vdm_secure_window" />
+ <java-symbol type="string" name="vdm_pip_blocked" />
<java-symbol type="color" name="camera_privacy_light_day"/>
<java-symbol type="color" name="camera_privacy_light_night"/>
diff --git a/core/res/res/xml/config_user_types.xml b/core/res/res/xml/config_user_types.xml
index 7663150..df6b7b2 100644
--- a/core/res/res/xml/config_user_types.xml
+++ b/core/res/res/xml/config_user_types.xml
@@ -83,6 +83,8 @@
For profile and full users:
default-restrictions (with values defined in UserRestrictionUtils.USER_RESTRICTIONS)
enabled
+ user-properties
+ max-allowed
For profile users only:
max-allowed-per-parent
icon-badge
diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
index 1cf4302..372bca4 100644
--- a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
@@ -88,6 +88,7 @@
}
}
+ @SuppressWarnings("ParcelableCreator")
@SuppressLint("ParcelCreator")
private static class PointArray implements Parcelable {
Rect mBounds = new Rect();
diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
index 7f81339..5ac84f8 100644
--- a/core/tests/coretests/src/android/app/activity/LocalReceiver.java
+++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
@@ -36,7 +36,8 @@
if (BroadcastTest.BROADCAST_FAIL_REGISTER.equals(intent.getAction())) {
resultString = "Successfully registered, but expected it to fail";
try {
- context.registerReceiver(this, new IntentFilter("foo.bar"));
+ context.registerReceiver(this, new IntentFilter("foo.bar"),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
context.unregisterReceiver(this);
} catch (ReceiverCallNotAllowedException e) {
//resultString = "This is the correct behavior but not yet implemented";
diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
index c89f37d..3f3d6a3 100644
--- a/core/tests/coretests/src/android/app/activity/ServiceTest.java
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -172,7 +172,7 @@
pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
mContext.unregisterReceiver(this);
}
- }, new IntentFilter(ACTION_SERVICE_STARTED));
+ }, new IntentFilter(ACTION_SERVICE_STARTED), Context.RECEIVER_EXPORTED_UNAUDITED);
serviceTrigger.run();
try {
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 67b24ec..bbd2ef3 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -63,18 +63,22 @@
public void testBackupLogger_rejectsRestoreLogs() {
mLogger = new BackupRestoreEventLogger(BACKUP);
- assertThat(mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5)).isFalse();
- assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
- assertThat(mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+ mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata");
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
}
@Test
public void testRestoreLogger_rejectsBackupLogs() {
mLogger = new BackupRestoreEventLogger(RESTORE);
- assertThat(mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5)).isFalse();
- assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
- assertThat(mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5);
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
+ mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata");
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
}
@Test
@@ -83,16 +87,17 @@
for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
String dataType = DATA_TYPE_1 + i;
- assertThat(mLogger.logItemsBackedUp(dataType, /* count */ 5)).isTrue();
- assertThat(mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null))
- .isTrue();
- assertThat(mLogger.logBackupMetaData(dataType, METADATA_1)).isTrue();
+ mLogger.logItemsBackedUp(dataType, /* count */ 5);
+ mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
+ mLogger.logBackupMetaData(dataType, METADATA_1);
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+ Optional.empty());
}
- assertThat(mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5)).isFalse();
- assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
- .isFalse();
- assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5);
+ mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
}
@@ -102,16 +107,17 @@
for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
String dataType = DATA_TYPE_1 + i;
- assertThat(mLogger.logItemsRestored(dataType, /* count */ 5)).isTrue();
- assertThat(mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null))
- .isTrue();
- assertThat(mLogger.logRestoreMetadata(dataType, METADATA_1)).isTrue();
+ mLogger.logItemsRestored(dataType, /* count */ 5);
+ mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(dataType, METADATA_1);
+
+ assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+ Optional.empty());
}
- assertThat(mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5)).isFalse();
- assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
- .isFalse();
- assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+ mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
similarity index 82%
rename from core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
rename to core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
index 0073d86..13e5e14 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ParcelableTestSupport.java
+++ b/core/tests/coretests/src/android/app/time/ParcelableTestSupport.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
import static org.junit.Assert.assertEquals;
@@ -48,6 +48,13 @@
}
public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
- assertEquals(instance, roundTripParcelable(instance));
+ assertEqualsAndHashCode(instance, roundTripParcelable(instance));
+ }
+
+ /** Asserts that the objects are equal and return identical hash codes. */
+ public static void assertEqualsAndHashCode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(two, one);
+ assertEquals(one.hashCode(), two.hashCode());
}
}
diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
index c9b96c6..1a276ad 100644
--- a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
@@ -21,7 +21,8 @@
import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
import static com.google.common.truth.Truth.assertThat;
@@ -55,7 +56,7 @@
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
- assertEquals(one, two);
+ assertEqualsAndHashCode(one, two);
}
builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -69,7 +70,7 @@
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
- assertEquals(one, two);
+ assertEqualsAndHashCode(one, two);
}
builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED);
@@ -83,7 +84,7 @@
{
TimeCapabilities one = builder1.build();
TimeCapabilities two = builder2.build();
- assertEquals(one, two);
+ assertEqualsAndHashCode(one, two);
}
}
diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java
index bce0909..25e6e2b 100644
--- a/core/tests/coretests/src/android/app/time/TimeStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java
@@ -16,7 +16,8 @@
package android.app.time;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
assertNotEquals(time1False_1, time2False);
}
- private static void assertEqualsAndHashCode(Object one, Object two) {
- assertEquals(one, two);
- assertEquals(one.hashCode(), two.hashCode());
- }
-
@Test
public void testParceling() {
UnixEpochTime time = new UnixEpochTime(1, 2);
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 3f7da8a..8bed31f 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -18,7 +18,7 @@
import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
import static com.google.common.truth.Truth.assertThat;
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
index 35a9dbc..595b700 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
@@ -16,7 +16,8 @@
package android.app.time;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
@@ -52,11 +53,6 @@
assertNotEquals(zone1False_1, zone2False);
}
- private static void assertEqualsAndHashCode(Object one, Object two) {
- assertEquals(one, two);
- assertEquals(one.hashCode(), two.hashCode());
- }
-
@Test
public void testParceling() {
assertRoundTripParcelable(new TimeZoneState("Europe/London", true));
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index 0c7c8c1..28da164 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -16,8 +16,8 @@
package android.app.timedetector;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
index 26cb902..e9ca069 100644
--- a/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/TelephonyTimeSuggestionTest.java
@@ -16,8 +16,8 @@
package android.app.timedetector;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
index 17838bb1..b5bdea7 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/ManualTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
package android.app.timezonedetector;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
index 28009d4..d5dcac2 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timezonedetector/TelephonyTimeZoneSuggestionTest.java
@@ -16,8 +16,8 @@
package android.app.timezonedetector;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.roundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
index d505492..86e95832 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import android.content.pm.PackageManager.Property;
@@ -162,40 +163,30 @@
@Test
public void testProperty_invalidName() throws Exception {
- try {
+ assertThrows(NullPointerException.class, () -> {
final Property p = new Property(null, 1, "android", null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
}
@Test
public void testProperty_invalidType() throws Exception {
- try {
+ assertThrows(IllegalArgumentException.class, () -> {
final Property p = new Property("invalidTypeProperty", 0, "android", null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
- try {
+ assertThrows(IllegalArgumentException.class, () -> {
final Property p = new Property("invalidTypeProperty", 6, "android", null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
- try {
+ assertThrows(IllegalArgumentException.class, () -> {
final Property p = new Property("invalidTypeProperty", -1, "android", null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
}
@Test
public void testProperty_noPackageName() throws Exception {
- try {
+ assertThrows(NullPointerException.class, () -> {
final Property p = new Property(null, 1, null, null);
- fail("expected assertion error");
- } catch (AssertionError expected) {
- }
+ });
}
}
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 7ebebc9..c59a3f5 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -246,10 +246,12 @@
@Test
public void getQFactorAndResonantFrequency_differentValues_returnsNaN() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(1f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
.build();
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(2f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
.build();
@@ -258,6 +260,7 @@
assertTrue(Float.isNaN(info.getQFactor()));
assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
// One vibrator with values undefined.
VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
@@ -266,16 +269,19 @@
assertTrue(Float.isNaN(info.getQFactor()));
assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(10f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 11, 10, 0.5f, null))
.build();
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(10f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 11, 5, 1, null))
@@ -285,113 +291,131 @@
assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+
+ // No frequency range defined.
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
}
@Test
public void getFrequencyProfile_noVibrator_returnsEmpty() {
VibratorInfo info = new SystemVibrator.NoVibratorInfo();
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, differentResonantFrequency});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
new float[] { 0, 1 }))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_missingValues_returnsEmpty() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingResonantFrequency});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
new float[] { 0, 1 }))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingMinFrequency});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
new float[] { 0, 1 }))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
new float[] { 0, 1, 1, 0 }))
.build();
VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
new float[] { 0, 1, 1, 0 }))
.build();
VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
new float[] { 0, 1, 1, 0 }))
.build();
VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
new float[] { 0.5f, 1, 1, 0.5f }))
.build();
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
new float[] { 1, 1, 1 }))
.build();
VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
new float[] { 0.8f, 1, 0.8f, 0.5f }))
.build();
@@ -401,6 +425,20 @@
assertEquals(
new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
info.getFrequencyProfile());
+ assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+
+ // Third vibrator without frequency control capability.
+ thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0.8f, 1, 0.8f, 0.5f }))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
+
+ assertEquals(
+ new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+ info.getFrequencyProfile());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
}
@Test
@@ -547,4 +585,12 @@
VibrationAttributes vibrationAttributes = captor.getValue();
assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
}
+
+ /**
+ * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
+ */
+ void assertEmptyFrequencyProfileAndControl(VibratorInfo info) {
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+ }
}
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
index 86ebdf3..7f772dd 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
@@ -16,7 +16,7 @@
package android.service.timezone;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
index b7a595c..9006cd9 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -16,20 +16,15 @@
package android.service.timezone;
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertThrows;
import org.junit.Test;
+/** Non-SDK tests. See CTS for SDK API tests. */
public class TimeZoneProviderStatusTest {
@Test
@@ -42,81 +37,4 @@
assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString()));
}
-
- @Test
- public void testStatusValidation() {
- TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
- .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
- .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
- .build();
-
- assertThrows(IllegalArgumentException.class,
- () -> new TimeZoneProviderStatus.Builder(status)
- .setLocationDetectionDependencyStatus(-1)
- .build());
- assertThrows(IllegalArgumentException.class,
- () -> new TimeZoneProviderStatus.Builder(status)
- .setConnectivityDependencyStatus(-1)
- .build());
- assertThrows(IllegalArgumentException.class,
- () -> new TimeZoneProviderStatus.Builder(status)
- .setTimeZoneResolutionOperationStatus(-1)
- .build());
- }
-
- @Test
- public void testEqualsAndHashcode() {
- TimeZoneProviderStatus status1_1 = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
- .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
- .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
- .build();
- assertEqualsAndHashcode(status1_1, status1_1);
- assertNotEquals(status1_1, null);
-
- {
- TimeZoneProviderStatus status1_2 =
- new TimeZoneProviderStatus.Builder(status1_1).build();
- assertEqualsAndHashcode(status1_1, status1_2);
- assertNotSame(status1_1, status1_2);
- }
-
- {
- TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
- .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
- .build();
- assertNotEquals(status1_1, status2);
- }
-
- {
- TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
- .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
- .build();
- assertNotEquals(status1_1, status2);
- }
-
- {
- TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
- .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
- .build();
- assertNotEquals(status1_1, status2);
- }
- }
-
- private static void assertEqualsAndHashcode(Object one, Object two) {
- assertEquals(one, two);
- assertEquals(two, one);
- assertEquals(one.hashCode(), two.hashCode());
- }
-
- @Test
- public void testParcelable() {
- TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
- .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
- .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
- .build();
- assertRoundTripParcelable(status);
- }
}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index ddcb175..0bf133f 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -24,6 +24,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -98,12 +99,12 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// test if setVisibility can show IME
mImeConsumer.onWindowFocusGained(true);
- mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
mController.cancelExistingAnimations();
assertTrue((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
// test if setVisibility can hide IME
- mController.hide(WindowInsets.Type.ime(), true /* fromIme */);
+ mController.hide(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
mController.cancelExistingAnimations();
assertFalse((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
});
@@ -117,7 +118,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// Request IME visible before control is available.
mImeConsumer.onWindowFocusGained(true);
- mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
// set control and verify visibility is applied.
InsetsSourceControl control =
@@ -125,9 +126,11 @@
mController.onControlsChanged(new InsetsSourceControl[] { control });
// IME show animation should be triggered when control becomes available.
verify(mController).applyAnimation(
- eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */);
+ eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */,
+ any() /* statsToken */);
verify(mController, never()).applyAnimation(
- eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */);
+ eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */,
+ any() /* statsToken */);
});
}
@@ -153,7 +156,8 @@
mImeConsumer.onWindowFocusGained(hasWindowFocus);
final boolean imeVisible = hasWindowFocus && hasViewFocus;
if (imeVisible) {
- mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */,
+ null /* statsToken */);
}
// set control and verify visibility is applied.
@@ -169,20 +173,21 @@
verify(control).getAndClearSkipAnimationOnce();
verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
eq(true) /* show */, eq(false) /* fromIme */,
- eq(expectSkipAnim) /* skipAnim */);
+ eq(expectSkipAnim) /* skipAnim */, null /* statsToken */);
}
// If previously hasViewFocus is false, verify when requesting the IME visible next
// time will not skip animation.
if (!hasViewFocus) {
- mController.show(WindowInsets.Type.ime(), true);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */,
+ null /* statsToken */);
mController.onControlsChanged(new InsetsSourceControl[]{ control });
// Verify IME show animation should be triggered when control becomes available and
// the animation will be skipped by getAndClearSkipAnimationOnce invoked.
verify(control).getAndClearSkipAnimationOnce();
verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
eq(true) /* show */, eq(true) /* fromIme */,
- eq(false) /* skipAnim */);
+ eq(false) /* skipAnim */, null /* statsToken */);
}
});
}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index e9cd8ad..c88255e 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -109,7 +109,8 @@
mController = new InsetsAnimationControlImpl(controls,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
mMockController, 10 /* durationMs */, new LinearInterpolator(),
- 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */);
+ 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
+ null /* statsToken */);
mController.setReadyDispatched(true);
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 409bae8..c6fa778 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -248,7 +248,7 @@
mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
verify(loggingListener).onReady(notNull(), anyInt());
});
}
@@ -260,14 +260,14 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
mController.show(all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimations();
final @InsetsType int types = navigationBars() | statusBars() | ime();
assertEquals(types, mController.getRequestedVisibleTypes() & types);
- mController.hide(ime(), true /* fromIme */);
+ mController.hide(ime(), true /* fromIme */, null /* statsToken */);
mController.hide(all());
mController.cancelExistingAnimations();
assertEquals(0, mController.getRequestedVisibleTypes() & types);
@@ -282,10 +282,10 @@
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
- mController.show(ime(), true /* fromIme */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
mController.cancelExistingAnimations();
assertTrue(isRequestedVisible(mController, ime()));
- mController.hide(ime(), true /* fromIme */);
+ mController.hide(ime(), true /* fromIme */, null /* statsToken */);
mController.cancelExistingAnimations();
assertFalse(isRequestedVisible(mController, ime()));
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
@@ -452,7 +452,7 @@
assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
// Gaining control shortly after
mController.onControlsChanged(createSingletonControl(ITYPE_IME));
@@ -476,7 +476,7 @@
mController.onControlsChanged(createSingletonControl(ITYPE_IME));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
mController.cancelExistingAnimations();
@@ -554,7 +554,7 @@
verify(listener, never()).onReady(any(), anyInt());
// Pretend that IME is calling.
- mController.show(ime(), true);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -638,7 +638,7 @@
mController.onControlsChanged(createSingletonControl(ITYPE_IME));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3);
@@ -845,7 +845,7 @@
// Showing invisible ime should only causes insets change once.
clearInvocations(mTestHost);
- mController.show(ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
verify(mTestHost, times(1)).notifyInsetsChanged();
// Sending the same insets state should not cause insets change.
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 35d5948..7a5ab045 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -27,6 +27,7 @@
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.window.ScreenCapture;
import java.util.Collections;
import java.util.List;
@@ -180,6 +181,10 @@
public void takeScreenshot(int displayId, RemoteCallback callback) {}
+ public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) {}
+
public void setFocusAppearance(int strokeWidth, int color) {}
public void setCacheEnabled(boolean enabled) {}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 00b3693..bbf9f3c 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -128,6 +128,7 @@
RemoteViews clone = child.clone();
}
+ @SuppressWarnings("ReturnValueIgnored")
@Test
public void clone_repeatedly() {
RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
@@ -485,6 +486,7 @@
}
}
+ @SuppressWarnings("ReturnValueIgnored")
@Test
public void nestedAddViews() {
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
@@ -509,6 +511,7 @@
parcelAndRecreate(views);
}
+ @SuppressWarnings("ReturnValueIgnored")
@Test
public void nestedLandscapeViews() {
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 82b2bf4..8207c9e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -1054,10 +1054,23 @@
super(new Injector() {
public Random getRandomGenerator() {
return new Random() {
- int mCallCount = 0;
+ int mCallCount = -1;
public int nextInt() {
- return mCallCount++;
+ throw new IllegalStateException("Should not use nextInt()");
+ }
+
+ public int nextInt(int x) {
+ if (mCallCount == -1) {
+ // The tests are written such that they expect
+ // the first call to nextInt() to be on the first
+ // callEnded(). However, the BinderCallsStats
+ // constructor also calls nextInt(). Fake 0 being
+ // rolled twice.
+ mCallCount++;
+ return 0;
+ }
+ return (mCallCount++) % x;
}
};
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index 5af7376..7bd53b9 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -98,7 +98,7 @@
assertEquals(1, latencyHistograms.size());
LatencyDims dims = latencyHistograms.keySet().iterator().next();
assertEquals(binder.getClass(), dims.getBinderClass());
- assertEquals(1, dims.getTransactionCode());
+ assertEquals(2, dims.getTransactionCode()); // the first nextInt() is in the constructor
assertThat(latencyHistograms.get(dims)).asList().containsExactly(1, 0, 0, 0, 0).inOrder();
}
@@ -313,11 +313,11 @@
int mCallCount = 0;
public int nextInt() {
- return mCallCount++;
+ throw new IllegalStateException("Should not use nextInt()");
}
public int nextInt(int x) {
- return 1;
+ return (mCallCount++) % x;
}
};
}
diff --git a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
index 6c50bce..8b30828 100644
--- a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
@@ -16,6 +16,8 @@
package com.android.internal.util;
+import static org.junit.Assert.assertThrows;
+
import android.os.SystemClock;
import android.text.format.DateUtils;
@@ -170,10 +172,9 @@
}
void assertThrow(Fn fn) {
- try {
+ assertThrows(Throwable.class, () -> {
fn.call();
- fail("expected n exception to be thrown.");
- } catch (Throwable t) { }
+ });
}
interface Fn { void call(); }
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
index 41b8956f..a226325 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/src/com/android/multidexlegacytestapp/Test.java
@@ -31,6 +31,7 @@
assertEquals(3366, getActivity().getValue());
}
+ @SuppressWarnings("ReturnValueIgnored")
public void testAnnotation() throws Exception {
assertEquals(ReferencedByAnnotation.B,
((AnnotationWithEnum) TestApplication.annotation).value());
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
index 3465989..2da9a2e 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
@@ -45,7 +44,6 @@
import org.junit.runners.JUnit4;
import java.util.Collections;
-import java.util.List;
import java.util.concurrent.TimeoutException;
@RunWith(JUnit4.class)
@@ -221,11 +219,56 @@
}
@Test
+ public void setResourceValue_withNullResourceName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(NullPointerException.class,
+ () -> builder.setResourceValue(null, TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withEmptyResourceName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue("", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withEmptyPackageName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue(":color/mycolor", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withInvalidTypeName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue("c/mycolor", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withEmptyTypeName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue("/mycolor", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
public void testInvalidResourceValues() throws Exception {
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
"android", TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
- .setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
+ .setResourceValue("color/something", TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
diff --git a/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
index c53f4cc..1581abb 100644
--- a/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
@@ -20,6 +20,7 @@
import org.junit.Test;
import java.util.ArrayList;
+import java.util.Objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -39,11 +40,11 @@
Integer argValue;
private void addNotifyCount(Integer callback) {
- if (callback == callback1) {
+ if (Objects.equals(callback, callback1)) {
notify1++;
- } else if (callback == callback2) {
+ } else if (Objects.equals(callback, callback2)) {
notify2++;
- } else if (callback == callback3) {
+ } else if (Objects.equals(callback, callback3)) {
notify3++;
}
deepNotifyCount[callback]++;
@@ -114,7 +115,7 @@
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
int arg1, Integer arg) {
addNotifyCount(callback);
- if (callback == callback1) {
+ if (Objects.equals(callback, callback1)) {
registry.remove(callback1);
registry.remove(callback2);
}
@@ -166,9 +167,9 @@
public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
int arg1, Integer arg) {
addNotifyCount(callback);
- if (callback == callback1) {
+ if (Objects.equals(callback, callback1)) {
registry.remove(callback2);
- } else if (callback == callback3) {
+ } else if (Objects.equals(callback, callback3)) {
registry.add(callback2);
}
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index decfb9f..3e2b71f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -495,6 +495,10 @@
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
<!-- Permission required for CTS test - CtsTelephonyTestCases -->
<permission name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
+ <!-- Permission required for CTS test - CtsAppTestCases -->
+ <permission name="android.permission.CAPTURE_MEDIA_OUTPUT" />
+ <permission name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
+ <permission name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
index 8706a68..42e3046 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientXmlChecker.java
@@ -168,6 +168,7 @@
*/
private static final Matcher<ExpressionTree> CONVERT_PRIMITIVE_TO_STRING =
new Matcher<ExpressionTree>() {
+ @SuppressWarnings("TreeToString") //TODO: Fix me
@Override
public boolean matches(ExpressionTree tree, VisitorState state) {
if (PRIMITIVE_TO_STRING.matches(tree, state)) {
@@ -205,6 +206,7 @@
*/
private static final Matcher<ExpressionTree> CONVERT_STRING_TO_PRIMITIVE =
new Matcher<ExpressionTree>() {
+ @SuppressWarnings("TreeToString") //TODO: Fix me
@Override
public boolean matches(ExpressionTree tree, VisitorState state) {
if (PRIMITIVE_PARSE.matches(tree, state)) {
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index abf7e99..42c892a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1667,6 +1667,9 @@
* effectively treating them as zeros. In API level {@value Build.VERSION_CODES#P} and above
* these parameters will be respected.
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param bitmap The bitmap to draw using the mesh
* @param meshWidth The number of columns in the mesh. Nothing is drawn if this is 0
* @param meshHeight The number of rows in the mesh. Nothing is drawn if this is 0
@@ -1678,7 +1681,7 @@
* null, there must be at least (meshWidth+1) * (meshHeight+1) + colorOffset values
* in the array.
* @param colorOffset Number of color elements to skip before drawing
- * @param paint May be null. The paint used to draw the bitmap
+ * @param paint May be null. The paint used to draw the bitmap. Antialiasing is not supported.
*/
public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
@NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
@@ -1832,9 +1835,12 @@
/**
* Draws the specified bitmap as an N-patch (most often, a 9-patch.)
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param patch The ninepatch object to render
* @param dst The destination rectangle.
- * @param paint The paint to draw the bitmap with. may be null
+ * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
*/
public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
super.drawPatch(patch, dst, paint);
@@ -1843,9 +1849,12 @@
/**
* Draws the specified bitmap as an N-patch (most often, a 9-patch.)
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param patch The ninepatch object to render
* @param dst The destination rectangle.
- * @param paint The paint to draw the bitmap with. may be null
+ * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
*/
public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
super.drawPatch(patch, dst, paint);
@@ -2278,6 +2287,9 @@
* array is optional, but if it is present, then it is used to specify the index of each
* triangle, rather than just walking through the arrays in order.
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param mode How to interpret the array of vertices
* @param vertexCount The number of values in the vertices array (and corresponding texs and
* colors arrays if non-null). Each logical vertex is two values (x, y), vertexCount
@@ -2292,8 +2304,9 @@
* @param colorOffset Number of values in colors to skip before drawing.
* @param indices If not null, array of indices to reference into the vertex (texs, colors)
* array.
- * @param indexCount number of entries in the indices array (if not null).
- * @param paint Specifies the shader to use if the texs array is non-null.
+ * @param indexCount Number of entries in the indices array (if not null).
+ * @param paint Specifies the shader to use if the texs array is non-null. Antialiasing is not
+ * supported.
*/
public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 1a80ab3..f0e496f 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -137,6 +137,13 @@
* <p>Enabling this flag will cause all draw operations that support
* antialiasing to use it.</p>
*
+ * <p>Notable draw operations that do <b>not</b> support antialiasing include:</p>
+ * <ul>
+ * <li>{@link android.graphics.Canvas#drawBitmapMesh}</li>
+ * <li>{@link android.graphics.Canvas#drawPatch}</li>
+ * <li>{@link android.graphics.Canvas#drawVertices}</li>
+ * </ul>
+ *
* @see #Paint(int)
* @see #setFlags(int)
*/
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 54c9f62..1a878df 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -766,7 +766,7 @@
if (mBackground != null) {
mBackground.onHotspotBoundsChanged();
}
- float newRadius = Math.round(getComputedRadius());
+ float newRadius = getComputedRadius();
for (int i = 0; i < mRunningAnimations.size(); i++) {
RippleAnimationSession s = mRunningAnimations.get(i);
s.setRadius(newRadius);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 4065bd1..e25ee90 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -60,7 +60,7 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Map;
import java.util.Stack;
/**
@@ -1171,18 +1171,14 @@
private static final int NATIVE_ALLOCATION_SIZE = 100;
- private static final HashMap<String, Integer> sPropertyIndexMap =
- new HashMap<String, Integer>() {
- {
- put("translateX", TRANSLATE_X_INDEX);
- put("translateY", TRANSLATE_Y_INDEX);
- put("scaleX", SCALE_X_INDEX);
- put("scaleY", SCALE_Y_INDEX);
- put("pivotX", PIVOT_X_INDEX);
- put("pivotY", PIVOT_Y_INDEX);
- put("rotation", ROTATION_INDEX);
- }
- };
+ private static final Map<String, Integer> sPropertyIndexMap = Map.of(
+ "translateX", TRANSLATE_X_INDEX,
+ "translateY", TRANSLATE_Y_INDEX,
+ "scaleX", SCALE_X_INDEX,
+ "scaleY", SCALE_Y_INDEX,
+ "pivotX", PIVOT_X_INDEX,
+ "pivotY", PIVOT_Y_INDEX,
+ "rotation", ROTATION_INDEX);
static int getPropertyIndex(String propertyName) {
if (sPropertyIndexMap.containsKey(propertyName)) {
@@ -1285,18 +1281,15 @@
}
};
- private static final HashMap<String, Property> sPropertyMap =
- new HashMap<String, Property>() {
- {
- put("translateX", TRANSLATE_X);
- put("translateY", TRANSLATE_Y);
- put("scaleX", SCALE_X);
- put("scaleY", SCALE_Y);
- put("pivotX", PIVOT_X);
- put("pivotY", PIVOT_Y);
- put("rotation", ROTATION);
- }
- };
+ private static final Map<String, Property> sPropertyMap = Map.of(
+ "translateX", TRANSLATE_X,
+ "translateY", TRANSLATE_Y,
+ "scaleX", SCALE_X,
+ "scaleY", SCALE_Y,
+ "pivotX", PIVOT_X,
+ "pivotY", PIVOT_Y,
+ "rotation", ROTATION);
+
// Temp array to store transform values obtained from native.
private float[] mTransform;
/////////////////////////////////////////////////////
@@ -1762,19 +1755,15 @@
private static final int NATIVE_ALLOCATION_SIZE = 264;
// Property map for animatable attributes.
- private final static HashMap<String, Integer> sPropertyIndexMap
- = new HashMap<String, Integer> () {
- {
- put("strokeWidth", STROKE_WIDTH_INDEX);
- put("strokeColor", STROKE_COLOR_INDEX);
- put("strokeAlpha", STROKE_ALPHA_INDEX);
- put("fillColor", FILL_COLOR_INDEX);
- put("fillAlpha", FILL_ALPHA_INDEX);
- put("trimPathStart", TRIM_PATH_START_INDEX);
- put("trimPathEnd", TRIM_PATH_END_INDEX);
- put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
- }
- };
+ private static final Map<String, Integer> sPropertyIndexMap = Map.of(
+ "strokeWidth", STROKE_WIDTH_INDEX,
+ "strokeColor", STROKE_COLOR_INDEX,
+ "strokeAlpha", STROKE_ALPHA_INDEX,
+ "fillColor", FILL_COLOR_INDEX,
+ "fillAlpha", FILL_ALPHA_INDEX,
+ "trimPathStart", TRIM_PATH_START_INDEX,
+ "trimPathEnd", TRIM_PATH_END_INDEX,
+ "trimPathOffset", TRIM_PATH_OFFSET_INDEX);
// Below are the Properties that wrap the setters to avoid reflection overhead in animations
private static final Property<VFullPath, Float> STROKE_WIDTH =
@@ -1881,19 +1870,15 @@
}
};
- private final static HashMap<String, Property> sPropertyMap
- = new HashMap<String, Property> () {
- {
- put("strokeWidth", STROKE_WIDTH);
- put("strokeColor", STROKE_COLOR);
- put("strokeAlpha", STROKE_ALPHA);
- put("fillColor", FILL_COLOR);
- put("fillAlpha", FILL_ALPHA);
- put("trimPathStart", TRIM_PATH_START);
- put("trimPathEnd", TRIM_PATH_END);
- put("trimPathOffset", TRIM_PATH_OFFSET);
- }
- };
+ private static final Map<String, Property> sPropertyMap = Map.of(
+ "strokeWidth", STROKE_WIDTH,
+ "strokeColor", STROKE_COLOR,
+ "strokeAlpha", STROKE_ALPHA,
+ "fillColor", FILL_COLOR,
+ "fillAlpha", FILL_ALPHA,
+ "trimPathStart", TRIM_PATH_START,
+ "trimPathEnd", TRIM_PATH_END,
+ "trimPathOffset", TRIM_PATH_OFFSET);
// Temp array to store property data obtained from native getter.
private byte[] mPropertyData;
diff --git a/graphics/java/android/graphics/fonts/FontStyle.java b/graphics/java/android/graphics/fonts/FontStyle.java
index 09799fd..48969aa 100644
--- a/graphics/java/android/graphics/fonts/FontStyle.java
+++ b/graphics/java/android/graphics/fonts/FontStyle.java
@@ -48,6 +48,10 @@
private static final String TAG = "FontStyle";
/**
+ * A default value when font weight is unspecified
+ */
+ public static final int FONT_WEIGHT_UNSPECIFIED = -1;
+ /**
* A minimum weight value for the font
*/
public static final int FONT_WEIGHT_MIN = 1;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index a7fa2d9..16760e26 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -96,7 +96,7 @@
ActivityEmbeddingComponent {
static final String TAG = "SplitController";
static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
@VisibleForTesting
@GuardedBy("mLock")
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index df5f921..c6197c8 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -111,4 +111,8 @@
<!-- Whether to dim a split-screen task when the other is the IME target -->
<bool name="config_dimNonImeAttachedSide">true</bool>
+
+ <!-- Components support to launch multiple instances into split-screen -->
+ <string-array name="config_componentsSupportMultiInstancesSplit">
+ </string-array>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index dec1e38..065fd95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,14 +16,12 @@
package com.android.wm.shell;
-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.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -49,7 +47,6 @@
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
-import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -568,6 +565,22 @@
}
}
+ /**
+ * Return list of {@link RunningTaskInfo}s for the given display.
+ *
+ * @return filtered list of tasks or empty list
+ */
+ public ArrayList<RunningTaskInfo> getRunningTasks(int displayId) {
+ ArrayList<RunningTaskInfo> result = new ArrayList<>();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId) {
+ result.add(taskInfo);
+ }
+ }
+ return result;
+ }
+
/** Gets running task by taskId. Returns {@code null} if no such task observed. */
@Nullable
public RunningTaskInfo getRunningTaskInfo(int taskId) {
@@ -694,57 +707,6 @@
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
}
- /**
- * Create a {@link WindowContainerTransaction} to clear task bounds.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have bounds cleared
- * @return {@link WindowContainerTransaction} with pending operations to clear bounds
- */
- public WindowContainerTransaction prepareClearBoundsForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if ((taskInfo.displayId == displayId) && (taskInfo.getActivityType()
- == WindowConfiguration.ACTIVITY_TYPE_STANDARD)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
- taskInfo.token, taskInfo);
- wct.setBounds(taskInfo.token, null);
- }
- }
- return wct;
- }
-
- /**
- * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have windowing mode reset to {@link
- * WindowConfiguration#WINDOWING_MODE_UNDEFINED}
- * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
- */
- public WindowContainerTransaction prepareClearFreeformForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if (taskInfo.displayId == displayId
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
- taskInfo);
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- }
- }
- return wct;
- }
-
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index b6327e5..07cd7d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -38,7 +38,6 @@
import android.provider.Settings.Global;
import android.util.Log;
import android.util.SparseArray;
-import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowFocusObserver;
import android.view.InputDevice;
@@ -69,7 +68,7 @@
* Controls the window animation run when a user initiates a back gesture.
*/
public class BackAnimationController implements RemoteCallable<BackAnimationController> {
- private static final String TAG = "BackAnimationController";
+ private static final String TAG = "ShellBackPreview";
private static final int SETTING_VALUE_OFF = 0;
private static final int SETTING_VALUE_ON = 1;
public static final boolean IS_ENABLED =
@@ -82,16 +81,15 @@
/** Predictive back animation developer option */
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
/**
- * Max duration to wait for a transition to finish before accepting another gesture start
- * request.
+ * Max duration to wait for an animation to finish before triggering the real back.
*/
- private static final long MAX_TRANSITION_DURATION = 2000;
+ private static final long MAX_ANIMATION_DURATION = 2000;
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
- /** Tracks if an uninterruptible transition is in progress */
- private boolean mTransitionInProgress = false;
+ /** Tracks if an uninterruptible animation is in progress */
+ private boolean mPostCommitAnimationInProgress = false;
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
/** @see #setTriggerBack(boolean) */
@@ -105,9 +103,9 @@
private final ShellController mShellController;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
- private final Runnable mResetTransitionRunnable = () -> {
- ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Transition didn't finish in %d ms. Resetting...",
- MAX_TRANSITION_DURATION);
+ private final Runnable mAnimationTimeoutRunnable = () -> {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...",
+ MAX_ANIMATION_DURATION);
onBackAnimationFinished();
};
@@ -119,6 +117,8 @@
private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
+ private IOnBackInvokedCallback mActiveCallback;
+
@VisibleForTesting
final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
@Override
@@ -126,9 +126,9 @@
@Override
public void focusLost(IBinder inputToken) {
mShellExecutor.execute(() -> {
- if (!mBackGestureStarted || mTransitionInProgress) {
- // If an uninterruptible transition is already in progress, we should ignore
- // this due to the transition may cause focus lost. (alpha = 0)
+ if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
+ // If an uninterruptible animation is already in progress, we should ignore
+ // this due to it may cause focus lost. (alpha = 0)
return;
}
ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus.");
@@ -180,26 +180,11 @@
}
private void initBackAnimationRunners() {
- final IOnBackInvokedCallback dummyCallback = new IOnBackInvokedCallback.Default();
- final IRemoteAnimationRunner dummyRunner = new IRemoteAnimationRunner.Default() {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
- // Animation missing. Simply finish animation.
- finishedCallback.onAnimationFinished();
- }
- };
-
- final BackAnimationRunner dummyBackRunner =
- new BackAnimationRunner(dummyCallback, dummyRunner);
final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
// TODO (238474994): register cross activity animation when it's completed.
- mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, dummyBackRunner);
// TODO (236760237): register dialog close animation when it's completed.
- mAnimationDefinition.set(BackNavigationInfo.TYPE_DIALOG_CLOSE, dummyBackRunner);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -222,10 +207,9 @@
private void updateEnableAnimationFromSetting() {
int settingValue = Global.getInt(mContext.getContentResolver(),
Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
- boolean isEnabled = settingValue == SETTING_VALUE_ON;
+ boolean isEnabled = settingValue == SETTING_VALUE_ON && IS_U_ANIMATION_ENABLED;
mEnableAnimations.set(isEnabled);
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s",
- isEnabled);
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
}
public BackAnimation getBackAnimationImpl() {
@@ -279,7 +263,9 @@
public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
IRemoteAnimationRunner runner) {
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
- (controller) -> controller.setBackToLauncherCallback(callback, runner));
+ (controller) -> controller.registerAnimation(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ new BackAnimationRunner(callback, runner)));
}
@Override
@@ -294,44 +280,22 @@
}
}
- @VisibleForTesting
- void setBackToLauncherCallback(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
- mAnimationDefinition.set(BackNavigationInfo.TYPE_RETURN_TO_HOME,
- new BackAnimationRunner(callback, runner));
+ void registerAnimation(@BackNavigationInfo.BackTargetType int type,
+ @NonNull BackAnimationRunner runner) {
+ mAnimationDefinition.set(type, runner);
}
private void clearBackToLauncherCallback() {
mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
}
- @VisibleForTesting
- void onBackAnimationFinished() {
- if (!mTransitionInProgress) {
- return;
- }
-
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
-
- // Trigger real back.
- if (mBackNavigationInfo != null) {
- IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
- if (mTriggerBack) {
- dispatchOnBackInvoked(callback);
- } else {
- dispatchOnBackCancelled(callback);
- }
- }
-
- finishBackNavigation();
- }
-
/**
* Called when a new motion event needs to be transferred to this
* {@link BackAnimationController}
*/
public void onMotionEvent(float touchX, float touchY, int keyAction,
@BackEvent.SwipeEdge int swipeEdge) {
- if (mTransitionInProgress) {
+ if (mPostCommitAnimationInProgress) {
return;
}
@@ -348,7 +312,7 @@
onGestureStarted(touchX, touchY, swipeEdge);
mShouldStartOnNextMoveEvent = false;
}
- onMove(touchX, touchY, swipeEdge);
+ onMove();
} else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
"Finishing gesture with event action: %d", keyAction);
@@ -386,30 +350,22 @@
return;
}
final int backType = backNavigationInfo.getType();
- final IOnBackInvokedCallback targetCallback;
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
if (shouldDispatchToAnimator) {
+ mActiveCallback = mAnimationDefinition.get(backType).getCallback();
mAnimationDefinition.get(backType).startGesture();
} else {
- targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- dispatchOnBackStarted(targetCallback, mTouchTracker.createStartEvent(null));
+ mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+ dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null));
}
}
- private void onMove(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
+ private void onMove() {
if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) {
return;
}
final BackEvent backEvent = mTouchTracker.createProgressEvent();
-
- int backType = mBackNavigationInfo.getType();
- IOnBackInvokedCallback targetCallback;
- if (shouldDispatchToAnimator(backType)) {
- targetCallback = mAnimationDefinition.get(backType).getCallback();
- } else {
- targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
- }
- dispatchOnBackProgressed(targetCallback, backEvent);
+ dispatchOnBackProgressed(mActiveCallback, backEvent);
}
private void injectBackKey() {
@@ -431,57 +387,6 @@
}
}
- private void onGestureFinished(boolean fromTouch) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
- if (!mBackGestureStarted) {
- finishBackNavigation();
- return;
- }
-
- if (fromTouch) {
- // Let touch reset the flag otherwise it will start a new back navigation and refresh
- // the info when received a new move event.
- mBackGestureStarted = false;
- }
-
- if (mTransitionInProgress) {
- return;
- }
-
- if (mBackNavigationInfo == null) {
- // No focus window found or core are running recents animation, inject back key as
- // legacy behavior.
- if (mTriggerBack) {
- injectBackKey();
- }
- finishBackNavigation();
- return;
- }
-
- int backType = mBackNavigationInfo.getType();
- boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
- final BackAnimationRunner runner = mAnimationDefinition.get(backType);
- IOnBackInvokedCallback targetCallback = shouldDispatchToAnimator
- ? runner.getCallback() : mBackNavigationInfo.getOnBackInvokedCallback();
-
- if (shouldDispatchToAnimator) {
- if (runner.onGestureFinished(mTriggerBack)) {
- Log.w(TAG, "Gesture released, but animation didn't ready.");
- return;
- }
- startTransition();
- }
- if (mTriggerBack) {
- dispatchOnBackInvoked(targetCallback);
- } else {
- dispatchOnBackCancelled(targetCallback);
- }
- if (!shouldDispatchToAnimator) {
- // Animation callback missing. Simply finish animation.
- finishBackNavigation();
- }
- }
-
private boolean shouldDispatchToAnimator(int backType) {
return mEnableAnimations.get()
&& mBackNavigationInfo != null
@@ -495,7 +400,7 @@
return;
}
try {
- if (shouldDispatchAnimation(callback)) {
+ if (mEnableAnimations.get()) {
callback.onBackStarted(backEvent);
}
} catch (RemoteException e) {
@@ -519,7 +424,7 @@
return;
}
try {
- if (shouldDispatchAnimation(callback)) {
+ if (mEnableAnimations.get()) {
callback.onBackCancelled();
}
} catch (RemoteException e) {
@@ -533,7 +438,7 @@
return;
}
try {
- if (shouldDispatchAnimation(callback)) {
+ if (mEnableAnimations.get()) {
callback.onBackProgressed(backEvent);
}
} catch (RemoteException e) {
@@ -541,17 +446,11 @@
}
}
- private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
- return (IS_U_ANIMATION_ENABLED || callback == mAnimationDefinition.get(
- BackNavigationInfo.TYPE_RETURN_TO_HOME).getCallback())
- && mEnableAnimations.get();
- }
-
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
public void setTriggerBack(boolean triggerBack) {
- if (mTransitionInProgress) {
+ if (mPostCommitAnimationInProgress) {
return;
}
mTriggerBack = triggerBack;
@@ -562,6 +461,109 @@
mTouchTracker.setProgressThreshold(progressThreshold);
}
+ /**
+ * Called when the gesture is released, then it could start the post commit animation.
+ */
+ private void onGestureFinished(boolean fromTouch) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
+ if (!mBackGestureStarted) {
+ finishBackNavigation();
+ return;
+ }
+
+ if (fromTouch) {
+ // Let touch reset the flag otherwise it will start a new back navigation and refresh
+ // the info when received a new move event.
+ mBackGestureStarted = false;
+ }
+
+ if (mPostCommitAnimationInProgress) {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running");
+ return;
+ }
+
+ if (mBackNavigationInfo == null) {
+ // No focus window found or core are running recents animation, inject back key as
+ // legacy behavior.
+ if (mTriggerBack) {
+ injectBackKey();
+ }
+ finishBackNavigation();
+ return;
+ }
+
+ final int backType = mBackNavigationInfo.getType();
+ // Directly finish back navigation if no animator defined.
+ if (!shouldDispatchToAnimator(backType)) {
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(mActiveCallback);
+ } else {
+ dispatchOnBackCancelled(mActiveCallback);
+ }
+ // Animation missing. Simply finish back navigation.
+ finishBackNavigation();
+ return;
+ }
+
+ final BackAnimationRunner runner = mAnimationDefinition.get(backType);
+ if (runner.isWaitingAnimation()) {
+ ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+ return;
+ }
+ startPostCommitAnimation();
+ }
+
+ /**
+ * Start the phase 2 animation when gesture is released.
+ * Callback to {@link #onBackAnimationFinished} when it is finished or timeout.
+ */
+ private void startPostCommitAnimation() {
+ if (mPostCommitAnimationInProgress) {
+ return;
+ }
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
+ mPostCommitAnimationInProgress = true;
+ mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
+
+ // The next callback should be {@link #onBackAnimationFinished}.
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(mActiveCallback);
+ } else {
+ dispatchOnBackCancelled(mActiveCallback);
+ }
+ }
+
+ /**
+ * Called when the post commit animation is completed or timeout.
+ * This will trigger the real {@link IOnBackInvokedCallback} behavior.
+ */
+ @VisibleForTesting
+ void onBackAnimationFinished() {
+ if (!mPostCommitAnimationInProgress) {
+ return;
+ }
+ // Stop timeout runner.
+ mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
+ mPostCommitAnimationInProgress = false;
+
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
+
+ // Trigger the real back.
+ if (mBackNavigationInfo != null) {
+ IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(callback);
+ } else {
+ dispatchOnBackCancelled(callback);
+ }
+ }
+
+ finishBackNavigation();
+ }
+
+ /**
+ * This should be called after the whole back navigation is completed.
+ */
@VisibleForTesting
void finishBackNavigation() {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
@@ -571,10 +573,10 @@
mTriggerBack = false;
mShouldStartOnNextMoveEvent = false;
mTouchTracker.reset();
+ mActiveCallback = null;
if (backNavigationInfo == null) {
return;
}
- stopTransition();
if (mBackAnimationFinishedCallback != null) {
try {
mBackAnimationFinishedCallback.onAnimationFinished(triggerBack);
@@ -586,19 +588,6 @@
backNavigationInfo.onBackNavigationFinished(triggerBack);
}
- private void startTransition() {
- if (mTransitionInProgress) {
- return;
- }
- mTransitionInProgress = true;
- mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION);
- }
-
- void stopTransition() {
- mShellExecutor.removeCallbacks(mResetTransitionRunnable);
- mTransitionInProgress = false;
- }
-
private void createAdapter() {
IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
@Override
@@ -622,22 +611,20 @@
mBackAnimationFinishedCallback = finishedCallback;
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
- runner.startAnimation(apps, wallpapers, nonApps,
- BackAnimationController.this::onBackAnimationFinished);
+ runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
+ BackAnimationController.this::onBackAnimationFinished));
+
if (apps.length >= 1) {
- final int backType = mBackNavigationInfo.getType();
- IOnBackInvokedCallback targetCallback = mAnimationDefinition.get(backType)
- .getCallback();
dispatchOnBackStarted(
- targetCallback, mTouchTracker.createStartEvent(apps[0]));
+ mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
}
if (!mBackGestureStarted) {
// if the down -> up gesture happened before animation start, we have to
// trigger the uninterruptible transition to finish the back animation.
- final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
- startTransition();
- runner.consumeIfGestureFinished(backFinish);
+ final BackEvent backFinish = mTouchTracker.createProgressEvent();
+ dispatchOnBackProgressed(mActiveCallback, backFinish);
+ startPostCommitAnimation();
}
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index c53fcfc..d70b8f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -18,12 +18,12 @@
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+import android.annotation.NonNull;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
-import android.window.BackEvent;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
@@ -38,11 +38,11 @@
private final IOnBackInvokedCallback mCallback;
private final IRemoteAnimationRunner mRunner;
- private boolean mTriggerBack;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
- BackAnimationRunner(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
+ BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner) {
mCallback = callback;
mRunner = runner;
}
@@ -83,25 +83,7 @@
mWaitingAnimation = true;
}
- boolean onGestureFinished(boolean triggerBack) {
- if (mWaitingAnimation) {
- mTriggerBack = triggerBack;
- return true;
- }
- return false;
- }
-
- void consumeIfGestureFinished(final BackEvent backFinish) {
- Log.d(TAG, "Start transition due to gesture is finished");
- try {
- mCallback.onBackProgressed(backFinish);
- if (mTriggerBack) {
- mCallback.onBackInvoked();
- } else {
- mCallback.onBackCancelled();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "dispatch error: ", e);
- }
+ boolean isWaitingAnimation() {
+ return mWaitingAnimation;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index a400555..1fd91de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -709,7 +709,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index bb7c4134..d9b4f47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.graphics.Point;
@@ -38,6 +39,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManagerGlobal;
import androidx.annotation.VisibleForTesting;
@@ -112,7 +114,7 @@
}
if (mDisplayController.getDisplayLayout(displayId).rotation()
!= pd.mRotation && isImeShowing(displayId)) {
- pd.startAnimation(true, false /* forceRestart */);
+ pd.startAnimation(true, false /* forceRestart */, null /* statsToken */);
}
}
@@ -244,7 +246,7 @@
mInsetsState.set(insetsState, true /* copySources */);
if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
- startAnimation(mImeShowing, true /* forceRestart */);
+ startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
}
}
@@ -280,7 +282,7 @@
!haveSameLeash(mImeSourceControl, imeSourceControl);
if (mAnimation != null) {
if (positionChanged) {
- startAnimation(mImeShowing, true /* forceRestart */);
+ startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
}
} else {
if (leashChanged) {
@@ -312,21 +314,23 @@
}
@Override
- public void showInsets(int types, boolean fromIme) {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
- startAnimation(true /* show */, false /* forceRestart */);
+ startAnimation(true /* show */, false /* forceRestart */, statsToken);
}
@Override
- public void hideInsets(int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
- startAnimation(false /* show */, false /* forceRestart */);
+ startAnimation(false /* show */, false /* forceRestart */, statsToken);
}
@Override
@@ -367,9 +371,11 @@
.navBarFrameHeight();
}
- private void startAnimation(final boolean show, final boolean forceRestart) {
+ private void startAnimation(final boolean show, final boolean forceRestart,
+ @Nullable ImeTracker.Token statsToken) {
final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
if (imeSource == null || mImeSourceControl == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
final Rect newFrame = imeSource.getFrame();
@@ -390,8 +396,9 @@
+ (mAnimationDirection == DIRECTION_SHOW ? "SHOW"
: (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE")));
}
- if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show)
+ if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
|| (mAnimationDirection == DIRECTION_HIDE && !show)) {
+ ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
boolean seek = false;
@@ -435,8 +442,11 @@
mTransactionPool.release(t);
});
mAnimation.setInterpolator(INTERPOLATOR);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
mAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled = false;
+ @Nullable
+ private final ImeTracker.Token mStatsToken = statsToken;
@Override
public void onAnimationStart(Animator animation) {
@@ -455,6 +465,8 @@
: 1.f;
t.setAlpha(mImeSourceControl.getLeash(), alpha);
if (mAnimationDirection == DIRECTION_SHOW) {
+ ImeTracker.get().onProgress(mStatsToken,
+ ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.show(mImeSourceControl.getLeash());
}
t.apply();
@@ -476,8 +488,16 @@
}
dispatchEndPositioning(mDisplayId, mCancelled, t);
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
+ ImeTracker.get().onProgress(mStatsToken,
+ ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.hide(mImeSourceControl.getLeash());
removeImeSurface();
+ ImeTracker.get().onHidden(mStatsToken);
+ } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
+ ImeTracker.get().onShown(mStatsToken);
+ } else if (mCancelled) {
+ ImeTracker.get().onCancelled(mStatsToken,
+ ImeTracker.PHASE_WM_ANIMATION_RUNNING);
}
t.apply();
mTransactionPool.release(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 8d4a09d..8759301 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.RemoteException;
import android.util.Slog;
@@ -25,6 +26,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
import androidx.annotation.BinderThread;
@@ -156,23 +158,29 @@
}
}
- private void showInsets(int types, boolean fromIme) {
+ private void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
- listener.showInsets(types, fromIme);
+ listener.showInsets(types, fromIme, statsToken);
}
}
- private void hideInsets(int types, boolean fromIme) {
+ private void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
- listener.hideInsets(types, fromIme);
+ listener.hideInsets(types, fromIme, statsToken);
}
}
@@ -214,16 +222,18 @@
}
@Override
- public void showInsets(int types, boolean fromIme) throws RemoteException {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.showInsets(types, fromIme);
+ PerDisplay.this.showInsets(types, fromIme, statsToken);
});
}
@Override
- public void hideInsets(int types, boolean fromIme) throws RemoteException {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.hideInsets(types, fromIme);
+ PerDisplay.this.hideInsets(types, fromIme, statsToken);
});
}
}
@@ -263,15 +273,21 @@
*
* @param types {@link InsetsType} to show
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME show request
+ * or {@code null} otherwise.
*/
- default void showInsets(@InsetsType int types, boolean fromIme) {}
+ default void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {}
/**
* Called when a set of insets source window should be hidden by policy.
*
* @param types {@link InsetsType} to hide
* @param fromIme true if this request originated from IME (InputMethodService).
+ * @param statsToken the token tracking the current IME hide request
+ * or {@code null} otherwise.
*/
- default void hideInsets(@InsetsType int types, boolean fromIme) {}
+ default void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index e270edb..af13bf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -19,6 +19,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Region;
@@ -46,6 +47,7 @@
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -351,10 +353,10 @@
InsetsSourceControl[] activeControls) {}
@Override
- public void showInsets(int types, boolean fromIme) {}
+ public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
@Override
- public void hideInsets(int types, boolean fromIme) {}
+ public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
@Override
public void moved(int newX, int newY) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 839edc8..3de1045 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -601,7 +601,7 @@
animator.start();
}
- /** Swich both surface position with animation. */
+ /** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
final boolean isLandscape = isLandscape();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 4459f57..f1670cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,7 +27,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
@@ -272,12 +271,11 @@
TaskStackListenerImpl taskStackListener,
UiEventLogger uiEventLogger,
InteractionJankMonitor jankMonitor,
- RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
- uiEventLogger, rootDisplayAreaOrganizer, mainExecutor, mainHandler);
+ uiEventLogger, mainExecutor, mainHandler);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 34ff6d8..abc4024 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -16,8 +16,11 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+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.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -151,21 +154,18 @@
int displayId = mContext.getDisplayId();
+ ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
+
WindowContainerTransaction wct = new WindowContainerTransaction();
- // Reset freeform windowing mode that is set per task level (tasks should inherit
- // container value)
- wct.merge(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(displayId),
- true /* transfer */);
- int targetWindowingMode;
+ // Reset freeform windowing mode that is set per task level so tasks inherit it
+ clearFreeformForStandardTasks(runningTasks, wct);
if (active) {
- targetWindowingMode = WINDOWING_MODE_FREEFORM;
+ moveHomeBehindVisibleTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
} else {
- targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
- // Clear any resized bounds
- wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
- true /* transfer */);
+ clearBoundsForStandardTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
}
- prepareWindowingModeChange(wct, displayId, targetWindowingMode);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
} else {
@@ -173,17 +173,69 @@
}
}
- private void prepareWindowingModeChange(WindowContainerTransaction wct,
- int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
- DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer
- .getDisplayAreaInfo(displayId);
+ private WindowContainerTransaction clearBoundsForStandardTasks(
+ ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+ taskInfo.token, taskInfo);
+ wct.setBounds(taskInfo.token, null);
+ }
+ }
+ return wct;
+ }
+
+ private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
+ "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+ taskInfo);
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ }
+ }
+ }
+
+ private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
+ RunningTaskInfo homeTask = null;
+ ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+ homeTask = taskInfo;
+ } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+ && taskInfo.isVisible()) {
+ visibleTasks.add(taskInfo);
+ }
+ }
+ if (homeTask == null) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
+ } else {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
+ visibleTasks.size());
+ wct.reorder(homeTask.getToken(), true /* onTop */);
+ for (RunningTaskInfo task : visibleTasks) {
+ wct.reorder(task.getToken(), true /* onTop */);
+ }
+ }
+ }
+
+ private void setDisplayAreaWindowingMode(int displayId,
+ @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
+ DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ displayId);
if (displayAreaInfo == null) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE,
"unable to update windowing mode for display %d display not found", displayId);
return;
}
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
"setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
windowingMode);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
index b5ed509..b310ee2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -34,7 +34,6 @@
import android.os.Binder;
import android.util.Slog;
import android.view.ContextThemeWrapper;
-import android.view.Display;
import android.view.IWindow;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
@@ -48,7 +47,6 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.R;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import java.io.PrintWriter;
@@ -69,14 +67,11 @@
private SurfaceControl mLeash;
private View mBackgroundView;
private @OneHandedState.State int mCurrentState;
- private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
- public BackgroundWindowManager(Context context,
- RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
+ public BackgroundWindowManager(Context context) {
super(context.getResources().getConfiguration(), null /* rootSurface */,
null /* hostInputToken */);
mContext = context;
- mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
mTransactionFactory = SurfaceControl.Transaction::new;
}
@@ -117,7 +112,6 @@
.setOpaque(true)
.setName(TAG)
.setCallsite("BackgroundWindowManager#attachToParentSurface");
- mRootDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, builder);
mLeash = builder.build();
b.setParent(mLeash);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index ad135d1..679d4ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -47,7 +47,6 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.wm.shell.R;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
@@ -205,14 +204,12 @@
DisplayController displayController, DisplayLayout displayLayout,
TaskStackListenerImpl taskStackListener,
InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
- RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
ShellExecutor mainExecutor, Handler mainHandler) {
OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
OneHandedState oneHandedState = new OneHandedState();
- BackgroundWindowManager backgroundWindowManager =
- new BackgroundWindowManager(context, rootDisplayAreaOrganizer);
+ BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
settingsUtil, windowManager, backgroundWindowManager);
OneHandedAnimationController animationController =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 7365b95..1f7a7fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -29,6 +29,7 @@
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.window.ScreenCapture;
import androidx.annotation.BinderThread;
@@ -362,6 +363,15 @@
}
@Override
+ public void takeScreenshotOfWindow(int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+ // AbstractAccessibilityServiceConnection uses the standard
+ // IAccessibilityInteractionConnection for takeScreenshotOfWindow for Pip windows,
+ // so do nothing here.
+ }
+
+ @Override
public void clearAccessibilityFocus() throws RemoteException {
// Do nothing
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index eb08d0e..5533ad5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -86,8 +86,8 @@
/**
* Starts a pair of intent and task in one transition.
*/
- oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
- in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
+ in Bundle options2, int sidePosition, float splitRatio,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
/**
@@ -108,9 +108,8 @@
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
- in InstanceId instanceId) = 12;
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
/**
* Starts a pair of shortcut and task using legacy transition system.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c6a2b83..cdc8cdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,6 +33,8 @@
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -60,13 +63,12 @@
import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -166,8 +168,11 @@
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
+ private final String[] mMultiInstancesComponents;
- private StageCoordinator mStageCoordinator;
+ @VisibleForTesting
+ StageCoordinator mStageCoordinator;
+
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mGoingToRecentsTasksLayer;
@@ -210,6 +215,51 @@
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
shellInit.addInitCallback(this::onInit, this);
}
+
+ // TODO(255224696): Remove the config once having a way for client apps to opt-in
+ // multi-instances split.
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
+ }
+
+ @VisibleForTesting
+ SplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ RecentTasksController recentTasks,
+ ShellExecutor mainExecutor,
+ StageCoordinator stageCoordinator) {
+ mShellCommandHandler = shellCommandHandler;
+ mShellController = shellController;
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDragAndDropController = dragAndDropController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mIconProvider = iconProvider;
+ mRecentTasksOptional = Optional.of(recentTasks);
+ mStageCoordinator = stageCoordinator;
+ mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
+ shellInit.addInitCallback(this::onInit, this);
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
}
public SplitScreen asSplitScreen() {
@@ -471,72 +521,116 @@
startIntent(intent, fillInIntent, position, options);
}
+ private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- // Flag this as a no-user-action launch to prevent sending user leaving event to the
- // current top activity since it's going to be put into another side of the split. This
- // prevents the current top activity from going into pip mode due to user leaving event.
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+ // top activity since it's going to be put into another side of the split. This prevents the
+ // current top activity from going into pip mode due to user leaving event.
+ if (fillInIntent == null) fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
- // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
- // split and there is no reusable background task.
- if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
- final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
- ? mRecentTasksOptional.get().findTaskInBackground(
- intent.getIntent().getComponent())
- : null;
- if (taskInfo != null) {
- startTask(taskInfo.taskId, position, options);
+ if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
+ final ComponentName launching = intent.getIntent().getComponent();
+ if (supportMultiInstancesSplit(launching)) {
+ // To prevent accumulating large number of instances in the background, reuse task
+ // in the background with priority.
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+ .orElse(null);
+ if (taskInfo != null) {
+ startTask(taskInfo.taskId, position, options);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Start task in background");
+ return;
+ }
+
+ // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
+ // the split and there is no reusable background task.
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition("startIntent");
return;
}
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
- if (!ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
- return;
- }
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
/** Returns {@code true} if it's launching the same component on both sides of the split. */
- @VisibleForTesting
- boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
- if (startIntent == null) {
- return false;
- }
+ private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
+ @SplitPosition int position, int taskId) {
+ if (pendingIntent == null || pendingIntent.getIntent() == null) return false;
- final ComponentName launchingActivity = startIntent.getComponent();
- if (launchingActivity == null) {
- return false;
- }
+ final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
+ if (launchingActivity == null) return false;
- if (isSplitScreenVisible()) {
- // To prevent users from constantly dropping the same app to the same side resulting in
- // a large number of instances in the background.
- final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
- final ComponentName targetActivity = targetTaskInfo != null
- ? targetTaskInfo.baseIntent.getComponent() : null;
- if (Objects.equals(launchingActivity, targetActivity)) {
- return false;
+ if (taskId != INVALID_TASK_ID) {
+ final ActivityManager.RunningTaskInfo taskInfo =
+ mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (taskInfo != null) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
}
-
- // Allow users to start a new instance the same to adjacent side.
- final ActivityManager.RunningTaskInfo pairedTaskInfo =
- getTaskInfo(SplitLayout.reversePosition(position));
- final ComponentName pairedActivity = pairedTaskInfo != null
- ? pairedTaskInfo.baseIntent.getComponent() : null;
- return Objects.equals(launchingActivity, pairedActivity);
+ return false;
}
- final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
- if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
- return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ if (!isSplitScreenVisible()) {
+ // Split screen is not yet activated, check if the current top running task is valid to
+ // split together.
+ final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
+ if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ }
+ return false;
+ }
+
+ // Compare to the adjacent side of the split to determine if this is launching the same
+ // component adjacently.
+ final ActivityManager.RunningTaskInfo pairedTaskInfo =
+ getTaskInfo(SplitLayout.reversePosition(position));
+ final ComponentName pairedActivity = pairedTaskInfo != null
+ ? pairedTaskInfo.baseIntent.getComponent() : null;
+ return Objects.equals(launchingActivity, pairedActivity);
+ }
+
+ @VisibleForTesting
+ /** Returns {@code true} if the component supports multi-instances split. */
+ boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
+ if (launching == null) return false;
+
+ final String componentName = launching.flattenToString();
+ for (int i = 0; i < mMultiInstancesComponents.length; i++) {
+ if (mMultiInstancesComponents[i].equals(componentName)) {
+ return true;
+ }
}
return false;
@@ -839,14 +933,13 @@
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
- int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
- controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, options1, taskId, options2,
- splitPosition, splitRatio, adapter, instanceId));
+ controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId));
}
@Override
@@ -872,14 +965,13 @@
}
@Override
- public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
- @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
- (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
- fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
- remoteTransition, instanceId));
+ (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e888c6f..c2ab7ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -24,7 +24,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -428,6 +427,11 @@
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, position, options);
+ return;
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -441,13 +445,7 @@
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
mSplitTransitions.startEnterTransition(transitType, wct, null, this,
- aborted -> {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
- } /* consumedCallback */,
+ null /* consumedCallback */,
(finishWct, finishT) -> {
if (!evictWct.isEmpty()) {
finishWct.merge(evictWct, true);
@@ -457,7 +455,7 @@
/** Launches an activity into split by legacy transition. */
void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
+ @SplitPosition int position, @Nullable Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -473,12 +471,6 @@
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
mSplitUnsupportedToast.show();
- } else {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePosition(SplitLayout.reversePosition(
- getSideStagePosition()), null);
- }
}
// Do nothing when the animation was cancelled.
@@ -771,9 +763,8 @@
mSideStage.evictInvisibleChildren(wct);
}
- Bundle resolveStartStage(@StageType int stage,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
- @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
switch (stage) {
case STAGE_TYPE_UNDEFINED: {
if (position != SPLIT_POSITION_UNDEFINED) {
@@ -844,9 +835,8 @@
: mMainStage.getTopVisibleChildTaskId();
}
- void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
- if (mSideStagePosition == sideStagePosition) return;
- SurfaceControl.Transaction t = mTransactionPool.acquire();
+ void switchSplitPosition(String reason) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty();
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
@@ -886,6 +876,11 @@
va.start();
});
});
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1617,10 +1612,7 @@
@Override
public void onDoubleTappedDivider() {
- setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
- mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ switchSplitPosition("double tap");
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index c6f31c2..56d51bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -80,7 +80,7 @@
/** Set to {@code true} to enable shell transitions. */
public static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
&& SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index ca15f00..ebe5c5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -124,7 +124,8 @@
TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
mDragStartListener);
CaptionTouchEventListener touchEventListener =
- new CaptionTouchEventListener(taskInfo, taskPositioner);
+ new CaptionTouchEventListener(taskInfo, taskPositioner,
+ windowDecoration.getDragDetector());
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
setupWindowDecorationForTransition(taskInfo, startT, finishT);
@@ -173,16 +174,18 @@
private final int mTaskId;
private final WindowContainerToken mTaskToken;
private final DragResizeCallback mDragResizeCallback;
+ private final DragDetector mDragDetector;
private int mDragPointerId = -1;
- private boolean mDragActive = false;
private CaptionTouchEventListener(
RunningTaskInfo taskInfo,
- DragResizeCallback dragResizeCallback) {
+ DragResizeCallback dragResizeCallback,
+ DragDetector dragDetector) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
mDragResizeCallback = dragResizeCallback;
+ mDragDetector = dragDetector;
}
@Override
@@ -231,19 +234,21 @@
@Override
public boolean onTouch(View v, MotionEvent e) {
+ boolean isDrag = false;
int id = v.getId();
if (id != R.id.caption_handle && id != R.id.caption) {
return false;
}
- if (id == R.id.caption_handle || mDragActive) {
+ if (id == R.id.caption_handle) {
+ isDrag = mDragDetector.detectDragEvent(e);
handleEventForMove(e);
}
if (e.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
+ return isDrag;
}
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.isFocused) {
- return false;
+ return isDrag;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mTaskToken, true /* onTop */);
@@ -251,6 +256,10 @@
return true;
}
+ /**
+ * @param e {@link MotionEvent} to process
+ * @return {@code true} if a drag is happening; or {@code false} if it is not
+ */
private void handleEventForMove(MotionEvent e) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
int windowingMode = mDesktopModeController
@@ -259,12 +268,12 @@
return;
}
switch (e.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDragActive = true;
- mDragPointerId = e.getPointerId(0);
+ case MotionEvent.ACTION_DOWN: {
+ mDragPointerId = e.getPointerId(0);
mDragResizeCallback.onDragResizeStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
break;
+ }
case MotionEvent.ACTION_MOVE: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragResizeCallback.onDragResizeMove(
@@ -273,7 +282,6 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- mDragActive = false;
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
.stableInsets().top;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 03cad04..affde30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -62,6 +62,8 @@
private boolean mDesktopActive;
+ private DragDetector mDragDetector;
+
private AdditionalWindow mHandleMenu;
CaptionWindowDecoration(
@@ -79,6 +81,7 @@
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mDesktopActive = DesktopModeStatus.isActive(mContext);
+ mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
void setCaptionListeners(
@@ -92,6 +95,10 @@
mDragResizeCallback = dragResizeCallback;
}
+ DragDetector getDragDetector() {
+ return mDragDetector;
+ }
+
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -182,6 +189,8 @@
}
int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+ mDragDetector.setTouchSlop(touchSlop);
+
int resize_handle = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_handle);
int resize_corner = mResult.mRootView.getResources()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
new file mode 100644
index 0000000..0abe8ab
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.windowdecor;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+/**
+ * A detector for touch inputs that differentiates between drag and click inputs.
+ * All touch events must be passed through this class to track a drag event.
+ */
+public class DragDetector {
+ private int mTouchSlop;
+ private PointF mInputDownPoint;
+ private boolean mIsDragEvent;
+ private int mDragPointerId;
+ public DragDetector(int touchSlop) {
+ mTouchSlop = touchSlop;
+ mInputDownPoint = new PointF();
+ mIsDragEvent = false;
+ mDragPointerId = -1;
+ }
+
+ /**
+ * Determine if {@link MotionEvent} is part of a drag event.
+ * @return {@code true} if this is a drag event, {@code false} if not
+ */
+ public boolean detectDragEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case ACTION_DOWN: {
+ mDragPointerId = ev.getPointerId(0);
+ float rawX = ev.getRawX(0);
+ float rawY = ev.getRawY(0);
+ mInputDownPoint.set(rawX, rawY);
+ return false;
+ }
+ case ACTION_MOVE: {
+ if (!mIsDragEvent) {
+ int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
+ float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
+ float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
+ if (Math.hypot(dx, dy) > mTouchSlop) {
+ mIsDragEvent = true;
+ }
+ }
+ return mIsDragEvent;
+ }
+ case ACTION_UP: {
+ boolean result = mIsDragEvent;
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ return result;
+ }
+ case ACTION_CANCEL: {
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ return false;
+ }
+ }
+ return mIsDragEvent;
+ }
+
+ public void setTouchSlop(int touchSlop) {
+ mTouchSlop = touchSlop;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index b9f16b6..48c0cea1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -22,7 +22,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.content.Context;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -38,6 +37,7 @@
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
import com.android.internal.view.BaseIWindow;
@@ -76,7 +76,7 @@
private Rect mRightBottomCornerBounds;
private int mDragPointerId = -1;
- private int mTouchSlop;
+ private DragDetector mDragDetector;
DragResizeInputListener(
Context context,
@@ -115,6 +115,7 @@
mInputEventReceiver = new TaskResizeInputEventReceiver(
mInputChannel, mHandler, mChoreographer);
mCallback = callback;
+ mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
/**
@@ -146,7 +147,7 @@
mHeight = height;
mResizeHandleThickness = resizeHandleThickness;
mCornerSize = cornerSize;
- mTouchSlop = touchSlop;
+ mDragDetector.setTouchSlop(touchSlop);
Region touchRegion = new Region();
final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
@@ -228,7 +229,6 @@
private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents;
private boolean mDragging;
- private final PointF mActionDownPoint = new PointF();
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -276,7 +276,9 @@
// Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges.
boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
-
+ if (isTouch) {
+ mDragging = mDragDetector.detectDragEvent(e);
+ }
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
float x = e.getX(0);
@@ -290,7 +292,6 @@
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
- mActionDownPoint.set(rawX, rawY);
int ctrlType = calculateCtrlType(isTouch, x, y);
mCallback.onDragResizeStart(ctrlType, rawX, rawY);
result = true;
@@ -304,14 +305,7 @@
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
- if (isTouch) {
- // Check for touch slop for touch events
- float dx = rawX - mActionDownPoint.x;
- float dy = rawY - mActionDownPoint.y;
- if (!mDragging && Math.hypot(dx, dy) > mTouchSlop) {
- mDragging = true;
- }
- } else {
+ if (!isTouch) {
// For all other types allow immediate dragging.
mDragging = true;
}
@@ -330,7 +324,6 @@
}
mDragging = false;
mShouldHandleEvents = false;
- mActionDownPoint.set(0, 0);
mDragPointerId = -1;
result = true;
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index f0f2db7..a49a300 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -40,6 +40,9 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mResizeStartPoint = new PointF();
private final Rect mResizeTaskBounds = new Rect();
+ // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
+ // Used to optimized fluid resizing of freeform tasks.
+ private boolean mPendingDragResizeHint = false;
private int mCtrlType;
private DragStartListener mDragStartListener;
@@ -53,6 +56,12 @@
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
+ if (ctrlType != CTRL_TYPE_UNDEFINED) {
+ // The task is being resized, send the |dragResizing| hint to core with the first
+ // bounds-change wct.
+ mPendingDragResizeHint = true;
+ }
+
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
@@ -63,19 +72,31 @@
@Override
public void onDragResizeMove(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (changeBounds(wct, x, y)) {
+ if (mPendingDragResizeHint) {
+ // This is the first bounds change since drag resize operation started.
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
+ mPendingDragResizeHint = false;
+ }
+ mTaskOrganizer.applyTransaction(wct);
+ }
}
@Override
public void onDragResizeEnd(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+ changeBounds(wct, x, y);
+ mTaskOrganizer.applyTransaction(wct);
mCtrlType = 0;
mTaskBoundsAtDragStart.setEmpty();
mResizeStartPoint.set(0, 0);
+ mPendingDragResizeHint = false;
}
- private void changeBounds(float x, float y) {
+ private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
float deltaX = x - mResizeStartPoint.x;
mResizeTaskBounds.set(mTaskBoundsAtDragStart);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
@@ -96,10 +117,10 @@
}
if (!mResizeTaskBounds.isEmpty()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
- mTaskOrganizer.applyTransaction(wct);
+ return true;
}
+ return false;
}
interface DragStartListener {
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 2d6e8f5..08913c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -13,6 +13,8 @@
<option name="run-command" value="cmd window tracing level all" />
<!-- set WM tracing to frame (avoid incomplete states) -->
<option name="run-command" value="cmd window tracing frame" />
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
<!-- ensure lock screen mode is swipe -->
<option name="run-command" value="locksettings set-disabled false" />
<!-- restart launcher to activate TAPL -->
@@ -34,4 +36,4 @@
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
</metrics_collector>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 2bce8e45..9533b91 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -24,6 +24,8 @@
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.EdgeExtensionComponentMatcher
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -49,6 +51,8 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+ private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
+ private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -159,8 +163,18 @@
/** {@inheritDoc} */
@Presubmit
@Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ ignoreLayers = listOf(
+ ComponentNameMatcher.SPLASH_SCREEN,
+ ComponentNameMatcher.SNAPSHOT,
+ ComponentNameMatcher.IME_SNAPSHOT,
+ EdgeExtensionComponentMatcher(),
+ MagnifierLayer,
+ PopupWindowLayer))
+ }
+ }
/** {@inheritDoc} */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7cbace5..081c8ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,13 +16,9 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-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.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,8 +30,6 @@
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.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -44,11 +38,9 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
-import android.app.WindowConfiguration;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -61,8 +53,6 @@
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -638,130 +628,10 @@
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
- @Test
- public void testPrepareClearBoundsForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- assertEquals(wct.getChanges().size(), 2);
- Change boundsChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(boundsChange1);
- assertNotEquals(
- (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
-
- Change boundsChange2 = wct.getChanges().get(token2.binder());
- assertNotNull(boundsChange2);
- assertNotEquals(
- (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
- }
-
- @Test
- public void testPrepareClearBoundsForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- // Only clear bounds for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only task with freeform windowing mode and the right display should be updated
- assertEquals(wct.getChanges().size(), 1);
- Change wmModeChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(wmModeChange1);
- assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FREEFORM, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only clear freeform for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
-
- private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, MockToken token) {
- RunningTaskInfo taskInfo = createTaskInfo(taskId, windowingMode);
- taskInfo.displayId = 1;
- taskInfo.token = token.token();
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
- return taskInfo;
- }
-
- private static class MockToken {
- private final WindowContainerToken mToken;
- private final IBinder mBinder;
-
- MockToken() {
- mToken = mock(WindowContainerToken.class);
- mBinder = mock(IBinder.class);
- when(mToken.asBinder()).thenReturn(mBinder);
- }
-
- WindowContainerToken token() {
- return mToken;
- }
-
- IBinder binder() {
- return mBinder;
- }
- }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 7eccbf4..b603e03 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -93,7 +94,10 @@
private IActivityTaskManager mActivityTaskManager;
@Mock
- private IOnBackInvokedCallback mIOnBackInvokedCallback;
+ private IOnBackInvokedCallback mAppCallback;
+
+ @Mock
+ private IOnBackInvokedCallback mAnimatorCallback;
@Mock
private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
@@ -105,8 +109,6 @@
private ShellController mShellController;
private BackAnimationController mController;
-
- private int mEventTime = 0;
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
@@ -126,16 +128,15 @@
mContentResolver);
mController.setEnableUAnimation(true);
mShellInit.init();
- mEventTime = 0;
mShellExecutor.flushAll();
}
- private void createNavigationInfo(int backType, IOnBackInvokedCallback onBackInvokedCallback) {
+ private void createNavigationInfo(int backType, boolean enableAnimation) {
BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
.setType(backType)
.setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
- .setOnBackInvokedCallback(onBackInvokedCallback)
- .setPrepareRemoteAnimation(true);
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(enableAnimation);
createNavigationInfo(builder);
}
@@ -176,26 +177,47 @@
}
@Test
- public void verifyAnimationFinishes() {
- RemoteAnimationTarget animationTarget = createAnimationTarget();
- boolean[] backNavigationDone = new boolean[]{false};
- boolean[] triggerBack = new boolean[]{false};
- createNavigationInfo(new BackNavigationInfo.Builder()
- .setType(BackNavigationInfo.TYPE_CROSS_ACTIVITY)
- .setOnBackNavigationDone(
- new RemoteCallback(result -> {
- backNavigationDone[0] = true;
- triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK);
- })));
- triggerBackGesture();
- assertTrue("Navigation Done callback not called", backNavigationDone[0]);
- assertTrue("TriggerBack should have been true", triggerBack[0]);
+ public void verifyNavigationFinishes() throws RemoteException {
+ final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE,
+ BackNavigationInfo.TYPE_CALLBACK };
+
+ for (int type: testTypes) {
+ registerAnimation(type);
+ }
+
+ for (int type: testTypes) {
+ boolean[] backNavigationDone = new boolean[]{false};
+ boolean[] triggerBack = new boolean[]{false};
+
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(true)
+ .setOnBackNavigationDone(
+ new RemoteCallback(result -> {
+ backNavigationDone[0] = true;
+ triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK);
+ })));
+ triggerBackGesture();
+ simulateRemoteAnimationStart(type);
+ simulateRemoteAnimationFinished();
+ mShellExecutor.flushAll();
+
+ assertTrue("Navigation Done callback not called for "
+ + BackNavigationInfo.typeToString(type), backNavigationDone[0]);
+ assertTrue("TriggerBack should have been true", triggerBack[0]);
+ }
}
+
+
@Test
public void backToHome_dispatchesEvents() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, mIOnBackInvokedCallback);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
@@ -203,14 +225,16 @@
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+
+ verify(mAnimatorCallback).onBackStarted(any(BackEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
- verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
+ ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
doMotionEvent(MotionEvent.ACTION_UP, 0);
- verify(mIOnBackInvokedCallback).onBackInvoked();
+ verify(mAnimatorCallback).onBackInvoked();
}
@Test
@@ -223,97 +247,94 @@
mActivityTaskManager, mContext,
mContentResolver);
shellInit.init();
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
triggerBackGesture();
- verify(appCallback, never()).onBackStarted(any(BackEvent.class));
- verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
- verify(appCallback, times(1)).onBackInvoked();
+ verify(mAppCallback, never()).onBackStarted(any());
+ verify(mAppCallback, never()).onBackProgressed(backEventCaptor.capture());
+ verify(mAppCallback, times(1)).onBackInvoked();
- verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
- verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
- verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+ verify(mAnimatorCallback, never()).onBackStarted(any());
+ verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture());
+ verify(mAnimatorCallback, never()).onBackInvoked();
verify(mBackAnimationRunner, never()).onAnimationStart(
anyInt(), any(), any(), any(), any());
}
@Test
public void ignoresGesture_transitionInProgress() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
// Check that back invocation is dispatched.
- verify(mIOnBackInvokedCallback).onBackInvoked();
+ verify(mAnimatorCallback).onBackInvoked();
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
- reset(mIOnBackInvokedCallback);
+ reset(mAnimatorCallback);
reset(mBackAnimationRunner);
// Verify that we prevent animation from restarting if another gestures happens before
// the previous transition is finished.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- verifyNoMoreInteractions(mIOnBackInvokedCallback);
- mController.onBackAnimationFinished();
- // Pretend the transition handler called finishAnimation.
- mController.finishBackNavigation();
+ verifyNoMoreInteractions(mAnimatorCallback);
+
+ // Finish back navigation.
+ simulateRemoteAnimationFinished();
// Verify that more events from a rejected swipe cannot start animation.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
doMotionEvent(MotionEvent.ACTION_UP, 0);
- verifyNoMoreInteractions(mIOnBackInvokedCallback);
+ verifyNoMoreInteractions(mAnimatorCallback);
// Verify that we start accepting gestures again once transition finishes.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mAnimatorCallback).onBackStarted(any());
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
}
@Test
public void acceptsGesture_transitionTimeout() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
+
+ // In case it is still running in animation.
+ doNothing().when(mAnimatorCallback).onBackInvoked();
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- reset(mIOnBackInvokedCallback);
-
// Simulate transition timeout.
mShellExecutor.flushAll();
- mController.onBackAnimationFinished();
- // Pretend the transition handler called finishAnimation.
- mController.finishBackNavigation();
+ reset(mAnimatorCallback);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mAnimatorCallback).onBackStarted(any());
}
-
@Test
public void cancelBackInvokeWhenLostFocus() throws RemoteException {
- mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+ verify(mAnimatorCallback).onBackStarted(any());
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
// Check that back invocation is dispatched.
@@ -323,11 +344,11 @@
IBinder token = mock(IBinder.class);
mController.mFocusObserver.focusLost(token);
mShellExecutor.flushAll();
- verify(mIOnBackInvokedCallback).onBackCancelled();
+ verify(mAnimatorCallback).onBackCancelled();
// No more back invoke.
doMotionEvent(MotionEvent.ACTION_UP, 0);
- verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+ verify(mAnimatorCallback, never()).onBackInvoked();
}
private void doMotionEvent(int actionDown, int coordinate) {
@@ -335,7 +356,6 @@
coordinate, coordinate,
actionDown,
BackEvent.EDGE_LEFT);
- mEventTime += 10;
}
private void simulateRemoteAnimationStart(int type) throws RemoteException {
@@ -347,4 +367,14 @@
mShellExecutor.flushAll();
}
}
+
+ private void simulateRemoteAnimationFinished() {
+ mController.onBackAnimationFinished();
+ mController.finishBackNavigation();
+ }
+
+ private void registerAnimation(int type) {
+ mController.registerAnimation(type,
+ new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index a6f19e7..40f2e88 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -97,13 +97,13 @@
@Test
public void showInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.showInsets(ime(), true);
+ mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */);
verifyZeroInteractions(mExecutor);
}
@Test
public void hideInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.hideInsets(ime(), true);
+ mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */);
verifyZeroInteractions(mExecutor);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 39db328..956f1cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.RemoteException;
import android.util.SparseArray;
@@ -33,6 +34,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
import androidx.test.filters.SmallTest;
@@ -111,8 +113,10 @@
WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
- mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false);
- mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false,
+ null /* statsToken */);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false,
+ null /* statsToken */);
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -131,8 +135,10 @@
WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
- mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false);
- mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false,
+ null /* statsToken */);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false,
+ null /* statsToken */);
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -191,12 +197,12 @@
}
@Override
- public void showInsets(int types, boolean fromIme) {
+ public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
showInsetsCount++;
}
@Override
- public void hideInsets(int types, boolean fromIme) {
+ public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
hideInsetsCount++;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 79b520c..89bafcb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -16,10 +16,13 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+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.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -30,13 +33,14 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -68,6 +72,9 @@
import org.mockito.Mock;
import org.mockito.Mockito;
+import java.util.ArrayList;
+import java.util.Arrays;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DesktopModeControllerTest extends ShellTestCase {
@@ -83,9 +90,7 @@
@Mock
private Handler mMockHandler;
@Mock
- private Transitions mMockTransitions;
- private TestShellExecutor mExecutor;
-
+ private Transitions mTransitions;
private DesktopModeController mController;
private DesktopModeTaskRepository mDesktopModeTaskRepository;
private ShellInit mShellInit;
@@ -97,20 +102,19 @@
when(DesktopModeStatus.isActive(any())).thenReturn(true);
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
- mExecutor = new TestShellExecutor();
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
mController = new DesktopModeController(mContext, mShellInit, mShellController,
- mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
- mDesktopModeTaskRepository, mMockHandler, mExecutor);
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+ mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
- new WindowContainerTransaction());
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
mShellInit.init();
clearInvocations(mShellTaskOrganizer);
clearInvocations(mRootTaskDisplayAreaOrganizer);
+ clearInvocations(mTransitions);
}
@After
@@ -124,113 +128,133 @@
}
@Test
- public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWct = new WindowContainerTransaction();
- MockToken taskMockToken = new MockToken();
- taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWct);
+ public void testDesktopModeEnabled_rootTdaSetToFreeform() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
-
- // The test
mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
-
- // WCT should have 2 changes - clear task wm mode and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(2);
-
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
- assertThat(taskWmModeChange).isNotNull();
- assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
-
- // Verify executed WCT has a change for setting display windowing mode to freeform
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to freeform
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
}
@Test
- public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
- MockToken taskWmMockToken = new MockToken();
- taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWmWct);
+ public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // Create a fake WCT to simulate clearing task bounds
- WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
- MockToken taskBoundsMockToken = new MockToken();
- taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
- when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskBoundsWct);
-
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
-
- // The test
mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to fullscreen
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ }
- // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(3);
+ @Test
+ public void testDesktopModeEnabled_windowingModeCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
- assertThat(taskWmMode).isNotNull();
- assertThat(taskWmMode.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
- // Verify executed WCT has a change for clearing task bounds
- Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
- assertThat(bounds).isNotNull();
- assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
- assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+ // 2 changes: Root TDA windowing mode and 1 task
+ assertThat(wct.getChanges().size()).isEqualTo(2);
+ // No changes for tasks that are not standard or freeform
+ assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard freeform task has windowing mode cleared
+ Change change = wct.getChanges().get(freeformTask.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+ }
- // Verify executed WCT has a change for setting display windowing mode to fullscreen
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ @Test
+ public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
+
+ mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 3 changes: Root TDA windowing mode and 2 tasks
+ assertThat(wct.getChanges().size()).isEqualTo(3);
+ // No changes to home task
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard tasks have bounds cleared
+ assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
+ assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
+ // Freeform standard tasks have windowing mode cleared
+ assertThat(wct.getChanges().get(
+ freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
+ WINDOWING_MODE_UNDEFINED);
+ }
+
+ @Test
+ public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
+ createMockDisplayArea();
+ RunningTaskInfo fullscreenTask1 = createFullscreenTask();
+ fullscreenTask1.isVisible = true;
+ RunningTaskInfo fullscreenTask2 = createFullscreenTask();
+ fullscreenTask2.isVisible = false;
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
+
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // Check that there are hierarchy changes for home task and visible task
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ // First show home task
+ WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
+
+ // Then visible task on top of it
+ WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
}
@Test
public void testShowDesktopApps() {
// Set up two active tasks on desktop
- mDesktopModeTaskRepository.addActiveTask(1);
- mDesktopModeTaskRepository.addActiveTask(2);
- MockToken token1 = new MockToken();
- MockToken token2 = new MockToken();
- ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
- token1.token()).setLastActiveTime(100).build();
- ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
- token2.token()).setLastActiveTime(200).build();
- when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
- when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
+ RunningTaskInfo freeformTask1 = createFreeformTask();
+ freeformTask1.lastActiveTime = 100;
+ RunningTaskInfo freeformTask2 = createFreeformTask();
+ freeformTask2.lastActiveTime = 200;
+ mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+ mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
+ freeformTask1);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
+ freeformTask2);
// Run show desktop apps logic
mController.showDesktopApps();
ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
- verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any());
+ } else {
+ verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+ }
WindowContainerTransaction wct = wctCaptor.getValue();
// Check wct has reorder calls
@@ -239,12 +263,12 @@
// Task 2 has activity later, must be first
WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(token2.binder());
+ assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
// Task 1 should be second
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
+ WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(token2.binder());
+ assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
}
@Test
@@ -266,7 +290,7 @@
@Test
public void testHandleTransitionRequest_notFreeform_returnsNull() {
- ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ RunningTaskInfo trigger = new RunningTaskInfo();
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
WindowContainerTransaction wct = mController.handleRequest(
new Binder(),
@@ -276,7 +300,7 @@
@Test
public void testHandleTransitionRequest_returnsWct() {
- ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ RunningTaskInfo trigger = new RunningTaskInfo();
trigger.token = new MockToken().mToken;
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
WindowContainerTransaction wct = mController.handleRequest(
@@ -285,6 +309,57 @@
assertThat(wct).isNotNull();
}
+ private DisplayAreaInfo createMockDisplayArea() {
+ DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
+ mContext.getDisplayId(), 0);
+ when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+ .thenReturn(displayAreaInfo);
+ return displayAreaInfo;
+ }
+
+ private RunningTaskInfo createFreeformTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private RunningTaskInfo createFullscreenTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private RunningTaskInfo createHomeTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private WindowContainerTransaction getDesktopModeSwitchTransaction() {
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
+ } else {
+ verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+ }
+ return arg.getValue();
+ }
+
+ private void assertThatBoundsCleared(Change change) {
+ assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
+ assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+ }
+
private static class MockToken {
private final WindowContainerToken mToken;
private final IBinder mBinder;
@@ -298,9 +373,5 @@
WindowContainerToken token() {
return mToken;
}
-
- IBinder binder() {
- return mBinder;
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
index 11948dbf..f3f7067 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -24,7 +24,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
@@ -42,13 +41,11 @@
private BackgroundWindowManager mBackgroundWindowManager;
@Mock
private DisplayLayout mMockDisplayLayout;
- @Mock
- private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mBackgroundWindowManager = new BackgroundWindowManager(mContext, mRootDisplayAreaOrganizer);
+ mBackgroundWindowManager = new BackgroundWindowManager(mContext);
mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index d01f3d3..38b75f8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -16,18 +16,24 @@
package com.android.wm.shell.splitscreen;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -35,6 +41,8 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -65,11 +73,11 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Tests for {@link SplitScreenController}
*/
@@ -91,18 +99,21 @@
@Mock Transitions mTransitions;
@Mock TransactionPool mTransactionPool;
@Mock IconProvider mIconProvider;
- @Mock Optional<RecentTasksController> mRecentTasks;
+ @Mock StageCoordinator mStageCoordinator;
+ @Mock RecentTasksController mRecentTasks;
+ @Captor ArgumentCaptor<Intent> mIntentCaptor;
private SplitScreenController mSplitScreenController;
@Before
public void setup() {
+ assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks, mMainExecutor));
+ mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
}
@Test
@@ -148,58 +159,100 @@
}
@Test
- public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
- doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
- doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
-
- // Verify launching the same activity returns true.
+ public void testStartIntent_appendsNoUserActionFlag() {
Intent startIntent = createStartIntent("startActivity");
- ActivityManager.RunningTaskInfo focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Verify launching different activity returns false.
- Intent diffIntent = createStartIntent("diffActivity");
- focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
}
@Test
- public void testShouldAddMultipleTaskFlag_inSplitScreen() {
- doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+ // Put the same component into a task in the background
+ ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
- Intent diffIntent = createStartIntent("diffActivity");
- ActivityManager.RunningTaskInfo differentTaskInfo =
- createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ // Put the same component into a task in the background
+ doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
+ .findTaskInBackground(any());
- // Verify launching the same activity return false.
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
- // Verify launching the same activity as adjacent returns true.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
- // Verify launching different activity from adjacent returns false.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ @Test
+ public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
+ doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ ActivityManager.RunningTaskInfo sameTaskInfo =
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).switchSplitPosition(anyString());
}
private Intent createStartIntent(String activityName) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 1a1bebd..5ee8bf3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -61,7 +61,7 @@
@RunWith(AndroidJUnit4.class)
public final class StageTaskListenerTests extends ShellTestCase {
private static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
@Mock
private ShellTaskOrganizer mTaskOrganizer;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
new file mode 100644
index 0000000..ac10ddb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -0,0 +1,130 @@
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [TaskPositioner].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:TaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TaskPositionerTest : ShellTestCase() {
+
+ @Mock
+ private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+ @Mock
+ private lateinit var mockWindowDecoration: WindowDecoration<*>
+ @Mock
+ private lateinit var mockDragStartListener: TaskPositioner.DragStartListener
+
+ @Mock
+ private lateinit var taskToken: WindowContainerToken
+ @Mock
+ private lateinit var taskBinder: IBinder
+
+ private lateinit var taskPositioner: TaskPositioner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ taskPositioner = TaskPositioner(
+ mockShellTaskOrganizer,
+ mockWindowDecoration,
+ mockDragStartListener
+ )
+ `when`(taskToken.asBinder()).thenReturn(taskBinder)
+ mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ }
+ }
+
+ @Test
+ fun testDragResize_move_skipsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_UNDEFINED, // Move
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Move the task 10px to the right.
+ val newX = STARTING_BOUNDS.left.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize the task by 10px to the right.
+ val newX = STARTING_BOUNDS.right.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ !change.dragResizing
+ }
+ })
+ }
+
+ companion object {
+ private const val TASK_ID = 5
+ private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ }
+}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index eb8d26a..b1f327c 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -211,6 +211,8 @@
"tests/data/**/*.apk",
"tests/data/**/*.arsc",
"tests/data/**/*.idmap",
+ ":FrameworkResourcesSparseTestApp",
+ ":FrameworkResourcesNotSparseTestApp",
],
test_suites: ["device-tests"],
}
diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
index 58fc5bb..a1385f2 100644
--- a/libs/androidfw/ZipUtils.cpp
+++ b/libs/androidfw/ZipUtils.cpp
@@ -35,7 +35,7 @@
using namespace android;
// TODO: This can go away once the only remaining usage in aapt goes away.
-class FileReader : public zip_archive::Reader {
+class FileReader final : public zip_archive::Reader {
public:
explicit FileReader(FILE* fp) : Reader(), mFp(fp), mCurrentOffset(0) {
}
@@ -66,7 +66,7 @@
mutable off64_t mCurrentOffset;
};
-class FdReader : public zip_archive::Reader {
+class FdReader final : public zip_archive::Reader {
public:
explicit FdReader(int fd) : mFd(fd) {
}
@@ -79,7 +79,7 @@
const int mFd;
};
-class BufferReader : public zip_archive::Reader {
+class BufferReader final : public zip_archive::Reader {
public:
BufferReader(incfs::map_ptr<void> input, size_t inputSize) : Reader(),
mInput(input.convert<uint8_t>()),
@@ -105,7 +105,7 @@
const size_t mInputSize;
};
-class BufferWriter : public zip_archive::Writer {
+class BufferWriter final : public zip_archive::Writer {
public:
BufferWriter(void* output, size_t outputSize) : Writer(),
mOutput(reinterpret_cast<uint8_t*>(output)), mOutputSize(outputSize), mBytesWritten(0) {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 9309091..a625889 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -53,7 +53,7 @@
// The version should only be changed when a backwards-incompatible change must be made to the
// fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
// to prevent losing fabricated overlay data.
-constexpr const uint32_t kFabricatedOverlayCurrentVersion = 2;
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3;
// Returns whether or not the path represents a fabricated overlay.
bool IsFabricatedOverlay(const std::string& path);
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index d214e2d..c90ec19 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -71,62 +71,6 @@
ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
}
-TEST(LoadedArscTest, LoadSparseEntryApp) {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
- &contents));
-
- std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
- contents.length());
- ASSERT_THAT(loaded_arsc, NotNull());
-
- const LoadedPackage* package =
- loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
- ASSERT_THAT(package, NotNull());
-
- const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
- const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
-
- const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
- ASSERT_THAT(type_spec, NotNull());
- ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
- auto type = type_spec->type_entries[0];
- ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
-}
-
-TEST(LoadedArscTest, FindSparseEntryApp) {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
- &contents));
-
- std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
- contents.length());
- ASSERT_THAT(loaded_arsc, NotNull());
-
- const LoadedPackage* package =
- loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26));
- ASSERT_THAT(package, NotNull());
-
- const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1;
- const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26);
-
- const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
- ASSERT_THAT(type_spec, NotNull());
- ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
- // Ensure that AAPT2 sparsely encoded the v26 config as expected.
- auto type_entry = std::find_if(
- type_spec->type_entries.begin(), type_spec->type_entries.end(),
- [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; });
- ASSERT_NE(type_entry, type_spec->type_entries.end());
- ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0);
-
- // Test fetching a resource with only sparsely encoded configs by name.
- auto id = package->FindEntryByName(u"string", u"only_v26");
- ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0));
-}
-
TEST(LoadedArscTest, LoadSharedLibrary) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
@@ -404,4 +348,84 @@
// sizeof(Res_value) might not be backwards compatible.
// TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); }
+class LoadedArscParameterizedTest :
+ public testing::TestWithParam<std::string> {
+};
+
+TEST_P(LoadedArscParameterizedTest, LoadSparseEntryApp) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+ contents.length());
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
+ ASSERT_THAT(package, NotNull());
+
+ const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
+ const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+ auto type = type_spec->type_entries[0];
+ ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
+}
+
+TEST_P(LoadedArscParameterizedTest, FindSparseEntryApp) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+ contents.length());
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_land));
+ ASSERT_THAT(package, NotNull());
+
+ const uint8_t type_index = get_type_id(sparse::R::string::only_land) - 1;
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+ // Type Entry with default orientation is not sparse encoded because the ratio of
+ // populated entries to total entries is above threshold.
+ // Only find out default locale because Soong build system will introduce pseudo
+ // locales for the apk generated at runtime.
+ auto type_entry_default = std::find_if(
+ type_spec->type_entries.begin(), type_spec->type_entries.end(),
+ [] (const TypeSpec::TypeEntry& x) { return x.config.orientation == 0 &&
+ x.config.locale == 0; });
+ ASSERT_NE(type_entry_default, type_spec->type_entries.end());
+ ASSERT_EQ(type_entry_default->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+ // Type Entry with land orientation is sparse encoded as expected.
+ // Only find out default locale because Soong build system will introduce pseudo
+ // locales for the apk generated at runtime.
+ auto type_entry_land = std::find_if(
+ type_spec->type_entries.begin(), type_spec->type_entries.end(),
+ [](const TypeSpec::TypeEntry& x) { return x.config.orientation ==
+ ResTable_config::ORIENTATION_LAND &&
+ x.config.locale == 0; });
+ ASSERT_NE(type_entry_land, type_spec->type_entries.end());
+ ASSERT_NE(type_entry_land->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+ // Test fetching a resource with only sparsely encoded configs by name.
+ auto id = package->FindEntryByName(u"string", u"only_land");
+ ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_land, 0));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ FrameWorkResourcesLoadedArscTests,
+ LoadedArscParameterizedTest,
+ ::testing::Values(
+ base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+ base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+ ));
+
} // namespace android
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
index 9aeb00c..fbf7098 100644
--- a/libs/androidfw/tests/ResTable_test.cpp
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -15,6 +15,7 @@
*/
#include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
#include <codecvt>
#include <locale>
@@ -41,34 +42,6 @@
ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
}
-TEST(ResTableTest, ShouldLoadSparseEntriesSuccessfully) {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
- &contents));
-
- ResTable table;
- ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
-
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- table.setParameters(&config);
-
- String16 name(u"com.android.sparse:integer/foo_9");
- uint32_t flags;
- uint32_t resid =
- table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
- ASSERT_NE(0u, resid);
-
- Res_value val;
- ResTable_config selected_config;
- ASSERT_GE(
- table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
- 0);
- EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
- EXPECT_EQ(900u, val.data);
-}
-
TEST(ResTableTest, SimpleTypeIsRetrievedCorrectly) {
std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
@@ -476,4 +449,43 @@
ASSERT_FALSE(invalid_pool->stringAt(invalid_val.data).has_value());
}
+class ResTableParameterizedTest :
+ public testing::TestWithParam<std::string> {
+};
+
+TEST_P(ResTableParameterizedTest, ShouldLoadSparseEntriesSuccessfully) {
+ std::string contents;
+ ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.orientation = ResTable_config::ORIENTATION_LAND;
+ table.setParameters(&config);
+
+ String16 name(u"com.android.sparse:integer/foo_9");
+ uint32_t flags;
+ uint32_t resid =
+ table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
+ ASSERT_NE(0u, resid);
+
+ Res_value val;
+ ResTable_config selected_config;
+ ASSERT_GE(
+ table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
+ 0);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ EXPECT_EQ(900u, val.data);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ FrameWorkResourcesResTableTests,
+ ResTableParameterizedTest,
+ ::testing::Values(
+ base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+ base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+ ));
+
} // namespace android
diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp
index c9b4ad8..fffeeb8 100644
--- a/libs/androidfw/tests/SparseEntry_bench.cpp
+++ b/libs/androidfw/tests/SparseEntry_bench.cpp
@@ -16,6 +16,7 @@
#include "androidfw/AssetManager.h"
#include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
#include "BenchmarkHelpers.h"
#include "data/sparse/R.h"
@@ -24,40 +25,74 @@
namespace android {
+static void BM_SparseEntryGetResourceHelper(const std::vector<std::string>& paths,
+ uint32_t resid, benchmark::State& state, void (*GetResourceBenchmarkFunc)(
+ const std::vector<std::string>&, const ResTable_config*,
+ uint32_t, benchmark::State&)){
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+ config.orientation = ResTable_config::ORIENTATION_LAND;
+ GetResourceBenchmarkFunc(paths, &config, resid, state);
+}
+
static void BM_SparseEntryGetResourceOldSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+ state, &GetResourceBenchmarkOld);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Large, sparse::R::string::foo_999);
static void BM_SparseEntryGetResourceOldNotSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+ state, &GetResourceBenchmarkOld);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Large, sparse::R::string::foo_999);
static void BM_SparseEntryGetResourceSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmark({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+ state, &GetResourceBenchmark);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Large, sparse::R::string::foo_999);
static void BM_SparseEntryGetResourceNotSparse(benchmark::State& state, uint32_t resid) {
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
- GetResourceBenchmark({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+ BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+ state, &GetResourceBenchmark);
}
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Small, sparse::R::integer::foo_9);
BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Large, sparse::R::string::foo_999);
+static void BM_SparseEntryGetResourceOldSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceOldNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+ BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+ "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+ &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Large, sparse::R::string::foo_999);
+
} // namespace android
diff --git a/libs/androidfw/tests/data/sparse/Android.bp b/libs/androidfw/tests/data/sparse/Android.bp
new file mode 100644
index 0000000..0fed79e
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/Android.bp
@@ -0,0 +1,14 @@
+android_test_helper_app {
+ name: "FrameworkResourcesSparseTestApp",
+ sdk_version: "current",
+ min_sdk_version: "32",
+ aaptflags: [
+ "--enable-sparse-encoding",
+ ],
+}
+
+android_test_helper_app {
+ name: "FrameworkResourcesNotSparseTestApp",
+ sdk_version: "current",
+ min_sdk_version: "32",
+}
diff --git a/libs/androidfw/tests/data/sparse/AndroidManifest.xml b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
index 27911b6..9c23a72 100644
--- a/libs/androidfw/tests/data/sparse/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
@@ -17,4 +17,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.sparse">
<application />
+ <uses-sdk android:minSdkVersion="32" />
</manifest>
diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h
index 2492dbf..a66e1af 100644
--- a/libs/androidfw/tests/data/sparse/R.h
+++ b/libs/androidfw/tests/data/sparse/R.h
@@ -42,7 +42,7 @@
struct string {
enum : uint32_t {
foo_999 = 0x7f0203e7,
- only_v26 = 0x7f0203e8
+ only_land = 0x7f0203e8
};
};
};
diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh
index 4ea5468..114ecbb 100755
--- a/libs/androidfw/tests/data/sparse/gen_strings.sh
+++ b/libs/androidfw/tests/data/sparse/gen_strings.sh
@@ -1,20 +1,20 @@
#!/bin/bash
OUTPUT_default=res/values/strings.xml
-OUTPUT_v26=res/values-v26/strings.xml
+OUTPUT_land=res/values-land/strings.xml
echo "<resources>" > $OUTPUT_default
-echo "<resources>" > $OUTPUT_v26
+echo "<resources>" > $OUTPUT_land
for i in {0..999}
do
echo " <string name=\"foo_$i\">$i</string>" >> $OUTPUT_default
if [ "$(($i % 3))" -eq "0" ]
then
- echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_v26
+ echo " <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_land
fi
done
echo "</resources>" >> $OUTPUT_default
-echo " <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26
-echo "</resources>" >> $OUTPUT_v26
+echo " <string name=\"only_land\">only land</string>" >> $OUTPUT_land
+echo "</resources>" >> $OUTPUT_land
diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk
index b08a621..4d4d4a8 100644
--- a/libs/androidfw/tests/data/sparse/not_sparse.apk
+++ b/libs/androidfw/tests/data/sparse/not_sparse.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
similarity index 99%
rename from libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
rename to libs/androidfw/tests/data/sparse/res/values-land/strings.xml
index d116087e..66222c3 100644
--- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
+++ b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
@@ -333,5 +333,5 @@
<string name="foo_993">9930</string>
<string name="foo_996">9960</string>
<string name="foo_999">9990</string>
- <string name="only_v26">only v26</string>
+ <string name="only_land">only land</string>
</resources>
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/values.xml b/libs/androidfw/tests/data/sparse/res/values-land/values.xml
similarity index 100%
rename from libs/androidfw/tests/data/sparse/res/values-v26/values.xml
rename to libs/androidfw/tests/data/sparse/res/values-land/values.xml
diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk
index 9fd01fb..0f2d75a 100644
--- a/libs/androidfw/tests/data/sparse/sparse.apk
+++ b/libs/androidfw/tests/data/sparse/sparse.apk
Binary files differ
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index cccc0f81..c0a4fdf 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -636,7 +636,7 @@
cc_defaults {
name: "hwui_test_defaults",
defaults: ["hwui_defaults"],
- test_suites: ["device-tests"],
+ test_suites: ["general-tests"],
header_libs: ["libandroid_headers_private"],
target: {
android: {
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/AndroidTest.xml
index 381fb9f..911315f 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/AndroidTest.xml
@@ -16,22 +16,22 @@
<configuration description="Config for hwuimicro">
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
- <option name="push" value="hwui_unit_tests->/data/nativetest/hwui_unit_tests" />
- <option name="push" value="hwuimicro->/data/benchmarktest/hwuimicro" />
- <option name="push" value="hwuimacro->/data/benchmarktest/hwuimacro" />
+ <option name="push" value="hwui_unit_tests->/data/local/tmp/nativetest/hwui_unit_tests" />
+ <option name="push" value="hwuimicro->/data/local/tmp/benchmarktest/hwuimicro" />
+ <option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
<test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/nativetest" />
+ <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
<option name="module-name" value="hwui_unit_tests" />
</test>
<test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
- <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+ <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
<option name="benchmark-module-name" value="hwuimicro" />
<option name="file-exclusion-filter-regex" value=".*\.config$" />
</test>
<test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
- <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+ <option name="native-benchmark-device-path" value="/data/local/tmp/benchmarktest" />
<option name="benchmark-module-name" value="hwuimacro" />
<option name="file-exclusion-filter-regex" value=".*\.config$" />
</test>
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 4f45602..a6da0a3 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -25,6 +25,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -125,7 +126,7 @@
* @hide
*/
public static GnssCapabilities empty() {
- return new GnssCapabilities(0, 0, 0, new ArrayList<>());
+ return new GnssCapabilities(0, 0, 0, Collections.emptyList());
}
private final @TopHalCapabilityFlags int mTopFlags;
@@ -142,7 +143,7 @@
mTopFlags = topFlags;
mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
mPowerFlags = powerFlags;
- mGnssSignalTypes = gnssSignalTypes;
+ mGnssSignalTypes = Collections.unmodifiableList(gnssSignalTypes);
}
/**
@@ -155,7 +156,7 @@
return this;
} else {
return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags,
- new ArrayList<>(mGnssSignalTypes));
+ mGnssSignalTypes);
}
}
@@ -171,7 +172,7 @@
return this;
} else {
return new GnssCapabilities(mTopFlags, flags, mPowerFlags,
- new ArrayList<>(mGnssSignalTypes));
+ mGnssSignalTypes);
}
}
@@ -186,7 +187,7 @@
return this;
} else {
return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags,
- new ArrayList<>(mGnssSignalTypes));
+ mGnssSignalTypes);
}
}
@@ -606,7 +607,7 @@
mTopFlags = 0;
mMeasurementCorrectionsFlags = 0;
mPowerFlags = 0;
- mGnssSignalTypes = new ArrayList<>();
+ mGnssSignalTypes = Collections.emptyList();
}
public Builder(@NonNull GnssCapabilities capabilities) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d975e96..17d7045 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2328,10 +2328,9 @@
return AudioSystem.SUCCESS;
}
- private final Map<Integer, Object> mDevRoleForCapturePresetListeners = new HashMap<>(){{
- put(AudioSystem.DEVICE_ROLE_PREFERRED,
- new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>());
- }};
+ private final Map<Integer, Object> mDevRoleForCapturePresetListeners = Map.of(
+ AudioSystem.DEVICE_ROLE_PREFERRED,
+ new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>());
private class DevRoleListenerInfo<T> {
final @NonNull Executor mExecutor;
@@ -6515,15 +6514,17 @@
// AudioPort implementation
//
- static final int AUDIOPORT_GENERATION_INIT = 0;
- static Integer sAudioPortGeneration = new Integer(AUDIOPORT_GENERATION_INIT);
- static ArrayList<AudioPort> sAudioPortsCached = new ArrayList<AudioPort>();
- static ArrayList<AudioPort> sPreviousAudioPortsCached = new ArrayList<AudioPort>();
- static ArrayList<AudioPatch> sAudioPatchesCached = new ArrayList<AudioPatch>();
+ private static final int AUDIOPORT_GENERATION_INIT = 0;
+ private static Object sAudioPortGenerationLock = new Object();
+ @GuardedBy("sAudioPortGenerationLock")
+ private static int sAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
+ private static ArrayList<AudioPort> sAudioPortsCached = new ArrayList<AudioPort>();
+ private static ArrayList<AudioPort> sPreviousAudioPortsCached = new ArrayList<AudioPort>();
+ private static ArrayList<AudioPatch> sAudioPatchesCached = new ArrayList<AudioPatch>();
static int resetAudioPortGeneration() {
int generation;
- synchronized (sAudioPortGeneration) {
+ synchronized (sAudioPortGenerationLock) {
generation = sAudioPortGeneration;
sAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
}
@@ -6533,7 +6534,7 @@
static int updateAudioPortCache(ArrayList<AudioPort> ports, ArrayList<AudioPatch> patches,
ArrayList<AudioPort> previousPorts) {
sAudioPortEventHandler.init();
- synchronized (sAudioPortGeneration) {
+ synchronized (sAudioPortGenerationLock) {
if (sAudioPortGeneration == AUDIOPORT_GENERATION_INIT) {
int[] patchGeneration = new int[1];
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
index ca175b4..0f962f9 100644
--- a/media/java/android/media/AudioMetadata.java
+++ b/media/java/android/media/AudioMetadata.java
@@ -30,6 +30,7 @@
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -446,14 +447,13 @@
// BaseMap is corresponding to audio_utils::metadata::Data
private static final int AUDIO_METADATA_OBJ_TYPE_BASEMAP = 6;
- private static final HashMap<Class, Integer> AUDIO_METADATA_OBJ_TYPES = new HashMap<>() {{
- put(Integer.class, AUDIO_METADATA_OBJ_TYPE_INT);
- put(Long.class, AUDIO_METADATA_OBJ_TYPE_LONG);
- put(Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT);
- put(Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE);
- put(String.class, AUDIO_METADATA_OBJ_TYPE_STRING);
- put(BaseMap.class, AUDIO_METADATA_OBJ_TYPE_BASEMAP);
- }};
+ private static final Map<Class, Integer> AUDIO_METADATA_OBJ_TYPES = Map.of(
+ Integer.class, AUDIO_METADATA_OBJ_TYPE_INT,
+ Long.class, AUDIO_METADATA_OBJ_TYPE_LONG,
+ Float.class, AUDIO_METADATA_OBJ_TYPE_FLOAT,
+ Double.class, AUDIO_METADATA_OBJ_TYPE_DOUBLE,
+ String.class, AUDIO_METADATA_OBJ_TYPE_STRING,
+ BaseMap.class, AUDIO_METADATA_OBJ_TYPE_BASEMAP);
private static final Charset AUDIO_METADATA_CHARSET = StandardCharsets.UTF_8;
@@ -634,8 +634,8 @@
* Datum corresponds to Object
****************************************************************************************/
- private static final HashMap<Integer, DataPackage<?>> DATA_PACKAGES = new HashMap<>() {{
- put(AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage<Integer>() {
+ private static final Map<Integer, DataPackage<?>> DATA_PACKAGES = Map.of(
+ AUDIO_METADATA_OBJ_TYPE_INT, new DataPackage<Integer>() {
@Override
@Nullable
public Integer unpack(ByteBuffer buffer) {
@@ -647,8 +647,8 @@
output.putInt(obj);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_LONG, new DataPackage<Long>() {
+ },
+ AUDIO_METADATA_OBJ_TYPE_LONG, new DataPackage<Long>() {
@Override
@Nullable
public Long unpack(ByteBuffer buffer) {
@@ -660,8 +660,8 @@
output.putLong(obj);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_FLOAT, new DataPackage<Float>() {
+ },
+ AUDIO_METADATA_OBJ_TYPE_FLOAT, new DataPackage<Float>() {
@Override
@Nullable
public Float unpack(ByteBuffer buffer) {
@@ -673,8 +673,8 @@
output.putFloat(obj);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_DOUBLE, new DataPackage<Double>() {
+ },
+ AUDIO_METADATA_OBJ_TYPE_DOUBLE, new DataPackage<Double>() {
@Override
@Nullable
public Double unpack(ByteBuffer buffer) {
@@ -686,8 +686,8 @@
output.putDouble(obj);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_STRING, new DataPackage<String>() {
+ },
+ AUDIO_METADATA_OBJ_TYPE_STRING, new DataPackage<String>() {
@Override
@Nullable
public String unpack(ByteBuffer buffer) {
@@ -713,9 +713,9 @@
output.put(valueArr);
return true;
}
- });
- put(AUDIO_METADATA_OBJ_TYPE_BASEMAP, new BaseMapPackage());
- }};
+ },
+ AUDIO_METADATA_OBJ_TYPE_BASEMAP, new BaseMapPackage());
+
// ObjectPackage is a special case that it is expected to unpack audio_utils::metadata::Datum,
// which contains data type and data size besides the payload for the data.
private static final ObjectPackage OBJECT_PACKAGE = new ObjectPackage();
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 85cd342..d51f1e1 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -48,8 +48,8 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.NioUtils;
-import java.util.HashMap;
import java.util.LinkedList;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -1867,26 +1867,24 @@
}
// General pair map
- private static final HashMap<String, Integer> CHANNEL_PAIR_MAP = new HashMap<>() {{
- put("front", AudioFormat.CHANNEL_OUT_FRONT_LEFT
- | AudioFormat.CHANNEL_OUT_FRONT_RIGHT);
- put("back", AudioFormat.CHANNEL_OUT_BACK_LEFT
- | AudioFormat.CHANNEL_OUT_BACK_RIGHT);
- put("front of center", AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER
- | AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER);
- put("side", AudioFormat.CHANNEL_OUT_SIDE_LEFT
- | AudioFormat.CHANNEL_OUT_SIDE_RIGHT);
- put("top front", AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT
- | AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT);
- put("top back", AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT
- | AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT);
- put("top side", AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT
- | AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT);
- put("bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
- | AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT);
- put("front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
- | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
- }};
+ private static final Map<String, Integer> CHANNEL_PAIR_MAP = Map.of(
+ "front", AudioFormat.CHANNEL_OUT_FRONT_LEFT
+ | AudioFormat.CHANNEL_OUT_FRONT_RIGHT,
+ "back", AudioFormat.CHANNEL_OUT_BACK_LEFT
+ | AudioFormat.CHANNEL_OUT_BACK_RIGHT,
+ "front of center", AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER
+ | AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER,
+ "side", AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT,
+ "top front", AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT
+ | AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT,
+ "top back", AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT
+ | AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT,
+ "top side", AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT
+ | AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT,
+ "bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
+ | AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT,
+ "front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
+ | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
/**
* Convenience method to check that the channel configuration (a.k.a channel mask) is supported
@@ -1924,7 +1922,7 @@
return false;
}
// Check all pairs to see that they are matched (front duplicated here).
- for (HashMap.Entry<String, Integer> e : CHANNEL_PAIR_MAP.entrySet()) {
+ for (Map.Entry<String, Integer> e : CHANNEL_PAIR_MAP.entrySet()) {
final int positionPair = e.getValue();
if ((channelConfig & positionPair) != 0
&& (channelConfig & positionPair) != positionPair) {
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 0c8cacd..524bde4 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -70,6 +70,7 @@
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
@@ -4836,12 +4837,13 @@
for (int i = 1; i < entryValues.length; ++i) {
final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]);
int first = -1, second = -1;
- if (guessDataFormat.first == dataFormat.first
- || guessDataFormat.second == dataFormat.first) {
+ if (Objects.equals(guessDataFormat.first, dataFormat.first)
+ || Objects.equals(guessDataFormat.second, dataFormat.first)) {
first = dataFormat.first;
}
- if (dataFormat.second != -1 && (guessDataFormat.first == dataFormat.second
- || guessDataFormat.second == dataFormat.second)) {
+ if (dataFormat.second != -1
+ && (Objects.equals(guessDataFormat.first, dataFormat.second)
+ || Objects.equals(guessDataFormat.second, dataFormat.second))) {
second = dataFormat.second;
}
if (first == -1 && second == -1) {
diff --git a/media/java/android/media/MediaHTTPService.java b/media/java/android/media/MediaHTTPService.java
index 3008067..2342a42 100644
--- a/media/java/android/media/MediaHTTPService.java
+++ b/media/java/android/media/MediaHTTPService.java
@@ -21,6 +21,8 @@
import android.os.IBinder;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookieStore;
@@ -31,7 +33,9 @@
public class MediaHTTPService extends IMediaHTTPService.Stub {
private static final String TAG = "MediaHTTPService";
@Nullable private List<HttpCookie> mCookies;
- private Boolean mCookieStoreInitialized = new Boolean(false);
+ private final Object mCookieStoreInitializedLock = new Object();
+ @GuardedBy("mCookieStoreInitializedLock")
+ private boolean mCookieStoreInitialized = false;
public MediaHTTPService(@Nullable List<HttpCookie> cookies) {
mCookies = cookies;
@@ -40,7 +44,7 @@
public IMediaHTTPConnection makeHTTPConnection() {
- synchronized (mCookieStoreInitialized) {
+ synchronized (mCookieStoreInitializedLock) {
// Only need to do it once for all connections
if ( !mCookieStoreInitialized ) {
CookieHandler cookieHandler = CookieHandler.getDefault();
@@ -78,8 +82,8 @@
Log.v(TAG, "makeHTTPConnection(" + this + "): cookieHandler: " + cookieHandler +
" Cookies: " + mCookies);
- } // mCookieStoreInitialized
- } // synchronized
+ }
+ }
return new MediaHTTPConnection();
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 77b5746..79a5902 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -2507,6 +2507,8 @@
*
* @see android.media.MediaPlayer#getTrackInfo
*/
+ // The creator needs to be pulic, which requires removing the @UnsupportedAppUsage
+ @SuppressWarnings("ParcelableCreator")
static public class TrackInfo implements Parcelable {
/**
* Gets the track type.
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 1bd12af..7e1bbe3 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -244,12 +244,9 @@
mCallback = null;
return;
}
- if (handler == null) {
- handler = new Handler();
- }
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
callback.mSession = this;
- CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
- callback);
+ CallbackMessageHandler msgHandler = new CallbackMessageHandler(looper, callback);
mCallback = msgHandler;
}
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 6ae7dfb..9b8ec5e 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -45,6 +45,7 @@
void onRequestTrackInfoList(int seq);
void onRequestCurrentTvInputId(int seq);
void onRequestStartRecording(in Uri programUri, int seq);
+ void onRequestStopRecording(in String recordingId, int seq);
void onRequestSigning(
in String id, in String algorithm, in String alias, in byte[] data, int seq);
void onAdRequest(in AdRequest request, int Seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 84b9c9e..38fc717 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -64,6 +64,7 @@
void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, int userId);
+ void notifyRecordingStopped(in IBinder sessionToken, in String recordingId, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 95b4ffa..9e33536 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -54,6 +54,7 @@
void notifyContentBlocked(in String rating);
void notifySignalStrength(int strength);
void notifyRecordingStarted(in String recordingId);
+ void notifyRecordingStopped(in String recordingId);
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 6478057..4ce5871 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -44,6 +44,7 @@
void onRequestTrackInfoList();
void onRequestCurrentTvInputId();
void onRequestStartRecording(in Uri programUri);
+ void onRequestStopRecording(in String recordingId);
void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
void onAdRequest(in AdRequest request);
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 042cb15..a2fdfe0 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -82,6 +82,7 @@
private static final int DO_RELAYOUT_MEDIA_VIEW = 28;
private static final int DO_REMOVE_MEDIA_VIEW = 29;
private static final int DO_NOTIFY_RECORDING_STARTED = 30;
+ private static final int DO_NOTIFY_RECORDING_STOPPED = 31;
private final HandlerCaller mCaller;
private Session mSessionImpl;
@@ -169,6 +170,10 @@
mSessionImpl.notifyRecordingStarted((String) msg.obj);
break;
}
+ case DO_NOTIFY_RECORDING_STOPPED: {
+ mSessionImpl.notifyRecordingStopped((String) msg.obj);
+ break;
+ }
case DO_SEND_SIGNING_RESULT: {
SomeArgs args = (SomeArgs) msg.obj;
mSessionImpl.sendSigningResult((String) args.arg1, (byte[]) args.arg2);
@@ -392,6 +397,12 @@
}
@Override
+ public void notifyRecordingStopped(String recordingId) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(
+ DO_NOTIFY_RECORDING_STOPPED, recordingId));
+ }
+
+ @Override
public void setSurface(Surface surface) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 7d84fb2..287df40 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -499,6 +499,18 @@
}
@Override
+ public void onRequestStopRecording(String recordingId, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestStopRecording(recordingId);
+ }
+ }
+
+ @Override
public void onRequestSigning(
String id, String algorithm, String alias, byte[] data, int seq) {
synchronized (mSessionCallbackRecordMap) {
@@ -1059,6 +1071,18 @@
}
}
+ void notifyRecordingStopped(String recordingId) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyRecordingStopped(mToken, recordingId, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
@@ -1729,6 +1753,15 @@
});
}
+ void postRequestStopRecording(String recordingId) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestStopRecording(mSession, recordingId);
+ }
+ });
+ }
+
void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
mHandler.post(new Runnable() {
@Override
@@ -1884,11 +1917,22 @@
* called.
*
* @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+ * @param programUri The Uri of the program to be recorded.
*/
public void onRequestStartRecording(Session session, Uri programUri) {
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#RequestStopRecording} is
+ * called.
+ *
+ * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+ * @param recordingId The recordingId of the recording to be stopped.
+ */
+ public void onRequestStopRecording(Session session, String recordingId) {
+ }
+
+ /**
* This is called when
* {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
* called.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 2956a0a..90eed9e 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -463,6 +463,16 @@
}
/**
+ * Receives stopped recording's ID.
+ *
+ * @param recordingId The ID of the recording stopped
+ * @hide
+ */
+ public void onRecordingStopped(@NonNull String recordingId) {
+ }
+
+
+ /**
* Receives signing result.
* @param signingId the ID to identify the request. It's the same as the corresponding ID in
* {@link Session#requestSigning(String, String, String, byte[])}
@@ -942,6 +952,33 @@
}
/**
+ * Requests starting of recording
+ *
+ * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+ * call {@link android.media.tv.TvRecordingClient#stopRecording()}.
+ * @see android.media.tv.TvRecordingClient#stopRecording()
+ *
+ * @hide
+ */
+ @CallSuper
+ public void requestStopRecording(@NonNull String recordingId) {
+ executeOrPostRunnableOnMainThread(() -> {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestStopRecording");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestStopRecording(recordingId);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestStopRecording", e);
+ }
+ });
+ }
+
+
+
+ /**
* Requests signing of the given data.
*
* <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -1151,11 +1188,21 @@
onAdResponse(response);
}
+ /**
+ * Calls {@link #onRecordingStarted(String)}.
+ */
void notifyRecordingStarted(String recordingId) {
onRecordingStarted(recordingId);
}
/**
+ * Calls {@link #onRecordingStopped(String)}.
+ */
+ void notifyRecordingStopped(String recordingId) {
+ onRecordingStopped(recordingId);
+ }
+
+ /**
* Notifies when the session state is changed.
*
* @param state the current session state.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 1f270d0..fcd781b 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -581,9 +581,10 @@
}
/**
- * Alerts the TV interactive app that a recording has been started with recordingId
+ * Alerts the TV interactive app that a recording has been started.
*
- * @param recordingId The ID of the recording started
+ * @param recordingId The ID of the recording started. This ID is created and maintained by the
+ * TV app and is used to identify the recording in the future.
*/
public void notifyRecordingStarted(@NonNull String recordingId) {
if (DEBUG) {
@@ -595,6 +596,23 @@
}
/**
+ * Alerts the TV interactive app that a recording has been stopped.
+ *
+ * @param recordingId The ID of the recording stopped. This ID is created and maintained
+ * by the TV app when a recording is started.
+ * @see TvInteractiveAppView#notifyRecordingStarted(String)
+ * @hide
+ */
+ public void notifyRecordingStopped(@NonNull String recordingId) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyRecordingStopped");
+ }
+ if (mSession != null) {
+ mSession.notifyRecordingStopped(recordingId);
+ }
+ }
+
+ /**
* Sends signing result to related TV interactive app.
*
* <p>This is used when the corresponding server of the broadcast-independent interactive
@@ -867,6 +885,19 @@
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#requestStopRecording()}
+ * is called.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @param recordingId The ID of the recording to stop.
+ * @hide
+ */
+ public void onRequestStopRecording(
+ @NonNull String iAppServiceId,
+ @NonNull String recordingId) {
+ }
+
+ /**
* This is called when
* {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
* called.
@@ -1204,6 +1235,20 @@
}
@Override
+ public void onRequestStopRecording(Session session, String recordingId) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestStopRecording");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestStopRecording - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onRequestStopRecording(mIAppServiceId, recordingId);
+ }
+ }
+
+ @Override
public void onRequestSigning(
Session session, String id, String algorithm, String alias, byte[] data) {
if (DEBUG) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 51b976b..fab63aa 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -2376,19 +2376,20 @@
}
/**
- * Request a frontend by frontend id.
+ * Request a frontend by frontend info.
*
* <p> This API is used if the applications want to select a desired frontend before
* {@link tune} to use a specific satellite or sending SatCR DiSEqC command for {@link tune}.
*
- * @param desiredId the desired fronted Id. It can be retrieved by
+ * @param desiredFrontendInfo the FrontendInfo of the desired fronted. It can be retrieved by
* {@link getAvailableFrontendInfos}
*
* @return result status of open operation.
* @throws SecurityException if the caller does not have appropriate permissions.
*/
@Result
- public int requestFrontendById(int desiredId) {
+ public int applyFrontend(@NonNull FrontendInfo desiredFrontendInfo) {
+ Objects.requireNonNull(desiredFrontendInfo, "desiredFrontendInfo must not be null");
mFrontendLock.lock();
try {
if (mFeOwnerTuner != null) {
@@ -2399,17 +2400,12 @@
Log.e(TAG, "A frontend has been opened before");
return RESULT_INVALID_STATE;
}
- FrontendInfo frontendInfo = getFrontendInfoById(desiredId);
- if (frontendInfo == null) {
- Log.e(TAG, "Failed to get a FrontendInfo by frontend id: " + desiredId);
- return RESULT_UNAVAILABLE;
- }
- int frontendType = frontendInfo.getType();
+ mFrontendType = desiredFrontendInfo.getType();
+ mDesiredFrontendId = desiredFrontendInfo.getId();
if (DEBUG) {
- Log.d(TAG, "Opening frontend with type " + frontendType + ", id " + desiredId);
+ Log.d(TAG, "Applying frontend with type " + mFrontendType + ", id "
+ + mDesiredFrontendId);
}
- mFrontendType = frontendType;
- mDesiredFrontendId = desiredId;
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
return RESULT_UNAVAILABLE;
}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index 1a65832..4bcc3c6 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -28,6 +28,7 @@
import android.os.Process;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FrameworkStatsLog;
import java.util.concurrent.Executor;
@@ -48,7 +49,9 @@
private static int sInstantId = 0;
private int mSegmentId = 0;
private int mOverflow;
- private Boolean mIsStopped = true;
+ private final Object mIsStoppedLock = new Object();
+ @GuardedBy("mIsStoppedLock")
+ private boolean mIsStopped = true;
private final Object mListenerLock = new Object();
private native int nativeAttachFilter(Filter filter);
@@ -178,7 +181,7 @@
.write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0);
- synchronized (mIsStopped) {
+ synchronized (mIsStoppedLock) {
int result = nativeStartDvr();
if (result == Tuner.RESULT_SUCCESS) {
mIsStopped = false;
@@ -201,7 +204,7 @@
.write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId,
FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD,
FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mOverflow);
- synchronized (mIsStopped) {
+ synchronized (mIsStoppedLock) {
int result = nativeStopDvr();
if (result == Tuner.RESULT_SUCCESS) {
mIsStopped = true;
@@ -219,7 +222,7 @@
*/
@Result
public int flush() {
- synchronized (mIsStopped) {
+ synchronized (mIsStoppedLock) {
if (mIsStopped) {
return nativeFlushDvr();
}
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index aff2e1b4..89e5e0d 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -230,7 +230,7 @@
case MtpConstants.PROPERTY_PERSISTENT_UID:
// The persistent uid must be unique and never reused among all objects,
// and remain the same between sessions.
- long puid = (object.getPath().toString().hashCode() << 32)
+ long puid = (((long) object.getPath().toString().hashCode()) << 32)
+ object.getModifiedTime();
list.append(id, property.code, property.type, puid);
break;
diff --git a/media/mca/effect/java/android/media/effect/EffectFactory.java b/media/mca/effect/java/android/media/effect/EffectFactory.java
index f6fcba7..cbb2736 100644
--- a/media/mca/effect/java/android/media/effect/EffectFactory.java
+++ b/media/mca/effect/java/android/media/effect/EffectFactory.java
@@ -486,11 +486,9 @@
private Effect instantiateEffect(Class effectClass, String name) {
// Make sure this is an Effect subclass
- try {
- effectClass.asSubclass(Effect.class);
- } catch (ClassCastException e) {
+ if (!Effect.class.isAssignableFrom(effectClass)) {
throw new IllegalArgumentException("Attempting to allocate effect '" + effectClass
- + "' which is not a subclass of Effect!", e);
+ + "' which is not a subclass of Effect!");
}
// Look for the correct constructor
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
index a608ef5..e82c046 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Filter.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -90,9 +90,7 @@
return false;
}
// Then make sure it's a subclass of Filter.
- try {
- filterClass.asSubclass(Filter.class);
- } catch (ClassCastException e) {
+ if (!Filter.class.isAssignableFrom(filterClass)) {
return false;
}
return true;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
index 779df99..736e511 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
@@ -112,9 +112,7 @@
public Filter createFilterByClass(Class filterClass, String filterName) {
// Make sure this is a Filter subclass
- try {
- filterClass.asSubclass(Filter.class);
- } catch (ClassCastException e) {
+ if (!Filter.class.isAssignableFrom(filterClass)) {
throw new IllegalArgumentException("Attempting to allocate class '" + filterClass
+ "' which is not a subclass of Filter!");
}
diff --git a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
index 8cf9a13..6ff1885 100644
--- a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
+++ b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
@@ -55,12 +55,12 @@
public int getInt(String key) {
Object result = get(key);
- return result != null ? (Integer)result : null;
+ return result != null ? (Integer) result : 0;
}
public float getFloat(String key) {
Object result = get(key);
- return result != null ? (Float)result : null;
+ return result != null ? (Float) result : 0;
}
@Override
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index c5281657..8c05725 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -296,7 +296,7 @@
mMemWriter.write("End Memory :" + mEndMemory + "\n");
}
} catch (Exception e) {
- e.toString();
+ // TODO
}
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
index 39add7e..c814eba 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java
@@ -264,8 +264,14 @@
builder.append("**");
}
- if (elem instanceof Number) {
- builder.append(String.format("%x", elem));
+ if (elem instanceof Byte) {
+ builder.append(String.format("%x", (Byte) elem));
+ } else if (elem instanceof Short) {
+ builder.append(String.format("%x", (Short) elem));
+ } else if (elem instanceof Integer) {
+ builder.append(String.format("%x", (Integer) elem));
+ } else if (elem instanceof Long) {
+ builder.append(String.format("%x", (Long) elem));
} else {
builder.append(elem);
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
index fd1c2d3..37dd4b5 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerGetCurrentPositionStateUnitTest.java
@@ -18,7 +18,7 @@
import android.media.MediaPlayer;
import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;;
+import android.test.suitebuilder.annotation.LargeTest;
/**
* Unit test class to test the set of valid and invalid states that
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 584d0ba2..9b0f020 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -238,7 +238,7 @@
ASurfaceControl_createFromWindow; # introduced=29
ASurfaceControl_acquire; # introduced=31
ASurfaceControl_release; # introduced=29
- ASurfaceControl_fromSurfaceControl; # introduced=34
+ ASurfaceControl_fromJava; # introduced=34
ASurfaceTexture_acquireANativeWindow; # introduced=28
ASurfaceTexture_attachToGLContext; # introduced=28
ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -256,7 +256,7 @@
ASurfaceTransaction_apply; # introduced=29
ASurfaceTransaction_create; # introduced=29
ASurfaceTransaction_delete; # introduced=29
- ASurfaceTransaction_fromTransaction; # introduced=34
+ ASurfaceTransaction_fromJava; # introduced=34
ASurfaceTransaction_reparent; # introduced=29
ASurfaceTransaction_setBuffer; # introduced=29
ASurfaceTransaction_setBufferAlpha; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 8913799..ea20c6c 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -138,17 +138,14 @@
SurfaceControl_release(surfaceControl);
}
-ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) {
- LOG_ALWAYS_FATAL_IF(!env,
- "nullptr passed to ASurfaceControl_fromSurfaceControl as env argument");
+ASurfaceControl* ASurfaceControl_fromJava(JNIEnv* env, jobject surfaceControlObj) {
+ LOG_ALWAYS_FATAL_IF(!env, "nullptr passed to ASurfaceControl_fromJava as env argument");
LOG_ALWAYS_FATAL_IF(!surfaceControlObj,
- "nullptr passed to ASurfaceControl_fromSurfaceControl as surfaceControlObj "
- "argument");
+ "nullptr passed to ASurfaceControl_fromJava as surfaceControlObj argument");
SurfaceControl* surfaceControl =
android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj);
LOG_ALWAYS_FATAL_IF(!surfaceControl,
- "surfaceControlObj passed to ASurfaceControl_fromSurfaceControl is not "
- "valid");
+ "surfaceControlObj passed to ASurfaceControl_fromJava is not valid");
SurfaceControl_acquire(surfaceControl);
return reinterpret_cast<ASurfaceControl*>(surfaceControl);
}
@@ -209,17 +206,15 @@
delete transaction;
}
-ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) {
- LOG_ALWAYS_FATAL_IF(!env,
- "nullptr passed to ASurfaceTransaction_fromTransaction as env argument");
+ASurfaceTransaction* ASurfaceTransaction_fromJava(JNIEnv* env, jobject transactionObj) {
+ LOG_ALWAYS_FATAL_IF(!env, "nullptr passed to ASurfaceTransaction_fromJava as env argument");
LOG_ALWAYS_FATAL_IF(!transactionObj,
- "nullptr passed to ASurfaceTransaction_fromTransaction as transactionObj "
+ "nullptr passed to ASurfaceTransaction_fromJava as transactionObj "
"argument");
Transaction* transaction =
android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj);
LOG_ALWAYS_FATAL_IF(!transaction,
- "surfaceControlObj passed to ASurfaceTransaction_fromTransaction is not "
- "valid");
+ "surfaceControlObj passed to ASurfaceTransaction_fromJava is not valid");
return reinterpret_cast<ASurfaceTransaction*>(transaction);
}
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 4c22ee6..c4bb17c 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -34,7 +34,8 @@
android:label="@string/app_name"
android:directBootAware="true"
android:usesCleartextTraffic="true"
- android:icon="@mipmap/ic_launcher_android">
+ android:icon="@mipmap/ic_launcher_android"
+ android:debuggable="true">
<receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver"
android:exported="true">
<intent-filter>
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index 8a19709..3dcdf00 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -15,7 +15,7 @@
<string name="ssl_error_continue">Continue anyway via browser</string>
<!-- Telephony notification channel name for network boost notifications. -->
- <string name="network_boost_notification_channel">Network Boost</string>
+ <string name="network_boost_notification_channel">Network boost</string>
<!-- Notification title text for the network boost notification. -->
<string name="network_boost_notification_title">%s recommends a data boost</string>
<!-- Notification detail text for the network boost notification. -->
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index e67ea7e..220aa33 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -58,8 +58,8 @@
public class SlicePurchaseActivity extends Activity {
private static final String TAG = "SlicePurchaseActivity";
- private @NonNull WebView mWebView;
- private @NonNull Context mApplicationContext;
+ @NonNull private WebView mWebView;
+ @NonNull private Context mApplicationContext;
private int mSubId;
@TelephonyManager.PremiumCapability protected int mCapability;
@@ -105,7 +105,7 @@
loge("Unable to start the slice purchase application on the non-default data "
+ "subscription: " + mSubId);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB);
+ intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
finishAndRemoveTask();
return;
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 5761b3c..b322b8b 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -33,6 +33,7 @@
import android.util.Log;
import android.webkit.WebView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.phone.slice.SlicePurchaseController;
import java.lang.ref.WeakReference;
@@ -173,7 +174,7 @@
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR)
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
&& isPendingIntentValid(intent,
- SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB)
+ SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
}
@@ -204,8 +205,8 @@
case SlicePurchaseController.EXTRA_INTENT_CANCELED: return "canceled";
case SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
- case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB:
- return "not default data sub";
+ case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
+ return "not default data subscription";
case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
default: {
loge("Unknown pending intent extra: " + extra);
@@ -239,11 +240,15 @@
return;
}
- context.getSystemService(NotificationManager.class).createNotificationChannel(
- new NotificationChannel(NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
- context.getResources().getString(
- R.string.network_boost_notification_channel),
- NotificationManager.IMPORTANCE_DEFAULT));
+ NotificationChannel channel = new NotificationChannel(
+ NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
+ context.getResources().getString(R.string.network_boost_notification_channel),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
+ // to allow users to disable notifications posted to this channel without affecting other
+ // notifications in this application.
+ channel.setBlockable(true);
+ context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
Notification notification =
new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
@@ -291,7 +296,8 @@
*
* @return The intent to start {@link SlicePurchaseActivity}.
*/
- @NonNull private PendingIntent createContentIntent(@NonNull Context context,
+ @VisibleForTesting
+ @NonNull public PendingIntent createContentIntent(@NonNull Context context,
@NonNull Intent intent, int requestCode) {
Intent i = new Intent(context, SlicePurchaseActivity.class);
i.setComponent(ComponentName.unflattenFromString(
@@ -314,7 +320,8 @@
*
* @return The canceled intent.
*/
- @NonNull private PendingIntent createCanceledIntent(@NonNull Context context,
+ @VisibleForTesting
+ @NonNull public PendingIntent createCanceledIntent(@NonNull Context context,
@NonNull Intent intent) {
Intent i = new Intent(ACTION_NOTIFICATION_CANCELED);
i.setComponent(ComponentName.unflattenFromString(
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp
index 54c9016..cdf7957 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.bp
+++ b/packages/CarrierDefaultApp/tests/unit/Android.bp
@@ -27,11 +27,13 @@
libs: [
"android.test.runner",
"android.test.base",
+ "SlicePurchaseController",
],
static_libs: [
"androidx.test.rules",
- "mockito-target-minus-junit4",
+ "mockito-target-inline-minus-junit4",
],
+ jni_libs: ["libdexmakerjvmtiagent"],
// Include all test java files.
srcs: ["src/**/*.java"],
platform_apis: true,
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
new file mode 100644
index 0000000..cecc86d
--- /dev/null
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 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.carrierdefaultapp;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.test.ActivityUnitTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchaseActivity> {
+ private static final String TAG = "SlicePurchaseActivityTest";
+ private static final String URL = "file:///android_asset/slice_purchase_test.html";
+ private static final int PHONE_ID = 0;
+
+ @Mock PendingIntent mPendingIntent;
+ @Mock PendingIntent mCanceledIntent;
+ @Mock CarrierConfigManager mCarrierConfigManager;
+ @Mock NotificationManager mNotificationManager;
+ @Mock PersistableBundle mPersistableBundle;
+
+ private SlicePurchaseActivity mSlicePurchaseActivity;
+ private Context mContext;
+
+ public SlicePurchaseActivityTest() {
+ super(SlicePurchaseActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+
+ // setup context
+ mContext = spy(getInstrumentation().getTargetContext());
+ doReturn(mCarrierConfigManager).when(mContext)
+ .getSystemService(eq(CarrierConfigManager.class));
+ doReturn(URL).when(mPersistableBundle).getString(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
+ doReturn(mPersistableBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+ doReturn(mNotificationManager).when(mContext)
+ .getSystemService(eq(NotificationManager.class));
+ doReturn(mContext).when(mContext).getApplicationContext();
+ setActivityContext(mContext);
+
+ // set up intent
+ Intent intent = new Intent();
+ intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+ intent.putExtra(SlicePurchaseController.EXTRA_SUB_ID,
+ SubscriptionManager.getDefaultDataSubscriptionId());
+ intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG);
+ Intent spiedIntent = spy(intent);
+
+ // set up pending intents
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+ doReturn(true).when(mPendingIntent).isBroadcast();
+ doReturn(mPendingIntent).when(spiedIntent).getParcelableExtra(
+ anyString(), eq(PendingIntent.class));
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mCanceledIntent).getCreatorPackage();
+ doReturn(true).when(mCanceledIntent).isBroadcast();
+ doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra(
+ eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class));
+
+ mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSlicePurchaseActivity.onDestroy();
+ super.tearDown();
+ }
+
+ @Test
+ public void testOnPurchaseSuccessful() throws Exception {
+ int duration = 5 * 60 * 1000; // 5 minutes
+ int invalidDuration = -1;
+ mSlicePurchaseActivity.onPurchaseSuccessful(duration);
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
+ Intent intent = intentCaptor.getValue();
+ assertEquals(duration, intent.getLongExtra(
+ SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration));
+ }
+
+ @Test
+ public void testOnPurchaseFailed() throws Exception {
+ int failureCode = SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE;
+ String failureReason = "Server unreachable";
+ mSlicePurchaseActivity.onPurchaseFailed(failureCode, failureReason);
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
+ Intent intent = intentCaptor.getValue();
+ assertEquals(failureCode, intent.getIntExtra(
+ SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode));
+ assertEquals(failureReason, intent.getStringExtra(
+ SlicePurchaseController.EXTRA_FAILURE_REASON));
+ }
+
+ @Test
+ public void testOnUserCanceled() throws Exception {
+ mSlicePurchaseActivity.onDestroy();
+ verify(mCanceledIntent).send();
+ }
+}
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
new file mode 100644
index 0000000..5765e5b
--- /dev/null
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2022 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.carrierdefaultapp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.DisplayMetrics;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SlicePurchaseBroadcastReceiverTest {
+ private static final int PHONE_ID = 0;
+ private static final String TAG = "SlicePurchaseBroadcastReceiverTest";
+ private static final String EXTRA = "EXTRA";
+
+ @Mock Intent mIntent;
+ @Mock Intent mDataIntent;
+ @Mock PendingIntent mPendingIntent;
+ @Mock PendingIntent mCanceledIntent;
+ @Mock PendingIntent mContentIntent1;
+ @Mock PendingIntent mContentIntent2;
+ @Mock Context mContext;
+ @Mock Resources mResources;
+ @Mock NotificationManager mNotificationManager;
+ @Mock ApplicationInfo mApplicationInfo;
+ @Mock PackageManager mPackageManager;
+ @Mock DisplayMetrics mDisplayMetrics;
+ @Mock SlicePurchaseActivity mSlicePurchaseActivity;
+
+ private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver;
+ private ArgumentCaptor<Intent> mIntentCaptor;
+ private ArgumentCaptor<Notification> mNotificationCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mNotificationManager).when(mContext)
+ .getSystemService(eq(NotificationManager.class));
+
+ mIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ mNotificationCaptor = ArgumentCaptor.forClass(Notification.class);
+ mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver());
+ }
+
+ @Test
+ public void testSendSlicePurchaseAppResponse() throws Exception {
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
+ verify(mPendingIntent, never()).send();
+
+ doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+ eq(EXTRA), eq(PendingIntent.class));
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
+ verify(mPendingIntent).send();
+ }
+
+ @Test
+ public void testSendSlicePurchaseAppResponseWithData() throws Exception {
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
+ mContext, mIntent, EXTRA, mDataIntent);
+ verify(mPendingIntent, never()).send(eq(mContext), eq(0), any(Intent.class));
+
+ doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+ eq(EXTRA), eq(PendingIntent.class));
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
+ mContext, mIntent, EXTRA, mDataIntent);
+ verify(mPendingIntent).send(eq(mContext), eq(0), mIntentCaptor.capture());
+ assertEquals(mDataIntent, mIntentCaptor.getValue());
+ }
+
+ @Test
+ public void testIsIntentValid() {
+ assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+
+ // set up intent
+ doReturn(PHONE_ID).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+ doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ doReturn(TAG).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+ assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+
+ // set up pending intent
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+ doReturn(true).when(mPendingIntent).isBroadcast();
+ doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+ anyString(), eq(PendingIntent.class));
+ assertTrue(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
+ }
+
+ @Test
+ public void testDisplayBoosterNotification() {
+ // set up intent
+ doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
+ doReturn(PHONE_ID).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+ doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ doReturn(TAG).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+
+ // set up pending intent
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
+ doReturn(true).when(mPendingIntent).isBroadcast();
+ doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
+ anyString(), eq(PendingIntent.class));
+
+ // set up notification
+ doReturn(mResources).when(mContext).getResources();
+ doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
+ doReturn("").when(mResources).getString(anyInt());
+ doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+
+ // set up intents created by broadcast receiver
+ doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
+ eq(mContext), eq(mIntent), eq(1));
+ doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
+ eq(mContext), eq(mIntent), eq(2));
+ doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent(
+ eq(mContext), eq(mIntent));
+
+ // send ACTION_START_SLICE_PURCHASE_APP
+ mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+ // verify network boost notification was shown
+ verify(mNotificationManager).notifyAsUser(
+ eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ mNotificationCaptor.capture(),
+ eq(UserHandle.ALL));
+
+ Notification notification = mNotificationCaptor.getValue();
+ assertEquals(mContentIntent1, notification.contentIntent);
+ assertEquals(mPendingIntent, notification.deleteIntent);
+ assertEquals(2, notification.actions.length);
+ assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
+ assertEquals(mContentIntent2, notification.actions[1].actionIntent);
+ }
+
+
+ @Test
+ public void testNotificationCanceled() {
+ // set up intent
+ doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction();
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+ // send ACTION_NOTIFICATION_CANCELED
+ mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+ // verify notification was canceled
+ verify(mNotificationManager).cancelAsUser(
+ eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ eq(UserHandle.ALL));
+ }
+
+ @Test
+ public void testNotificationTimeout() {
+ // set up intent
+ doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
+ .getAction();
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+ // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+ mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+ // verify notification was canceled
+ verify(mNotificationManager).cancelAsUser(
+ eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ eq(UserHandle.ALL));
+ }
+
+ @Test
+ // TODO: WebView/Activity should not close on timeout.
+ // This test should be removed once implementation is fixed.
+ public void testActivityTimeout() {
+ // create and track activity
+ SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mSlicePurchaseActivity);
+
+ // set up intent
+ doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
+ .getAction();
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+
+ // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+ mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
+
+ // verify activity was canceled
+ verify(mSlicePurchaseActivity).finishAndRemoveTask();
+
+ // untrack activity
+ SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ }
+}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index a7e1a59..ae40460 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -203,6 +203,7 @@
initUI();
}
+ @SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
protected void onNewIntent(Intent intent) {
// Force cancels the CDM dialog if this activity receives another intent with
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
index f1f453d..61e11fe 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -62,6 +62,7 @@
import com.android.credentialmanager.common.material.ModalBottomSheetValue.Expanded
import com.android.credentialmanager.common.material.ModalBottomSheetValue.HalfExpanded
import com.android.credentialmanager.common.material.ModalBottomSheetValue.Hidden
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import kotlin.math.max
@@ -318,7 +319,7 @@
rememberModalBottomSheetState(Hidden),
sheetShape: Shape = MaterialTheme.shapes.large,
sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
- sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ sheetBackgroundColor: Color = ModalBottomSheetDefaults.scrimColor,
sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
content: @Composable () -> Unit
@@ -476,5 +477,5 @@
*/
val scrimColor: Color
@Composable
- get() = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f)
+ get() = LocalAndroidColorScheme.current.colorSurfaceHighlight
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 67b704f..8a1f83d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -1,3 +1,5 @@
+@file:OptIn(ExperimentalMaterial3Api::class)
+
package com.android.credentialmanager.createflow
import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
@@ -19,6 +21,7 @@
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.NewReleases
@@ -40,6 +43,7 @@
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.common.ui.CancelButton
import com.android.credentialmanager.common.ui.ConfirmButton
+import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
@OptIn(ExperimentalMaterial3Api::class)
@@ -91,7 +95,7 @@
}
},
scrimColor = MaterialTheme.colorScheme.scrim,
- sheetShape = MaterialTheme.shapes.medium,
+ sheetShape = EntryShape.TopRoundedCorner,
) {}
LaunchedEffect(state.currentValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
@@ -100,6 +104,7 @@
}
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConfirmationCard(
onConfirm: () -> Unit,
@@ -179,7 +184,7 @@
color = Color.Transparent
)
Card(
- shape = MaterialTheme.shapes.large,
+ shape = EntryShape.FullRoundedCorner,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally),
@@ -243,14 +248,16 @@
Icons.Filled.ArrowBack,
stringResource(R.string.accessibility_back_arrow_button))
}
- }
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors
+ (containerColor = Color.Transparent),
)
Divider(
thickness = 8.dp,
color = Color.Transparent
)
Card(
- shape = MaterialTheme.shapes.large,
+ shape = EntryShape.FullRoundedCorner,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally)
@@ -362,7 +369,7 @@
// TODO: add description.
contentDescription = "")
},
- shape = MaterialTheme.shapes.large,
+ shape = EntryShape.FullRoundedCorner,
label = {
Text(
text = providerInfo.displayName,
@@ -391,7 +398,8 @@
bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = Color.Unspecified,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(all = 24.dp).size(32.dp)
)
Text(
text = when (requestDisplayInfo.type) {
@@ -425,7 +433,7 @@
)
}
Card(
- shape = MaterialTheme.shapes.large,
+ shape = EntryShape.FullRoundedCorner,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally),
@@ -499,7 +507,7 @@
bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
contentDescription = null)
},
- shape = MaterialTheme.shapes.large,
+ shape = EntryShape.FullRoundedCorner,
label = {
Column() {
Text(
@@ -532,7 +540,7 @@
bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
contentDescription = null)
},
- shape = MaterialTheme.shapes.large,
+ shape = EntryShape.FullRoundedCorner,
label = {
Column() {
Text(
@@ -603,7 +611,7 @@
modifier = Modifier.padding(start = 16.dp)
)
},
- shape = MaterialTheme.shapes.large,
+ shape = EntryShape.FullRoundedCorner,
label = {
Column() {
Text(
@@ -637,7 +645,7 @@
modifier = Modifier.padding(start = 18.dp)
)
},
- shape = MaterialTheme.shapes.large,
+ shape = EntryShape.FullRoundedCorner,
label = {
Column() {
Text(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
new file mode 100644
index 0000000..e0954ad
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.ui.theme
+
+import android.annotation.ColorInt
+import android.content.Context
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import com.android.internal.R
+
+/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
+val LocalAndroidColorScheme =
+ staticCompositionLocalOf<AndroidColorScheme> {
+ throw IllegalStateException(
+ "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
+ "Composable surrounded by a CredentialSelectorTheme {}."
+ )
+ }
+
+/**
+ * The Android color scheme.
+ *
+ * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
+ * most of the colors in this class will be removed in favor of their M3 counterpart.
+ */
+class AndroidColorScheme internal constructor(context: Context) {
+
+ val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
+
+ private fun getColor(context: Context, attr: Int): Color {
+ val ta = context.obtainStyledAttributes(intArrayOf(attr))
+ @ColorInt val color = ta.getColor(0, 0)
+ ta.recycle()
+ return Color(color)
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
index 5ea6993..32def89 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -9,3 +9,8 @@
medium = RoundedCornerShape(20.dp),
large = RoundedCornerShape(0.dp)
)
+
+object EntryShape {
+ val TopRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 0.dp, 0.dp)
+ val FullRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 28.dp, 28.dp)
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
index 248df92..3ca0e44 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -2,37 +2,37 @@
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.lightColorScheme
-import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
-
-private val AppDarkColorScheme = darkColorScheme(
- primary = Purple200,
- secondary = Purple700,
- tertiary = Teal200
-)
-
-private val AppLightColorScheme = lightColorScheme(
- primary = Purple500,
- secondary = Purple700,
- tertiary = Teal200
-)
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
@Composable
fun CredentialSelectorTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
- val AppColorScheme = if (darkTheme) {
- AppDarkColorScheme
- } else {
- AppLightColorScheme
- }
+ val context = LocalContext.current
+
+ val colorScheme =
+ if (darkTheme) {
+ dynamicDarkColorScheme(context)
+ } else {
+ dynamicLightColorScheme(context)
+ }
+ val androidColorScheme = AndroidColorScheme(context)
+ val typography = Typography
MaterialTheme(
- colorScheme = AppColorScheme,
- typography = Typography,
- shapes = Shapes,
- content = content
- )
+ colorScheme,
+ typography = typography,
+ shapes = Shapes
+ ) {
+ CompositionLocalProvider(
+ LocalAndroidColorScheme provides androidColorScheme,
+ ) {
+ content()
+ }
+ }
}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
index 93e6271..3029d10 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/google/CloudPrintPlugin.java
@@ -55,18 +55,15 @@
private static final String PRIVET_SERVICE = "_privet._tcp";
/** The required mDNS service types */
- private static final Set<String> PRINTER_SERVICE_TYPE = new HashSet<String>() {{
- // Not checking _printer_._sub
- add(PRIVET_SERVICE);
- }};
+ private static final Set<String> PRINTER_SERVICE_TYPE = Set.of(
+ PRIVET_SERVICE); // Not checking _printer_._sub
/** All possible connection states */
- private static final Set<String> POSSIBLE_CONNECTION_STATES = new HashSet<String>() {{
- add("online");
- add("offline");
- add("connecting");
- add("not-configured");
- }};
+ private static final Set<String> POSSIBLE_CONNECTION_STATES = Set.of(
+ "online",
+ "offline",
+ "connecting",
+ "not-configured");
private static final byte SUPPORTED_TXTVERS = '1';
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
index 34e7e3d..0c5de27 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
@@ -37,9 +37,7 @@
public class MDNSFilterPlugin implements PrintServicePlugin {
/** The mDNS service types supported */
- private static final Set<String> PRINTER_SERVICE_TYPES = new HashSet<String>() {{
- add("_ipp._tcp");
- }};
+ private static final Set<String> PRINTER_SERVICE_TYPES = Set.of("_ipp._tcp");
/**
* The printer filter for {@link MDNSFilteredDiscovery} passing only mDNS results
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
index d03bb1d..b9983c3 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
@@ -23,7 +23,6 @@
import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
import com.android.printservice.recommendation.util.MDNSUtils;
-import java.util.HashSet;
import java.util.Set;
/**
@@ -32,10 +31,7 @@
class PrinterFilterMopria implements MDNSFilteredDiscovery.PrinterFilter {
private static final String TAG = "PrinterFilterMopria";
- static final Set<String> MOPRIA_MDNS_SERVICES = new HashSet<String>() {{
- add("_ipp._tcp");
- add("_ipps._tcp");
- }};
+ static final Set<String> MOPRIA_MDNS_SERVICES = Set.of("_ipp._tcp", "_ipps._tcp");
private static final String PDL__PDF = "application/pdf";
private static final String PDL__PCLM = "application/PCLm";
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
index b9b9098..680dd84 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
@@ -25,7 +25,6 @@
import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
import com.android.printservice.recommendation.util.MDNSUtils;
-import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -36,9 +35,7 @@
class PrinterFilterSamsung implements MDNSFilteredDiscovery.PrinterFilter {
private static final String TAG = "PrinterFilterSamsung";
- static final Set<String> SAMSUNG_MDNS_SERVICES = new HashSet<String>() {{
- add("_pdl-datastream._tcp");
- }};
+ static final Set<String> SAMSUNG_MDNS_SERVICES = Set.of("_pdl-datastream._tcp");
private static final String[] NOT_SUPPORTED_MODELS = new String[]{
"SCX-5x15",
@@ -57,9 +54,7 @@
private static final String ATTR_PRODUCT = "product";
private static final String ATTR_TY = "ty";
- private static Set<String> SAMUNG_VENDOR_SET = new HashSet<String>() {{
- add("samsung");
- }};
+ private static final Set<String> SAMUNG_VENDOR_SET = Set.of("samsung");
@Override
public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
index ae1bdce..cbd5833 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
@@ -29,10 +29,11 @@
import java.util.Set;
public class SamsungRecommendationPlugin implements PrintServicePlugin {
- private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>() {{
- addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
- addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
- }};
+ private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>();
+ static {
+ ALL_MDNS_SERVICES.addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
+ ALL_MDNS_SERVICES.addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
+ }
private final @NonNull Context mContext;
private final @NonNull MDNSFilteredDiscovery mMDNSFilteredDiscovery;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index 00b3736..b0aa8f1 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -402,7 +402,7 @@
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
- if ((isOptionsClosed() || isOptionsClosed()) && dy <= 0) {
+ if (isOptionsClosed() && dy <= 0) {
return;
}
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 8eafbdf..a53782a 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,10 +3,11 @@
edgarwang@google.com
emilychuang@google.com
evanlaird@google.com
+hanxu@google.com
juliacr@google.com
leifhendrik@google.com
-tmfang@google.com
virgild@google.com
+ykhung@google.com
# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
per-file *.xml=*
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 4fb77d7..b8fd579 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -16,9 +16,10 @@
buildscript {
ext {
- spa_min_sdk = 21
- spa_target_sdk = 33
- jetpack_compose_version = '1.3.0'
+ BUILD_TOOLS_VERSION = "30.0.3"
+ MIN_SDK = 21
+ TARGET_SDK = 33
+ jetpack_compose_version = '1.4.0-alpha01'
jetpack_compose_compiler_version = '1.3.2'
}
}
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 50cab84..1e52aaf 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -41,6 +41,20 @@
android:exported="false">
</provider>
+ <provider android:name="com.android.settingslib.spa.framework.SpaSliceProvider"
+ android:authorities="com.android.spa.gallery.slice.provider"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.app.slice.category.SLICE" />
+ </intent-filter>
+ </provider>
+
+ <receiver
+ android:name="com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver"
+ android:exported="false">
+ </receiver>
+
<activity
android:name="com.android.settingslib.spa.framework.debug.BlankActivity"
android:exported="true">
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index e04a9be..7868aff 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -21,12 +21,13 @@
android {
namespace 'com.android.settingslib.spa.gallery'
- compileSdk spa_target_sdk
+ compileSdk TARGET_SDK
+ buildToolsVersion = BUILD_TOOLS_VERSION
defaultConfig {
applicationId "com.android.settingslib.spa.gallery"
- minSdk spa_min_sdk
- targetSdk spa_target_sdk
+ minSdk MIN_SDK
+ targetSdk TARGET_SDK
versionCode 1
versionName "1.0"
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 016b27f..941e770 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.gallery
import android.content.Context
+import com.android.settingslib.spa.framework.SpaSliceBroadcastReceiver
import com.android.settingslib.spa.framework.common.LocalLogger
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironment
@@ -79,8 +80,8 @@
}
override val browseActivityClass = GalleryMainActivity::class.java
-
+ override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
-
+ override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
override val logger = LocalLogger()
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 26e59ff..ff89f2b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -27,6 +27,7 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.EntrySliceData
import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -37,6 +38,7 @@
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
+import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -46,10 +48,15 @@
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
+import com.android.settingslib.spa.slice.createBrowsePendingIntent
+import com.android.settingslib.spa.slice.provider.createDemoActionSlice
+import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice
+import com.android.settingslib.spa.slice.provider.createDemoSlice
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.ui.SettingsIcon
+import kotlinx.coroutines.delay
private const val TAG = "PreferencePage"
@@ -134,6 +141,26 @@
override val enabled = model.asyncEnable
}
)
+ }
+ .setSliceDataFn { sliceUri, _ ->
+ val createSliceImpl = { s: String ->
+ createDemoBrowseSlice(
+ sliceUri = sliceUri,
+ title = ASYNC_PREFERENCE_TITLE,
+ summary = s,
+ )
+ }
+ return@setSliceDataFn object : EntrySliceData() {
+ init {
+ postValue(createSliceImpl("(loading)"))
+ }
+
+ override suspend fun asyncRunner() {
+ spaLogger.message(TAG, "Async entry loading")
+ delay(2000L)
+ postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY))
+ }
+ }
}.build()
)
entryList.add(
@@ -152,6 +179,27 @@
}
}
)
+ }.setSliceDataFn { sliceUri, args ->
+ val createSliceImpl = { v: Int ->
+ createDemoActionSlice(
+ sliceUri = sliceUri,
+ title = MANUAL_UPDATE_PREFERENCE_TITLE,
+ summary = "manual update value $v",
+ )
+ }
+
+ return@setSliceDataFn object : EntrySliceData() {
+ private var tick = args?.getString("init")?.toInt() ?: 0
+
+ init {
+ postValue(createSliceImpl(tick))
+ }
+
+ override suspend fun asyncAction() {
+ tick++
+ postValue(createSliceImpl(tick))
+ }
+ }
}.build()
)
entryList.add(
@@ -170,7 +218,33 @@
}
)
}
- .build()
+ .setSliceDataFn { sliceUri, args ->
+ val createSliceImpl = { v: Int ->
+ createDemoBrowseSlice(
+ sliceUri = sliceUri,
+ title = AUTO_UPDATE_PREFERENCE_TITLE,
+ summary = "auto update value $v",
+ )
+ }
+
+ return@setSliceDataFn object : EntrySliceData() {
+ private var tick = args?.getString("init")?.toInt() ?: 0
+
+ init {
+ postValue(createSliceImpl(tick))
+ }
+
+ override suspend fun asyncRunner() {
+ spaLogger.message(TAG, "autoUpdater.active")
+ while (true) {
+ delay(1000L)
+ tick++
+ spaLogger.message(TAG, "autoUpdater.value $tick")
+ postValue(createSliceImpl(tick))
+ }
+ }
+ }
+ }.build()
)
return entryList
@@ -201,6 +275,22 @@
clickRoute = SettingsPageProviderEnum.PREFERENCE.name
)
}
+ .setSliceDataFn { sliceUri, _ ->
+ val intent = owner.createBrowseIntent()?.createBrowsePendingIntent()
+ ?: return@setSliceDataFn null
+ return@setSliceDataFn object : EntrySliceData() {
+ init {
+ postValue(
+ createDemoSlice(
+ sliceUri = sliceUri,
+ title = PAGE_TITLE,
+ summary = "Injected Entry",
+ intent = intent,
+ )
+ )
+ }
+ }
+ }
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
index d874417..fc6f10f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
@@ -44,7 +44,7 @@
const val DISABLE_PREFERENCE_TITLE = "Disabled"
const val DISABLE_PREFERENCE_SUMMARY = "Disabled summary"
const val ASYNC_PREFERENCE_TITLE = "Async Preference"
- private const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
+ const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
const val MANUAL_UPDATE_PREFERENCE_TITLE = "Manual Updater"
const val AUTO_UPDATE_PREFERENCE_TITLE = "Auto Updater"
val SIMPLE_PREFERENCE_KEYWORDS = listOf("simple keyword1", "simple keyword2")
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 037b45e..2eaa73e 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -24,6 +24,9 @@
srcs: ["src/**/*.kt"],
static_libs: [
+ "androidx.slice_slice-builders",
+ "androidx.slice_slice-core",
+ "androidx.slice_slice-view",
"androidx.compose.material3_material3",
"androidx.compose.material_material-icons-extended",
"androidx.compose.runtime_runtime",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 7a20c747..4944784 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -21,11 +21,12 @@
android {
namespace 'com.android.settingslib.spa'
- compileSdk spa_target_sdk
+ compileSdk TARGET_SDK
+ buildToolsVersion = BUILD_TOOLS_VERSION
defaultConfig {
- minSdk spa_min_sdk
- targetSdk spa_target_sdk
+ minSdk MIN_SDK
+ targetSdk TARGET_SDK
}
sourceSets {
@@ -55,6 +56,9 @@
dependencies {
api "androidx.appcompat:appcompat:1.7.0-alpha01"
+ api "androidx.slice:slice-builders:1.1.0-alpha02"
+ api "androidx.slice:slice-core:1.1.0-alpha02"
+ api "androidx.slice:slice-view:1.1.0-alpha02"
api "androidx.compose.material3:material3:1.1.0-alpha01"
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
index 35b9c0f..3689e4e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
@@ -119,7 +119,7 @@
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
- if (!entry.isAllowSearch || entry.mutableStatus) continue
+ if (!entry.isAllowSearch || entry.hasMutableStatus) continue
fetchStatusData(entry, cursor)
}
return cursor
@@ -129,7 +129,7 @@
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
- if (!entry.isAllowSearch || !entry.mutableStatus) continue
+ if (!entry.isAllowSearch || !entry.hasMutableStatus) continue
fetchStatusData(entry, cursor)
}
return cursor
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
new file mode 100644
index 0000000..58131e6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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.spa.framework
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+class SpaSliceBroadcastReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
+ val sliceUri = intent?.data ?: return
+ val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
+ sliceData.doAction()
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
new file mode 100644
index 0000000..faa04fd
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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.spa.framework
+
+import android.net.Uri
+import android.util.Log
+import androidx.lifecycle.Observer
+import androidx.slice.Slice
+import androidx.slice.SliceProvider
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+
+private const val TAG = "SpaSliceProvider"
+
+class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
+ private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
+ if (!SpaEnvironmentFactory.isReady()) return null
+ val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
+ return sliceRepository.getOrBuildSliceData(sliceUri)
+ }
+
+ override fun onBindSlice(sliceUri: Uri): Slice? {
+ if (context == null) return null
+ Log.d(TAG, "onBindSlice: $sliceUri")
+ return getOrPutSliceData(sliceUri)?.value
+ }
+
+ override fun onSlicePinned(sliceUri: Uri) {
+ Log.d(TAG, "onSlicePinned: $sliceUri")
+ super.onSlicePinned(sliceUri)
+ val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
+ runBlocking {
+ withContext(Dispatchers.Main) {
+ sliceLiveData.observeForever(this@SpaSliceProvider)
+ }
+ }
+ }
+
+ override fun onSliceUnpinned(sliceUri: Uri) {
+ Log.d(TAG, "onSliceUnpinned: $sliceUri")
+ super.onSliceUnpinned(sliceUri)
+ val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
+ runBlocking {
+ withContext(Dispatchers.Main) {
+ sliceLiveData.removeObserver(this@SpaSliceProvider)
+ }
+ }
+ }
+
+ override fun onChanged(slice: Slice?) {
+ val uri = slice?.uri ?: return
+ Log.d(TAG, "onChanged: $uri")
+ context?.contentResolver?.notifyChange(uri, null)
+ }
+
+ override fun onCreateSliceProvider(): Boolean {
+ Log.d(TAG, "onCreateSliceProvider")
+ return true
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt
new file mode 100644
index 0000000..fc551a8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.spa.framework.common
+
+import androidx.lifecycle.LiveData
+import androidx.slice.Slice
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+open class EntrySliceData : LiveData<Slice?>() {
+ private val asyncRunnerScope = CoroutineScope(Dispatchers.IO)
+ private var asyncRunnerJob: Job? = null
+ private var asyncActionJob: Job? = null
+ private var isActive = false
+
+ open suspend fun asyncRunner() {}
+
+ open suspend fun asyncAction() {}
+
+ override fun onActive() {
+ asyncRunnerJob?.cancel()
+ asyncRunnerJob = asyncRunnerScope.launch { asyncRunner() }
+ isActive = true
+ }
+
+ override fun onInactive() {
+ asyncRunnerJob?.cancel()
+ asyncRunnerJob = null
+ asyncActionJob?.cancel()
+ asyncActionJob = null
+ isActive = false
+ }
+
+ fun isActive(): Boolean {
+ return isActive
+ }
+
+ fun doAction() {
+ asyncActionJob?.cancel()
+ asyncActionJob = asyncRunnerScope.launch { asyncAction() }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 224fe1d..9ee7f9e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.common
+import android.net.Uri
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -39,6 +40,11 @@
val LocalEntryDataProvider =
compositionLocalOf<EntryData> { object : EntryData {} }
+typealias UiLayerRenderer = @Composable (arguments: Bundle?) -> Unit
+typealias StatusDataGetter = (arguments: Bundle?) -> EntryStatusData?
+typealias SearchDataGetter = (arguments: Bundle?) -> EntrySearchData?
+typealias SliceDataGetter = (sliceUri: Uri, arguments: Bundle?) -> EntrySliceData?
+
/**
* Defines data of a Settings entry.
*/
@@ -71,7 +77,10 @@
// Indicate whether the status of entry is mutable.
// If so, for instance, we'll reindex its status for search.
- val mutableStatus: Boolean = false,
+ val hasMutableStatus: Boolean = false,
+
+ // Indicate whether the entry has SliceProvider support.
+ val hasSliceSupport: Boolean = false,
/**
* ========================================
@@ -83,13 +92,19 @@
* API to get the status data of the entry, such as isDisabled / isSwitchOff.
* Returns null if this entry do NOT have any status.
*/
- private val statusDataImpl: (arguments: Bundle?) -> EntryStatusData? = { null },
+ private val statusDataImpl: StatusDataGetter = { null },
/**
* API to get Search indexing data for this entry, such as title / keyword.
* Returns null if this entry do NOT support search.
*/
- private val searchDataImpl: (arguments: Bundle?) -> EntrySearchData? = { null },
+ private val searchDataImpl: SearchDataGetter = { null },
+
+ /**
+ * API to get Slice data of this entry. The Slice data is implemented as a LiveData,
+ * and is associated with the Slice's lifecycle (pin / unpin) by the framework.
+ */
+ private val sliceDataImpl: SliceDataGetter = { _: Uri, _: Bundle? -> null },
/**
* API to Render UI of this entry directly. For now, we use it in the internal injection, to
@@ -97,7 +112,7 @@
* injected entry. In the long term, we may deprecate the @Composable Page() API in SPP, and
* use each entries' UI rendering function in the page instead.
*/
- private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
+ private val uiLayoutImpl: UiLayerRenderer = {},
) {
fun containerPage(): SettingsPage {
// The Container page of the entry, which is the from-page or
@@ -121,6 +136,10 @@
return searchDataImpl(fullArgument(runtimeArguments))
}
+ fun getSliceData(sliceUri: Uri, runtimeArguments: Bundle? = null): EntrySliceData? {
+ return sliceDataImpl(sliceUri, fullArgument(runtimeArguments))
+ }
+
@Composable
fun UiLayout(runtimeArguments: Bundle? = null) {
CompositionLocalProvider(provideLocalEntryData()) {
@@ -152,12 +171,14 @@
// Attributes
private var isAllowSearch: Boolean = false
private var isSearchDataDynamic: Boolean = false
- private var mutableStatus: Boolean = false
+ private var hasMutableStatus: Boolean = false
+ private var hasSliceSupport: Boolean = false
// Functions
- private var statusDataFn: (arguments: Bundle?) -> EntryStatusData? = { null }
- private var searchDataFn: (arguments: Bundle?) -> EntrySearchData? = { null }
- private var uiLayoutFn: (@Composable (arguments: Bundle?) -> Unit) = { }
+ private var uiLayoutFn: UiLayerRenderer = { }
+ private var statusDataFn: StatusDataGetter = { null }
+ private var searchDataFn: SearchDataGetter = { null }
+ private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null }
fun build(): SettingsEntry {
return SettingsEntry(
@@ -173,11 +194,13 @@
// attributes
isAllowSearch = isAllowSearch,
isSearchDataDynamic = isSearchDataDynamic,
- mutableStatus = mutableStatus,
+ hasMutableStatus = hasMutableStatus,
+ hasSliceSupport = hasSliceSupport,
// functions
statusDataImpl = statusDataFn,
searchDataImpl = searchDataFn,
+ sliceDataImpl = sliceDataFn,
uiLayoutImpl = uiLayoutFn,
)
}
@@ -207,7 +230,7 @@
}
fun setHasMutableStatus(hasMutableStatus: Boolean): SettingsEntryBuilder {
- this.mutableStatus = hasMutableStatus
+ this.hasMutableStatus = hasMutableStatus
return this
}
@@ -221,17 +244,23 @@
return this
}
- fun setStatusDataFn(fn: (arguments: Bundle?) -> EntryStatusData?): SettingsEntryBuilder {
+ fun setStatusDataFn(fn: StatusDataGetter): SettingsEntryBuilder {
this.statusDataFn = fn
return this
}
- fun setSearchDataFn(fn: (arguments: Bundle?) -> EntrySearchData?): SettingsEntryBuilder {
+ fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
this.searchDataFn = fn
return this
}
- fun setUiLayoutFn(fn: @Composable (arguments: Bundle?) -> Unit): SettingsEntryBuilder {
+ fun setSliceDataFn(fn: SliceDataGetter): SettingsEntryBuilder {
+ this.sliceDataFn = fn
+ this.hasSliceSupport = true
+ return this
+ }
+
+ fun setUiLayoutFn(fn: UiLayerRenderer): SettingsEntryBuilder {
this.uiLayoutFn = fn
return this
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index bb287d1..a372bbd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -113,6 +113,12 @@
)
}
+ fun createBrowseIntent(entryId: String? = null): Intent? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass
+ return createBrowseIntent(context, browseActivityClass, entryId)
+ }
+
fun createBrowseIntent(
context: Context?,
browseActivityClass: Class<out Activity>?,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 151b50cd..60599d4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -30,14 +30,14 @@
val name: String
/** The display name of this page provider, for better readability. */
- val displayName: String?
- get() = null
+ val displayName: String
+ get() = name
/** The page parameters, default is no parameters. */
val parameter: List<NamedNavArgument>
get() = emptyList()
- fun getTitle(arguments: Bundle?): String = displayName ?: name
+ fun getTitle(arguments: Bundle?): String = displayName
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index a9cb041..945add4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -17,10 +17,12 @@
package com.android.settingslib.spa.framework.common
import android.app.Activity
+import android.content.BroadcastReceiver
import android.content.Context
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.slice.SettingsSliceDataRepository
private const val TAG = "SpaEnvironment"
@@ -46,6 +48,10 @@
Log.d(TAG, "resetForPreview")
}
+ fun isReady(): Boolean {
+ return spaEnvironment != null
+ }
+
val instance: SpaEnvironment
get() {
if (spaEnvironment == null)
@@ -59,13 +65,15 @@
val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
+ val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
+
// In Robolectric test, applicationContext is not available. Use context as fallback.
val appContext: Context = context.applicationContext ?: context
open val browseActivityClass: Class<out Activity>? = null
-
+ open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
open val searchProviderAuthorities: String? = null
-
+ open val sliceProviderAuthorities: String? = null
open val logger: SpaLogger = object : SpaLogger {}
// TODO: add other environment setup here.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
index 26491d5..760064a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.debug
+import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -38,6 +39,8 @@
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.slice.appendSliceParams
+import com.android.settingslib.spa.slice.presenter.SliceDemo
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.HomeScaffold
@@ -47,6 +50,7 @@
private const val ROUTE_ROOT = "root"
private const val ROUTE_All_PAGES = "pages"
private const val ROUTE_All_ENTRIES = "entries"
+private const val ROUTE_All_SLICES = "slices"
private const val ROUTE_PAGE = "page"
private const val ROUTE_ENTRY = "entry"
private const val PARAM_NAME_PAGE_ID = "pid"
@@ -81,6 +85,7 @@
composable(route = ROUTE_ROOT) { RootPage() }
composable(route = ROUTE_All_PAGES) { AllPages() }
composable(route = ROUTE_All_ENTRIES) { AllEntries() }
+ composable(route = ROUTE_All_SLICES) { AllSlices() }
composable(
route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}",
arguments = listOf(
@@ -102,6 +107,8 @@
val entryRepository by spaEnvironment.entryRepository
val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
val allEntry = remember { entryRepository.getAllEntries() }
+ val allSliceEntry =
+ remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
HomeScaffold(title = "Settings Debug") {
Preference(object : PreferenceModel {
override val title = "List All Pages (${allPageWithEntry.size})"
@@ -111,6 +118,10 @@
override val title = "List All Entries (${allEntry.size})"
override val onClick = navigator(route = ROUTE_All_ENTRIES)
})
+ Preference(object : PreferenceModel {
+ override val title = "List All Slices (${allSliceEntry.size})"
+ override val onClick = navigator(route = ROUTE_All_SLICES)
+ })
}
}
@@ -140,6 +151,19 @@
}
@Composable
+ fun AllSlices() {
+ val entryRepository by spaEnvironment.entryRepository
+ val authority = spaEnvironment.sliceProviderAuthorities
+ val allSliceEntry =
+ remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
+ RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
+ for (entry in allSliceEntry) {
+ SliceDemo(sliceUri = entry.createSliceUri(authority))
+ }
+ }
+ }
+
+ @Composable
fun OnePage(arguments: Bundle?) {
val context = LocalContext.current
val entryRepository by spaEnvironment.entryRepository
@@ -221,6 +245,18 @@
}
}
+private fun SettingsEntry.createSliceUri(
+ authority: String?,
+ runtimeArguments: Bundle? = null
+): Uri {
+ if (authority == null) return Uri.EMPTY
+ return Uri.Builder().scheme("content").authority(authority).appendSliceParams(
+ route = this.containerPage().buildRoute(),
+ entryId = this.id,
+ runtimeArguments = runtimeArguments,
+ ).build()
+}
+
/**
* A blank activity without any page.
*/
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
new file mode 100644
index 0000000..14855a8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.spa.slice
+
+import android.net.Uri
+import android.util.Log
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+
+private const val TAG = "SliceDataRepository"
+
+class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) {
+ // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
+ private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
+
+ // Note: mark this function synchronized, so that we can get the same livedata during the
+ // whole lifecycle of a Slice.
+ @Synchronized
+ fun getOrBuildSliceData(sliceUri: Uri): EntrySliceData? {
+ val sliceString = sliceUri.getSliceId() ?: return null
+ return sliceDataMap[sliceString] ?: buildLiveDataImpl(sliceUri)?.let {
+ sliceDataMap[sliceString] = it
+ it
+ }
+ }
+
+ fun getActiveSliceData(sliceUri: Uri): EntrySliceData? {
+ val sliceString = sliceUri.getSliceId() ?: return null
+ val sliceData = sliceDataMap[sliceString] ?: return null
+ return if (sliceData.isActive()) sliceData else null
+ }
+
+ private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
+ Log.d(TAG, "buildLiveData: $sliceUri")
+
+ val entryId = sliceUri.getEntryId() ?: return null
+ val entry = entryRepository.getEntry(entryId) ?: return null
+ if (!entry.hasSliceSupport) return null
+ val arguments = sliceUri.getRuntimeArguments()
+ return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
new file mode 100644
index 0000000..ff143ed
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 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.spa.slice
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
+import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+// Defines SliceUri, which contains special query parameters:
+// -- KEY_DESTINATION: The route that this slice is navigated to.
+// -- KEY_HIGHLIGHT_ENTRY: The entry id of this slice
+// Other parameters can considered as runtime parameters.
+// Use {entryId, runtimeParams} as the unique Id of this Slice.
+typealias SliceUri = Uri
+
+val RESERVED_KEYS = listOf(
+ KEY_DESTINATION,
+ KEY_HIGHLIGHT_ENTRY
+)
+
+fun SliceUri.getEntryId(): String? {
+ return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
+}
+
+fun SliceUri.getDestination(): String? {
+ return getQueryParameter(KEY_DESTINATION)
+}
+
+fun SliceUri.getRuntimeArguments(): Bundle {
+ val params = Bundle()
+ for (queryName in queryParameterNames) {
+ if (RESERVED_KEYS.contains(queryName)) continue
+ params.putString(queryName, getQueryParameter(queryName))
+ }
+ return params
+}
+
+fun SliceUri.getSliceId(): String? {
+ val entryId = getEntryId() ?: return null
+ val params = getRuntimeArguments()
+ return "${entryId}_$params"
+}
+
+fun Uri.Builder.appendSliceParams(
+ route: String? = null,
+ entryId: String? = null,
+ runtimeArguments: Bundle? = null
+): Uri.Builder {
+ if (route != null) appendQueryParameter(KEY_DESTINATION, route)
+ if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId)
+ if (runtimeArguments != null) {
+ for (key in runtimeArguments.keySet()) {
+ appendQueryParameter(key, runtimeArguments.getString(key, ""))
+ }
+ }
+ return this
+}
+
+fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val sliceBroadcastClass =
+ SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null
+ val entryId = getEntryId() ?: return null
+ return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId)
+}
+
+fun SliceUri.createBrowsePendingIntent(): PendingIntent? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+ val destination = getDestination() ?: return null
+ val entryId = getEntryId()
+ return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
+}
+
+fun Intent.createBrowsePendingIntent(): PendingIntent? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+ val destination = getStringExtra(KEY_DESTINATION) ?: return null
+ val entryId = getStringExtra(KEY_HIGHLIGHT_ENTRY)
+ return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
+}
+
+private fun createBrowsePendingIntent(
+ context: Context,
+ browseActivityClass: Class<out Activity>,
+ destination: String,
+ entryId: String?
+): PendingIntent {
+ val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
+ .apply {
+ // Set both extra and data (which is a Uri) in Slice Intent:
+ // 1) extra is used in SPA navigation framework
+ // 2) data is used in Slice framework
+ putExtra(KEY_DESTINATION, destination)
+ if (entryId != null) {
+ putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
+ }
+ data = Uri.Builder().appendSliceParams(destination, entryId).build()
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+
+ return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+}
+
+private fun createBroadcastPendingIntent(
+ context: Context,
+ sliceBroadcastClass: Class<out BroadcastReceiver>,
+ entryId: String
+): PendingIntent {
+ val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
+ .apply { data = Uri.Builder().appendSliceParams(entryId = entryId).build() }
+ return PendingIntent.getBroadcast(
+ context, 0 /* requestCode */, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
new file mode 100644
index 0000000..cff1c0c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.spa.slice.presenter
+
+import android.net.Uri
+import androidx.compose.material3.Divider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.slice.widget.SliceLiveData
+import androidx.slice.widget.SliceView
+
+@Composable
+fun SliceDemo(sliceUri: Uri) {
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val sliceData = remember {
+ SliceLiveData.fromUri(context, sliceUri)
+ }
+
+ Divider()
+ AndroidView(
+ factory = { localContext ->
+ val view = SliceView(localContext)
+ view.setShowTitleItems(true)
+ view.isScrollable = false
+ view
+ },
+ update = { view -> sliceData.observe(lifecycleOwner, view) }
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
new file mode 100644
index 0000000..b65b91f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.spa.slice.provider
+
+import android.app.PendingIntent
+import android.content.Context
+import android.net.Uri
+import androidx.core.graphics.drawable.IconCompat
+import androidx.slice.Slice
+import androidx.slice.SliceManager
+import androidx.slice.builders.ListBuilder
+import androidx.slice.builders.SliceAction
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.slice.createBroadcastPendingIntent
+import com.android.settingslib.spa.slice.createBrowsePendingIntent
+
+fun createDemoBrowseSlice(sliceUri: Uri, title: String, summary: String): Slice? {
+ val intent = sliceUri.createBrowsePendingIntent() ?: return null
+ return createDemoSlice(sliceUri, title, summary, intent)
+}
+
+fun createDemoActionSlice(sliceUri: Uri, title: String, summary: String): Slice? {
+ val intent = sliceUri.createBroadcastPendingIntent() ?: return null
+ return createDemoSlice(sliceUri, title, summary, intent)
+}
+
+fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: PendingIntent): Slice? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ if (!SliceManager.getInstance(context).pinnedSlices.contains(sliceUri)) return null
+ return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
+ .addRow(ListBuilder.RowBuilder().apply {
+ setPrimaryAction(createSliceAction(context, intent))
+ setTitle(title)
+ setSubtitle(summary)
+ }).build()
+}
+
+private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
+ return SliceAction.create(
+ intent,
+ IconCompat.createWithResource(
+ context,
+ com.google.android.material.R.drawable.navigation_empty_icon
+ ),
+ ListBuilder.ICON_IMAGE,
+ "Enter app"
+ )
+}
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 529a201..2d501fc 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -21,11 +21,12 @@
android {
namespace 'com.android.settingslib.spa.tests'
- compileSdk spa_target_sdk
+ compileSdk TARGET_SDK
+ buildToolsVersion = BUILD_TOOLS_VERSION
defaultConfig {
- minSdk spa_min_sdk
- targetSdk spa_target_sdk
+ minSdk MIN_SDK
+ targetSdk TARGET_SDK
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -60,7 +61,6 @@
dependencies {
androidTestImplementation project(":spa")
androidTestImplementation project(":testutils")
- androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
androidTestImplementation "com.google.truth:truth:1.1.3"
androidTestImplementation "org.mockito:mockito-android:3.4.6"
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 31d2ae4..2017d53 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -63,7 +63,7 @@
assertThat(entry.toPage).isNull()
assertThat(entry.isAllowSearch).isFalse()
assertThat(entry.isSearchDataDynamic).isFalse()
- assertThat(entry.mutableStatus).isFalse()
+ assertThat(entry.hasMutableStatus).isFalse()
}
@Test
@@ -133,7 +133,7 @@
assertThat(entry.toPage).isNull()
assertThat(entry.isAllowSearch).isTrue()
assertThat(entry.isSearchDataDynamic).isFalse()
- assertThat(entry.mutableStatus).isTrue()
+ assertThat(entry.hasMutableStatus).isTrue()
}
@Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 539e56b..7097a5d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -118,6 +118,7 @@
page.enterPage()
page.leavePage()
page.enterPage()
+ assertThat(page.createBrowseIntent()).isNotNull()
assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
.isEqualTo(2)
assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index 71d7d8a..cbfbb9c 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -20,11 +20,12 @@
}
android {
- compileSdk spa_target_sdk
+ compileSdk TARGET_SDK
+ buildToolsVersion = BUILD_TOOLS_VERSION
defaultConfig {
- minSdk spa_min_sdk
- targetSdk spa_target_sdk
+ minSdk MIN_SDK
+ targetSdk TARGET_SDK
}
sourceSets {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index 373b57f..a7122d0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -54,6 +54,14 @@
)
/**
+ * Gets the group title of this item.
+ *
+ * Note: Items should be sorted by group in [getComparator] first, this [getGroupTitle] will not
+ * change the list order.
+ */
+ fun getGroupTitle(option: Int, record: T): String? = null
+
+ /**
* Gets the summary for the given app record.
*
* @return null if no summary should be displayed.
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index 215d22c..cb0bfc6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -26,43 +26,76 @@
private const val TAG = "PackageManagers"
-object PackageManagers {
- private val iPackageManager by lazy { AppGlobals.getPackageManager() }
-
- fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
- getPackageInfoAsUser(packageName, 0, userId)
-
- fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
- PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+interface IPackageManagers {
+ fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo?
+ fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo?
/** Checks whether a package is installed for a given user. */
- fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
+ fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean
+ fun ApplicationInfo.hasRequestPermission(permission: String): Boolean
+
+ /** Checks whether a permission is currently granted to the application. */
+ fun ApplicationInfo.hasGrantPermission(permission: String): Boolean
+
+ suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String>
+ fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo?
+}
+
+object PackageManagers : IPackageManagers by PackageManagersImpl(PackageManagerWrapperImpl)
+
+internal interface PackageManagerWrapper {
+ fun getPackageInfoAsUserCached(
+ packageName: String,
+ flags: Long,
+ userId: Int,
+ ): PackageInfo?
+}
+
+internal object PackageManagerWrapperImpl : PackageManagerWrapper {
+ override fun getPackageInfoAsUserCached(
+ packageName: String,
+ flags: Long,
+ userId: Int,
+ ): PackageInfo? = PackageManager.getPackageInfoAsUserCached(packageName, flags, userId)
+}
+
+internal class PackageManagersImpl(
+ private val packageManagerWrapper: PackageManagerWrapper,
+) : IPackageManagers {
+ private val iPackageManager by lazy { AppGlobals.getPackageManager() }
+
+ override fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
+ getPackageInfoAsUser(packageName, 0, userId)
+
+ override fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
+ PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+
+ override fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
getApplicationInfoAsUser(packageName, userId)?.hasFlag(ApplicationInfo.FLAG_INSTALLED)
?: false
- fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
+ override fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
return packageInfo?.requestedPermissions?.let {
permission in it
} ?: false
}
- fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
+ override fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
- ?: return false
- val index = packageInfo.requestedPermissions.indexOf(permission)
+ val index = packageInfo?.requestedPermissions?.indexOf(permission) ?: return false
return index >= 0 &&
packageInfo.requestedPermissionsFlags[index].hasFlag(REQUESTED_PERMISSION_GRANTED)
}
- suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
+ override suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
iPackageManager.getAppOpPermissionPackages(permission, userId).asIterable().asyncFilter {
iPackageManager.isPackageAvailable(it, userId)
}.toSet()
- fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
+ override fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
try {
- PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
+ packageManagerWrapper.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
} catch (e: PackageManager.NameNotFoundException) {
Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 7f5fe9f..681eb1c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -34,9 +34,11 @@
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.ui.CategoryTitle
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
+import com.android.settingslib.spaprivileged.model.app.AppEntry
import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
import com.android.settingslib.spaprivileged.model.app.AppListModel
@@ -100,17 +102,28 @@
}
items(count = list.size, key = { option to list[it].record.app.packageName }) {
+ remember(list) { listModel.getGroupTitleIfFirst(option, list, it) }
+ ?.let { group -> CategoryTitle(title = group) }
+
val appEntry = list[it]
val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
- val itemModel = remember(appEntry) {
+ appItem(remember(appEntry) {
AppListItemModel(appEntry.record, appEntry.label, summary)
- }
- appItem(itemModel)
+ })
}
}
}
}
+/** Returns group title if this is the first item of the group. */
+private fun <T : AppRecord> AppListModel<T>.getGroupTitleIfFirst(
+ option: Int,
+ list: List<AppEntry<T>>,
+ index: Int,
+): String? = getGroupTitle(option, list[index].record)?.takeIf {
+ index == 0 || it != getGroupTitle(option, list[index - 1].record)
+}
+
@Composable
private fun <T : AppRecord> loadAppListData(
config: AppListConfig,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
new file mode 100644
index 0000000..6c31f4b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 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.spaprivileged.model.app
+
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PackageManagersTest {
+
+ private val fakePackageManagerWrapper = FakePackageManagerWrapper()
+
+ private val packageManagersImpl = PackageManagersImpl(fakePackageManagerWrapper)
+
+ @Test
+ fun hasGrantPermission_packageInfoIsNull_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = null
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isFalse()
+ }
+
+ @Test
+ fun hasGrantPermission_requestedPermissionsIsNull_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo()
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isFalse()
+ }
+
+ @Test
+ fun hasGrantPermission_notRequested_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = arrayOf(PERMISSION_B)
+ requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED)
+ }
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isFalse()
+ }
+
+ @Test
+ fun hasGrantPermission_notGranted_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+ requestedPermissionsFlags = intArrayOf(0, PackageInfo.REQUESTED_PERMISSION_GRANTED)
+ }
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isFalse()
+ }
+
+ @Test
+ fun hasGrantPermission_granted_returnTrue() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = arrayOf(PERMISSION_A, PERMISSION_B)
+ requestedPermissionsFlags = intArrayOf(PackageInfo.REQUESTED_PERMISSION_GRANTED, 0)
+ }
+
+ val hasGrantPermission = with(packageManagersImpl) {
+ APP.hasGrantPermission(PERMISSION_A)
+ }
+
+ assertThat(hasGrantPermission).isTrue()
+ }
+
+ private inner class FakePackageManagerWrapper : PackageManagerWrapper {
+ var fakePackageInfo: PackageInfo? = null
+
+ override fun getPackageInfoAsUserCached(
+ packageName: String,
+ flags: Long,
+ userId: Int,
+ ): PackageInfo? = fakePackageInfo
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "packageName"
+ const val PERMISSION_A = "permission.A"
+ const val PERMISSION_B = "permission.B"
+ const val UID = 123
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = UID
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 80c4eac..9f20c78 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -58,26 +58,43 @@
@Test
fun couldShowAppItem() {
- setContent(appEntries = listOf(APP_ENTRY))
+ setContent(appEntries = listOf(APP_ENTRY_A))
- composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed()
+ composeTestRule.onNodeWithText(APP_ENTRY_A.label).assertIsDisplayed()
}
@Test
fun couldShowHeader() {
- setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY))
+ setContent(appEntries = listOf(APP_ENTRY_A), header = { Text(HEADER) })
composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
}
+ @Test
+ fun whenNotGrouped_groupTitleDoesNotExist() {
+ setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = false)
+
+ composeTestRule.onNodeWithText(GROUP_A).assertDoesNotExist()
+ composeTestRule.onNodeWithText(GROUP_B).assertDoesNotExist()
+ }
+
+ @Test
+ fun whenGrouped_groupTitleDisplayed() {
+ setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = true)
+
+ composeTestRule.onNodeWithText(GROUP_A).assertIsDisplayed()
+ composeTestRule.onNodeWithText(GROUP_B).assertIsDisplayed()
+ }
+
private fun setContent(
- header: @Composable () -> Unit = {},
appEntries: List<AppEntry<TestAppRecord>>,
+ header: @Composable () -> Unit = {},
+ enableGrouping: Boolean = false,
) {
composeTestRule.setContent {
AppList(
config = AppListConfig(userId = USER_ID, showInstantApps = false),
- listModel = TestAppListModel(),
+ listModel = TestAppListModel(enableGrouping),
state = AppListState(
showSystem = false.toState(),
option = 0.toState(),
@@ -96,17 +113,37 @@
private companion object {
const val USER_ID = 0
const val HEADER = "Header"
- val APP_ENTRY = AppEntry(
- record = TestAppRecord(ApplicationInfo()),
- label = "AAA",
+ const val GROUP_A = "Group A"
+ const val GROUP_B = "Group B"
+ val APP_ENTRY_A = AppEntry(
+ record = TestAppRecord(
+ app = ApplicationInfo().apply {
+ packageName = "package.name.a"
+ },
+ group = GROUP_A,
+ ),
+ label = "Label A",
+ labelCollationKey = CollationKey("", byteArrayOf()),
+ )
+ val APP_ENTRY_B = AppEntry(
+ record = TestAppRecord(
+ app = ApplicationInfo().apply {
+ packageName = "package.name.b"
+ },
+ group = GROUP_B,
+ ),
+ label = "Label B",
labelCollationKey = CollationKey("", byteArrayOf()),
)
}
}
-private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+private data class TestAppRecord(
+ override val app: ApplicationInfo,
+ val group: String? = null,
+) : AppRecord
-private class TestAppListModel : AppListModel<TestAppRecord> {
+private class TestAppListModel(val enableGrouping: Boolean) : AppListModel<TestAppRecord> {
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
appListFlow.asyncMapItem { TestAppRecord(it) }
@@ -118,4 +155,7 @@
option: Int,
recordListFlow: Flow<List<TestAppRecord>>,
) = recordListFlow
+
+ override fun getGroupTitle(option: Int, record: TestAppRecord) =
+ if (enableGrouping) record.group else null
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index cec6d7d..b3638c2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -28,7 +28,6 @@
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
import com.android.settingslib.spaprivileged.model.app.userHandle
-import com.google.common.truth.Truth.assertThat
import java.util.UUID
import org.junit.Before
import org.junit.Rule
@@ -77,7 +76,7 @@
}
}
- assertThat(storageSize.value).isEqualTo("123 B")
+ composeTestRule.waitUntil { storageSize.value == "123 B" }
}
companion object {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
index 6ce72bb..3af64e2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
@@ -82,4 +82,4 @@
Log.e(TAG, "localBluetoothAdapter is NULL!!");
}
}
-};
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
index 3e33da5..ece8986 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -50,6 +50,34 @@
@Override
public void clicked(int sourceCategory, String key) {
+ final LogMaker logMaker = new LogMaker(MetricsProto.MetricsEvent.ACTION_SETTINGS_TILE_CLICK)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+ if (sourceCategory != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, sourceCategory);
+ }
+ if (!TextUtils.isEmpty(key)) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME,
+ key);
+ }
+ MetricsLogger.action(logMaker);
+ }
+
+ @Override
+ public void changed(int category, String key, int value) {
+ final LogMaker logMaker = new LogMaker(
+ MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+ if (category != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, category);
+ }
+ if (!TextUtils.isEmpty(key)) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME,
+ key);
+ logMaker.addTaggedData(
+ MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+ value);
+ }
+ MetricsLogger.action(logMaker);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index cceca13..dcd6cce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -39,6 +39,11 @@
void clicked(int category, String key);
/**
+ * Logs a value changed event when user changed item value.
+ */
+ void changed(int category, String key, int value);
+
+ /**
* Logs an user action.
*/
void action(Context context, int category, Pair<Integer, Object>... taggedData);
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 915421a..09abc39 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -108,6 +108,19 @@
}
/**
+ * Logs a value changed event when user changed item value.
+ *
+ * @param category the target page id
+ * @param key the key id that user clicked
+ * @param value the value that user changed which converted to integer
+ */
+ public void changed(int category, String key, int value) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.changed(category, key, value);
+ }
+ }
+
+ /**
* Logs a simple action without page id or attribution
*
* @param category the target page
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
index 869de0de..067afa4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -35,15 +35,22 @@
private static final String LOG_TAG = "SharedPreferencesLogger";
private final String mTag;
+ private final int mMetricCategory;
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeature;
private final Set<String> mPreferenceKeySet;
public SharedPreferencesLogger(Context context, String tag,
MetricsFeatureProvider metricsFeature) {
+ this(context, tag, metricsFeature, SettingsEnums.PAGE_UNKNOWN);
+ }
+
+ public SharedPreferencesLogger(Context context, String tag,
+ MetricsFeatureProvider metricsFeature, int metricCategory) {
mContext = context;
mTag = tag;
mMetricsFeature = metricsFeature;
+ mMetricCategory = metricCategory;
mPreferenceKeySet = new ConcurrentSkipListSet<>();
}
@@ -151,20 +158,15 @@
return;
}
// Pref key exists in set, log its change in metrics.
- mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- prefKey,
- intVal);
+ mMetricsFeature.changed(mMetricCategory, key, intVal);
}
@VisibleForTesting
void logPackageName(String key, String value) {
- final String prefKey = mTag + "/" + key;
- mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
+ mMetricsFeature.action(mMetricCategory,
SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
SettingsEnums.PAGE_UNKNOWN,
- prefKey + ":" + value,
+ key + ":" + value,
0);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 34da305..3e710e4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -24,7 +24,6 @@
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
-import android.text.TextUtils;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -221,17 +220,14 @@
}
/**
- * Reverts battery saver schedule mode to none if we are in a bad state where routine mode
- * is selected but no app is configured to actually provide the signal.
+ * Reverts battery saver schedule mode to none if routine mode is selected.
* @param context a valid context
*/
public static void revertScheduleToNoneIfNeeded(Context context) {
ContentResolver resolver = context.getContentResolver();
final int currentMode = Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
- boolean providerConfigured = !TextUtils.isEmpty(context.getString(
- com.android.internal.R.string.config_batterySaverScheduleProvider));
- if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC && !providerConfigured) {
+ if (currentMode == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC) {
Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index 5fa04f9..faea5b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -412,14 +412,13 @@
}
companion object {
- private const val TAG = "ThemedBatteryDrawable"
- private const val WIDTH = 12f
- private const val HEIGHT = 20f
+ const val WIDTH = 12f
+ const val HEIGHT = 20f
private const val CRITICAL_LEVEL = 15
// On a 12x20 grid, how wide to make the fill protection stroke.
// Scales when our size changes
private const val PROTECTION_STROKE_WIDTH = 3f
// Arbitrarily chosen for visibility at small sizes
- private const val PROTECTION_MIN_STROKE_WIDTH = 6f
+ const val PROTECTION_MIN_STROKE_WIDTH = 6f
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
index 89de81f..a2b208a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
@@ -39,7 +39,6 @@
private static final String TEST_TAG = "tag";
private static final String TEST_KEY = "key";
- private static final String TEST_TAGGED_KEY = TEST_TAG + "/" + TEST_KEY;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@@ -65,10 +64,8 @@
editor.putInt(TEST_KEY, 2);
editor.putInt(TEST_KEY, 2);
- verify(mMetricsFeature, times(6)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(6)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
@@ -82,15 +79,11 @@
editor.putBoolean(TEST_KEY, false);
- verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- TEST_TAGGED_KEY,
+ verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+ TEST_KEY,
1);
- verify(mMetricsFeature, times(3)).action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- TEST_TAGGED_KEY,
+ verify(mMetricsFeature, times(3)).changed(SettingsEnums.PAGE_UNKNOWN,
+ TEST_KEY,
0);
}
@@ -103,10 +96,8 @@
editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, 2);
- verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
@@ -117,10 +108,8 @@
editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, veryBigNumber);
- verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- TEST_TAGGED_KEY,
+ verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+ TEST_KEY,
Integer.MAX_VALUE);
}
@@ -131,10 +120,8 @@
editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, veryNegativeNumber);
- verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
- SettingsEnums.PAGE_UNKNOWN,
- TEST_TAGGED_KEY, Integer.MIN_VALUE);
+ verify(mMetricsFeature).changed(SettingsEnums.PAGE_UNKNOWN,
+ TEST_KEY, Integer.MIN_VALUE);
}
@Test
@@ -146,10 +133,8 @@
editor.putFloat(TEST_KEY, 1);
editor.putFloat(TEST_KEY, 2);
- verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(4)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
@@ -159,7 +144,7 @@
verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
ACTION_SETTINGS_PREFERENCE_CHANGE,
SettingsEnums.PAGE_UNKNOWN,
- "tag/key:com.android.settings",
+ "key:com.android.settings",
0);
}
@@ -170,10 +155,8 @@
mSharedPrefLogger.logValue(TEST_KEY, "62");
mSharedPrefLogger.logValue(TEST_KEY, "0");
- verify(mMetricsFeature, times(3)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(3)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
@@ -185,10 +168,8 @@
mSharedPrefLogger.logValue(TEST_KEY, "4.2");
mSharedPrefLogger.logValue(TEST_KEY, "3.0");
- verify(mMetricsFeature, times(0)).action(eq(SettingsEnums.PAGE_UNKNOWN),
- eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq(SettingsEnums.PAGE_UNKNOWN),
- eq(TEST_TAGGED_KEY),
+ verify(mMetricsFeature, times(0)).changed(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_KEY),
anyInt());
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index aea2f52..f5c9bcd 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -884,7 +884,7 @@
Settings.Secure.SHOW_QR_CODE_SCANNER_SETTING,
Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION,
Settings.Secure.SPATIAL_AUDIO_ENABLED,
- Settings.Secure.TIMEOUT_TO_USER_ZERO,
+ Settings.Secure.TIMEOUT_TO_DOCK_USER,
Settings.Secure.UI_NIGHT_MODE_LAST_COMPUTED,
Settings.Secure.UI_NIGHT_MODE_OVERRIDE_OFF,
Settings.Secure.UI_NIGHT_MODE_OVERRIDE_ON);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 90fab08..2e4a245 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -720,6 +720,57 @@
<!-- Permission required for CTS test - CtsDeviceLockTestCases -->
<uses-permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE" />
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+
+ <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
deleted file mode 100644
index 4d94bab..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2022 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.user.ui.compose
-
-import android.graphics.drawable.Drawable
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.core.graphics.drawable.toBitmap
-import com.android.systemui.common.ui.compose.load
-import com.android.systemui.compose.SysUiOutlinedButton
-import com.android.systemui.compose.SysUiTextButton
-import com.android.systemui.compose.features.R
-import com.android.systemui.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import java.lang.Integer.min
-import kotlin.math.ceil
-
-@Composable
-fun UserSwitcherScreen(
- viewModel: UserSwitcherViewModel,
- onFinished: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- val isFinishRequested: Boolean by viewModel.isFinishRequested.collectAsState(false)
- val users: List<UserViewModel> by viewModel.users.collectAsState(emptyList())
- val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1)
- val menuActions: List<UserActionViewModel> by viewModel.menu.collectAsState(emptyList())
- val isOpenMenuButtonVisible: Boolean by viewModel.isOpenMenuButtonVisible.collectAsState(false)
- val isMenuVisible: Boolean by viewModel.isMenuVisible.collectAsState(false)
-
- UserSwitcherScreenStateless(
- isFinishRequested = isFinishRequested,
- users = users,
- maxUserColumns = maxUserColumns,
- menuActions = menuActions,
- isOpenMenuButtonVisible = isOpenMenuButtonVisible,
- isMenuVisible = isMenuVisible,
- onMenuClosed = viewModel::onMenuClosed,
- onOpenMenuButtonClicked = viewModel::onOpenMenuButtonClicked,
- onCancelButtonClicked = viewModel::onCancelButtonClicked,
- onFinished = {
- onFinished()
- viewModel.onFinished()
- },
- modifier = modifier,
- )
-}
-
-@Composable
-private fun UserSwitcherScreenStateless(
- isFinishRequested: Boolean,
- users: List<UserViewModel>,
- maxUserColumns: Int,
- menuActions: List<UserActionViewModel>,
- isOpenMenuButtonVisible: Boolean,
- isMenuVisible: Boolean,
- onMenuClosed: () -> Unit,
- onOpenMenuButtonClicked: () -> Unit,
- onCancelButtonClicked: () -> Unit,
- onFinished: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- LaunchedEffect(isFinishRequested) {
- if (isFinishRequested) {
- onFinished()
- }
- }
-
- Box(
- modifier =
- modifier
- .fillMaxSize()
- .padding(
- horizontal = 60.dp,
- vertical = 40.dp,
- ),
- ) {
- UserGrid(
- users = users,
- maxUserColumns = maxUserColumns,
- modifier = Modifier.align(Alignment.Center),
- )
-
- Buttons(
- menuActions = menuActions,
- isOpenMenuButtonVisible = isOpenMenuButtonVisible,
- isMenuVisible = isMenuVisible,
- onMenuClosed = onMenuClosed,
- onOpenMenuButtonClicked = onOpenMenuButtonClicked,
- onCancelButtonClicked = onCancelButtonClicked,
- modifier = Modifier.align(Alignment.BottomEnd),
- )
- }
-}
-
-@Composable
-private fun UserGrid(
- users: List<UserViewModel>,
- maxUserColumns: Int,
- modifier: Modifier = Modifier,
-) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(44.dp),
- modifier = modifier,
- ) {
- val rowCount = ceil(users.size / maxUserColumns.toFloat()).toInt()
- (0 until rowCount).forEach { rowIndex ->
- Row(
- horizontalArrangement = Arrangement.spacedBy(64.dp),
- modifier = modifier,
- ) {
- val fromIndex = rowIndex * maxUserColumns
- val toIndex = min(users.size, (rowIndex + 1) * maxUserColumns)
- users.subList(fromIndex, toIndex).forEach { user ->
- UserItem(
- viewModel = user,
- )
- }
- }
- }
- }
-}
-
-@Composable
-private fun UserItem(
- viewModel: UserViewModel,
-) {
- val onClicked = viewModel.onClicked
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- if (onClicked != null) {
- Modifier.clickable { onClicked() }
- } else {
- Modifier
- }
- .alpha(viewModel.alpha),
- ) {
- Box {
- UserItemBackground(modifier = Modifier.align(Alignment.Center).size(222.dp))
-
- UserItemIcon(
- image = viewModel.image,
- isSelectionMarkerVisible = viewModel.isSelectionMarkerVisible,
- modifier = Modifier.align(Alignment.Center).size(222.dp)
- )
- }
-
- // User name
- val text = viewModel.name.load()
- if (text != null) {
- // We use the box to center-align the text vertically as that is not possible with Text
- // alone.
- Box(
- modifier = Modifier.size(width = 222.dp, height = 48.dp),
- ) {
- Text(
- text = text,
- style = MaterialTheme.typography.titleLarge,
- color = colorResource(com.android.internal.R.color.system_neutral1_50),
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
- }
-}
-
-@Composable
-private fun UserItemBackground(
- modifier: Modifier = Modifier,
-) {
- Image(
- painter = ColorPainter(LocalAndroidColorScheme.current.colorBackground),
- contentDescription = null,
- modifier = modifier.clip(CircleShape),
- )
-}
-
-@Composable
-private fun UserItemIcon(
- image: Drawable,
- isSelectionMarkerVisible: Boolean,
- modifier: Modifier = Modifier,
-) {
- Image(
- bitmap = image.toBitmap().asImageBitmap(),
- contentDescription = null,
- modifier =
- if (isSelectionMarkerVisible) {
- // Draws a ring
- modifier.border(
- width = 8.dp,
- color = LocalAndroidColorScheme.current.colorAccentPrimary,
- shape = CircleShape,
- )
- } else {
- modifier
- }
- .padding(16.dp)
- .clip(CircleShape)
- )
-}
-
-@Composable
-private fun Buttons(
- menuActions: List<UserActionViewModel>,
- isOpenMenuButtonVisible: Boolean,
- isMenuVisible: Boolean,
- onMenuClosed: () -> Unit,
- onOpenMenuButtonClicked: () -> Unit,
- onCancelButtonClicked: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- Row(
- modifier = modifier,
- ) {
- // Cancel button.
- SysUiTextButton(
- onClick = onCancelButtonClicked,
- ) {
- Text(stringResource(R.string.cancel))
- }
-
- // "Open menu" button.
- if (isOpenMenuButtonVisible) {
- Spacer(modifier = Modifier.width(8.dp))
- // To properly use a DropdownMenu in Compose, we need to wrap the button that opens it
- // and the menu itself in a Box.
- Box {
- SysUiOutlinedButton(
- onClick = onOpenMenuButtonClicked,
- ) {
- Text(stringResource(R.string.add))
- }
- Menu(
- viewModel = menuActions,
- isMenuVisible = isMenuVisible,
- onMenuClosed = onMenuClosed,
- )
- }
- }
- }
-}
-
-@Composable
-private fun Menu(
- viewModel: List<UserActionViewModel>,
- isMenuVisible: Boolean,
- onMenuClosed: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- val maxItemWidth = LocalConfiguration.current.screenWidthDp.dp / 4
- DropdownMenu(
- expanded = isMenuVisible,
- onDismissRequest = onMenuClosed,
- modifier =
- modifier.background(
- color = MaterialTheme.colorScheme.inverseOnSurface,
- ),
- ) {
- viewModel.forEachIndexed { index, action ->
- MenuItem(
- viewModel = action,
- onClicked = { action.onClicked() },
- topPadding =
- if (index == 0) {
- 16.dp
- } else {
- 0.dp
- },
- bottomPadding =
- if (index == viewModel.size - 1) {
- 16.dp
- } else {
- 0.dp
- },
- modifier = Modifier.sizeIn(maxWidth = maxItemWidth),
- )
- }
- }
-}
-
-@Composable
-private fun MenuItem(
- viewModel: UserActionViewModel,
- onClicked: () -> Unit,
- topPadding: Dp,
- bottomPadding: Dp,
- modifier: Modifier = Modifier,
-) {
- val context = LocalContext.current
- val density = LocalDensity.current
-
- val icon =
- remember(viewModel.iconResourceId) {
- val drawable =
- checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
- val size = with(density) { 20.dp.toPx() }.toInt()
- drawable
- .toBitmap(
- width = size,
- height = size,
- )
- .asImageBitmap()
- }
-
- DropdownMenuItem(
- text = {
- Text(
- text = stringResource(viewModel.textResourceId),
- style = MaterialTheme.typography.bodyMedium,
- )
- },
- onClick = onClicked,
- leadingIcon = {
- Spacer(modifier = Modifier.width(10.dp))
- Image(
- bitmap = icon,
- contentDescription = null,
- )
- },
- modifier =
- modifier
- .heightIn(
- min = 56.dp,
- )
- .padding(
- start = 18.dp,
- end = 65.dp,
- top = topPadding,
- bottom = bottomPadding,
- ),
- )
-}
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 06ea381..7fc9a83 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -16,7 +16,6 @@
-packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
-packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
-packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
-packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 89f5c2c..66e44b9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -70,10 +70,10 @@
}
/** Optional method for dumping debug information */
- fun dump(pw: PrintWriter) { }
+ fun dump(pw: PrintWriter) {}
/** Optional method for debug logging */
- fun setLogBuffer(logBuffer: LogBuffer) { }
+ fun setLogBuffer(logBuffer: LogBuffer) {}
}
/** Interface for a specific clock face version rendered by the clock */
@@ -88,40 +88,37 @@
/** Events that should call when various rendering parameters change */
interface ClockEvents {
/** Call every time tick */
- fun onTimeTick() { }
+ fun onTimeTick() {}
/** Call whenever timezone changes */
- fun onTimeZoneChanged(timeZone: TimeZone) { }
+ fun onTimeZoneChanged(timeZone: TimeZone) {}
/** Call whenever the text time format changes (12hr vs 24hr) */
- fun onTimeFormatChanged(is24Hr: Boolean) { }
+ fun onTimeFormatChanged(is24Hr: Boolean) {}
/** Call whenever the locale changes */
- fun onLocaleChanged(locale: Locale) { }
-
- /** Call whenever font settings change */
- fun onFontSettingChanged() { }
+ fun onLocaleChanged(locale: Locale) {}
/** Call whenever the color palette should update */
- fun onColorPaletteChanged(resources: Resources) { }
+ fun onColorPaletteChanged(resources: Resources) {}
}
/** Methods which trigger various clock animations */
interface ClockAnimations {
/** Runs an enter animation (if any) */
- fun enter() { }
+ fun enter() {}
/** Sets how far into AOD the device currently is. */
- fun doze(fraction: Float) { }
+ fun doze(fraction: Float) {}
/** Sets how far into the folding animation the device is. */
- fun fold(fraction: Float) { }
+ fun fold(fraction: Float) {}
/** Runs the battery animation (if any). */
- fun charge() { }
+ fun charge() {}
/** Move the clock, for example, if the notification tray appears in split-shade mode. */
- fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
+ fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {}
/**
* Whether this clock has a custom position update animation. If true, the keyguard will call
@@ -135,11 +132,26 @@
/** Events that have specific data about the related face */
interface ClockFaceEvents {
/** Region Darkness specific to the clock face */
- fun onRegionDarknessChanged(isDark: Boolean) { }
+ fun onRegionDarknessChanged(isDark: Boolean) {}
+
+ /**
+ * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
+ * design is allowed to ignore this target size on a case-by-case basis.
+ */
+ fun onFontSettingChanged(fontSizePx: Float) {}
+
+ /**
+ * Target region information for the clock face. For small clock, this will match the bounds of
+ * the parent view mostly, but have a target height based on the height of the default clock.
+ * For large clocks, the parent view is the entire device size, but most clocks will want to
+ * render within the centered targetRect to avoid obstructing other elements. The specified
+ * targetRegion is relative to the parent view.
+ */
+ fun onTargetRegionChanged(targetRegion: Rect?) {}
}
/** Some data about a clock design */
data class ClockMetadata(
val clockId: ClockId,
- val name: String
+ val name: String,
)
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index c297149..b49afee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -37,7 +37,6 @@
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
android:clipChildren="false"
android:visibility="gone" />
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
new file mode 100644
index 0000000..eefe364
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_button_bg.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2022 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="@color/accessibility_window_magnifier_button_bg" />
+ <size
+ android:width="40dp"
+ android:height="40dp"/>
+ <corners android:radius="2dp"/>
+ <stroke
+ android:color="@color/accessibility_window_magnifier_button_bg_stroke"
+ android:width="1dp" />
+ </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml
new file mode 100644
index 0000000..d25cd65
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_left.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2022 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M0,24l0,-4l24,-0l0,4z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="M0,24l0,-24l4,-0l0,24z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml
new file mode 100644
index 0000000..bb6df3a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_bottom_right.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2022 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M24,24l-4,-0l-0,-24l4,-0z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="M24,24l-24,-0l-0,-4l24,-0z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml
new file mode 100644
index 0000000..8f25930
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_top_left.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2022 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M0,0h4v24h-4z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="M0,0h24v4h-24z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml b/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml
new file mode 100644
index 0000000..291db44
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_corner_top_right.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2022 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M24,0l-0,4l-24,0l-0,-4z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+ <path
+ android:pathData="M24,0l-0,24l-4,0l-0,-24z"
+ android:strokeWidth="1"
+ android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
+ android:fillType="evenOdd"
+ android:strokeColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml
new file mode 100644
index 0000000..8f6b4c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white"/>
+ <corners android:radius="?android:attr/buttonCornerRadius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index ae2537f..f14be41 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -312,22 +312,15 @@
<LinearLayout
android:id="@+id/see_all_layout"
- android:layout_width="match_parent"
+ style="@style/InternetDialog.Network"
android:layout_height="64dp"
- android:clickable="true"
- android:focusable="true"
- android:background="?android:attr/selectableItemBackground"
- android:gravity="center_vertical|center_horizontal"
- android:orientation="horizontal"
- android:paddingStart="22dp"
- android:paddingEnd="22dp">
+ android:paddingStart="20dp">
<FrameLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:clickable="false"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+ android:layout_gravity="center_vertical|start">
<ImageView
android:id="@+id/arrow_forward"
android:src="@drawable/ic_arrow_forward"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 9b8b611..530db0d 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -44,7 +44,7 @@
android:background="@drawable/qs_media_outline_album_bg"
/>
- <com.android.systemui.ripple.MultiRippleView
+ <com.android.systemui.surfaceeffects.ripple.MultiRippleView
android:id="@+id/touch_ripple_view"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_media_session_height_expanded"
@@ -53,6 +53,15 @@
app:layout_constraintTop_toTopOf="@id/album_art"
app:layout_constraintBottom_toBottomOf="@id/album_art" />
+ <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
+ android:id="@+id/turbulence_noise_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded"
+ app:layout_constraintStart_toStartOf="@id/album_art"
+ app:layout_constraintEnd_toEndOf="@id/album_art"
+ app:layout_constraintTop_toTopOf="@id/album_art"
+ app:layout_constraintBottom_toBottomOf="@id/album_art" />
+
<!-- Guideline for output switcher -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_vertical_guideline"
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 0bff47c..0be7328 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -40,19 +40,22 @@
android:id="@+id/top_handle"
android:layout_width="match_parent"
android:layout_height="@dimen/magnification_border_drag_size"
- android:layout_alignParentTop="true"/>
+ android:layout_alignParentTop="true"
+ android:accessibilityTraversalAfter="@id/left_handle"/>
<View
android:id="@+id/right_handle"
android:layout_width="@dimen/magnification_border_drag_size"
android:layout_height="match_parent"
- android:layout_alignParentEnd="true"/>
+ android:layout_alignParentEnd="true"
+ android:accessibilityTraversalAfter="@id/top_handle"/>
<View
android:id="@+id/bottom_handle"
android:layout_width="match_parent"
android:layout_height="@dimen/magnification_border_drag_size"
- android:layout_alignParentBottom="true"/>
+ android:layout_alignParentBottom="true"
+ android:accessibilityTraversalAfter="@id/right_handle"/>
<SurfaceView
android:id="@+id/surface_view"
@@ -62,6 +65,58 @@
</RelativeLayout>
<ImageView
+ android:id="@+id/top_right_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_gravity="right|top"
+ android:paddingTop="@dimen/magnifier_drag_handle_padding"
+ android:paddingEnd="@dimen/magnifier_drag_handle_padding"
+ android:scaleType="center"
+ android:contentDescription="@string/magnification_drag_corner_to_resize"
+ android:src="@drawable/ic_magnification_corner_top_right"
+ android:accessibilityTraversalAfter="@id/top_left_corner"/>
+
+ <ImageView
+ android:id="@+id/top_left_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_gravity="left|top"
+ android:paddingTop="@dimen/magnifier_drag_handle_padding"
+ android:paddingStart="@dimen/magnifier_drag_handle_padding"
+ android:scaleType="center"
+ android:contentDescription="@string/magnification_drag_corner_to_resize"
+ android:src="@drawable/ic_magnification_corner_top_left"
+ android:accessibilityTraversalAfter="@id/bottom_handle"/>
+
+ <ImageView
+ android:id="@+id/bottom_right_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_gravity="right|bottom"
+ android:paddingEnd="@dimen/magnifier_drag_handle_padding"
+ android:paddingBottom="@dimen/magnifier_drag_handle_padding"
+ android:scaleType="center"
+ android:contentDescription="@string/magnification_drag_corner_to_resize"
+ android:src="@drawable/ic_magnification_corner_bottom_right"
+ android:accessibilityTraversalAfter="@id/top_right_corner"/>
+
+ <ImageView
+ android:id="@+id/bottom_left_corner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_gravity="left|bottom"
+ android:paddingStart="@dimen/magnifier_drag_handle_padding"
+ android:paddingBottom="@dimen/magnifier_drag_handle_padding"
+ android:scaleType="center"
+ android:contentDescription="@string/magnification_drag_corner_to_resize"
+ android:src="@drawable/ic_magnification_corner_bottom_left"
+ android:accessibilityTraversalAfter="@id/bottom_right_corner"/>
+
+ <ImageView
android:id="@+id/drag_handle"
android:layout_width="@dimen/magnification_drag_view_size"
android:layout_height="@dimen/magnification_drag_view_size"
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 887e3e7..f1bc883 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,7 +22,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.android.systemui.ripple.RippleView
+ <com.android.systemui.surfaceeffects.ripple.RippleView
android:id="@+id/wireless_charging_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 55b59b6..6b4bea1 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -172,6 +172,10 @@
<color name="accessibility_magnifier_bg">#FCFCFC</color>
<color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
<color name="accessibility_magnifier_icon_color">#252525</color>
+ <color name="accessibility_window_magnifier_button_bg">#0680FD</color>
+ <color name="accessibility_window_magnifier_icon_color">#FAFAFA</color>
+ <color name="accessibility_window_magnifier_button_bg_stroke">#252525</color>
+ <color name="accessibility_window_magnifier_corner_view_color">#0680FD</color>
<!-- Volume dialog colors -->
<color name="volume_dialog_background_color">@android:color/transparent</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ce9829b..f4d802b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -485,6 +485,12 @@
<!-- Whether to show a severe low battery dialog. -->
<bool name="config_severe_battery_dialog">false</bool>
+ <!-- A path representing a shield. Will sometimes be displayed with the battery icon when
+ needed. This path is a 10px wide and 13px tall. -->
+ <string name="config_batterymeterShieldPath" translatable="false">
+ M5 0L0 1.88V6.19C0 9.35 2.13 12.29 5 13.01C7.87 12.29 10 9.35 10 6.19V1.88L5 0Z
+ </string>
+
<!-- A path similar to frameworks/base/core/res/res/values/config.xml
config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
@@ -737,12 +743,35 @@
<!-- How long in milliseconds before full burn-in protection is achieved. -->
<integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
+ <!-- The duration in milliseconds of the y-translation animation when waking up from
+ the dream -->
+ <integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
+ <!-- The delay in milliseconds of the y-translation animation when waking up from
+ the dream for the complications at the bottom of the screen -->
+ <integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
+ <!-- The delay in milliseconds of the y-translation animation when waking up from
+ the dream for the complications at the top of the screen -->
+ <integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
+ <!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
+ <integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
+ <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+ complications at the top of the screen -->
+ <integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
+ <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+ complications at the bottom of the screen -->
+ <integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
+ <!-- The duration in milliseconds of the blur animation when waking up from
+ the dream -->
+ <integer name="config_dreamOverlayOutBlurDurationMs">250</integer>
+
<integer name="complicationFadeOutMs">500</integer>
<integer name="complicationFadeInMs">500</integer>
<integer name="complicationRestoreMs">1000</integer>
+ <integer name="complicationFadeOutDelayMs">200</integer>
+
<!-- Duration in milliseconds of the dream in un-blur animation. -->
<integer name="config_dreamOverlayInBlurDurationMs">249</integer>
<!-- Delay in milliseconds of the dream in un-blur animation. -->
@@ -774,27 +803,6 @@
<item>com.android.systemui</item>
</string-array>
- <!-- The thresholds which determine the color used by the AQI dream overlay.
- NOTE: This must always be kept sorted from low to high -->
- <integer-array name="config_dreamAqiThresholds">
- <item>-1</item>
- <item>50</item>
- <item>100</item>
- <item>150</item>
- <item>200</item>
- <item>300</item>
- </integer-array>
-
- <!-- The color values which correspond to the thresholds above -->
- <integer-array name="config_dreamAqiColorValues">
- <item>@color/dream_overlay_aqi_good</item>
- <item>@color/dream_overlay_aqi_moderate</item>
- <item>@color/dream_overlay_aqi_unhealthy_sensitive</item>
- <item>@color/dream_overlay_aqi_unhealthy</item>
- <item>@color/dream_overlay_aqi_very_unhealthy</item>
- <item>@color/dream_overlay_aqi_hazardous</item>
- </integer-array>
-
<!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
is available. If false, UI will never show regardless of tethering availability" -->
<bool name="config_show_wifi_tethering">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f9f2195..fbdccff 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -105,6 +105,12 @@
so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
<dimen name="status_bar_battery_icon_width">7.8dp</dimen>
+ <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+ @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
+ the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
+ bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
+ <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+
<!-- The font size for the clock in the status bar. -->
<dimen name="status_bar_clock_size">14sp</dimen>
@@ -1546,6 +1552,7 @@
<dimen name="dream_overlay_complication_margin">0dp</dimen>
<dimen name="dream_overlay_y_offset">80dp</dimen>
+ <dimen name="dream_overlay_exit_y_offset">40dp</dimen>
<dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
<dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6824d7f..eb291fd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -316,7 +316,7 @@
<!-- Content description of the QR Code scanner for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_qr_code_scanner_button">QR Code Scanner</string>
<!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_unlock_button">Unlock</string>
+ <string name="accessibility_unlock_button">Unlocked</string>
<!-- Content description of the lock icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_lock_icon">Device locked</string>
<!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -405,8 +405,8 @@
<string name="keyguard_face_failed">Can\u2019t recognize face</string>
<!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
<string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
- <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=65] -->
- <string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string>
+ <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=59] -->
+ <string name="keyguard_face_unlock_unavailable">Face Unlock unavailable</string>
<!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
@@ -439,11 +439,17 @@
<string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$s</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
+ <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
<!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
+ <!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent. Charging paused for battery protection.</string>
+
+ <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage. Charging paused for battery protection.</string>
+
<!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
<string name="accessibility_overflow_action">See all notifications</string>
@@ -2231,6 +2237,8 @@
<string name="magnification_mode_switch_state_window">Magnify part of screen</string>
<!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_click_label">Switch</string>
+ <!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
+ <string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
<!-- Title of the magnification option button allow diagonal scrolling [CHAR LIMIT=NONE]-->
<string name="accessibility_allow_diagonal_scrolling">Allow diagonal scrolling</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4e4bfe2..ae80070 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1091,7 +1091,7 @@
<item name="android:orientation">horizontal</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
- <item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="android:background">@drawable/internet_dialog_selected_effect</item>
</style>
<style name="InternetDialog.NetworkTitle">
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index 148e5ec..1eb621e 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -44,6 +44,16 @@
app:layout_constraintTop_toTopOf="@+id/album_art"
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+ <!-- Turbulence noise must have the same constraint as the album art. -->
+ <Constraint
+ android:id="@+id/turbulence_noise_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_collapsed"
+ app:layout_constraintStart_toStartOf="@+id/album_art"
+ app:layout_constraintEnd_toEndOf="@+id/album_art"
+ app:layout_constraintTop_toTopOf="@+id/album_art"
+ app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml
index ac484d7..64c2ef1 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -37,6 +37,16 @@
app:layout_constraintTop_toTopOf="@+id/album_art"
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+ <!-- Turbulence noise must have the same constraint as the album art. -->
+ <Constraint
+ android:id="@+id/turbulence_noise_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded"
+ app:layout_constraintStart_toStartOf="@+id/album_art"
+ app:layout_constraintEnd_toEndOf="@+id/album_art"
+ app:layout_constraintTop_toTopOf="@+id/album_art"
+ app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index af4be1a..5d3650c 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -25,7 +25,7 @@
android:id="@+id/clock">
<Layout
android:layout_width="wrap_content"
- android:layout_height="0dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toStartOf="@id/begin_guide"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -42,7 +42,7 @@
<Constraint
android:id="@+id/date">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
android:layout_marginStart="8dp"
app:layout_constrainedWidth="true"
@@ -57,14 +57,16 @@
<Constraint
android:id="@+id/statusIcons">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="packed"
/>
</Constraint>
@@ -80,12 +82,16 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="packed"
/>
</Constraint>
<Constraint
android:id="@+id/carrier_group">
<Layout
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
index d8a4e77..982c422 100644
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ b/packages/SystemUI/res/xml/qs_header_new.xml
@@ -43,6 +43,7 @@
app:layout_constraintBottom_toBottomOf="@id/carrier_group"
app:layout_constraintEnd_toStartOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
<Transform
android:scaleX="2.57"
@@ -53,7 +54,7 @@
<Constraint
android:id="@+id/date">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/space"
@@ -67,16 +68,15 @@
<Constraint
android:id="@+id/carrier_group">
<Layout
- app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
- android:minHeight="@dimen/large_screen_shade_header_min_height"
app:layout_constraintWidth_min="48dp"
- android:layout_width="0dp"
- android:layout_height="0dp"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toEndOf="@id/clock"
app:layout_constraintTop_toBottomOf="@id/privacy_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
<PropertySet
android:alpha="1"
@@ -86,7 +86,7 @@
<Constraint
android:id="@+id/statusIcons">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/space"
@@ -108,6 +108,7 @@
app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
</Constraint>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 485a0d3..8a0fca0 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -63,9 +63,6 @@
resource_dirs: [
"res",
],
- optimize: {
- proguard_flags_files: ["proguard.flags"],
- },
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
kotlincflags: ["-Xjvm-default=enable"],
diff --git a/packages/SystemUI/shared/proguard.flags b/packages/SystemUI/shared/proguard.flags
deleted file mode 100644
index 5eda045..0000000
--- a/packages/SystemUI/shared/proguard.flags
+++ /dev/null
@@ -1,4 +0,0 @@
-# Retain signatures of TypeToken and its subclasses for gson usage in ClockRegistry
--keepattributes Signature
--keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
--keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index ca780c8..599cd23 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -20,6 +20,7 @@
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
+import android.view.View
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import com.android.systemui.plugins.ClockAnimations
@@ -80,7 +81,7 @@
}
override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
- largeClock.recomputePadding()
+ largeClock.recomputePadding(null)
animations = DefaultClockAnimations(dozeFraction, foldFraction)
events.onColorPaletteChanged(resources)
events.onTimeZoneChanged(TimeZone.getDefault())
@@ -101,6 +102,7 @@
// MAGENTA is a placeholder, and will be assigned correctly in initialize
private var currentColor = Color.MAGENTA
private var isRegionDark = false
+ protected var targetRegion: Rect? = null
init {
view.setColors(currentColor, currentColor)
@@ -112,8 +114,20 @@
this@DefaultClockFaceController.isRegionDark = isRegionDark
updateColor()
}
+
+ override fun onTargetRegionChanged(targetRegion: Rect?) {
+ this@DefaultClockFaceController.targetRegion = targetRegion
+ recomputePadding(targetRegion)
+ }
+
+ override fun onFontSettingChanged(fontSizePx: Float) {
+ view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
+ recomputePadding(targetRegion)
+ }
}
+ open fun recomputePadding(targetRegion: Rect?) {}
+
fun updateColor() {
val color =
if (isRegionDark) {
@@ -135,9 +149,16 @@
inner class LargeClockFaceController(
view: AnimatableClockView,
) : DefaultClockFaceController(view) {
- fun recomputePadding() {
+ override fun recomputePadding(targetRegion: Rect?) {
+ // We center the view within the targetRegion instead of within the parent
+ // view by computing the difference and adding that to the padding.
+ val parent = view.parent
+ val yDiff =
+ if (targetRegion != null && parent is View && parent.isLaidOut())
+ targetRegion.centerY() - parent.height / 2f
+ else 0f
val lp = view.getLayoutParams() as FrameLayout.LayoutParams
- lp.topMargin = (-0.5f * view.bottom).toInt()
+ lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
view.setLayoutParams(lp)
}
@@ -155,18 +176,6 @@
override fun onTimeZoneChanged(timeZone: TimeZone) =
clocks.forEach { it.onTimeZoneChanged(timeZone) }
- override fun onFontSettingChanged() {
- smallClock.view.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
- )
- largeClock.view.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
- )
- largeClock.recomputePadding()
- }
-
override fun onColorPaletteChanged(resources: Resources) {
largeClock.updateColor()
smallClock.updateColor()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt
deleted file mode 100644
index 8612b3a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2022 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.shared.keyguard.data.content
-
-import android.annotation.SuppressLint
-import android.content.ContentValues
-import android.content.Context
-import android.database.ContentObserver
-import android.graphics.drawable.Drawable
-import android.net.Uri
-import android.os.UserHandle
-import androidx.annotation.DrawableRes
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
-
-/** Collection of utility functions for using a content provider implementing the [Contract]. */
-object KeyguardQuickAffordanceProviderClient {
-
- /**
- * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
- *
- * Note that the maximum number of selected affordances on this slot is automatically enforced.
- * Selecting a slot that is already full (e.g. already has a number of selected affordances at
- * its maximum capacity) will automatically remove the oldest selected affordance before adding
- * the one passed in this call. Additionally, selecting an affordance that's already one of the
- * selected affordances on the slot will move the selected affordance to the newest location in
- * the slot.
- */
- suspend fun insertSelection(
- context: Context,
- slotId: String,
- affordanceId: String,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ) {
- withContext(dispatcher) {
- context.contentResolver.insert(
- Contract.SelectionTable.URI,
- ContentValues().apply {
- put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
- put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
- }
- )
- }
- }
-
- /** Returns all available slots supported by the device. */
- suspend fun querySlots(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): List<Slot> {
- return withContext(dispatcher) {
- context.contentResolver
- .query(
- Contract.SlotTable.URI,
- null,
- null,
- null,
- null,
- )
- ?.use { cursor ->
- buildList {
- val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
- val capacityColumnIndex =
- cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
- if (idColumnIndex == -1 || capacityColumnIndex == -1) {
- return@buildList
- }
-
- while (cursor.moveToNext()) {
- add(
- Slot(
- id = cursor.getString(idColumnIndex),
- capacity = cursor.getInt(capacityColumnIndex),
- )
- )
- }
- }
- }
- }
- ?: emptyList()
- }
-
- /**
- * Returns [Flow] for observing the collection of slots.
- *
- * @see [querySlots]
- */
- fun observeSlots(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): Flow<List<Slot>> {
- return observeUri(
- context,
- Contract.SlotTable.URI,
- )
- .map { querySlots(context, dispatcher) }
- }
-
- /**
- * Returns all available affordances supported by the device, regardless of current slot
- * placement.
- */
- suspend fun queryAffordances(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): List<Affordance> {
- return withContext(dispatcher) {
- context.contentResolver
- .query(
- Contract.AffordanceTable.URI,
- null,
- null,
- null,
- null,
- )
- ?.use { cursor ->
- buildList {
- val idColumnIndex =
- cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
- val nameColumnIndex =
- cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
- val iconColumnIndex =
- cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
- if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) {
- return@buildList
- }
-
- while (cursor.moveToNext()) {
- add(
- Affordance(
- id = cursor.getString(idColumnIndex),
- name = cursor.getString(nameColumnIndex),
- iconResourceId = cursor.getInt(iconColumnIndex),
- )
- )
- }
- }
- }
- }
- ?: emptyList()
- }
-
- /**
- * Returns [Flow] for observing the collection of affordances.
- *
- * @see [queryAffordances]
- */
- fun observeAffordances(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): Flow<List<Affordance>> {
- return observeUri(
- context,
- Contract.AffordanceTable.URI,
- )
- .map { queryAffordances(context, dispatcher) }
- }
-
- /** Returns the current slot-affordance selections. */
- suspend fun querySelections(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): List<Selection> {
- return withContext(dispatcher) {
- context.contentResolver
- .query(
- Contract.SelectionTable.URI,
- null,
- null,
- null,
- null,
- )
- ?.use { cursor ->
- buildList {
- val slotIdColumnIndex =
- cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
- val affordanceIdColumnIndex =
- cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
- if (slotIdColumnIndex == -1 || affordanceIdColumnIndex == -1) {
- return@buildList
- }
-
- while (cursor.moveToNext()) {
- add(
- Selection(
- slotId = cursor.getString(slotIdColumnIndex),
- affordanceId = cursor.getString(affordanceIdColumnIndex),
- )
- )
- }
- }
- }
- }
- ?: emptyList()
- }
-
- /**
- * Returns [Flow] for observing the collection of selections.
- *
- * @see [querySelections]
- */
- fun observeSelections(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): Flow<List<Selection>> {
- return observeUri(
- context,
- Contract.SelectionTable.URI,
- )
- .map { querySelections(context, dispatcher) }
- }
-
- /** Unselects an affordance with the given ID from the slot with the given ID. */
- suspend fun deleteSelection(
- context: Context,
- slotId: String,
- affordanceId: String,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ) {
- withContext(dispatcher) {
- context.contentResolver.delete(
- Contract.SelectionTable.URI,
- "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
- " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
- arrayOf(
- slotId,
- affordanceId,
- ),
- )
- }
- }
-
- /** Unselects all affordances from the slot with the given ID. */
- suspend fun deleteAllSelections(
- context: Context,
- slotId: String,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ) {
- withContext(dispatcher) {
- context.contentResolver.delete(
- Contract.SelectionTable.URI,
- "${Contract.SelectionTable.Columns.SLOT_ID}",
- arrayOf(
- slotId,
- ),
- )
- }
- }
-
- private fun observeUri(
- context: Context,
- uri: Uri,
- ): Flow<Unit> {
- return callbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- context.contentResolver.registerContentObserver(
- uri,
- /* notifyForDescendants= */ true,
- observer,
- UserHandle.USER_CURRENT,
- )
-
- awaitClose { context.contentResolver.unregisterContentObserver(observer) }
- }
- .onStart { emit(Unit) }
- }
-
- @SuppressLint("UseCompatLoadingForDrawables")
- suspend fun getAffordanceIcon(
- context: Context,
- @DrawableRes iconResourceId: Int,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): Drawable {
- return withContext(dispatcher) {
- context.packageManager
- .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
- .getDrawable(iconResourceId)
- }
- }
-
- data class Slot(
- val id: String,
- val capacity: Int,
- )
-
- data class Affordance(
- val id: String,
- val name: String,
- val iconResourceId: Int,
- )
-
- data class Selection(
- val slotId: String,
- val affordanceId: String,
- )
-
- private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index c9b8712..87e9d56 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -26,6 +26,7 @@
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -43,6 +44,11 @@
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
@@ -50,11 +56,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.Executor
-import javax.inject.Inject
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -84,6 +85,7 @@
value.initialize(resources, dozeAmount, 0f)
updateRegionSamplers(value)
+ updateFontSizes()
}
}
@@ -150,7 +152,7 @@
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateFun = { updateColors() } )
+ updateColors)
}
var smallRegionSampler: RegionSampler? = null
@@ -166,7 +168,7 @@
}
override fun onDensityOrFontScaleChanged() {
- clock?.events?.onFontSettingChanged()
+ updateFontSizes()
}
}
@@ -251,6 +253,13 @@
largeRegionSampler?.stopRegionSampler()
}
+ private fun updateFontSizes() {
+ clock?.smallClock?.events?.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat())
+ clock?.largeClock?.events?.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
+ }
+
/**
* Dump information for debugging
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 8ebad6c..40423cd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,6 +5,7 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -22,6 +23,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
@@ -46,6 +48,7 @@
*/
private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
+ private ClockController mClock;
private View mStatusArea;
private int mSmartspaceTopOffset;
@@ -95,6 +98,8 @@
}
void setClock(ClockController clock, int statusBarState) {
+ mClock = clock;
+
// Disconnect from existing plugin.
mSmallClockFrame.removeAllViews();
mLargeClockFrame.removeAllViews();
@@ -108,6 +113,35 @@
Log.i(TAG, "Attached new clock views to switch");
mSmallClockFrame.addView(clock.getSmallClock().getView());
mLargeClockFrame.addView(clock.getLargeClock().getView());
+ updateClockTargetRegions();
+ }
+
+ void updateClockTargetRegions() {
+ if (mClock != null) {
+ if (mSmallClockFrame.isLaidOut()) {
+ int targetHeight = getResources()
+ .getDimensionPixelSize(R.dimen.small_clock_text_size);
+ mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
+ mSmallClockFrame.getLeft(),
+ mSmallClockFrame.getTop(),
+ mSmallClockFrame.getRight(),
+ mSmallClockFrame.getTop() + targetHeight));
+ }
+
+ if (mLargeClockFrame.isLaidOut()) {
+ int largeClockTopMargin = getResources()
+ .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
+ int targetHeight = getResources()
+ .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
+ int top = mLargeClockFrame.getHeight() / 2 - targetHeight / 2
+ + largeClockTopMargin / 2;
+ mClock.getLargeClock().getEvents().onTargetRegionChanged(new Rect(
+ mLargeClockFrame.getLeft(),
+ top,
+ mLargeClockFrame.getRight(),
+ top + targetHeight));
+ }
+ }
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -214,6 +248,10 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
+ if (changed) {
+ post(() -> updateClockTargetRegions());
+ }
+
if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index ace942d..e6aae9b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -77,7 +77,7 @@
@KeyguardClockSwitch.ClockSize
private int mCurrentClockSize = SMALL;
- private int mKeyguardClockTopMargin = 0;
+ private int mKeyguardSmallClockTopMargin = 0;
private final ClockRegistry.ClockChangeListener mClockChangedListener;
private ViewGroup mStatusArea;
@@ -162,7 +162,7 @@
mClockRegistry.registerClockChangeListener(mClockChangedListener);
setClock(mClockRegistry.createCurrentClock());
mClockEventController.registerListeners(mView);
- mKeyguardClockTopMargin =
+ mKeyguardSmallClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
@@ -244,10 +244,12 @@
*/
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
- mKeyguardClockTopMargin =
+ mKeyguardSmallClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+ mView.updateClockTargetRegions();
}
+
/**
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
@@ -327,7 +329,7 @@
return frameHeight / 2 + clockHeight / 2;
} else {
int clockHeight = clock.getSmallClock().getView().getHeight();
- return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 83e23bd..8b9823b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import android.content.Context;
+import android.os.Trace;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -112,4 +113,11 @@
mKeyguardSlice.dump(pw, args);
}
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("KeyguardStatusView#onMeasure");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2ac93b5..d694dc0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -144,6 +144,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
@@ -263,6 +264,7 @@
"com.android.settings", "com.android.settings.FallbackHome");
private final Context mContext;
+ private final UserTracker mUserTracker;
private final KeyguardUpdateMonitorLogger mLogger;
private final boolean mIsPrimaryUser;
private final AuthController mAuthController;
@@ -848,13 +850,7 @@
mHandler.removeCallbacks(mFpCancelNotReceived);
}
try {
- final int userId;
- try {
- userId = ActivityManager.getService().getCurrentUser().id;
- } catch (RemoteException e) {
- mLogger.logException(e, "Failed to get current user id");
- return;
- }
+ final int userId = mUserTracker.getUserId();
if (userId != authUserId) {
mLogger.logFingerprintAuthForWrongUser(authUserId);
return;
@@ -1072,13 +1068,7 @@
mLogger.d("Aborted successful auth because device is going to sleep.");
return;
}
- final int userId;
- try {
- userId = ActivityManager.getService().getCurrentUser().id;
- } catch (RemoteException e) {
- mLogger.logException(e, "Failed to get current user id");
- return;
- }
+ final int userId = mUserTracker.getUserId();
if (userId != authUserId) {
mLogger.logFaceAuthForWrongUser(authUserId);
return;
@@ -1928,6 +1918,7 @@
@Inject
protected KeyguardUpdateMonitor(
Context context,
+ UserTracker userTracker,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings,
@@ -1960,6 +1951,7 @@
FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
mContext = context;
mSubscriptionManager = subscriptionManager;
+ mUserTracker = userTracker;
mTelephonyListenerManager = telephonyListenerManager;
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged,
@@ -2192,7 +2184,7 @@
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
mIsPrimaryUser = mUserManager.isPrimaryUser();
- int user = ActivityManager.getCurrentUser();
+ int user = mUserTracker.getUserId();
mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
updateSecondaryLockscreenRequirement(user);
@@ -3818,7 +3810,7 @@
pw.println(" " + subId + "=" + mServiceStates.get(subId));
}
if (mFpm != null && mFpm.isHardwareDetected()) {
- final int userId = ActivityManager.getCurrentUser();
+ final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
pw.println(" Fingerprint state (user=" + userId + ")");
@@ -3858,7 +3850,7 @@
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
- final int userId = ActivityManager.getCurrentUser();
+ final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
pw.println(" Face authentication state (user=" + userId + ")");
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 9a0bfc1..ad9609f 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -29,17 +29,17 @@
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.Observer;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManager.DockEventListener;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.settings.CurrentUserObservable;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import java.util.ArrayList;
@@ -47,6 +47,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
@@ -69,7 +70,8 @@
private final ContentResolver mContentResolver;
private final SettingsWrapper mSettingsWrapper;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
- private final CurrentUserObservable mCurrentUserObservable;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
/**
* Observe settings changes to know when to switch the clock face.
@@ -80,7 +82,7 @@
public void onChange(boolean selfChange, Collection<Uri> uris,
int flags, int userId) {
if (Objects.equals(userId,
- mCurrentUserObservable.getCurrentUser().getValue())) {
+ mUserTracker.getUserId())) {
reload();
}
}
@@ -89,7 +91,13 @@
/**
* Observe user changes and react by potentially loading the custom clock for the new user.
*/
- private final Observer<Integer> mCurrentUserObserver = (newUserId) -> reload();
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ reload();
+ }
+ };
private final PluginManager mPluginManager;
@Nullable private final DockManager mDockManager;
@@ -129,22 +137,24 @@
@Inject
public ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
- @Nullable DockManager dockManager, BroadcastDispatcher broadcastDispatcher) {
+ @Nullable DockManager dockManager, UserTracker userTracker,
+ @Main Executor mainExecutor) {
this(context, layoutInflater, pluginManager, colorExtractor,
- context.getContentResolver(), new CurrentUserObservable(broadcastDispatcher),
+ context.getContentResolver(), userTracker, mainExecutor,
new SettingsWrapper(context.getContentResolver()), dockManager);
}
@VisibleForTesting
ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
- ContentResolver contentResolver, CurrentUserObservable currentUserObservable,
+ ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor,
SettingsWrapper settingsWrapper, DockManager dockManager) {
mContext = context;
mPluginManager = pluginManager;
mContentResolver = contentResolver;
mSettingsWrapper = settingsWrapper;
- mCurrentUserObservable = currentUserObservable;
+ mUserTracker = userTracker;
+ mMainExecutor = mainExecutor;
mDockManager = dockManager;
mPreviewClocks = new AvailableClocks();
@@ -226,7 +236,7 @@
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
false, mContentObserver, UserHandle.USER_ALL);
- mCurrentUserObservable.getCurrentUser().observeForever(mCurrentUserObserver);
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
if (mDockManager != null) {
mDockManager.addListener(mDockEventListener);
}
@@ -235,7 +245,7 @@
private void unregister() {
mPluginManager.removePluginListener(mPreviewClocks);
mContentResolver.unregisterContentObserver(mContentObserver);
- mCurrentUserObservable.getCurrentUser().removeObserver(mCurrentUserObserver);
+ mUserTracker.removeCallback(mUserChangedCallback);
if (mDockManager != null) {
mDockManager.removeListener(mDockEventListener);
}
@@ -363,7 +373,7 @@
ClockPlugin plugin = null;
if (ClockManager.this.isDocked()) {
final String name = mSettingsWrapper.getDockedClockFace(
- mCurrentUserObservable.getCurrentUser().getValue());
+ mUserTracker.getUserId());
if (name != null) {
plugin = mClocks.get(name);
if (plugin != null) {
@@ -372,7 +382,7 @@
}
}
final String name = mSettingsWrapper.getLockScreenCustomClockFace(
- mCurrentUserObservable.getCurrentUser().getValue());
+ mUserTracker.getUserId());
if (name != null) {
plugin = mClocks.get(name);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 32ce537..9e58500 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -18,14 +18,11 @@
import com.android.systemui.log.dagger.KeyguardLog
import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.plugins.log.LogLevel.ERROR
import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.plugins.log.LogLevel.VERBOSE
import com.android.systemui.plugins.log.LogLevel.WARNING
-import com.android.systemui.plugins.log.MessageInitializer
-import com.android.systemui.plugins.log.MessagePrinter
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -37,18 +34,16 @@
* an overkill.
*/
class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
- fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+ fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
- fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+ fun e(@CompileTimeConstant msg: String) = buffer.log(TAG, ERROR, msg)
- fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+ fun v(@CompileTimeConstant msg: String) = buffer.log(TAG, VERBOSE, msg)
- fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+ fun w(@CompileTimeConstant msg: String) = buffer.log(TAG, WARNING, msg)
- fun log(msg: String, level: LogLevel) = buffer.log(TAG, level, msg)
-
- private fun debugLog(messageInitializer: MessageInitializer, messagePrinter: MessagePrinter) {
- buffer.log(TAG, DEBUG, messageInitializer, messagePrinter)
+ fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
+ buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
}
fun v(msg: String, arg: Any) {
@@ -61,17 +56,24 @@
// TODO: remove after b/237743330 is fixed
fun logStatusBarCalculatedAlpha(alpha: Float) {
- debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
+ buffer.log(TAG, DEBUG, { double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
}
// TODO: remove after b/237743330 is fixed
fun logStatusBarExplicitAlpha(alpha: Float) {
- debugLog({ double1 = alpha.toDouble() }, { "new mExplicitAlpha value: $double1" })
+ buffer.log(
+ TAG,
+ DEBUG,
+ { double1 = alpha.toDouble() },
+ { "new mExplicitAlpha value: $double1" }
+ )
}
// TODO: remove after b/237743330 is fixed
fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
- debugLog(
+ buffer.log(
+ TAG,
+ DEBUG,
{
int1 = visibility
double1 = alpha.toDouble()
@@ -80,4 +82,22 @@
{ "changing visibility to $int1 with alpha $double1 in state: $str1" }
)
}
+
+ @JvmOverloads
+ fun logBiometricMessage(
+ @CompileTimeConstant context: String,
+ msgId: Int? = null,
+ msg: String? = null
+ ) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = context
+ str2 = "$msgId"
+ str3 = msg
+ },
+ { "$str1 msgId: $str2 msg: $str3" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a5fdc68..51bcd6b 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -70,6 +70,7 @@
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -357,6 +358,7 @@
@Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
@Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
@Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
+ @Inject Lazy<UserTracker> mUserTrackerLazy;
@Inject
public Dependency() {
@@ -564,6 +566,7 @@
mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get);
+ mProviders.put(UserTracker.class, mUserTrackerLazy::get);
Dependency.setInstance(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 45f9385..7e3b1389 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -26,7 +26,6 @@
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -901,7 +900,7 @@
private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- int newUserId = ActivityManager.getCurrentUser();
+ int newUserId = mUserTracker.getUserId();
if (DEBUG) {
Log.d(TAG, "UserSwitched newUserId=" + newUserId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 0a2dc5b..d60cc75 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -291,8 +291,11 @@
mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
- mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
- mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+ if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+ // These two actions require the CentralSurfaces instance.
+ mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
+ mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+ }
mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a6e767c..ec15d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -158,6 +158,10 @@
private View mTopDrag;
private View mRightDrag;
private View mBottomDrag;
+ private ImageView mTopLeftCornerView;
+ private ImageView mTopRightCornerView;
+ private ImageView mBottomLeftCornerView;
+ private ImageView mBottomRightCornerView;
private final Configuration mConfiguration;
@NonNull
@@ -357,13 +361,15 @@
return false;
}
- private void changeMagnificationSize(@MagnificationSize int index) {
+ @VisibleForTesting
+ void changeMagnificationSize(@MagnificationSize int index) {
final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
int size = (int) (initSize * MAGNIFICATION_SCALE_OPTIONS[index]);
setWindowSize(size, size);
}
- private void setEditMagnifierSizeMode(boolean enable) {
+ @VisibleForTesting
+ void setEditMagnifierSizeMode(boolean enable) {
mEditSizeEnable = enable;
applyResourcesValues();
@@ -639,10 +645,37 @@
Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
mMirrorView.getWidth() - mBorderDragSize,
mMirrorView.getHeight() - mBorderDragSize);
+
+ Region tapExcludeRegion = new Region();
+
Rect dragArea = new Rect();
mDragView.getHitRect(dragArea);
- regionInsideDragBorder.op(dragArea, Region.Op.DIFFERENCE);
+ Rect topLeftArea = new Rect();
+ mTopLeftCornerView.getHitRect(topLeftArea);
+
+ Rect topRightArea = new Rect();
+ mTopRightCornerView.getHitRect(topRightArea);
+
+ Rect bottomLeftArea = new Rect();
+ mBottomLeftCornerView.getHitRect(bottomLeftArea);
+
+ Rect bottomRightArea = new Rect();
+ mBottomRightCornerView.getHitRect(bottomRightArea);
+
+ Rect closeArea = new Rect();
+ mCloseView.getHitRect(closeArea);
+
+ // add tapExcludeRegion for Drag or close
+ tapExcludeRegion.op(dragArea, Region.Op.UNION);
+ tapExcludeRegion.op(topLeftArea, Region.Op.UNION);
+ tapExcludeRegion.op(topRightArea, Region.Op.UNION);
+ tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION);
+ tapExcludeRegion.op(bottomRightArea, Region.Op.UNION);
+ tapExcludeRegion.op(closeArea, Region.Op.UNION);
+
+ regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE);
+
return regionInsideDragBorder;
}
@@ -756,6 +789,10 @@
mRightDrag = mMirrorView.findViewById(R.id.right_handle);
mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
mCloseView = mMirrorView.findViewById(R.id.close_button);
+ mTopRightCornerView = mMirrorView.findViewById(R.id.top_right_corner);
+ mTopLeftCornerView = mMirrorView.findViewById(R.id.top_left_corner);
+ mBottomRightCornerView = mMirrorView.findViewById(R.id.bottom_right_corner);
+ mBottomLeftCornerView = mMirrorView.findViewById(R.id.bottom_left_corner);
mDragView.setOnTouchListener(this);
mLeftDrag.setOnTouchListener(this);
@@ -763,6 +800,10 @@
mRightDrag.setOnTouchListener(this);
mBottomDrag.setOnTouchListener(this);
mCloseView.setOnTouchListener(this);
+ mTopLeftCornerView.setOnTouchListener(this);
+ mTopRightCornerView.setOnTouchListener(this);
+ mBottomLeftCornerView.setOnTouchListener(this);
+ mBottomRightCornerView.setOnTouchListener(this);
}
/**
@@ -831,8 +872,16 @@
@Override
public boolean onTouch(View v, MotionEvent event) {
- if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
- || v == mBottomDrag || v == mCloseView) {
+ if (v == mDragView
+ || v == mLeftDrag
+ || v == mTopDrag
+ || v == mRightDrag
+ || v == mBottomDrag
+ || v == mTopLeftCornerView
+ || v == mTopRightCornerView
+ || v == mBottomLeftCornerView
+ || v == mBottomRightCornerView
+ || v == mCloseView) {
return mGestureDetector.onTouch(v, event);
}
return false;
@@ -1195,7 +1244,7 @@
@Override
public boolean onDrag(View view, float offsetX, float offsetY) {
if (mEditSizeEnable) {
- changeWindowSize(view, offsetX, offsetY);
+ return changeWindowSize(view, offsetX, offsetY);
} else {
move((int) offsetX, (int) offsetY);
}
@@ -1220,13 +1269,47 @@
if (mEditSizeEnable) {
mDragView.setVisibility(View.GONE);
mCloseView.setVisibility(View.VISIBLE);
+ mTopRightCornerView.setVisibility(View.VISIBLE);
+ mTopLeftCornerView.setVisibility(View.VISIBLE);
+ mBottomRightCornerView.setVisibility(View.VISIBLE);
+ mBottomLeftCornerView.setVisibility(View.VISIBLE);
} else {
mDragView.setVisibility(View.VISIBLE);
mCloseView.setVisibility(View.GONE);
+ mTopRightCornerView.setVisibility(View.GONE);
+ mTopLeftCornerView.setVisibility(View.GONE);
+ mBottomRightCornerView.setVisibility(View.GONE);
+ mBottomLeftCornerView.setVisibility(View.GONE);
}
}
- public boolean changeWindowSize(View view, float offsetX, float offsetY) {
+ private boolean changeWindowSize(View view, float offsetX, float offsetY) {
+ if (view == mLeftDrag) {
+ changeMagnificationFrameSize(offsetX, 0, 0, 0);
+ } else if (view == mRightDrag) {
+ changeMagnificationFrameSize(0, 0, offsetX, 0);
+ } else if (view == mTopDrag) {
+ changeMagnificationFrameSize(0, offsetY, 0, 0);
+ } else if (view == mBottomDrag) {
+ changeMagnificationFrameSize(0, 0, 0, offsetY);
+ } else if (view == mTopLeftCornerView) {
+ changeMagnificationFrameSize(offsetX, offsetY, 0, 0);
+ } else if (view == mTopRightCornerView) {
+ changeMagnificationFrameSize(0, offsetY, offsetX, 0);
+ } else if (view == mBottomLeftCornerView) {
+ changeMagnificationFrameSize(offsetX, 0, 0, offsetY);
+ } else if (view == mBottomRightCornerView) {
+ changeMagnificationFrameSize(0, 0, offsetX, offsetY);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void changeMagnificationFrameSize(
+ float leftOffset, float topOffset, float rightOffset,
+ float bottomOffset) {
boolean bRTL = isRTL(mContext);
final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
@@ -1236,54 +1319,26 @@
Rect tempRect = new Rect();
tempRect.set(mMagnificationFrame);
- if (view == mLeftDrag) {
- if (bRTL) {
- tempRect.right += offsetX;
- if (tempRect.right > mWindowBounds.width()) {
- return false;
- }
- } else {
- tempRect.left += offsetX;
- if (tempRect.left < 0) {
- return false;
- }
- }
- } else if (view == mRightDrag) {
- if (bRTL) {
- tempRect.left += offsetX;
- if (tempRect.left < 0) {
- return false;
- }
- } else {
- tempRect.right += offsetX;
- if (tempRect.right > mWindowBounds.width()) {
- return false;
- }
- }
- } else if (view == mTopDrag) {
- tempRect.top += offsetY;
- if (tempRect.top < 0) {
- return false;
- }
- } else if (view == mBottomDrag) {
- tempRect.bottom += offsetY;
- if (tempRect.bottom > mWindowBounds.height()) {
- return false;
- }
+ if (bRTL) {
+ tempRect.left += (int) (rightOffset);
+ tempRect.right += (int) (leftOffset);
+ } else {
+ tempRect.right += (int) (rightOffset);
+ tempRect.left += (int) (leftOffset);
}
+ tempRect.top += (int) (topOffset);
+ tempRect.bottom += (int) (bottomOffset);
if (tempRect.width() < initSize || tempRect.height() < initSize
|| tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) {
- return false;
+ return;
}
-
mMagnificationFrame.set(tempRect);
computeBounceAnimationScale();
calculateMagnificationFrameBoundary();
modifyWindowMagnification(true);
- return true;
}
private static boolean isRTL(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 9cffd5d..069c0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -103,7 +103,7 @@
MagnificationSize.LARGE,
})
/** Denotes the Magnification size type. */
- @interface MagnificationSize {
+ public @interface MagnificationSize {
int NONE = 0;
int SMALL = 1;
int MEDIUM = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 33e155d..b8f14ae 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -16,14 +16,19 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
+import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
+import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.PluralsMessageFormatter;
import android.view.MotionEvent;
@@ -82,8 +87,22 @@
final Runnable mDismissMenuAction = new Runnable() {
@Override
public void run() {
- Settings.Secure.putString(getContext().getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "");
+ Settings.Secure.putStringForUser(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
+ UserHandle.USER_CURRENT);
+
+ // Should disable the corresponding service when the fragment type is
+ // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
+ final List<AccessibilityServiceInfo> serviceInfoList =
+ mAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ serviceInfoList.forEach(info -> {
+ if (getAccessibilityServiceFragmentType(info) == INVISIBLE_TOGGLE) {
+ setAccessibilityServiceState(mContext, info.getComponentName(), /* enabled= */
+ false);
+ }
+ });
+
mFloatingMenu.hide();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
new file mode 100644
index 0000000..b52ddc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 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.battery
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.drawable.DrawableWrapper
+import android.util.PathParser
+import com.android.settingslib.graph.ThemedBatteryDrawable
+import com.android.systemui.R
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET
+import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE
+import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET
+
+/**
+ * A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if
+ * necessary.
+ *
+ * For now, it adds a shield in the bottom-right corner when [displayShield] is true.
+ */
+class AccessorizedBatteryDrawable(
+ private val context: Context,
+ frameColor: Int,
+) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) {
+ private val mainBatteryDrawable: ThemedBatteryDrawable
+ get() = drawable as ThemedBatteryDrawable
+
+ private val shieldPath = Path()
+ private val scaledShield = Path()
+ private val scaleMatrix = Matrix()
+
+ private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET
+ private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET
+
+ private var density = context.resources.displayMetrics.density
+
+ private val dualTone =
+ context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone)
+
+ private val shieldTransparentOutlinePaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.color = Color.TRANSPARENT
+ p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+ p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
+ p.style = Paint.Style.FILL_AND_STROKE
+ }
+
+ private val shieldPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.color = Color.MAGENTA
+ p.style = Paint.Style.FILL
+ p.isDither = true
+ }
+
+ init {
+ loadPaths()
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+ updateSizes()
+ }
+
+ var displayShield: Boolean = false
+
+ private fun updateSizes() {
+ val b = bounds
+ if (b.isEmpty) {
+ return
+ }
+
+ val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield)
+ val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield)
+
+ drawable?.setBounds(
+ b.left,
+ b.top,
+ /* right= */ b.left + mainWidth.toInt(),
+ /* bottom= */ b.top + mainHeight.toInt()
+ )
+
+ if (displayShield) {
+ val sx = b.right / BATTERY_WIDTH_WITH_SHIELD
+ val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD
+ scaleMatrix.setScale(sx, sy)
+ shieldPath.transform(scaleMatrix, scaledShield)
+
+ shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET
+ shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET
+
+ val scaledStrokeWidth =
+ (sx * SHIELD_STROKE).coerceAtLeast(
+ ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+ )
+ shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth
+ }
+ }
+
+ override fun getIntrinsicHeight(): Int {
+ val height =
+ if (displayShield) {
+ BATTERY_HEIGHT_WITH_SHIELD
+ } else {
+ BATTERY_HEIGHT
+ }
+ return (height * density).toInt()
+ }
+
+ override fun getIntrinsicWidth(): Int {
+ val width =
+ if (displayShield) {
+ BATTERY_WIDTH_WITH_SHIELD
+ } else {
+ BATTERY_WIDTH
+ }
+ return (width * density).toInt()
+ }
+
+ override fun draw(c: Canvas) {
+ c.saveLayer(null, null)
+ // Draw the main battery icon
+ super.draw(c)
+
+ if (displayShield) {
+ c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled)
+ // We need a transparent outline around the shield, so first draw the transparent-ness
+ // then draw the shield
+ c.drawPath(scaledShield, shieldTransparentOutlinePaint)
+ c.drawPath(scaledShield, shieldPaint)
+ }
+ c.restore()
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.OPAQUE
+ }
+
+ override fun setAlpha(p0: Int) {
+ // Unused internally -- see [ThemedBatteryDrawable.setAlpha].
+ }
+
+ override fun setColorFilter(colorfilter: ColorFilter?) {
+ super.setColorFilter(colorFilter)
+ shieldPaint.colorFilter = colorFilter
+ }
+
+ /** Sets whether the battery is currently charging. */
+ fun setCharging(charging: Boolean) {
+ mainBatteryDrawable.charging = charging
+ }
+
+ /** Sets the current level (out of 100) of the battery. */
+ fun setBatteryLevel(level: Int) {
+ mainBatteryDrawable.setBatteryLevel(level)
+ }
+
+ /** Sets whether power save is enabled. */
+ fun setPowerSaveEnabled(powerSaveEnabled: Boolean) {
+ mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled
+ }
+
+ /** Returns whether power save is currently enabled. */
+ fun getPowerSaveEnabled(): Boolean {
+ return mainBatteryDrawable.powerSaveEnabled
+ }
+
+ /** Sets the colors to use for the icon. */
+ fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) {
+ shieldPaint.color = if (dualTone) fgColor else singleToneColor
+ mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor)
+ }
+
+ /** Notifies this drawable that the density might have changed. */
+ fun notifyDensityChanged() {
+ density = context.resources.displayMetrics.density
+ }
+
+ private fun loadPaths() {
+ val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
+ shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 6a10d4a..03d999f 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,7 +45,6 @@
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
-import com.android.settingslib.graph.ThemedBatteryDrawable;
import com.android.systemui.DualToneHandler;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -68,7 +67,7 @@
public static final int MODE_OFF = 2;
public static final int MODE_ESTIMATE = 3;
- private final ThemedBatteryDrawable mDrawable;
+ private final AccessorizedBatteryDrawable mDrawable;
private final ImageView mBatteryIconView;
private TextView mBatteryPercentView;
@@ -77,7 +76,10 @@
private int mLevel;
private int mShowPercentMode = MODE_DEFAULT;
private boolean mShowPercentAvailable;
+ private String mEstimateText = null;
private boolean mCharging;
+ private boolean mIsOverheated;
+ private boolean mDisplayShieldEnabled;
// Error state where we know nothing about the current battery state
private boolean mBatteryStateUnknown;
// Lazily-loaded since this is expected to be a rare-if-ever state
@@ -106,7 +108,7 @@
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
context.getColor(R.color.meter_background_color));
mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
- mDrawable = new ThemedBatteryDrawable(context, frameColor);
+ mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
atts.recycle();
mShowPercentAvailable = context.getResources().getBoolean(
@@ -170,12 +172,14 @@
if (mode == mShowPercentMode) return;
mShowPercentMode = mode;
updateShowPercent();
+ updatePercentText();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updatePercentView();
+ mDrawable.notifyDensityChanged();
}
public void setColorsFromContext(Context context) {
@@ -203,6 +207,17 @@
mDrawable.setPowerSaveEnabled(isPowerSave);
}
+ void onIsOverheatedChanged(boolean isOverheated) {
+ boolean valueChanged = mIsOverheated != isOverheated;
+ mIsOverheated = isOverheated;
+ if (valueChanged) {
+ updateContentDescription();
+ // The battery drawable is a different size depending on whether it's currently
+ // overheated or not, so we need to re-scale the view when overheated changes.
+ scaleBatteryMeterViews();
+ }
+ }
+
private TextView loadPercentView() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null);
@@ -227,13 +242,17 @@
mBatteryEstimateFetcher = fetcher;
}
+ void setDisplayShieldEnabled(boolean displayShieldEnabled) {
+ mDisplayShieldEnabled = displayShieldEnabled;
+ }
+
void updatePercentText() {
if (mBatteryStateUnknown) {
- setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
return;
}
if (mBatteryEstimateFetcher == null) {
+ setPercentTextAtCurrentLevel();
return;
}
@@ -245,10 +264,9 @@
return;
}
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
+ mEstimateText = estimate;
mBatteryPercentView.setText(estimate);
- setContentDescription(getContext().getString(
- R.string.accessibility_battery_level_with_estimate,
- mLevel, estimate));
+ updateContentDescription();
} else {
setPercentTextAtCurrentLevel();
}
@@ -257,28 +275,49 @@
setPercentTextAtCurrentLevel();
}
} else {
- setContentDescription(
- getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
- : R.string.accessibility_battery_level, mLevel));
+ updateContentDescription();
}
}
private void setPercentTextAtCurrentLevel() {
- if (mBatteryPercentView == null) {
- return;
+ if (mBatteryPercentView != null) {
+ mEstimateText = null;
+ String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
+ // Setting text actually triggers a layout pass (because the text view is set to
+ // wrap_content width and TextView always relayouts for this). Avoid needless
+ // relayout if the text didn't actually change.
+ if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
+ mBatteryPercentView.setText(percentText);
+ }
}
- String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
- // Setting text actually triggers a layout pass (because the text view is set to
- // wrap_content width and TextView always relayouts for this). Avoid needless
- // relayout if the text didn't actually change.
- if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
- mBatteryPercentView.setText(percentText);
+ updateContentDescription();
+ }
+
+ private void updateContentDescription() {
+ Context context = getContext();
+
+ String contentDescription;
+ if (mBatteryStateUnknown) {
+ contentDescription = context.getString(R.string.accessibility_battery_unknown);
+ } else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) {
+ contentDescription = context.getString(
+ mIsOverheated
+ ? R.string.accessibility_battery_level_charging_paused_with_estimate
+ : R.string.accessibility_battery_level_with_estimate,
+ mLevel,
+ mEstimateText);
+ } else if (mIsOverheated) {
+ contentDescription =
+ context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
+ } else if (mCharging) {
+ contentDescription =
+ context.getString(R.string.accessibility_battery_level_charging, mLevel);
+ } else {
+ contentDescription = context.getString(R.string.accessibility_battery_level, mLevel);
}
- setContentDescription(
- getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
- : R.string.accessibility_battery_level, mLevel));
+ setContentDescription(contentDescription);
}
void updateShowPercent() {
@@ -329,6 +368,7 @@
}
mBatteryStateUnknown = isUnknown;
+ updateContentDescription();
if (mBatteryStateUnknown) {
mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
@@ -349,15 +389,43 @@
res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
float iconScaleFactor = typedValue.getFloat();
- int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
- int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
+ float mainBatteryHeight =
+ res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor;
+ float mainBatteryWidth =
+ res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
+
+ // If the battery is marked as overheated, we should display a shield indicating that the
+ // battery is being "defended".
+ boolean displayShield = mDisplayShieldEnabled && mIsOverheated;
+ float fullBatteryIconHeight =
+ BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
+ float fullBatteryIconWidth =
+ BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield);
+
+ int marginTop;
+ if (displayShield) {
+ // If the shield is displayed, we need some extra marginTop so that the bottom of the
+ // main icon is still aligned with the bottom of all the other system icons.
+ int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight);
+ // However, the other system icons have some embedded bottom padding that the battery
+ // doesn't have, so we shouldn't move the battery icon down by the full amount.
+ // See b/258672854.
+ marginTop = shieldHeightAddition
+ - res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing);
+ } else {
+ marginTop = 0;
+ }
+
int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
- (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
- scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
+ Math.round(fullBatteryIconWidth),
+ Math.round(fullBatteryIconHeight));
+ scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom);
+ mDrawable.setDisplayShield(displayShield);
mBatteryIconView.setLayoutParams(scaledLayoutParams);
+ mBatteryIconView.invalidateDrawable(mDrawable);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index ae9a323..f4ec33a 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -17,19 +17,23 @@
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
-import android.app.ActivityManager;
import android.content.ContentResolver;
+import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.View;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import androidx.annotation.NonNull;
+
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -42,12 +46,13 @@
public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
private final ConfigurationController mConfigurationController;
private final TunerService mTunerService;
+ private final Handler mMainHandler;
private final ContentResolver mContentResolver;
private final BatteryController mBatteryController;
private final String mSlotBattery;
private final SettingObserver mSettingObserver;
- private final CurrentUserTracker mCurrentUserTracker;
+ private final UserTracker mUserTracker;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -84,6 +89,21 @@
public void onBatteryUnknownStateChanged(boolean isUnknown) {
mView.onBatteryUnknownStateChanged(isUnknown);
}
+
+ @Override
+ public void onIsOverheatedChanged(boolean isOverheated) {
+ mView.onIsOverheatedChanged(isOverheated);
+ }
+ };
+
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mContentResolver.unregisterContentObserver(mSettingObserver);
+ registerShowBatteryPercentObserver(newUser);
+ mView.updateShowPercent();
+ }
};
// Some places may need to show the battery conditionally, and not obey the tuner
@@ -93,30 +113,26 @@
@Inject
public BatteryMeterViewController(
BatteryMeterView view,
+ UserTracker userTracker,
ConfigurationController configurationController,
TunerService tunerService,
- BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler,
ContentResolver contentResolver,
+ FeatureFlags featureFlags,
BatteryController batteryController) {
super(view);
+ mUserTracker = userTracker;
mConfigurationController = configurationController;
mTunerService = tunerService;
+ mMainHandler = mainHandler;
mContentResolver = contentResolver;
mBatteryController = batteryController;
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
+ mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
- mSettingObserver = new SettingObserver(mainHandler);
- mCurrentUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- contentResolver.unregisterContentObserver(mSettingObserver);
- registerShowBatteryPercentObserver(newUserId);
- mView.updateShowPercent();
- }
- };
+ mSettingObserver = new SettingObserver(mMainHandler);
}
@Override
@@ -125,9 +141,9 @@
subscribeForTunerUpdates();
mBatteryController.addCallback(mBatteryStateChangeCallback);
- registerShowBatteryPercentObserver(ActivityManager.getCurrentUser());
+ registerShowBatteryPercentObserver(mUserTracker.getUserId());
registerGlobalBatteryUpdateObserver();
- mCurrentUserTracker.startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
mView.updateShowPercent();
}
@@ -138,7 +154,7 @@
unsubscribeFromTunerUpdates();
mBatteryController.removeCallback(mBatteryStateChangeCallback);
- mCurrentUserTracker.stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
mContentResolver.unregisterContentObserver(mSettingObserver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
new file mode 100644
index 0000000..6455a96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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.battery
+
+import com.android.settingslib.graph.ThemedBatteryDrawable
+
+/** An object storing specs related to the battery icon in the status bar. */
+object BatterySpecs {
+
+ /** Width of the main battery icon, not including the shield. */
+ const val BATTERY_WIDTH = ThemedBatteryDrawable.WIDTH
+ /** Height of the main battery icon, not including the shield. */
+ const val BATTERY_HEIGHT = ThemedBatteryDrawable.HEIGHT
+
+ private const val SHIELD_WIDTH = 10f
+ private const val SHIELD_HEIGHT = 13f
+
+ /**
+ * Amount that the left side of the shield should be offset from the left side of the battery.
+ */
+ const val SHIELD_LEFT_OFFSET = 8f
+ /** Amount that the top of the shield should be offset from the top of the battery. */
+ const val SHIELD_TOP_OFFSET = 10f
+
+ const val SHIELD_STROKE = 4f
+
+ /** The full width of the battery icon, including the main battery icon *and* the shield. */
+ const val BATTERY_WIDTH_WITH_SHIELD = SHIELD_LEFT_OFFSET + SHIELD_WIDTH
+ /** The full height of the battery icon, including the main battery icon *and* the shield. */
+ const val BATTERY_HEIGHT_WITH_SHIELD = SHIELD_TOP_OFFSET + SHIELD_HEIGHT
+
+ /**
+ * Given the desired height of the main battery icon in pixels, returns the height that the full
+ * battery icon will take up in pixels.
+ *
+ * If there's no shield, this will just return [mainBatteryHeight]. Otherwise, the shield
+ * extends slightly below the bottom of the main battery icon so we need some extra height.
+ */
+ @JvmStatic
+ fun getFullBatteryHeight(mainBatteryHeight: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ mainBatteryHeight
+ } else {
+ val verticalScaleFactor = mainBatteryHeight / BATTERY_HEIGHT
+ verticalScaleFactor * BATTERY_HEIGHT_WITH_SHIELD
+ }
+ }
+
+ /**
+ * Given the desired width of the main battery icon in pixels, returns the width that the full
+ * battery icon will take up in pixels.
+ *
+ * If there's no shield, this will just return [mainBatteryWidth]. Otherwise, the shield extends
+ * past the right side of the main battery icon so we need some extra width.
+ */
+ @JvmStatic
+ fun getFullBatteryWidth(mainBatteryWidth: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ mainBatteryWidth
+ } else {
+ val horizontalScaleFactor = mainBatteryWidth / BATTERY_WIDTH
+ horizontalScaleFactor * BATTERY_WIDTH_WITH_SHIELD
+ }
+ }
+
+ /**
+ * Given the height of the full battery icon, return how tall the main battery icon should be.
+ *
+ * If there's no shield, this will just return [fullBatteryHeight]. Otherwise, the shield takes
+ * up some of the view's height so the main battery width will be just a portion of
+ * [fullBatteryHeight].
+ */
+ @JvmStatic
+ fun getMainBatteryHeight(fullBatteryHeight: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ fullBatteryHeight
+ } else {
+ return (BATTERY_HEIGHT / BATTERY_HEIGHT_WITH_SHIELD) * fullBatteryHeight
+ }
+ }
+
+ /**
+ * Given the width of the full battery icon, return how wide the main battery icon should be.
+ *
+ * If there's no shield, this will just return [fullBatteryWidth]. Otherwise, the shield takes
+ * up some of the view's width so the main battery width will be just a portion of
+ * [fullBatteryWidth].
+ */
+ @JvmStatic
+ fun getMainBatteryWidth(fullBatteryWidth: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ fullBatteryWidth
+ } else {
+ return (BATTERY_WIDTH / BATTERY_WIDTH_WITH_SHIELD) * fullBatteryWidth
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index c93fe6a..4b57d45 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -29,7 +29,7 @@
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators
-import com.android.systemui.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleShader
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index c619648..5110a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -6,6 +6,8 @@
import android.view.inputmethod.InputMethodManager
import android.widget.ImeAwareEditText
import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
@@ -13,7 +15,7 @@
import com.android.systemui.biometrics.ui.CredentialView
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch
/** Sub-binder for the [CredentialPasswordView]. */
@@ -29,6 +31,8 @@
val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
+ val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() }
+
view.repeatWhenAttached {
passwordField.requestFocus()
passwordField.scheduleShowSoftInput()
@@ -43,9 +47,7 @@
launch { viewModel.checkCredential(text, header) }
}
)
- passwordField.setOnKeyListener(
- OnBackButtonListener { host.onCredentialAborted() }
- )
+ passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
}
}
@@ -66,18 +68,35 @@
}
}
}
+
+ val onBackInvokedDispatcher = view.findOnBackInvokedDispatcher()
+ if (onBackInvokedDispatcher != null) {
+ launch {
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ onBackInvokedCallback
+ )
+ awaitCancellation()
+ }
+ .invokeOnCompletion {
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
+ onBackInvokedCallback
+ )
+ }
+ }
}
}
}
}
-private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener {
+private class OnBackButtonListener(private val onBackInvokedCallback: OnBackInvokedCallback) :
+ View.OnKeyListener {
override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
if (keyCode != KeyEvent.KEYCODE_BACK) {
return false
}
if (event.action == KeyEvent.ACTION_UP) {
- onBack()
+ onBackInvokedCallback.onBackInvoked()
}
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 616e49c..1454210 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -31,7 +31,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index e82d0ea..3808ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -30,7 +30,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
/**
* A WirelessChargingAnimation is a view containing view + animation for wireless charging.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 1455699..36103f8 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -33,9 +33,9 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.ripple.RippleAnimationConfig;
-import com.android.systemui.ripple.RippleShader.RippleShape;
-import com.android.systemui.ripple.RippleView;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleView;
import java.text.NumberFormat;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index beaccba..e8e1f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -231,7 +231,8 @@
// check for false tap if it is a seekbar interaction
if (interactionType == MEDIA_SEEKBAR) {
- localResult[0] &= isFalseTap(LOW_PENALTY);
+ localResult[0] &= isFalseTap(mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+ ? FalsingManager.MODERATE_PENALTY : FalsingManager.LOW_PENALTY);
}
logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index b11103a..7df0865 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -18,6 +18,7 @@
import android.app.ActivityOptions
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
@@ -33,21 +34,23 @@
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Activity for rearranging and removing controls for a given structure
*/
open class ControlsEditingActivity @Inject constructor(
+ @Main private val mainExecutor: Executor,
private val controller: ControlsControllerImpl,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val customIconCache: CustomIconCache,
private val uiController: ControlsUiController
) : ComponentActivity() {
@@ -66,12 +69,12 @@
private lateinit var subtitle: TextView
private lateinit var saveButton: View
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
@@ -104,7 +107,7 @@
super.onStart()
setUpList()
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, mainExecutor)
if (DEBUG) {
Log.d(TAG, "Registered onBackInvokedCallback")
@@ -115,7 +118,7 @@
override fun onStop() {
super.onStop()
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
if (DEBUG) {
Log.d(TAG, "Unregistered onBackInvokedCallback")
@@ -248,7 +251,7 @@
}
override fun onDestroy() {
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
super.onDestroy()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 9b2a728..3e97d31 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter
import android.app.ActivityOptions
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
@@ -39,7 +40,6 @@
import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.Prefs
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.TooltipManager
import com.android.systemui.controls.controller.ControlsControllerImpl
@@ -47,7 +47,7 @@
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
import java.text.Collator
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -57,7 +57,7 @@
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
private val listingController: ControlsListingController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val uiController: ControlsUiController
) : ComponentActivity() {
@@ -95,12 +95,12 @@
private var cancelLoadRunnable: Runnable? = null
private var isPagerLoaded = false
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
@@ -363,7 +363,7 @@
super.onStart()
listingController.addCallback(listingCallback)
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, executor)
if (DEBUG) {
Log.d(TAG, "Registered onBackInvokedCallback")
@@ -388,7 +388,7 @@
super.onStop()
listingController.removeCallback(listingCallback)
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
if (DEBUG) {
Log.d(TAG, "Unregistered onBackInvokedCallback")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 47690a7..90bc5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -18,6 +18,7 @@
import android.app.ActivityOptions
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
@@ -33,13 +34,12 @@
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -51,7 +51,7 @@
@Background private val backExecutor: Executor,
private val listingController: ControlsListingController,
private val controlsController: ControlsController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val uiController: ControlsUiController
) : ComponentActivity() {
@@ -62,12 +62,12 @@
}
private var backShouldExit = false
private lateinit var recyclerView: RecyclerView
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = listingController.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
@@ -129,7 +129,7 @@
override fun onStart() {
super.onStart()
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, executor)
recyclerView.alpha = 0.0f
recyclerView.adapter = AppAdapter(
@@ -161,7 +161,7 @@
override fun onStop() {
super.onStop()
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
if (DEBUG) {
Log.d(TAG, "Unregistered onBackInvokedCallback")
@@ -190,7 +190,7 @@
}
override fun onDestroy() {
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
super.onDestroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
index b376455..86bde5c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
@@ -19,6 +19,7 @@
import android.app.AlertDialog
import android.app.Dialog
import android.content.ComponentName
+import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
@@ -32,18 +33,20 @@
import android.widget.TextView
import androidx.activity.ComponentActivity
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.ui.RenderInfo
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
+import java.util.concurrent.Executor
import javax.inject.Inject
open class ControlsRequestDialog @Inject constructor(
+ @Main private val mainExecutor: Executor,
private val controller: ControlsController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val controlsListingController: ControlsListingController
) : ComponentActivity(), DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
@@ -58,12 +61,12 @@
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {}
}
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
@@ -72,7 +75,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, mainExecutor)
controlsListingController.addCallback(callback)
val requestUser = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL)
@@ -118,7 +121,7 @@
override fun onDestroy() {
dialog?.dismiss()
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
controlsListingController.removeCallback(callback)
super.onDestroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 25418c3..0664e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.dagger;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
@@ -69,6 +70,7 @@
import android.os.ServiceManager;
import android.os.UserManager;
import android.os.Vibrator;
+import android.os.storage.StorageManager;
import android.permission.PermissionManager;
import android.safetycenter.SafetyCenterManager;
import android.service.dreams.DreamService;
@@ -110,6 +112,7 @@
/**
* Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
*/
+@SuppressLint("NonInjectedService")
@Module
public class FrameworkServicesModule {
@Provides
@@ -469,7 +472,13 @@
@Provides
@Singleton
- static SubscriptionManager provideSubcriptionManager(Context context) {
+ static StorageManager provideStorageManager(Context context) {
+ return context.getSystemService(StorageManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static SubscriptionManager provideSubscriptionManager(Context context) {
return context.getSystemService(SubscriptionManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index fe89c9a..9e8c0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -21,24 +21,21 @@
import com.android.systemui.dagger.qualifiers.InstrumentationTest;
import com.android.systemui.util.InitializationChecker;
-import javax.inject.Singleton;
-
import dagger.BindsInstance;
-import dagger.Component;
/**
* Base root component for Dagger injection.
*
+ * This class is not actually annotated as a Dagger component, since it is not used directly as one.
+ * Doing so generates unnecessary code bloat.
+ *
* See {@link ReferenceGlobalRootComponent} for the one actually used by AOSP.
*/
-@Singleton
-@Component(modules = {GlobalModule.class})
public interface GlobalRootComponent {
/**
* Builder for a GlobalRootComponent.
*/
- @Component.Builder
interface Builder {
@BindsInstance
Builder context(Context context);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index d0258d3..f64d918 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -24,7 +24,6 @@
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
import android.annotation.AnyThread;
-import android.app.ActivityManager;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorManager;
@@ -50,6 +49,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -99,6 +99,7 @@
private final SecureSettings mSecureSettings;
private final DevicePostureController mDevicePostureController;
private final AuthController mAuthController;
+ private final UserTracker mUserTracker;
private final boolean mScreenOffUdfpsEnabled;
// Sensors
@@ -152,7 +153,8 @@
ProximitySensor proximitySensor,
SecureSettings secureSettings,
AuthController authController,
- DevicePostureController devicePostureController
+ DevicePostureController devicePostureController,
+ UserTracker userTracker
) {
mSensorManager = sensorManager;
mConfig = config;
@@ -170,6 +172,7 @@
mDevicePostureController = devicePostureController;
mDevicePosture = mDevicePostureController.getDevicePosture();
mAuthController = authController;
+ mUserTracker = userTracker;
mUdfpsEnrolled =
mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
@@ -441,7 +444,7 @@
private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
- if (userId != ActivityManager.getCurrentUser()) {
+ if (userId != mUserTracker.getUserId()) {
return;
}
for (TriggerSensor s : mTriggerSensors) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 32cb1c0..0b69b80 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -45,6 +45,7 @@
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -188,7 +189,8 @@
UiEventLogger uiEventLogger,
SessionTracker sessionTracker,
KeyguardStateController keyguardStateController,
- DevicePostureController devicePostureController) {
+ DevicePostureController devicePostureController,
+ UserTracker userTracker) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -200,7 +202,7 @@
mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
- secureSettings, authController, devicePostureController);
+ secureSettings, authController, devicePostureController, userTracker);
mDockManager = dockManager;
mProxCheck = proxCheck;
mDozeLog = dozeLog;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index d8dd6a2..0087c84 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -17,17 +17,19 @@
package com.android.systemui.dreams
import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.view.View
+import android.view.animation.Interpolator
+import androidx.annotation.FloatRange
import androidx.core.animation.doOnEnd
import com.android.systemui.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.statusbar.BlurUtils
-import java.util.function.Consumer
+import com.android.systemui.statusbar.CrossFadeHelper
import javax.inject.Inject
import javax.inject.Named
@@ -40,108 +42,239 @@
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
- private val mDreamInBlurAnimDuration: Int,
- @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+ private val mDreamInBlurAnimDurationMs: Long,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY)
+ private val mDreamInBlurAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
- private val mDreamInComplicationsAnimDuration: Int,
+ private val mDreamInComplicationsAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInTopComplicationsAnimDelay: Int,
+ private val mDreamInTopComplicationsAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInBottomComplicationsAnimDelay: Int
+ private val mDreamInBottomComplicationsAnimDelayMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
+ private val mDreamOutTranslationYDistance: Int,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
+ private val mDreamOutTranslationYDurationMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+ private val mDreamOutTranslationYDelayBottomMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+ private val mDreamOutTranslationYDelayTopMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
+ private val mDreamOutAlphaDelayBottomMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
) {
- var mEntryAnimations: AnimatorSet? = null
+ private var mAnimator: Animator? = null
+
+ /**
+ * Store the current alphas at the various positions. This is so that we may resume an animation
+ * at the current alpha.
+ */
+ private var mCurrentAlphaAtPosition = mutableMapOf<Int, Float>()
+
+ @FloatRange(from = 0.0, to = 1.0) private var mBlurProgress: Float = 0f
/** Starts the dream content and dream overlay entry animations. */
- fun startEntryAnimations(view: View) {
- cancelRunningEntryAnimations()
+ @JvmOverloads
+ fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+ cancelAnimations()
- mEntryAnimations = AnimatorSet()
- mEntryAnimations?.apply {
- playTogether(
- buildDreamInBlurAnimator(view),
- buildDreamInTopComplicationsAnimator(),
- buildDreamInBottomComplicationsAnimator()
- )
- doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
- start()
- }
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ blurAnimator(
+ view = view,
+ from = 1f,
+ to = 0f,
+ durationMs = mDreamInBlurAnimDurationMs,
+ delayMs = mDreamInBlurAnimDelayMs
+ ),
+ alphaAnimator(
+ from = 0f,
+ to = 1f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = mDreamInTopComplicationsAnimDelayMs,
+ position = ComplicationLayoutParams.POSITION_TOP
+ ),
+ alphaAnimator(
+ from = 0f,
+ to = 1f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = mDreamInBottomComplicationsAnimDelayMs,
+ position = ComplicationLayoutParams.POSITION_BOTTOM
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setEntryAnimationsFinished(true)
+ }
+ start()
+ }
+ }
+
+ /** Starts the dream content and dream overlay exit animations. */
+ @JvmOverloads
+ fun startExitAnimations(
+ view: View,
+ doneCallback: () -> Unit,
+ animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
+ ) {
+ cancelAnimations()
+
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ blurAnimator(
+ view = view,
+ // Start the blurring wherever the entry animation ended, in
+ // case it was cancelled early.
+ from = mBlurProgress,
+ to = 1f,
+ durationMs = mDreamOutBlurDurationMs
+ ),
+ translationYAnimator(
+ from = 0f,
+ to = mDreamOutTranslationYDistance.toFloat(),
+ durationMs = mDreamOutTranslationYDurationMs,
+ delayMs = mDreamOutTranslationYDelayBottomMs,
+ position = ComplicationLayoutParams.POSITION_BOTTOM,
+ animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ ),
+ translationYAnimator(
+ from = 0f,
+ to = mDreamOutTranslationYDistance.toFloat(),
+ durationMs = mDreamOutTranslationYDurationMs,
+ delayMs = mDreamOutTranslationYDelayTopMs,
+ position = ComplicationLayoutParams.POSITION_TOP,
+ animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = ComplicationLayoutParams.POSITION_BOTTOM,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamOutAlphaDurationMs,
+ delayMs = mDreamOutAlphaDelayBottomMs,
+ position = ComplicationLayoutParams.POSITION_BOTTOM
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = ComplicationLayoutParams.POSITION_TOP,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamOutAlphaDurationMs,
+ delayMs = mDreamOutAlphaDelayTopMs,
+ position = ComplicationLayoutParams.POSITION_TOP
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setExitAnimationsRunning(false)
+ doneCallback()
+ }
+ start()
+ }
+ mOverlayStateController.setExitAnimationsRunning(true)
}
/** Cancels the dream content and dream overlay animations, if they're currently running. */
- fun cancelRunningEntryAnimations() {
- if (mEntryAnimations?.isRunning == true) {
- mEntryAnimations?.cancel()
- }
- mEntryAnimations = null
+ fun cancelAnimations() {
+ mAnimator =
+ mAnimator?.let {
+ it.cancel()
+ null
+ }
}
- private fun buildDreamInBlurAnimator(view: View): Animator {
- return ValueAnimator.ofFloat(1f, 0f).apply {
- duration = mDreamInBlurAnimDuration.toLong()
- startDelay = mDreamInBlurAnimDelay.toLong()
+ private fun blurAnimator(
+ view: View,
+ from: Float,
+ to: Float,
+ durationMs: Long,
+ delayMs: Long = 0
+ ): Animator {
+ return ValueAnimator.ofFloat(from, to).apply {
+ duration = durationMs
+ startDelay = delayMs
interpolator = Interpolators.LINEAR
addUpdateListener { animator: ValueAnimator ->
+ mBlurProgress = animator.animatedValue as Float
mBlurUtils.applyBlur(
- view.viewRootImpl,
- mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
- false /*opaque*/
+ viewRootImpl = view.viewRootImpl,
+ radius = mBlurUtils.blurRadiusOfRatio(mBlurProgress).toInt(),
+ opaque = false
)
}
}
}
- private fun buildDreamInTopComplicationsAnimator(): Animator {
- return ValueAnimator.ofFloat(0f, 1f).apply {
- duration = mDreamInComplicationsAnimDuration.toLong()
- startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+ private fun alphaAnimator(
+ from: Float,
+ to: Float,
+ durationMs: Long,
+ delayMs: Long,
+ @Position position: Int
+ ): Animator {
+ return ValueAnimator.ofFloat(from, to).apply {
+ duration = durationMs
+ startDelay = delayMs
interpolator = Interpolators.LINEAR
addUpdateListener { va: ValueAnimator ->
- setTopElementsAlpha(va.animatedValue as Float)
+ setElementsAlphaAtPosition(
+ alpha = va.animatedValue as Float,
+ position = position,
+ fadingOut = to < from
+ )
}
}
}
- private fun buildDreamInBottomComplicationsAnimator(): Animator {
- return ValueAnimator.ofFloat(0f, 1f).apply {
- duration = mDreamInComplicationsAnimDuration.toLong()
- startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
- interpolator = Interpolators.LINEAR
+ private fun translationYAnimator(
+ from: Float,
+ to: Float,
+ durationMs: Long,
+ delayMs: Long,
+ @Position position: Int,
+ animInterpolator: Interpolator
+ ): Animator {
+ return ValueAnimator.ofFloat(from, to).apply {
+ duration = durationMs
+ startDelay = delayMs
+ interpolator = animInterpolator
addUpdateListener { va: ValueAnimator ->
- setBottomElementsAlpha(va.animatedValue as Float)
+ setElementsTranslationYAtPosition(va.animatedValue as Float, position)
}
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- mComplicationHostViewController
- .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
- .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
- }
- }
- )
}
}
- /** Sets alpha of top complications and the status bar. */
- private fun setTopElementsAlpha(alpha: Float) {
- mComplicationHostViewController
- .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
- .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
- mStatusBarViewController.setAlpha(alpha)
- }
-
- /** Sets alpha of bottom complications. */
- private fun setBottomElementsAlpha(alpha: Float) {
- mComplicationHostViewController
- .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
- .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
- }
-
- private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
- if (alpha > 0 && view.visibility != View.VISIBLE) {
- view.visibility = View.VISIBLE
+ /** Sets alpha of complications at the specified position. */
+ private fun setElementsAlphaAtPosition(alpha: Float, position: Int, fadingOut: Boolean) {
+ mCurrentAlphaAtPosition[position] = alpha
+ mComplicationHostViewController.getViewsAtPosition(position).forEach { view ->
+ if (fadingOut) {
+ CrossFadeHelper.fadeOut(view, 1 - alpha, /* remap= */ false)
+ } else {
+ CrossFadeHelper.fadeIn(view, alpha, /* remap= */ false)
+ }
}
+ if (position == ComplicationLayoutParams.POSITION_TOP) {
+ mStatusBarViewController.setFadeAmount(alpha, fadingOut)
+ }
+ }
- view.alpha = alpha
+ /** Sets y translation of complications at the specified position. */
+ private fun setElementsTranslationYAtPosition(translationY: Float, position: Int) {
+ mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
+ v.translationY = translationY
+ }
+ if (position == ComplicationLayoutParams.POSITION_TOP) {
+ mStatusBarViewController.setTranslationY(translationY)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 5c6d248..9d7ad30 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -29,6 +29,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
@@ -42,6 +44,7 @@
import com.android.systemui.util.ViewController;
import java.util.Arrays;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -194,7 +197,7 @@
}
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
- mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
+ mDreamOverlayAnimationsController.cancelAnimations();
}
View getContainerView() {
@@ -251,4 +254,17 @@
: aboutToShowBouncerProgress(expansion + 0.03f));
return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction);
}
+
+ /**
+ * Handle the dream waking up and run any necessary animations.
+ *
+ * @param onAnimationEnd Callback to trigger once animations are finished.
+ * @param callbackExecutor Executor to execute the callback on.
+ */
+ public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
+ mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
+ callbackExecutor.execute(onAnimationEnd);
+ return null;
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 8542412..e76d5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -213,6 +213,15 @@
mLifecycleRegistry.setCurrentState(state);
}
+ @Override
+ public void onWakeUp(@NonNull Runnable onCompletedCallback) {
+ mExecutor.execute(() -> {
+ if (mDreamOverlayContainerViewController != null) {
+ mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
+ }
+ });
+ }
+
/**
* Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
* called from the main executing thread. The window attributes closely mirror those that are
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index e80d0be..5f942b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -52,6 +52,7 @@
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
+ public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -211,6 +212,14 @@
return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
}
+ /**
+ * Returns whether the dream content and dream overlay exit animations are running.
+ * @return {@code true} if animations are running, {@code false} otherwise.
+ */
+ public boolean areExitAnimationsRunning() {
+ return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -257,6 +266,15 @@
}
/**
+ * Sets whether dream content and dream overlay exit animations are running.
+ * @param running {@code true} if exit animations are running, {@code false} otherwise.
+ */
+ public void setExitAnimationsRunning(boolean running) {
+ modifyState(running ? OP_SET_STATE : OP_CLEAR_STATE,
+ STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+ }
+
+ /**
* Returns the available complication types.
*/
@Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index d17fbe3..f1bb156 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -37,6 +37,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -217,18 +218,29 @@
}
/**
- * Sets alpha of the dream overlay status bar.
+ * Sets fade of the dream overlay status bar.
*
* No-op if the dream overlay status bar should not be shown.
*/
- protected void setAlpha(float alpha) {
+ protected void setFadeAmount(float fadeAmount, boolean fadingOut) {
updateVisibility();
if (mView.getVisibility() != View.VISIBLE) {
return;
}
- mView.setAlpha(alpha);
+ if (fadingOut) {
+ CrossFadeHelper.fadeOut(mView, 1 - fadeAmount, /* remap= */ false);
+ } else {
+ CrossFadeHelper.fadeIn(mView, fadeAmount, /* remap= */ false);
+ }
+ }
+
+ /**
+ * Sets the y translation of the dream overlay status bar.
+ */
+ public void setTranslationY(float translationY) {
+ mView.setTranslationY(translationY);
}
private boolean shouldShowStatusBar() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 41f5578..b07efdf 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -197,11 +197,11 @@
*/
interface VisibilityController {
/**
- * Called to set the visibility of all shown and future complications.
+ * Called to set the visibility of all shown and future complications. Changes in visibility
+ * will always be animated.
* @param visibility The desired future visibility.
- * @param animate whether the change should be animated.
*/
- void setVisibility(@View.Visibility int visibility, boolean animate);
+ void setVisibility(@View.Visibility int visibility);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 440dcbc..48159ae 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -21,12 +21,9 @@
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Constraints;
@@ -34,6 +31,7 @@
import com.android.systemui.R;
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.touch.TouchInsetManager;
import java.util.ArrayList;
@@ -481,7 +479,6 @@
private final TouchInsetManager.TouchInsetSession mSession;
private final int mFadeInDuration;
private final int mFadeOutDuration;
- private ViewPropertyAnimator mViewPropertyAnimator;
/** */
@Inject
@@ -498,26 +495,16 @@
}
@Override
- public void setVisibility(int visibility, boolean animate) {
- final boolean appearing = visibility == View.VISIBLE;
-
- if (mViewPropertyAnimator != null) {
- mViewPropertyAnimator.cancel();
+ public void setVisibility(int visibility) {
+ if (visibility == View.VISIBLE) {
+ CrossFadeHelper.fadeIn(mLayout, mFadeInDuration, /* delay= */ 0);
+ } else {
+ CrossFadeHelper.fadeOut(
+ mLayout,
+ mFadeOutDuration,
+ /* delay= */ 0,
+ /* endRunnable= */ null);
}
-
- if (appearing) {
- mLayout.setVisibility(View.VISIBLE);
- }
-
- mViewPropertyAnimator = mLayout.animate()
- .alpha(appearing ? 1f : 0f)
- .setDuration(appearing ? mFadeInDuration : mFadeOutDuration)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mLayout.setVisibility(visibility);
- }
- });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index 2b32d34..4fae68d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -38,7 +38,7 @@
POSITION_START,
})
- @interface Position {}
+ public @interface Position {}
/** Align view with the top of parent or bottom of preceding {@link Complication}. */
public static final int POSITION_TOP = 1 << 0;
/** Align view with the bottom of parent or top of preceding {@link Complication}. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index cedd850a..c01cf43 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -33,6 +33,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
@@ -42,6 +43,8 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.util.ViewController;
+import java.util.List;
+
import javax.inject.Inject;
import javax.inject.Named;
@@ -76,16 +79,25 @@
private final DreamOverlayStateController mDreamOverlayStateController;
private final ControlsComponent mControlsComponent;
- private boolean mControlServicesAvailable = false;
+ private boolean mOverlayActive = false;
// Callback for when the home controls service availability changes.
private final ControlsListingController.ControlsListingCallback mControlsCallback =
- serviceInfos -> {
- boolean available = !serviceInfos.isEmpty();
+ services -> updateHomeControlsComplication();
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateComplicationAvailability();
+ private final DreamOverlayStateController.Callback mOverlayStateCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) {
+ return;
+ }
+
+ mOverlayActive = !mOverlayActive;
+
+ if (mOverlayActive) {
+ updateHomeControlsComplication();
+ }
}
};
@@ -102,18 +114,29 @@
public void start() {
mControlsComponent.getControlsListingController().ifPresent(
c -> c.addCallback(mControlsCallback));
+ mDreamOverlayStateController.addCallback(mOverlayStateCallback);
}
- private void updateComplicationAvailability() {
+ private void updateHomeControlsComplication() {
+ mControlsComponent.getControlsListingController().ifPresent(c -> {
+ if (isHomeControlsAvailable(c.getCurrentServices())) {
+ mDreamOverlayStateController.addComplication(mComplication);
+ } else {
+ mDreamOverlayStateController.removeComplication(mComplication);
+ }
+ });
+ }
+
+ private boolean isHomeControlsAvailable(List<ControlsServiceInfo> controlsServices) {
+ if (controlsServices.isEmpty()) {
+ return false;
+ }
+
final boolean hasFavorites = mControlsComponent.getControlsController()
.map(c -> !c.getFavorites().isEmpty())
.orElse(false);
- if (!hasFavorites || !mControlServicesAvailable
- || mControlsComponent.getVisibility() == UNAVAILABLE) {
- mDreamOverlayStateController.removeComplication(mComplication);
- } else {
- mDreamOverlayStateController.addComplication(mComplication);
- }
+ final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility();
+ return hasFavorites && visibility != UNAVAILABLE;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index c9fecc9..09cc7c5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -41,6 +41,7 @@
public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
+ public static final String COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay";
/**
* Generates a {@link ConstraintLayout}, which can host
@@ -75,6 +76,16 @@
}
/**
+ * Provides the delay to wait for before fading out complications.
+ */
+ @Provides
+ @Named(COMPLICATIONS_FADE_OUT_DELAY)
+ @DreamOverlayComponent.DreamOverlayScope
+ static int providesComplicationsFadeOutDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.complicationFadeOutDelayMs);
+ }
+
+ /**
* Provides the fade in duration for complications.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index f9dca08..101f4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -44,7 +44,7 @@
DreamOverlayComponent.class,
})
public interface DreamModule {
- String DREAM_ONLY_ENABLED_FOR_SYSTEM_USER = "dream_only_enabled_for_system_user";
+ String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
String DREAM_SUPPORTED = "dream_supported";
@@ -70,10 +70,10 @@
/** */
@Provides
- @Named(DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
- static boolean providesDreamOnlyEnabledForSystemUser(@Main Resources resources) {
+ @Named(DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+ static boolean providesDreamOnlyEnabledForDockUser(@Main Resources resources) {
return resources.getBoolean(
- com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser);
+ com.android.internal.R.bool.config_dreamsOnlyEnabledForDockUser);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index cb012fa..ed0e1d9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -55,6 +55,22 @@
"dream_in_top_complications_anim_delay";
public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
"dream_in_bottom_complications_anim_delay";
+ public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
+ "dream_out_complications_translation_y";
+ public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
+ "dream_out_complications_translation_y_duration";
+ public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM =
+ "dream_out_complications_translation_y_delay_bottom";
+ public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP =
+ "dream_out_complications_translation_y_delay_top";
+ public static final String DREAM_OUT_ALPHA_DURATION =
+ "dream_out_complications_alpha_duration";
+ public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM =
+ "dream_out_complications_alpha_delay_bottom";
+ public static final String DREAM_OUT_ALPHA_DELAY_TOP =
+ "dream_out_complications_alpha_delay_top";
+ public static final String DREAM_OUT_BLUR_DURATION =
+ "dream_out_blur_duration";
/** */
@Provides
@@ -127,8 +143,8 @@
*/
@Provides
@Named(DREAM_IN_BLUR_ANIMATION_DURATION)
- static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+ static long providesDreamInBlurAnimationDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
}
/**
@@ -136,8 +152,8 @@
*/
@Provides
@Named(DREAM_IN_BLUR_ANIMATION_DELAY)
- static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+ static long providesDreamInBlurAnimationDelay(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
}
/**
@@ -145,8 +161,8 @@
*/
@Provides
@Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
- static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
+ static long providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
}
/**
@@ -154,8 +170,8 @@
*/
@Provides
@Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+ static long providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
}
/**
@@ -163,8 +179,69 @@
*/
@Provides
@Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ static long providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+ return (long) resources.getInteger(
+ R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ }
+
+ /**
+ * Provides the number of pixels to translate complications when waking up from dream.
+ */
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DISTANCE)
+ @DreamOverlayComponent.DreamOverlayScope
+ static int providesDreamOutComplicationsTranslationY(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) {
+ return (long) resources.getInteger(
+ R.integer.config_dreamOverlayOutTranslationYDelayBottomMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_ALPHA_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_ALPHA_DELAY_BOTTOM)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_ALPHA_DELAY_TOP)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_BLUR_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutBlurDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs);
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
index 3087cdf..e276e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
@@ -16,22 +16,26 @@
package com.android.systemui.dreams.touch;
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DELAY;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_RESTORE_TIMEOUT;
-import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayDeque;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -49,33 +53,58 @@
private static final String TAG = "HideComplicationHandler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final Complication.VisibilityController mVisibilityController;
private final int mRestoreTimeout;
+ private final int mFadeOutDelay;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final Handler mHandler;
- private final Executor mExecutor;
+ private final DelayableExecutor mExecutor;
+ private final DreamOverlayStateController mOverlayStateController;
private final TouchInsetManager mTouchInsetManager;
+ private final Complication.VisibilityController mVisibilityController;
+ private boolean mHidden = false;
+ @Nullable
+ private Runnable mHiddenCallback;
+ private final ArrayDeque<Runnable> mCancelCallbacks = new ArrayDeque<>();
+
private final Runnable mRestoreComplications = new Runnable() {
@Override
public void run() {
- mVisibilityController.setVisibility(View.VISIBLE, true);
+ mVisibilityController.setVisibility(View.VISIBLE);
+ mHidden = false;
+ }
+ };
+
+ private final Runnable mHideComplications = new Runnable() {
+ @Override
+ public void run() {
+ if (mOverlayStateController.areExitAnimationsRunning()) {
+ // Avoid interfering with the exit animations.
+ return;
+ }
+ mVisibilityController.setVisibility(View.INVISIBLE);
+ mHidden = true;
+ if (mHiddenCallback != null) {
+ mHiddenCallback.run();
+ mHiddenCallback = null;
+ }
}
};
@Inject
HideComplicationTouchHandler(Complication.VisibilityController visibilityController,
@Named(COMPLICATIONS_RESTORE_TIMEOUT) int restoreTimeout,
+ @Named(COMPLICATIONS_FADE_OUT_DELAY) int fadeOutDelay,
TouchInsetManager touchInsetManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- @Main Executor executor,
- @Main Handler handler) {
+ @Main DelayableExecutor executor,
+ DreamOverlayStateController overlayStateController) {
mVisibilityController = visibilityController;
mRestoreTimeout = restoreTimeout;
+ mFadeOutDelay = fadeOutDelay;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- mHandler = handler;
mTouchInsetManager = touchInsetManager;
mExecutor = executor;
+ mOverlayStateController = overlayStateController;
}
@Override
@@ -87,7 +116,8 @@
final boolean bouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
// If other sessions are interested in this touch, do not fade out elements.
- if (session.getActiveSessionCount() > 1 || bouncerShowing) {
+ if (session.getActiveSessionCount() > 1 || bouncerShowing
+ || mOverlayStateController.areExitAnimationsRunning()) {
if (DEBUG) {
Log.d(TAG, "not fading. Active session count: " + session.getActiveSessionCount()
+ ". Bouncer showing: " + bouncerShowing);
@@ -115,8 +145,11 @@
touchCheck.addListener(() -> {
try {
if (!touchCheck.get()) {
- mHandler.removeCallbacks(mRestoreComplications);
- mVisibilityController.setVisibility(View.INVISIBLE, true);
+ // Cancel all pending callbacks.
+ while (!mCancelCallbacks.isEmpty()) mCancelCallbacks.pop().run();
+ mCancelCallbacks.add(
+ mExecutor.executeDelayed(
+ mHideComplications, mFadeOutDelay));
} else {
// If a touch occurred inside the dream overlay touch insets, do not
// handle the touch.
@@ -130,7 +163,23 @@
|| motionEvent.getAction() == MotionEvent.ACTION_UP) {
// End session and initiate delayed reappearance of the complications.
session.pop();
- mHandler.postDelayed(mRestoreComplications, mRestoreTimeout);
+ runAfterHidden(() -> mCancelCallbacks.add(
+ mExecutor.executeDelayed(mRestoreComplications,
+ mRestoreTimeout)));
+ }
+ });
+ }
+
+ /**
+ * Triggers a runnable after complications have been hidden. Will override any previously set
+ * runnable currently waiting for hide to happen.
+ */
+ private void runAfterHidden(Runnable runnable) {
+ mExecutor.execute(() -> {
+ if (mHidden) {
+ runnable.run();
+ } else {
+ mHiddenCallback = runnable;
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c4cc338..784e92d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -79,10 +79,18 @@
val NOTIFICATION_GROUP_CORNER =
unreleasedFlag(116, "notification_group_corner", teamfood = true)
+ // TODO(b/259217907)
+ @JvmField
+ val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
+ unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
+
// TODO(b/257506350): Tracking Bug
val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
- // next id: 118
+ // TODO(b/257315550): Tracking Bug
+ val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
+
+ // next id: 119
// 200 - keyguard/lockscreen
// ** Flag retired **
@@ -112,28 +120,6 @@
@JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer")
/**
- * Whether the user interactor and repository should use `UserSwitcherController`.
- *
- * If this is `false`, the interactor and repo skip the controller and directly access the
- * framework APIs.
- */
- // TODO(b/254513286): Tracking Bug
- val USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
- unreleasedFlag(210, "user_interactor_and_repo_use_controller")
-
- /**
- * Whether `UserSwitcherController` should use the user interactor.
- *
- * When this is `true`, the controller does not directly access framework APIs. Instead, it goes
- * through the interactor.
- *
- * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it
- * would created a cycle between controller -> interactor -> controller.
- */
- // TODO(b/254513102): Tracking Bug
- val USER_CONTROLLER_USES_INTERACTOR = releasedFlag(211, "user_controller_uses_interactor")
-
- /**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
* the digits when the clock moves.
*/
@@ -207,9 +193,7 @@
@JvmField val NEW_FOOTER_ACTIONS = releasedFlag(507, "new_footer_actions")
// TODO(b/244064524): Tracking Bug
- @JvmField
- val QS_SECONDARY_DATA_SUB_INFO =
- unreleasedFlag(508, "qs_secondary_data_sub_info", teamfood = true)
+ @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info")
// 600- status bar
// TODO(b/254513246): Tracking Bug
@@ -239,6 +223,9 @@
// TODO(b/256613548): Tracking Bug
val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
+ // TODO(b/256623670): Tracking Bug
+ @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
@@ -291,6 +278,8 @@
// TODO(b/254513168): Tracking Bug
@JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
+ @JvmField val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media")
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -301,7 +290,7 @@
@Keep
@JvmField
val WM_ENABLE_SHELL_TRANSITIONS =
- sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = true)
+ sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = false)
// TODO(b/254513207): Tracking Bug
@Keep
@@ -380,7 +369,8 @@
// TODO(b/254513155): Tracking Bug
@JvmField
- val SCREENSHOT_WORK_PROFILE_POLICY = unreleasedFlag(1301, "screenshot_work_profile_policy")
+ val SCREENSHOT_WORK_PROFILE_POLICY =
+ unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
index 18fb423..d9bcb50 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
@@ -50,13 +50,12 @@
@Override
public void accept(T extension) {
- try {
- Fragment.class.cast(extension);
+ if (Fragment.class.isInstance(extension)) {
mFragmentHostManager.getExtensionManager().setCurrentExtension(mId, mTag,
mOldClass, extension.getClass().getName(), mExtension.getContext());
mOldClass = extension.getClass().getName();
- } catch (ClassCastException e) {
- Log.e(TAG, extension.getClass().getName() + " must be a Fragment", e);
+ } else {
+ Log.e(TAG, extension.getClass().getName() + " must be a Fragment");
}
mExtension.clearItem(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3ef5499..db2cd91 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -125,6 +125,7 @@
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -201,6 +202,7 @@
protected final SecureSettings mSecureSettings;
protected final Resources mResources;
private final ConfigurationController mConfigurationController;
+ private final UserTracker mUserTracker;
private final UserManager mUserManager;
private final TrustManager mTrustManager;
private final IActivityManager mIActivityManager;
@@ -339,6 +341,7 @@
@NonNull VibratorHelper vibrator,
@Main Resources resources,
ConfigurationController configurationController,
+ UserTracker userTracker,
KeyguardStateController keyguardStateController,
UserManager userManager,
TrustManager trustManager,
@@ -370,6 +373,7 @@
mSecureSettings = secureSettings;
mResources = resources;
mConfigurationController = configurationController;
+ mUserTracker = userTracker;
mUserManager = userManager;
mTrustManager = trustManager;
mIActivityManager = iActivityManager;
@@ -1198,11 +1202,7 @@
}
protected UserInfo getCurrentUser() {
- try {
- return mIActivityManager.getCurrentUser();
- } catch (RemoteException re) {
- return null;
- }
+ return mUserTracker.getUserInfo();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 5d564f7..bafd2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard;
import android.annotation.AnyThread;
-import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -52,6 +51,7 @@
import com.android.systemui.R;
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -140,6 +140,8 @@
public KeyguardBypassController mKeyguardBypassController;
@Inject
public KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Inject
+ UserTracker mUserTracker;
private CharSequence mMediaTitle;
private CharSequence mMediaArtist;
protected boolean mDozing;
@@ -355,7 +357,7 @@
synchronized (this) {
if (withinNHoursLocked(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
- ActivityManager.getCurrentUser()) ? "HH:mm" : "h:mm";
+ mUserTracker.getUserId()) ? "HH:mm" : "h:mm";
mNextAlarm = android.text.format.DateFormat.format(pattern,
mNextAlarmInfo.getTriggerTime()).toString();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d52efab..3d976d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -36,7 +36,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -124,6 +123,7 @@
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -263,6 +263,7 @@
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
+ private final UserTracker mUserTracker;
private final SysuiStatusBarStateController mStatusBarStateController;
private final Executor mUiBgExecutor;
private final ScreenOffAnimationController mScreenOffAnimationController;
@@ -409,6 +410,11 @@
private final int mDreamOpenAnimationDuration;
/**
+ * The duration in milliseconds of the dream close animation.
+ */
+ private final int mDreamCloseAnimationDuration;
+
+ /**
* The animation used for hiding keyguard. This is used to fetch the animation timings if
* WindowManager is not providing us with them.
*/
@@ -715,7 +721,7 @@
@Override
public void keyguardDone(boolean strongAuth, int targetUserId) {
- if (targetUserId != ActivityManager.getCurrentUser()) {
+ if (targetUserId != mUserTracker.getUserId()) {
return;
}
if (DEBUG) Log.d(TAG, "keyguardDone");
@@ -738,7 +744,7 @@
public void keyguardDonePending(boolean strongAuth, int targetUserId) {
Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
if (DEBUG) Log.d(TAG, "keyguardDonePending");
- if (targetUserId != ActivityManager.getCurrentUser()) {
+ if (targetUserId != mUserTracker.getUserId()) {
Trace.endSection();
return;
}
@@ -1055,7 +1061,8 @@
}
mUnoccludeAnimator = ValueAnimator.ofFloat(1f, 0f);
- mUnoccludeAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
+ mUnoccludeAnimator.setDuration(isDream ? mDreamCloseAnimationDuration
+ : UNOCCLUDE_ANIMATION_DURATION);
mUnoccludeAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE);
mUnoccludeAnimator.addUpdateListener(
animation -> {
@@ -1131,6 +1138,7 @@
*/
public KeyguardViewMediator(
Context context,
+ UserTracker userTracker,
FalsingCollector falsingCollector,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
@@ -1156,6 +1164,7 @@
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
mContext = context;
+ mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
mLockPatternUtils = lockPatternUtils;
mBroadcastDispatcher = broadcastDispatcher;
@@ -1205,6 +1214,8 @@
mDreamOpenAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamOpenAnimationDuration);
+ mDreamCloseAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_dreamCloseAnimationDuration);
}
public void userActivity() {
@@ -1234,7 +1245,7 @@
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
+ KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
// Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
// is disabled.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 78a7c9e..ef3c443 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -47,6 +47,7 @@
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -86,6 +87,7 @@
@SysUISingleton
public static KeyguardViewMediator newKeyguardViewMediator(
Context context,
+ UserTracker userTracker,
FalsingCollector falsingCollector,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
@@ -114,6 +116,7 @@
Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
return new KeyguardViewMediator(
context,
+ userTracker,
falsingCollector,
lockPatternUtils,
broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 783f752..9a90fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -23,7 +23,10 @@
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer
import javax.inject.Inject
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
/** Encapsulates app state for the lock screen primary and alternate bouncer. */
@@ -68,8 +71,12 @@
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
/** Determines if user is already unlocked */
val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
- private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
- val showMessage = _showMessage.asStateFlow()
+ private val _showMessage =
+ MutableSharedFlow<BouncerShowMessageModel?>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+ val showMessage = _showMessage.asSharedFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
val bouncerPromptReason: Int
@@ -118,7 +125,7 @@
}
fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
- _showMessage.value = bouncerShowMessageModel
+ _showMessage.tryEmit(bouncerShowMessageModel)
}
fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index fcd653b..910cdf2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -117,7 +117,6 @@
@JvmOverloads
fun show(isScrimmed: Boolean) {
// Reset some states as we show the bouncer.
- repository.setShowMessage(null)
repository.setOnScreenTurnedOff(false)
repository.setKeyguardAuthenticated(null)
repository.setPrimaryHide(false)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
index a7f1b95..a8f39fa9a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
@@ -26,7 +26,8 @@
import androidx.constraintlayout.widget.Barrier
import com.android.systemui.R
import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "MediaViewHolder"
@@ -38,6 +39,8 @@
// Player information
val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view)
+ val turbulenceNoiseView =
+ itemView.requireViewById<TurbulenceNoiseView>(R.id.turbulence_noise_view)
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 45b319b..cf71d67 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -20,13 +20,12 @@
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.time.SystemClock
import java.util.SortedMap
@@ -62,14 +61,13 @@
@Inject
constructor(
private val context: Context,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val broadcastSender: BroadcastSender,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@Main private val executor: Executor,
private val systemClock: SystemClock,
private val logger: MediaUiEventLogger
) : MediaDataManager.Listener {
- private val userTracker: CurrentUserTracker
private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
internal val listeners: Set<MediaDataManager.Listener>
get() = _listeners.toSet()
@@ -81,15 +79,15 @@
private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
private var reactivatedKey: String? = null
- init {
- userTracker =
- object : CurrentUserTracker(broadcastDispatcher) {
- override fun onUserSwitched(newUserId: Int) {
- // Post this so we can be sure lockscreenUserManager already got the broadcast
- executor.execute { handleUserSwitched(newUserId) }
- }
+ private val userTrackerCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ handleUserSwitched(newUser)
}
- userTracker.startTracking()
+ }
+
+ init {
+ userTracker.addCallback(userTrackerCallback, executor)
}
override fun onMediaDataLoaded(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 918417f..93be6a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -29,7 +29,8 @@
import com.android.settingslib.Utils
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.monet.ColorScheme
-import com.android.systemui.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
/**
* A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -102,13 +103,21 @@
private val context: Context,
private val mediaViewHolder: MediaViewHolder,
private val multiRippleController: MultiRippleController,
+ private val turbulenceNoiseController: TurbulenceNoiseController,
animatingColorTransitionFactory: AnimatingColorTransitionFactory
) {
constructor(
context: Context,
mediaViewHolder: MediaViewHolder,
multiRippleController: MultiRippleController,
- ) : this(context, mediaViewHolder, multiRippleController, ::AnimatingColorTransition)
+ turbulenceNoiseController: TurbulenceNoiseController
+ ) : this(
+ context,
+ mediaViewHolder,
+ multiRippleController,
+ turbulenceNoiseController,
+ ::AnimatingColorTransition
+ )
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
val surfaceColor =
@@ -129,6 +138,7 @@
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
multiRippleController.updateColor(accentPrimary)
+ turbulenceNoiseController.updateNoiseColor(accentPrimary)
}
val accentSecondary =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 215fa03..21e64e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -31,6 +31,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
@@ -64,6 +65,7 @@
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.settingslib.widget.AdaptiveIcon;
@@ -97,13 +99,16 @@
import com.android.systemui.monet.Style;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.ripple.MultiRippleController;
-import com.android.systemui.ripple.RippleAnimation;
-import com.android.systemui.ripple.RippleAnimationConfig;
-import com.android.systemui.ripple.RippleShader;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimation;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock;
@@ -216,7 +221,9 @@
private boolean mShowBroadcastDialogButton = false;
private String mSwitchBroadcastApp;
private MultiRippleController mMultiRippleController;
+ private TurbulenceNoiseController mTurbulenceNoiseController;
private FeatureFlags mFeatureFlags;
+ private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig = null;
/**
* Initialize a new control panel
@@ -394,9 +401,20 @@
AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
- mMultiRippleController = new MultiRippleController(vh.getMultiRippleView());
+ MultiRippleView multiRippleView = vh.getMultiRippleView();
+ mMultiRippleController = new MultiRippleController(multiRippleView);
+ mTurbulenceNoiseController = new TurbulenceNoiseController(vh.getTurbulenceNoiseView());
+ multiRippleView.addRipplesFinishedListener(
+ () -> {
+ if (mTurbulenceNoiseAnimationConfig == null) {
+ mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation();
+ }
+ // Color will be correctly updated in ColorSchemeTransition.
+ mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
+ }
+ );
mColorSchemeTransition = new ColorSchemeTransition(
- mContext, mMediaViewHolder, mMultiRippleController);
+ mContext, mMediaViewHolder, mMultiRippleController, mTurbulenceNoiseController);
mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
}
@@ -571,7 +589,10 @@
seamlessView.setContentDescription(deviceString);
seamlessView.setOnClickListener(
v -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ if (mFalsingManager.isFalseTap(
+ mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+ ? FalsingManager.MODERATE_PENALTY :
+ FalsingManager.LOW_PENALTY)) {
return;
}
@@ -994,7 +1015,10 @@
} else {
button.setEnabled(true);
button.setOnClickListener(v -> {
- if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ if (!mFalsingManager.isFalseTap(
+ mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+ ? FalsingManager.MODERATE_PENALTY :
+ FalsingManager.LOW_PENALTY)) {
mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
action.run();
@@ -1027,7 +1051,7 @@
/* maxWidth= */ maxSize,
/* maxHeight= */ maxSize,
/* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
- mColorSchemeTransition.getAccentPrimary().getTargetColor(),
+ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
/* opacity= */ 100,
/* shouldFillRipple= */ false,
/* sparkleStrength= */ 0f,
@@ -1036,6 +1060,26 @@
);
}
+ private TurbulenceNoiseAnimationConfig createLingeringNoiseAnimation() {
+ return new TurbulenceNoiseAnimationConfig(
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+ TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+ /* noiseMoveSpeedX= */ 0f,
+ /* noiseMoveSpeedY= */ 0f,
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
+ /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
+ // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
+ // Thus, set the background color with alpha 0.
+ /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
+ TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+ /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
+ /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_DURATION_IN_MILLIS,
+ this.getContext().getResources().getDisplayMetrics().density,
+ BlendMode.PLUS,
+ /* onAnimationEnd= */ null
+ );
+ }
private void clearButton(final ImageButton button) {
button.setImageDrawable(null);
button.setContentDescription(null);
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index e354a03..1ea2025 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -18,8 +18,8 @@
import android.content.Context
import android.util.AttributeSet
-import com.android.systemui.ripple.RippleShader
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleView
/**
* An expanding ripple effect for the media tap-to-transfer receiver chip.
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index e8b49cd..7a77c47 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -16,19 +16,19 @@
package com.android.systemui.mediaprojection.appselector.data
-import android.app.ActivityManager
import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.recents.RecentTasks
import com.android.wm.shell.util.GroupedRecentTaskInfo
import java.util.Optional
+import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
-import java.util.concurrent.Executor
interface RecentTaskListProvider {
/** Loads recent tasks, the returned task list is from the most-recent to least-recent order */
@@ -40,7 +40,8 @@
constructor(
@Background private val coroutineDispatcher: CoroutineDispatcher,
@Background private val backgroundExecutor: Executor,
- private val recentTasks: Optional<RecentTasks>
+ private val recentTasks: Optional<RecentTasks>,
+ private val userTracker: UserTracker
) : RecentTaskListProvider {
private val recents by lazy { recentTasks.getOrNull() }
@@ -67,10 +68,8 @@
getRecentTasks(
Integer.MAX_VALUE,
RECENT_IGNORE_UNAVAILABLE,
- ActivityManager.getCurrentUser(),
+ userTracker.userId,
backgroundExecutor
- ) { tasks ->
- continuation.resume(tasks)
- }
+ ) { tasks -> continuation.resume(tasks) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 59bb2278e..2a7704f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -45,11 +45,10 @@
import com.android.systemui.shared.system.QuickStepContract;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.Objects;
-public class NavigationBarInflaterView extends FrameLayout
- implements NavigationModeController.ModeChangedListener {
-
+public class NavigationBarInflaterView extends FrameLayout {
private static final String TAG = "NavBarInflater";
public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
@@ -83,6 +82,24 @@
private static final String ABSOLUTE_SUFFIX = "A";
private static final String ABSOLUTE_VERTICAL_CENTERED_SUFFIX = "C";
+ private static class Listener implements NavigationModeController.ModeChangedListener {
+ private final WeakReference<NavigationBarInflaterView> mSelf;
+
+ Listener(NavigationBarInflaterView self) {
+ mSelf = new WeakReference<>(self);
+ }
+
+ @Override
+ public void onNavigationModeChanged(int mode) {
+ NavigationBarInflaterView self = mSelf.get();
+ if (self != null) {
+ self.onNavigationModeChanged(mode);
+ }
+ }
+ }
+
+ private final Listener mListener;
+
protected LayoutInflater mLayoutInflater;
protected LayoutInflater mLandscapeInflater;
@@ -106,7 +123,8 @@
super(context, attrs);
createInflaters();
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
- mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
+ mListener = new Listener(this);
+ mNavBarMode = Dependency.get(NavigationModeController.class).addListener(mListener);
}
@VisibleForTesting
@@ -146,14 +164,13 @@
return getContext().getString(defaultResource);
}
- @Override
- public void onNavigationModeChanged(int mode) {
+ private void onNavigationModeChanged(int mode) {
mNavBarMode = mode;
}
@Override
protected void onDetachedFromWindow() {
- Dependency.get(NavigationModeController.class).removeListener(this);
+ Dependency.get(NavigationModeController.class).removeListener(mListener);
super.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 7964d16..4e3831c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -59,7 +59,6 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -71,7 +70,7 @@
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat;
@@ -102,8 +101,8 @@
/**
* Utility class to handle edge swipes for back gesture
*/
-public class EdgeBackGestureHandler extends CurrentUserTracker
- implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
+public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin>,
+ ProtoTraceable<SystemUiTraceProto> {
private static final String TAG = "EdgeBackGestureHandler";
private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
@@ -172,6 +171,7 @@
private final Context mContext;
+ private final UserTracker mUserTracker;
private final OverviewProxyService mOverviewProxyService;
private final SysUiState mSysUiState;
private Runnable mStateChangeCallback;
@@ -321,6 +321,15 @@
private final Consumer<Boolean> mOnIsInPipStateChangedListener =
(isInPip) -> mIsInPip = isInPip;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ updateIsEnabled();
+ updateCurrentUserResources();
+ }
+ };
+
EdgeBackGestureHandler(
Context context,
OverviewProxyService overviewProxyService,
@@ -328,7 +337,7 @@
PluginManager pluginManager,
@Main Executor executor,
@Background Executor backgroundExecutor,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
@@ -340,11 +349,11 @@
Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
FeatureFlags featureFlags) {
- super(broadcastDispatcher);
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = executor;
mBackgroundExecutor = backgroundExecutor;
+ mUserTracker = userTracker;
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -463,12 +472,6 @@
}
}
- @Override
- public void onUserSwitched(int newUserId) {
- updateIsEnabled();
- updateCurrentUserResources();
- }
-
/**
* @see NavigationBarView#onAttachedToWindow()
*/
@@ -478,7 +481,7 @@
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
updateIsEnabled();
- startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
}
/**
@@ -490,7 +493,7 @@
mOverviewProxyService.removeCallback(mQuickSwitchListener);
mSysUiState.removeCallback(mSysUiStateCallback);
updateIsEnabled();
- stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
}
/**
@@ -1093,7 +1096,7 @@
private final PluginManager mPluginManager;
private final Executor mExecutor;
private final Executor mBackgroundExecutor;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
private final BackPanelController.Factory mBackPanelControllerFactory;
@@ -1113,7 +1116,7 @@
PluginManager pluginManager,
@Main Executor executor,
@Background Executor backgroundExecutor,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
@@ -1131,7 +1134,7 @@
mPluginManager = pluginManager;
mExecutor = executor;
mBackgroundExecutor = backgroundExecutor;
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
mBackPanelControllerFactory = backPanelControllerFactory;
@@ -1154,7 +1157,7 @@
mPluginManager,
mExecutor,
mBackgroundExecutor,
- mBroadcastDispatcher,
+ mUserTracker,
mProtoTracer,
mNavigationModeController,
mBackPanelControllerFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index be82b1f..67e9664 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -1096,7 +1096,7 @@
Pair<Integer, Integer> first = emojiIndices.get(i - 1);
// Check if second emoji starts right after first starts
- if (second.first == first.second) {
+ if (Objects.equals(second.first, first.second)) {
// Check if emojis in sequence are the same
if (Objects.equals(emojiTexts.get(i), emojiTexts.get(i - 1))) {
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 0697133..f92bbf7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -364,13 +364,18 @@
private void distributeTiles() {
emptyAndInflateOrRemovePages();
- final int tileCount = mPages.get(0).maxTiles();
- if (DEBUG) Log.d(TAG, "Distributing tiles");
+ final int tilesPerPageCount = mPages.get(0).maxTiles();
int index = 0;
- final int NT = mTiles.size();
- for (int i = 0; i < NT; i++) {
+ final int totalTilesCount = mTiles.size();
+ if (DEBUG) {
+ Log.d(TAG, "Distributing tiles: "
+ + "[tilesPerPageCount=" + tilesPerPageCount + "]"
+ + "[totalTilesCount=" + totalTilesCount + "]"
+ );
+ }
+ for (int i = 0; i < totalTilesCount; i++) {
TileRecord tile = mTiles.get(i);
- if (mPages.get(index).mRecords.size() == tileCount) index++;
+ if (mPages.get(index).mRecords.size() == tilesPerPageCount) index++;
if (DEBUG) {
Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
+ index);
@@ -577,8 +582,8 @@
});
setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
int dx = getWidth() * lastPageNumber;
- mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
- REVEAL_SCROLL_DURATION_MILLIS);
+ mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
+ REVEAL_SCROLL_DURATION_MILLIS);
postInvalidateOnAnimation();
}
@@ -738,6 +743,7 @@
public interface PageListener {
int INVALID_PAGE = -1;
+
void onPageChanged(boolean isFirst, int pageNumber);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 6b0abd4..7794fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -16,7 +16,6 @@
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.database.ContentObserver;
import android.os.Handler;
@@ -47,10 +46,6 @@
this(settingsProxy, handler, settingName, userId, 0);
}
- public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) {
- this(settingsProxy, handler, settingName, ActivityManager.getCurrentUser());
- }
-
public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
int userId, int defaultValue) {
super(handler);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3d00dd4..7ee4047 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -123,7 +123,6 @@
public boolean updateResources() {
final Resources res = mContext.getResources();
mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
- updateColumns();
mMaxCellHeight = mContext.getResources().getDimensionPixelSize(mCellHeightResId);
mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
mSidePadding = useSidePadding() ? mCellMarginHorizontal / 2 : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index cf10c79..79fcc7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -82,12 +82,12 @@
DefaultItemAnimator animator = new DefaultItemAnimator();
animator.setMoveDuration(TileAdapter.MOVE_DURATION);
mRecyclerView.setItemAnimator(animator);
+
+ updateTransparentViewHeight();
}
void updateResources() {
- LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
- lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
- mTransparentView.setLayoutParams(lp);
+ updateTransparentViewHeight();
mRecyclerView.getAdapter().notifyItemChanged(0);
}
@@ -236,4 +236,10 @@
public boolean isOpening() {
return mOpening;
}
+
+ private void updateTransparentViewHeight() {
+ LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
+ lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
+ mTransparentView.setLayoutParams(lp);
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 86d4fa3..033dbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -48,6 +48,7 @@
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.settings.GlobalSettings;
import javax.inject.Inject;
@@ -74,14 +75,16 @@
QSLogger qsLogger,
BroadcastDispatcher broadcastDispatcher,
Lazy<ConnectivityManager> lazyConnectivityManager,
- GlobalSettings globalSettings
+ GlobalSettings globalSettings,
+ UserTracker userTracker
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mBroadcastDispatcher = broadcastDispatcher;
mLazyConnectivityManager = lazyConnectivityManager;
- mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) {
+ mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON,
+ userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
// mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index bebd580..5bc209a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -70,7 +70,7 @@
private final SettingObserver mDreamSettingObserver;
private final UserTracker mUserTracker;
private final boolean mDreamSupported;
- private final boolean mDreamOnlyEnabledForSystemUser;
+ private final boolean mDreamOnlyEnabledForDockUser;
private boolean mIsDocked = false;
@@ -100,22 +100,22 @@
BroadcastDispatcher broadcastDispatcher,
UserTracker userTracker,
@Named(DreamModule.DREAM_SUPPORTED) boolean dreamSupported,
- @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
- boolean dreamOnlyEnabledForSystemUser
+ @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+ boolean dreamOnlyEnabledForDockUser
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mDreamManager = dreamManager;
mBroadcastDispatcher = broadcastDispatcher;
mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler,
- Settings.Secure.SCREENSAVER_ENABLED) {
+ Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
refreshState();
}
};
mDreamSettingObserver = new SettingObserver(secureSettings, mHandler,
- Settings.Secure.SCREENSAVER_COMPONENTS) {
+ Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
refreshState();
@@ -123,7 +123,7 @@
};
mUserTracker = userTracker;
mDreamSupported = dreamSupported;
- mDreamOnlyEnabledForSystemUser = dreamOnlyEnabledForSystemUser;
+ mDreamOnlyEnabledForDockUser = dreamOnlyEnabledForDockUser;
}
@Override
@@ -203,7 +203,8 @@
// For now, restrict to debug users.
return Build.isDebuggable()
&& mDreamSupported
- && (!mDreamOnlyEnabledForSystemUser || mUserTracker.getUserHandle().isSystem());
+ // TODO(b/257333623): Allow the Dock User to be non-SystemUser user in HSUM.
+ && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserHandle().isSystem());
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index a895d72..9743c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -249,15 +249,7 @@
mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
mInternetDialogTitle.setText(getDialogTitleText());
mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
-
- TypedArray typedArray = mContext.obtainStyledAttributes(
- new int[]{android.R.attr.selectableItemBackground});
- try {
- mBackgroundOff = typedArray.getDrawable(0 /* index */);
- } finally {
- typedArray.recycle();
- }
-
+ mBackgroundOff = mContext.getDrawable(R.drawable.internet_dialog_selected_effect);
setOnClickListener();
mTurnWifiOnLayout.setBackground(null);
mAirplaneModeButton.setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ba97297..547b496 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -78,8 +78,8 @@
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -90,12 +90,11 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -108,20 +107,19 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
import dagger.Lazy;
-
/**
* Class to send information from overview to launcher with a binder.
*/
@SysUISingleton
-public class OverviewProxyService extends CurrentUserTracker implements
- CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
- Dumpable {
+public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
+ NavigationModeController.ModeChangedListener, Dumpable {
private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
@@ -133,6 +131,7 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
+ private final Executor mMainExecutor;
private final ShellInterface mShellInterface;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private SysUiState mSysUiState;
@@ -145,6 +144,7 @@
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
private final CommandQueue mCommandQueue;
+ private final UserTracker mUserTracker;
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
private final UiEventLogger mUiEventLogger;
@@ -417,7 +417,7 @@
return;
}
- mCurrentBoundedUserId = getCurrentUserId();
+ mCurrentBoundedUserId = mUserTracker.getUserId();
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
Bundle params = new Bundle();
@@ -498,34 +498,44 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mConnectionBackoffAttempts = 0;
+ internalConnectToCurrentUser();
+ }
+ };
+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public OverviewProxyService(Context context,
+ @Main Executor mainExecutor,
CommandQueue commandQueue,
ShellInterface shellInterface,
Lazy<NavigationBarController> navBarControllerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ScreenLifecycle screenLifecycle,
UiEventLogger uiEventLogger,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
AssistUtils assistUtils,
DumpManager dumpManager) {
- super(broadcastDispatcher);
-
// b/241601880: This component shouldn't be running for a non-primary user
if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
Log.e(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
}
mContext = context;
+ mMainExecutor = mainExecutor;
mShellInterface = shellInterface;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
mStatusBarWinController = statusBarWinController;
+ mUserTracker = userTracker;
mConnectionBackoffAttempts = 0;
mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
com.android.internal.R.string.config_recentsComponentName));
@@ -566,7 +576,7 @@
mCommandQueue = commandQueue;
// Listen for user setup
- startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
screenLifecycle.addObserver(mLifecycleObserver);
@@ -579,12 +589,6 @@
assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener);
}
- @Override
- public void onUserSwitched(int newUserId) {
- mConnectionBackoffAttempts = 0;
- internalConnectToCurrentUser();
- }
-
public void onVoiceSessionWindowVisibilityChanged(boolean visible) {
mSysUiState.setFlag(SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, visible)
.commitUpdate(mContext.getDisplayId());
@@ -712,7 +716,7 @@
mBound = mContext.bindServiceAsUser(launcherServiceIntent,
mOverviewServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- UserHandle.of(getCurrentUserId()));
+ UserHandle.of(mUserTracker.getUserId()));
} catch (SecurityException e) {
Log.e(TAG_OPS, "Unable to bind because of security error", e);
}
@@ -941,7 +945,7 @@
}
private void updateEnabledState() {
- final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
+ final int currentUser = mUserTracker.getUserId();
mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
MATCH_SYSTEM_ONLY, currentUser) != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
deleted file mode 100644
index 6de4648..0000000
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2022 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.ripple
-
-/** A common utility functions that are used for computing [RippleShader]. */
-class RippleShaderUtilLibrary {
- //language=AGSL
- companion object {
- const val SHADER_LIB = """
- float triangleNoise(vec2 n) {
- n = fract(n * vec2(5.3987, 5.4421));
- n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
- float xy = n.x * n.y;
- return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
- }
- const float PI = 3.1415926535897932384626;
-
- float sparkles(vec2 uv, float t) {
- float n = triangleNoise(uv);
- float s = 0.0;
- for (float i = 0; i < 4; i += 1) {
- float l = i * 0.01;
- float h = l + 0.1;
- float o = smoothstep(n - l, h, n);
- o *= abs(sin(PI * o * (t + 0.55 * i)));
- s += o;
- }
- return s;
- }
-
- vec2 distort(vec2 p, float time, float distort_amount_radial,
- float distort_amount_xy) {
- float angle = atan(p.y, p.x);
- return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
- cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
- + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
- cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
- }"""
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
deleted file mode 100644
index dea8c32..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings;
-
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-/**
- * A class that has an observable for the current user.
- */
-public class CurrentUserObservable {
-
- private final CurrentUserTracker mTracker;
-
- private final MutableLiveData<Integer> mCurrentUser = new MutableLiveData<Integer>() {
- @Override
- protected void onActive() {
- super.onActive();
- mTracker.startTracking();
- }
-
- @Override
- protected void onInactive() {
- super.onInactive();
- mTracker.stopTracking();
- }
- };
-
- public CurrentUserObservable(BroadcastDispatcher broadcastDispatcher) {
- mTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mCurrentUser.setValue(newUserId);
- }
- };
- }
-
- /**
- * Returns the current user that can be observed.
- */
- public LiveData<Integer> getCurrentUser() {
- if (mCurrentUser.getValue() == null) {
- mCurrentUser.setValue(mTracker.getCurrentUserId());
- }
- return mCurrentUser;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
deleted file mode 100644
index 9599d77..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings;
-
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-public abstract class CurrentUserTracker {
- private final UserReceiver mUserReceiver;
-
- private Consumer<Integer> mCallback = this::onUserSwitched;
-
- public CurrentUserTracker(BroadcastDispatcher broadcastDispatcher) {
- this(UserReceiver.getInstance(broadcastDispatcher));
- }
-
- @VisibleForTesting
- CurrentUserTracker(UserReceiver receiver) {
- mUserReceiver = receiver;
- }
-
- public int getCurrentUserId() {
- return mUserReceiver.getCurrentUserId();
- }
-
- public void startTracking() {
- mUserReceiver.addTracker(mCallback);
- }
-
- public void stopTracking() {
- mUserReceiver.removeTracker(mCallback);
- }
-
- public abstract void onUserSwitched(int newUserId);
-
- @VisibleForTesting
- static class UserReceiver extends BroadcastReceiver {
- private static UserReceiver sInstance;
-
- private boolean mReceiverRegistered;
- private int mCurrentUserId;
- private final BroadcastDispatcher mBroadcastDispatcher;
-
- private List<Consumer<Integer>> mCallbacks = new ArrayList<>();
-
- @VisibleForTesting
- UserReceiver(BroadcastDispatcher broadcastDispatcher) {
- mBroadcastDispatcher = broadcastDispatcher;
- }
-
- static UserReceiver getInstance(BroadcastDispatcher broadcastDispatcher) {
- if (sInstance == null) {
- sInstance = new UserReceiver(broadcastDispatcher);
- }
- return sInstance;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
- notifyUserSwitched(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- }
- }
-
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- private void addTracker(Consumer<Integer> callback) {
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- }
- if (!mReceiverRegistered) {
- mCurrentUserId = ActivityManager.getCurrentUser();
- IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(this, filter, null,
- UserHandle.ALL);
- mReceiverRegistered = true;
- }
- }
-
- private void removeTracker(Consumer<Integer> callback) {
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- if (mCallbacks.size() == 0 && mReceiverRegistered) {
- mBroadcastDispatcher.unregisterReceiver(this);
- mReceiverRegistered = false;
- }
- }
- }
-
- private void notifyUserSwitched(int newUserId) {
- if (mCurrentUserId != newUserId) {
- mCurrentUserId = newUserId;
- List<Consumer<Integer>> callbacks = new ArrayList<>(mCallbacks);
- for (Consumer<Integer> consumer : callbacks) {
- // Accepting may modify this list
- if (mCallbacks.contains(consumer)) {
- consumer.accept(newUserId);
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 7801c68..5880003 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -21,6 +21,7 @@
import static com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -46,11 +47,13 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtilsInternal;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
@@ -74,9 +77,10 @@
private final Context mContext;
private final ToggleSlider mControl;
private final DisplayManager mDisplayManager;
- private final CurrentUserTracker mUserTracker;
+ private final UserTracker mUserTracker;
private final IVrManager mVrManager;
+ private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
private final BrightnessObserver mBrightnessObserver;
@@ -169,7 +173,7 @@
}
mBrightnessObserver.startObserving();
- mUserTracker.startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
// Update the slider and mode before attaching the listener so we don't
// receive the onChanged notifications for the initial values.
@@ -197,7 +201,7 @@
}
mBrightnessObserver.stopObserving();
- mUserTracker.stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
}
@@ -275,22 +279,27 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mBackgroundHandler.post(mUpdateModeRunnable);
+ mBackgroundHandler.post(mUpdateSliderRunnable);
+ }
+ };
+
public BrightnessController(
Context context,
ToggleSlider control,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
+ @Main Executor mainExecutor,
@Background Handler bgHandler) {
mContext = context;
mControl = control;
mControl.setMax(GAMMA_SPACE_MAX);
+ mMainExecutor = mainExecutor;
mBackgroundHandler = bgHandler;
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mBackgroundHandler.post(mUpdateModeRunnable);
- mBackgroundHandler.post(mUpdateSliderRunnable);
- }
- };
+ mUserTracker = userTracker;
mBrightnessObserver = new BrightnessObserver(mHandler);
mDisplayId = mContext.getDisplayId();
@@ -364,7 +373,7 @@
mControl.setEnforcedAdmin(
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
UserManager.DISALLOW_CONFIG_BRIGHTNESS,
- mUserTracker.getCurrentUserId()));
+ mUserTracker.getUserId()));
}
});
}
@@ -440,16 +449,19 @@
/** Factory for creating a {@link BrightnessController}. */
public static class Factory {
private final Context mContext;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
@Inject
public Factory(
Context context,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
+ @Main Executor mainExecutor,
@Background Handler bgHandler) {
mContext = context;
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
+ mMainExecutor = mainExecutor;
mBackgroundHandler = bgHandler;
}
@@ -458,7 +470,8 @@
return new BrightnessController(
mContext,
toggleSlider,
- mBroadcastDispatcher,
+ mUserTracker,
+ mMainExecutor,
mBackgroundHandler);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d5a3954..e208be9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -34,10 +34,12 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -46,16 +48,19 @@
private BrightnessController mBrightnessController;
private final BrightnessSliderController.Factory mToggleSliderFactory;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
@Inject
public BrightnessDialog(
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
BrightnessSliderController.Factory factory,
+ @Main Executor mainExecutor,
@Background Handler bgHandler) {
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
mToggleSliderFactory = factory;
+ mMainExecutor = mainExecutor;
mBackgroundHandler = bgHandler;
}
@@ -101,7 +106,7 @@
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
mBrightnessController = new BrightnessController(
- this, controller, mBroadcastDispatcher, mBackgroundHandler);
+ this, controller, mUserTracker, mMainExecutor, mBackgroundHandler);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 4063af3..5011227 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -51,6 +51,8 @@
connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+ constrainedWidth(R.id.date, true)
+ constrainedWidth(R.id.statusIcons, true)
}
)
}
@@ -92,7 +94,8 @@
centerEnd,
ConstraintSet.END
)
- constrainWidth(R.id.statusIcons, 0)
+ constrainedWidth(R.id.date, true)
+ constrainedWidth(R.id.statusIcons, true)
},
qsConstraintsChanges = {
setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index e52170e..400b0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -16,6 +16,7 @@
package com.android.systemui.shade;
+import static android.os.Trace.TRACE_TAG_ALWAYS;
import static android.view.WindowInsets.Type.systemBars;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
@@ -33,6 +34,7 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Trace;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.DisplayCutout;
@@ -299,6 +301,19 @@
return mode;
}
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationShadeWindowView#onMeasure");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
+
+ @Override
+ public void requestLayout() {
+ Trace.instant(TRACE_TAG_ALWAYS, "NotificationShadeWindowView#requestLayout");
+ super.requestLayout();
+ }
+
private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
private final ActionMode.Callback mWrapped;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 15f4b12..2101efb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -55,7 +55,6 @@
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -64,7 +63,6 @@
import android.os.UserManager;
import android.text.TextUtils;
import android.text.format.Formatter;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
@@ -76,6 +74,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
@@ -123,7 +122,6 @@
private static final String TAG = "KeyguardIndication";
private static final boolean DEBUG_CHARGING_SPEED = false;
- private static final boolean DEBUG = Build.IS_DEBUGGABLE;
private static final int MSG_HIDE_TRANSIENT = 1;
private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
@@ -139,6 +137,7 @@
protected final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final AuthController mAuthController;
+ private final KeyguardLogger mKeyguardLogger;
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -229,7 +228,8 @@
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
AccessibilityManager accessibilityManager,
- FaceHelpMessageDeferral faceHelpMessageDeferral) {
+ FaceHelpMessageDeferral faceHelpMessageDeferral,
+ KeyguardLogger keyguardLogger) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mDevicePolicyManager = devicePolicyManager;
@@ -249,6 +249,7 @@
mKeyguardBypassController = keyguardBypassController;
mAccessibilityManager = accessibilityManager;
mScreenLifecycle = screenLifecycle;
+ mKeyguardLogger = keyguardLogger;
mScreenLifecycle.addObserver(mScreenObserver);
mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
@@ -1024,7 +1025,7 @@
mChargingTimeRemaining = mPowerPluggedIn
? mBatteryInfo.computeChargeTimeRemaining() : -1;
} catch (RemoteException e) {
- Log.e(TAG, "Error calling IBatteryStats: ", e);
+ mKeyguardLogger.logException(e, "Error calling IBatteryStats");
mChargingTimeRemaining = -1;
}
updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
@@ -1072,8 +1073,10 @@
final boolean isCoExFaceAcquisitionMessage =
faceAuthSoftError && isUnlockWithFingerprintPossible;
if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) {
- debugLog("skip showing msgId=" + msgId + " helpString=" + helpString
- + ", due to co-ex logic");
+ mKeyguardLogger.logBiometricMessage(
+ "skipped showing help message due to co-ex logic",
+ msgId,
+ helpString);
} else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
mInitialTextColorState);
@@ -1131,7 +1134,7 @@
CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
mFaceAcquiredMessageDeferral.reset();
if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
- debugLog("suppressingFaceError msgId=" + msgId + " errString= " + errString);
+ mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -1145,8 +1148,9 @@
private void onFingerprintAuthError(int msgId, String errString) {
if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
- debugLog("suppressingFingerprintError msgId=" + msgId
- + " errString= " + errString);
+ mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
+ msgId,
+ errString);
} else {
showErrorMessageNowOrLater(errString, null);
}
@@ -1282,7 +1286,8 @@
}
private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
- debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage);
+ mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
+ null, String.valueOf(deferredFaceMessage));
if (canUnlockWithFingerprint()) {
// Co-ex: show deferred message OR nothing
// if we're on the lock screen (bouncer isn't showing), show the deferred msg
@@ -1294,7 +1299,8 @@
);
} else {
// otherwise, don't show any message
- debugLog("skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
+ mKeyguardLogger.logBiometricMessage(
+ "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
}
} else if (deferredFaceMessage != null) {
// Face-only: The face timeout message is not very actionable, let's ask the
@@ -1315,12 +1321,6 @@
KeyguardUpdateMonitor.getCurrentUser());
}
- private void debugLog(String logMsg) {
- if (DEBUG) {
- Log.d(TAG, logMsg);
- }
- }
-
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 9d2750f..bc456d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -18,6 +18,8 @@
import com.android.systemui.animation.Interpolators
import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
import com.android.systemui.util.getColorWithAlpha
+import com.android.systemui.util.leak.RotationUtils
+import com.android.systemui.util.leak.RotationUtils.Rotation
import java.util.function.Consumer
/**
@@ -67,22 +69,19 @@
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
val ovalWidthIncreaseAmount =
- getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
+ getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
val initialWidthMultiplier = (1f - OVAL_INITIAL_WIDTH_PERCENT) / 2f
with(scrim) {
- revealGradientEndColorAlpha = 1f - getPercentPastThreshold(
- amount, FADE_END_COLOR_OUT_THRESHOLD)
+ revealGradientEndColorAlpha =
+ 1f - getPercentPastThreshold(amount, FADE_END_COLOR_OUT_THRESHOLD)
setRevealGradientBounds(
- scrim.width * initialWidthMultiplier +
- -scrim.width * ovalWidthIncreaseAmount,
- scrim.height * OVAL_INITIAL_TOP_PERCENT -
- scrim.height * interpolatedAmount,
- scrim.width * (1f - initialWidthMultiplier) +
- scrim.width * ovalWidthIncreaseAmount,
- scrim.height * OVAL_INITIAL_BOTTOM_PERCENT +
- scrim.height * interpolatedAmount)
+ scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount,
+ scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount,
+ scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount,
+ scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount
+ )
}
}
}
@@ -97,12 +96,17 @@
scrim.interpolatedRevealAmount = interpolatedAmount
scrim.startColorAlpha =
- getPercentPastThreshold(1 - interpolatedAmount,
- threshold = 1 - START_COLOR_REVEAL_PERCENTAGE)
+ getPercentPastThreshold(
+ 1 - interpolatedAmount,
+ threshold = 1 - START_COLOR_REVEAL_PERCENTAGE
+ )
scrim.revealGradientEndColorAlpha =
- 1f - getPercentPastThreshold(interpolatedAmount,
- threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE)
+ 1f -
+ getPercentPastThreshold(
+ interpolatedAmount,
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ )
// Start changing gradient bounds later to avoid harsh gradient in the beginning
val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1.0f, interpolatedAmount)
@@ -179,7 +183,7 @@
*/
private val OFF_SCREEN_START_AMOUNT = 0.05f
- private val WIDTH_INCREASE_MULTIPLIER = 1.25f
+ private val INCREASE_MULTIPLIER = 1.25f
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN_REVERSE.getInterpolation(amount)
@@ -188,15 +192,36 @@
with(scrim) {
revealGradientEndColorAlpha = 1f - fadeAmount
interpolatedRevealAmount = interpolatedAmount
- setRevealGradientBounds(
+ @Rotation val rotation = RotationUtils.getRotation(scrim.getContext())
+ if (rotation == RotationUtils.ROTATION_NONE) {
+ setRevealGradientBounds(
width * (1f + OFF_SCREEN_START_AMOUNT) -
- width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount,
- powerButtonY -
- height * interpolatedAmount,
+ width * INCREASE_MULTIPLIER * interpolatedAmount,
+ powerButtonY - height * interpolatedAmount,
width * (1f + OFF_SCREEN_START_AMOUNT) +
- width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount,
- powerButtonY +
- height * interpolatedAmount)
+ width * INCREASE_MULTIPLIER * interpolatedAmount,
+ powerButtonY + height * interpolatedAmount
+ )
+ } else if (rotation == RotationUtils.ROTATION_LANDSCAPE) {
+ setRevealGradientBounds(
+ powerButtonY - width * interpolatedAmount,
+ (-height * OFF_SCREEN_START_AMOUNT) -
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
+ powerButtonY + width * interpolatedAmount,
+ (-height * OFF_SCREEN_START_AMOUNT) +
+ height * INCREASE_MULTIPLIER * interpolatedAmount
+ )
+ } else {
+ // RotationUtils.ROTATION_SEASCAPE
+ setRevealGradientBounds(
+ (width - powerButtonY) - width * interpolatedAmount,
+ height * (1f + OFF_SCREEN_START_AMOUNT) -
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
+ (width - powerButtonY) + width * interpolatedAmount,
+ height * (1f + OFF_SCREEN_START_AMOUNT) +
+ height * INCREASE_MULTIPLIER * interpolatedAmount
+ )
+ }
}
}
}
@@ -208,9 +233,7 @@
*/
class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- /**
- * Listener that is called if the scrim's opaqueness changes
- */
+ /** Listener that is called if the scrim's opaqueness changes */
lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
/**
@@ -224,8 +247,11 @@
revealEffect.setRevealAmountOnScrim(value, this)
updateScrimOpaque()
- Trace.traceCounter(Trace.TRACE_TAG_APP, "light_reveal_amount",
- (field * 100).toInt())
+ Trace.traceCounter(
+ Trace.TRACE_TAG_APP,
+ "light_reveal_amount",
+ (field * 100).toInt()
+ )
invalidate()
}
}
@@ -250,10 +276,10 @@
/**
* Alpha of the fill that can be used in the beginning of the animation to hide the content.
- * Normally the gradient bounds are animated from small size so the content is not visible,
- * but if the start gradient bounds allow to see some content this could be used to make the
- * reveal smoother. It can help to add fade in effect in the beginning of the animation.
- * The color of the fill is determined by [revealGradientEndColor].
+ * Normally the gradient bounds are animated from small size so the content is not visible, but
+ * if the start gradient bounds allow to see some content this could be used to make the reveal
+ * smoother. It can help to add fade in effect in the beginning of the animation. The color of
+ * the fill is determined by [revealGradientEndColor].
*
* 0 - no fill and content is visible, 1 - the content is covered with the start color
*/
@@ -281,9 +307,7 @@
}
}
- /**
- * Is the scrim currently fully opaque
- */
+ /** Is the scrim currently fully opaque */
var isScrimOpaque = false
private set(value) {
if (field != value) {
@@ -318,16 +342,22 @@
* Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated
* via local matrix in [onDraw] so we never need to construct a new shader.
*/
- private val gradientPaint = Paint().apply {
- shader = RadialGradient(
- 0f, 0f, 1f,
- intArrayOf(Color.TRANSPARENT, Color.WHITE), floatArrayOf(0f, 1f),
- Shader.TileMode.CLAMP)
+ private val gradientPaint =
+ Paint().apply {
+ shader =
+ RadialGradient(
+ 0f,
+ 0f,
+ 1f,
+ intArrayOf(Color.TRANSPARENT, Color.WHITE),
+ floatArrayOf(0f, 1f),
+ Shader.TileMode.CLAMP
+ )
- // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
- // window, rather than outright replacing them.
- xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
- }
+ // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
+ // window, rather than outright replacing them.
+ xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
+ }
/**
* Matrix applied to [gradientPaint]'s RadialGradient shader to move the gradient to
@@ -347,8 +377,8 @@
* simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
* [revealGradientHeight] for you.
*
- * This method does not call [invalidate] - you should do so once you're done changing
- * properties.
+ * This method does not call [invalidate]
+ * - you should do so once you're done changing properties.
*/
fun setRevealGradientBounds(left: Float, top: Float, right: Float, bottom: Float) {
revealGradientWidth = right - left
@@ -359,8 +389,12 @@
}
override fun onDraw(canvas: Canvas?) {
- if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0 ||
- revealAmount == 0f) {
+ if (
+ canvas == null ||
+ revealGradientWidth <= 0 ||
+ revealGradientHeight <= 0 ||
+ revealAmount == 0f
+ ) {
if (revealAmount < 1f) {
canvas?.drawColor(revealGradientEndColor)
}
@@ -383,8 +417,10 @@
}
private fun setPaintColorFilter() {
- gradientPaint.colorFilter = PorterDuffColorFilter(
- getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
- PorterDuff.Mode.MULTIPLY)
+ gradientPaint.colorFilter =
+ PorterDuffColorFilter(
+ getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
+ PorterDuff.Mode.MULTIPLY
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 184dc25..cdefae6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,7 +19,6 @@
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.admin.DevicePolicyManager;
@@ -50,6 +49,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -93,6 +93,7 @@
private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
private final UserManager mUserManager;
+ private final UserTracker mUserTracker;
private final List<UserChangedListener> mListeners = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
@@ -195,6 +196,7 @@
BroadcastDispatcher broadcastDispatcher,
DevicePolicyManager devicePolicyManager,
UserManager userManager,
+ UserTracker userTracker,
Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
Lazy<CommonNotifCollection> commonNotifCollectionLazy,
NotificationClickNotifier clickNotifier,
@@ -210,7 +212,8 @@
mMainHandler = mainHandler;
mDevicePolicyManager = devicePolicyManager;
mUserManager = userManager;
- mCurrentUserId = ActivityManager.getCurrentUser();
+ mUserTracker = userTracker;
+ mCurrentUserId = mUserTracker.getUserId();
mVisibilityProviderLazy = visibilityProviderLazy;
mCommonNotifCollectionLazy = commonNotifCollectionLazy;
mClickNotifier = clickNotifier;
@@ -295,7 +298,7 @@
mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
- mCurrentUserId = ActivityManager.getCurrentUser(); // in case we reg'd receiver too late
+ mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
updateCurrentProfilesCache();
mSettingsObserver.onChange(false); // set up
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 815b86e..cd13085 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -132,8 +132,11 @@
mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
- layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
- setLayoutParams(layoutParams);
+ final int newShelfHeight = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
+ if (newShelfHeight != layoutParams.height) {
+ layoutParams.height = newShelfHeight;
+ setLayoutParams(layoutParams);
+ }
final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
mShelfIcons.setPadding(padding, 0, padding, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 73d6483..99ff06a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -39,6 +39,7 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -76,7 +77,7 @@
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
@@ -128,7 +129,7 @@
private final boolean mHasMobileDataFeature;
private final SubscriptionDefaults mSubDefaults;
private final DataSaverController mDataSaverController;
- private final CurrentUserTracker mUserTracker;
+ private final UserTracker mUserTracker;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DemoModeController mDemoModeController;
private final Object mLock = new Object();
@@ -212,6 +213,14 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ NetworkControllerImpl.this.onUserSwitched(newUser);
+ }
+ };
+
/**
* Construct this controller object and register for updates.
*/
@@ -224,6 +233,7 @@
CallbackHandler callbackHandler,
DeviceProvisionedController deviceProvisionedController,
BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ConnectivityManager connectivityManager,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
@@ -251,6 +261,7 @@
new SubscriptionDefaults(),
deviceProvisionedController,
broadcastDispatcher,
+ userTracker,
demoModeController,
carrierConfigTracker,
trackerFactory,
@@ -277,6 +288,7 @@
SubscriptionDefaults defaultsHandler,
DeviceProvisionedController deviceProvisionedController,
BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
WifiStatusTrackerFactory trackerFactory,
@@ -333,13 +345,9 @@
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- NetworkControllerImpl.this.onUserSwitched(newUserId);
- }
- };
- mUserTracker.startTracking();
+ mUserTracker = userTracker;
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+
deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
@Override
public void onUserSetupChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 5873837..6bd9502 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -235,19 +235,24 @@
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
- activityStarter.startActivity(
- intent,
- true, /* dismissShade */
- null, /* launch animator - looks bad with the transparent smartspace bg */
- showOnLockscreen
- )
+ if (showOnLockscreen) {
+ activityStarter.startActivity(
+ intent,
+ true, /* dismissShade */
+ // launch animator - looks bad with the transparent smartspace bg
+ null,
+ true
+ )
+ } else {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+ }
}
override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
if (showOnLockscreen) {
pi.send()
} else {
- activityStarter.startPendingIntentDismissingKeyguard(pi)
+ activityStarter.postStartActivityDismissingKeyguard(pi)
}
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 58f59be..5f6a5cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -53,6 +53,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -534,7 +535,7 @@
return;
}
if (loggedExpansionState != null
- && state.mIsExpanded == loggedExpansionState) {
+ && Objects.equals(state.mIsExpanded, loggedExpansionState)) {
return;
}
mLoggedExpansionState.put(key, state.mIsExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 3021414..b93e150 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -135,6 +135,8 @@
private static final String TAG = "ExpandableNotifRow";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG_ONMEASURE =
+ Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
private static final int COLORED_DIVIDER_ALPHA = 0x7B;
private static final int MENU_VIEW_INDEX = 0;
@@ -1724,6 +1726,11 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
+ if (DEBUG_ONMEASURE) {
+ Log.d(TAG, "onMeasure("
+ + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 645a02d..d43ca823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -25,6 +25,7 @@
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.drawable.ColorDrawable;
+import android.os.Trace;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
@@ -219,6 +220,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationChildrenContainer#onMeasure");
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
@@ -267,6 +269,7 @@
}
setMeasuredDimension(width, height);
+ Trace.endSection();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 2c3330e..41dbf1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import static android.os.Trace.TRACE_TAG_ALWAYS;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
@@ -44,6 +46,7 @@
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Trace;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
@@ -1074,6 +1077,12 @@
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationStackScrollLayout#onMeasure");
+ if (SPEW) {
+ Log.d(TAG, "onMeasure("
+ + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
@@ -1090,6 +1099,13 @@
for (int i = 0; i < size; i++) {
measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
}
+ Trace.endSection();
+ }
+
+ @Override
+ public void requestLayout() {
+ Trace.instant(TRACE_TAG_ALWAYS, "NotificationStackScrollLayout#requestLayout");
+ super.requestLayout();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 334f1af..18a08f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -172,7 +172,6 @@
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.ripple.RippleShader.RippleShape;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.CameraLauncher;
@@ -232,6 +231,7 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -1131,7 +1131,6 @@
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationIconAreaController.setupShelf(mNotificationShelfController);
mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
- mUserSwitcherController.init(mNotificationShadeWindowView);
// Allow plugins to reference DarkIconDispatcher and StatusBarStateController
mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
@@ -4266,7 +4265,6 @@
}
// TODO: Bring these out of CentralSurfaces.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- mUserSwitcherController.onDensityOrFontScaleChanged();
mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
mHeadsUpManager.onDensityOrFontScaleChanged();
}
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 18877f9..7a49a49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -26,6 +26,7 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue;
@@ -527,4 +528,11 @@
mClipRect.set(0, mTopClipping, getWidth(), getHeight());
setClipBounds(mClipRect);
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("KeyguardStatusBarView#onMeasure");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 16fddb42..6bf5443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
@@ -41,12 +42,54 @@
/**
* Class to control all aspects about light bar changes.
*/
-public class LightBarTransitionsController implements Dumpable, Callbacks,
- StatusBarStateController.StateListener {
+public class LightBarTransitionsController implements Dumpable {
public static final int DEFAULT_TINT_ANIMATION_DURATION = 120;
private static final String EXTRA_DARK_INTENSITY = "dark_intensity";
+ private static class Callback implements Callbacks, StatusBarStateController.StateListener {
+ private final WeakReference<LightBarTransitionsController> mSelf;
+
+ Callback(LightBarTransitionsController self) {
+ mSelf = new WeakReference<>(self);
+ }
+
+ @Override
+ public void appTransitionPending(int displayId, boolean forced) {
+ LightBarTransitionsController self = mSelf.get();
+ if (self != null) {
+ self.appTransitionPending(displayId, forced);
+ }
+ }
+
+ @Override
+ public void appTransitionCancelled(int displayId) {
+ LightBarTransitionsController self = mSelf.get();
+ if (self != null) {
+ self.appTransitionCancelled(displayId);
+ }
+ }
+
+ @Override
+ public void appTransitionStarting(int displayId, long startTime, long duration,
+ boolean forced) {
+ LightBarTransitionsController self = mSelf.get();
+ if (self != null) {
+ self.appTransitionStarting(displayId, startTime, duration, forced);
+ }
+ }
+
+ @Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ LightBarTransitionsController self = mSelf.get();
+ if (self != null) {
+ self.onDozeAmountChanged(linear, eased);
+ }
+ }
+ }
+
+ private final Callback mCallback;
+
private final Handler mHandler;
private final DarkIntensityApplier mApplier;
private final KeyguardStateController mKeyguardStateController;
@@ -86,8 +129,9 @@
mKeyguardStateController = keyguardStateController;
mStatusBarStateController = statusBarStateController;
mCommandQueue = commandQueue;
- mCommandQueue.addCallback(this);
- mStatusBarStateController.addCallback(this);
+ mCallback = new Callback(this);
+ mCommandQueue.addCallback(mCallback);
+ mStatusBarStateController.addCallback(mCallback);
mDozeAmount = mStatusBarStateController.getDozeAmount();
mContext = context;
mDisplayId = mContext.getDisplayId();
@@ -95,8 +139,8 @@
/** Call to cleanup the LightBarTransitionsController when done with it. */
public void destroy() {
- mCommandQueue.removeCallback(this);
- mStatusBarStateController.removeCallback(this);
+ mCommandQueue.removeCallback(mCallback);
+ mStatusBarStateController.removeCallback(mCallback);
}
public void saveState(Bundle outState) {
@@ -110,16 +154,14 @@
mNextDarkIntensity = mDarkIntensity;
}
- @Override
- public void appTransitionPending(int displayId, boolean forced) {
+ private void appTransitionPending(int displayId, boolean forced) {
if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
return;
}
mTransitionPending = true;
}
- @Override
- public void appTransitionCancelled(int displayId) {
+ private void appTransitionCancelled(int displayId) {
if (mDisplayId != displayId) {
return;
}
@@ -131,9 +173,7 @@
mTransitionPending = false;
}
- @Override
- public void appTransitionStarting(int displayId, long startTime, long duration,
- boolean forced) {
+ private void appTransitionStarting(int displayId, long startTime, long duration, boolean forced) {
if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
return;
}
@@ -230,10 +270,6 @@
pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity);
}
- @Override
- public void onStateChanged(int newState) { }
-
- @Override
public void onDozeAmountChanged(float linear, float eased) {
mDozeAmount = eased;
dispatchDark();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 8793a57..1d7dfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
import android.app.WallpaperColors;
@@ -45,6 +44,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
import libcore.io.IoUtils;
@@ -82,10 +82,11 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
DumpManager dumpManager,
NotificationMediaManager mediaManager,
- @Main Handler mainHandler) {
+ @Main Handler mainHandler,
+ UserTracker userTracker) {
dumpManager.registerDumpable(getClass().getSimpleName(), this);
mWallpaperManager = wallpaperManager;
- mCurrentUserId = ActivityManager.getCurrentUser();
+ mCurrentUserId = userTracker.getUserId();
mUpdateMonitor = keyguardUpdateMonitor;
mMediaManager = mediaManager;
mH = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 94d1bf4..26e6db6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -14,7 +14,6 @@
package com.android.systemui.statusbar.phone;
-import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -28,6 +27,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -44,6 +44,7 @@
private final Context mContext;
private final UserManager mUserManager;
+ private final UserTracker mUserTracker;
private final BroadcastDispatcher mBroadcastDispatcher;
private final LinkedList<UserInfo> mProfiles;
private boolean mListening;
@@ -52,9 +53,11 @@
/**
*/
@Inject
- public ManagedProfileControllerImpl(Context context, BroadcastDispatcher broadcastDispatcher) {
+ public ManagedProfileControllerImpl(Context context, UserTracker userTracker,
+ BroadcastDispatcher broadcastDispatcher) {
mContext = context;
mUserManager = UserManager.get(mContext);
+ mUserTracker = userTracker;
mBroadcastDispatcher = broadcastDispatcher;
mProfiles = new LinkedList<UserInfo>();
}
@@ -90,7 +93,7 @@
private void reloadManagedProfiles() {
synchronized (mProfiles) {
boolean hadProfile = mProfiles.size() > 0;
- int user = ActivityManager.getCurrentUser();
+ int user = mUserTracker.getUserId();
mProfiles.clear();
for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
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 d54a863..c527f30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -53,7 +53,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -205,7 +204,6 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private KeyguardViewMediator mKeyguardViewMediator;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -275,8 +273,7 @@
@Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- KeyguardViewMediator keyguardViewMediator) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
@@ -315,8 +312,6 @@
}
});
mColors = new GradientColors();
-
- mKeyguardViewMediator = keyguardViewMediator;
}
/**
@@ -812,13 +807,6 @@
mBehindTint,
interpolatedFraction);
}
-
- // If we're unlocked but still playing the occlude animation, remain at the keyguard
- // alpha temporarily.
- if (mKeyguardViewMediator.isOccludeAnimationPlaying()
- || mState.mLaunchingAffordanceWithPreview) {
- mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
- }
} else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
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 681d21b..9e075e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -469,7 +469,7 @@
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
return;
- } else if (primaryBouncerNeedsScrimming()) {
+ } else if (needsFullscreenBouncer()) {
if (mPrimaryBouncer != null) {
mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 0369845..344d233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -28,13 +28,13 @@
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.biometrics.AuthRippleView;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CombinedShadeHeadersConstraintManager;
import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl;
import com.android.systemui.shade.NotificationPanelView;
@@ -220,20 +220,22 @@
@Named(LARGE_SCREEN_BATTERY_CONTROLLER)
static BatteryMeterViewController getBatteryMeterViewController(
@Named(SPLIT_SHADE_BATTERY_VIEW) BatteryMeterView batteryMeterView,
+ UserTracker userTracker,
ConfigurationController configurationController,
TunerService tunerService,
- BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler,
ContentResolver contentResolver,
+ FeatureFlags featureFlags,
BatteryController batteryController
) {
return new BatteryMeterViewController(
batteryMeterView,
+ userTracker,
configurationController,
tunerService,
- broadcastDispatcher,
mainHandler,
contentResolver,
+ featureFlags,
batteryController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index cf4106c..68d30d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -21,7 +21,6 @@
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.drawable.Drawable
-import android.os.UserHandle
import android.widget.BaseAdapter
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
import com.android.systemui.user.data.source.UserRecord
@@ -84,7 +83,7 @@
}
fun refresh() {
- controller.refreshUsers(UserHandle.USER_NULL)
+ controller.refreshUsers()
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 149ed0a..d10d7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -155,6 +155,9 @@
default void onWirelessChargingChanged(boolean isWirlessCharging) {
}
+
+ default void onIsOverheatedChanged(boolean isOverheated) {
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index c7ad767..3c2ac7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.policy;
+import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
import static android.os.BatteryManager.EXTRA_PRESENT;
import android.annotation.WorkerThread;
@@ -87,6 +90,7 @@
protected boolean mPowerSave;
private boolean mAodPowerSave;
private boolean mWirelessCharging;
+ private boolean mIsOverheated = false;
private boolean mTestMode = false;
@VisibleForTesting
boolean mHasReceivedBattery = false;
@@ -184,6 +188,7 @@
cb.onPowerSaveChanged(mPowerSave);
cb.onBatteryUnknownStateChanged(mStateUnknown);
cb.onWirelessChargingChanged(mWirelessCharging);
+ cb.onIsOverheatedChanged(mIsOverheated);
}
@Override
@@ -222,6 +227,13 @@
fireBatteryUnknownStateChanged();
}
+ int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+ boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT;
+ if (isOverheated != mIsOverheated) {
+ mIsOverheated = isOverheated;
+ fireIsOverheatedChanged();
+ }
+
fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave();
@@ -292,6 +304,10 @@
return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
+ public boolean isOverheated() {
+ return mIsOverheated;
+ }
+
@Override
public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
// Need to fetch or refresh the estimate, but it may involve binder calls so offload the
@@ -402,6 +418,15 @@
}
}
+ private void fireIsOverheatedChanged() {
+ synchronized (mChangeCallbacks) {
+ final int n = mChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mChangeCallbacks.get(i).onIsOverheatedChanged(mIsOverheated);
+ }
+ }
+ }
+
@Override
public void dispatchDemoCommand(String command, Bundle args) {
if (!mDemoModeController.isInDemoMode()) {
@@ -412,6 +437,7 @@
String plugged = args.getString("plugged");
String powerSave = args.getString("powersave");
String present = args.getString("present");
+ String overheated = args.getString("overheated");
if (level != null) {
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
}
@@ -426,6 +452,10 @@
mStateUnknown = !present.equals("true");
fireBatteryUnknownStateChanged();
}
+ if (overheated != null) {
+ mIsOverheated = overheated.equals("true");
+ fireIsOverheatedChanged();
+ }
fireBatteryLevelChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index aae0f93..38b3769 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -41,6 +40,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -83,6 +83,7 @@
@Inject
public BluetoothControllerImpl(
Context context,
+ UserTracker userTracker,
DumpManager dumpManager,
BluetoothLogger logger,
@Background Looper bgLooper,
@@ -100,7 +101,7 @@
mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
}
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mCurrentUser = ActivityManager.getCurrentUser();
+ mCurrentUser = userTracker.getUserId();
mDumpManager.registerDumpable(TAG, this);
}
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 576962d..d84cbcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -49,7 +50,7 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -79,7 +80,7 @@
private static final String SHOW_SECONDS = "show_seconds";
private static final String VISIBILITY = "visibility";
- private final CurrentUserTracker mCurrentUserTracker;
+ private final UserTracker mUserTracker;
private final CommandQueue mCommandQueue;
private int mCurrentUserId;
@@ -114,6 +115,14 @@
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mCurrentUserId = newUser;
+ }
+ };
+
public Clock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -132,12 +141,7 @@
a.recycle();
}
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
- mCurrentUserTracker = new CurrentUserTracker(mBroadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mCurrentUserId = newUserId;
- }
- };
+ mUserTracker = Dependency.get(UserTracker.class);
}
@Override
@@ -196,8 +200,8 @@
Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_HIDE_LIST);
mCommandQueue.addCallback(this);
- mCurrentUserTracker.startTracking();
- mCurrentUserId = mCurrentUserTracker.getCurrentUserId();
+ mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mCurrentUserId = mUserTracker.getUserId();
}
// The time zone may have changed while the receiver wasn't registered, so update the Time
@@ -227,7 +231,7 @@
mAttached = false;
Dependency.get(TunerService.class).removeTunable(this);
mCommandQueue.removeCallback(this);
- mCurrentUserTracker.stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
}
}
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 69b55c8..a4821e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -18,7 +18,6 @@
import static android.net.TetheringManager.TETHERING_WIFI;
-import android.app.ActivityManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.TetheringManager;
@@ -38,6 +37,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -59,6 +59,7 @@
private final WifiManager mWifiManager;
private final Handler mMainHandler;
private final Context mContext;
+ private final UserTracker mUserTracker;
private int mHotspotState;
private volatile int mNumConnectedDevices;
@@ -95,10 +96,12 @@
@Inject
public HotspotControllerImpl(
Context context,
+ UserTracker userTracker,
@Main Handler mainHandler,
@Background Handler backgroundHandler,
DumpManager dumpManager) {
mContext = context;
+ mUserTracker = userTracker;
mTetheringManager = context.getSystemService(TetheringManager.class);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mMainHandler = mainHandler;
@@ -125,7 +128,7 @@
@Override
public boolean isHotspotSupported() {
return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
- && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
+ && UserManager.get(mContext).isUserAdmin(mUserTracker.getUserId());
}
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index cc241d9..ba94714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.DeviceOwnerType;
@@ -55,8 +54,9 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import org.xmlpull.v1.XmlPullParserException;
@@ -70,7 +70,7 @@
/**
*/
@SysUISingleton
-public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
+public class SecurityControllerImpl implements SecurityController {
private static final String TAG = "SecurityController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -84,11 +84,13 @@
private static final int CA_CERT_LOADING_RETRY_TIME_IN_MS = 30_000;
private final Context mContext;
+ private final UserTracker mUserTracker;
private final ConnectivityManager mConnectivityManager;
private final VpnManager mVpnManager;
private final DevicePolicyManager mDevicePolicyManager;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
+ private final Executor mMainExecutor;
private final Executor mBgExecutor;
@GuardedBy("mCallbacks")
@@ -102,18 +104,28 @@
// Needs to be cached here since the query has to be asynchronous
private ArrayMap<Integer, Boolean> mHasCACerts = new ArrayMap<Integer, Boolean>();
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ onUserSwitched(newUser);
+ }
+ };
+
/**
*/
@Inject
public SecurityControllerImpl(
Context context,
+ UserTracker userTracker,
@Background Handler bgHandler,
BroadcastDispatcher broadcastDispatcher,
+ @Main Executor mainExecutor,
@Background Executor bgExecutor,
DumpManager dumpManager
) {
- super(broadcastDispatcher);
mContext = context;
+ mUserTracker = userTracker;
mDevicePolicyManager = (DevicePolicyManager)
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
mConnectivityManager = (ConnectivityManager)
@@ -121,6 +133,7 @@
mVpnManager = context.getSystemService(VpnManager.class);
mPackageManager = context.getPackageManager();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -133,8 +146,8 @@
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
- onUserSwitched(ActivityManager.getCurrentUser());
- startTracking();
+ onUserSwitched(mUserTracker.getUserId());
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
}
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 29285f8..a593d51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -28,7 +27,6 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract;
@@ -40,8 +38,11 @@
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -53,6 +54,7 @@
private static final String TAG = "UserInfoController";
private final Context mContext;
+ private final UserTracker mUserTracker;
private final ArrayList<OnUserInfoChangedListener> mCallbacks =
new ArrayList<OnUserInfoChangedListener>();
private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
@@ -64,11 +66,11 @@
/**
*/
@Inject
- public UserInfoControllerImpl(Context context) {
+ public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+ UserTracker userTracker) {
mContext = context;
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(mReceiver, filter);
+ mUserTracker = userTracker;
+ mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
IntentFilter profileFilter = new IntentFilter();
profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
@@ -88,15 +90,13 @@
mCallbacks.remove(callback);
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- reloadUserInfo();
- }
- }
- };
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ reloadUserInfo();
+ }
+ };
private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
@Override
@@ -104,15 +104,11 @@
final String action = intent.getAction();
if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
- try {
- final int currentUser = ActivityManager.getService().getCurrentUser().id;
- final int changedUser =
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
- if (changedUser == currentUser) {
- reloadUserInfo();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't get current user id for profile change", e);
+ final int currentUser = mUserTracker.getUserId();
+ final int changedUser =
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+ if (changedUser == currentUser) {
+ reloadUserInfo();
}
}
}
@@ -130,15 +126,12 @@
Context currentUserContext;
UserInfo userInfo;
try {
- userInfo = ActivityManager.getService().getCurrentUser();
+ userInfo = mUserTracker.getUserInfo();
currentUserContext = mContext.createPackageContextAsUser("android", 0,
new UserHandle(userInfo.id));
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Couldn't create user context", e);
throw new RuntimeException(e);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't get user info", e);
- throw new RuntimeException(e);
}
final int userId = userInfo.id;
final boolean isGuest = userInfo.isGuest();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index 146b222..bdb656b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -14,35 +14,74 @@
* limitations under the License.
*
*/
+
package com.android.systemui.statusbar.policy
-import android.annotation.UserIdInt
+import android.content.Context
import android.content.Intent
import android.view.View
-import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
+import dagger.Lazy
+import java.io.PrintWriter
import java.lang.ref.WeakReference
-import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
-/** Defines interface for a class that provides user switching functionality and state. */
-interface UserSwitcherController : Dumpable {
+/** Access point into multi-user switching logic. */
+@Deprecated("Use UserInteractor or GuestUserInteractor instead.")
+@SysUISingleton
+class UserSwitcherController
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val userInteractorLazy: Lazy<UserInteractor>,
+ private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
+ private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+ private val activityStarter: ActivityStarter,
+) {
+
+ /** Defines interface for classes that can be called back when the user is switched. */
+ fun interface UserSwitchCallback {
+ /** Notifies that the user has switched. */
+ fun onUserSwitched()
+ }
+
+ private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+ private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
+ private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
+
+ private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>()
/** The current list of [UserRecord]. */
val users: ArrayList<UserRecord>
+ get() = userInteractor.userRecords.value
/** Whether the user switcher experience should use the simple experience. */
val isSimpleUserSwitcher: Boolean
-
- /** Require a view for jank detection */
- fun init(view: View)
+ get() = userInteractor.isSimpleUserSwitcher
/** The [UserRecord] of the current user or `null` when none. */
val currentUserRecord: UserRecord?
+ get() = userInteractor.selectedUserRecord.value
/** The name of the current user of the device or `null`, when none is selected. */
val currentUserName: String?
+ get() =
+ currentUserRecord?.let {
+ LegacyUserUiHelper.getUserRecordName(
+ context = applicationContext,
+ record = it,
+ isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
+ isGuestUserResetting = userInteractor.isGuestUserResetting,
+ )
+ }
/**
* Notifies that a user has been selected.
@@ -55,34 +94,40 @@
* @param userId The ID of the user to switch to.
* @param dialogShower An optional [DialogShower] in case we need to show dialogs.
*/
- fun onUserSelected(userId: Int, dialogShower: DialogShower?)
-
- /** Whether it is allowed to add users while the device is locked. */
- val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+ fun onUserSelected(userId: Int, dialogShower: DialogShower?) {
+ userInteractor.selectUser(userId, dialogShower)
+ }
/** Whether the guest user is configured to always be present on the device. */
val isGuestUserAutoCreated: Boolean
+ get() = userInteractor.isGuestUserAutoCreated
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean
-
- /** Creates and switches to the guest user. */
- fun createAndSwitchToGuestUser(dialogShower: DialogShower?)
-
- /** Shows the add user dialog. */
- fun showAddUserDialog(dialogShower: DialogShower?)
-
- /** Starts an activity to add a supervised user to the device. */
- fun startSupervisedUserActivity()
-
- /** Notifies when the display density or font scale has changed. */
- fun onDensityOrFontScaleChanged()
+ get() = userInteractor.isGuestUserResetting
/** Registers an adapter to notify when the users change. */
- fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>)
+ fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
+ userInteractor.addCallback(
+ object : UserInteractor.UserCallback {
+ override fun isEvictable(): Boolean {
+ return adapter.get() == null
+ }
+
+ override fun onUserStateChanged() {
+ adapter.get()?.notifyDataSetChanged()
+ }
+ }
+ )
+ }
/** Notifies the item for a user has been clicked. */
- fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?)
+ fun onUserListItemClicked(
+ record: UserRecord,
+ dialogShower: DialogShower?,
+ ) {
+ userInteractor.onRecordSelected(record, dialogShower)
+ }
/**
* Removes guest user and switches to target user. The guest must be the current user and its id
@@ -103,7 +148,12 @@
* @param targetUserId id of the user to switch to after guest is removed. If
* `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
*/
- fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int)
+ fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
+ userInteractor.removeGuestUser(
+ guestUserId = guestUserId,
+ targetUserId = targetUserId,
+ )
+ }
/**
* Exits guest user and switches to previous non-guest user. The guest must be the current user.
@@ -114,43 +164,58 @@
* @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest
* only if its ephemeral, else keep guest
*/
- fun exitGuestUser(
- @UserIdInt guestUserId: Int,
- @UserIdInt targetUserId: Int,
- forceRemoveGuestOnExit: Boolean
- )
+ fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) {
+ userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+ }
/**
* Guarantee guest is present only if the device is provisioned. Otherwise, create a content
* observer to wait until the device is provisioned, then schedule the guest creation.
*/
- fun schedulePostBootGuestCreation()
+ fun schedulePostBootGuestCreation() {
+ guestUserInteractor.onDeviceBootCompleted()
+ }
/** Whether keyguard is showing. */
val isKeyguardShowing: Boolean
+ get() = keyguardInteractor.isKeyguardShowing()
/** Starts an activity with the given [Intent]. */
- fun startActivity(intent: Intent)
+ fun startActivity(intent: Intent) {
+ activityStarter.startActivity(intent, /* dismissShade= */ true)
+ }
/**
* Refreshes users from UserManager.
*
* The pictures are only loaded if they have not been loaded yet.
- *
- * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
*/
- fun refreshUsers(forcePictureLoadForId: Int)
+ fun refreshUsers() {
+ userInteractor.refreshUsers()
+ }
/** Adds a subscriber to when user switches. */
- fun addUserSwitchCallback(callback: UserSwitchCallback)
+ fun addUserSwitchCallback(callback: UserSwitchCallback) {
+ val interactorCallback =
+ object : UserInteractor.UserCallback {
+ override fun onUserStateChanged() {
+ callback.onUserSwitched()
+ }
+ }
+ callbackCompatMap[callback] = interactorCallback
+ userInteractor.addCallback(interactorCallback)
+ }
/** Removes a previously-added subscriber. */
- fun removeUserSwitchCallback(callback: UserSwitchCallback)
+ fun removeUserSwitchCallback(callback: UserSwitchCallback) {
+ val interactorCallback = callbackCompatMap.remove(callback)
+ if (interactorCallback != null) {
+ userInteractor.removeCallback(interactorCallback)
+ }
+ }
- /** Defines interface for classes that can be called back when the user is switched. */
- fun interface UserSwitchCallback {
- /** Notifies that the user has switched. */
- fun onUserSwitched()
+ fun dump(pw: PrintWriter, args: Array<out String>) {
+ userInteractor.dump(pw)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
deleted file mode 100644
index 935fc7f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2022 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.policy
-
-import android.content.Context
-import android.content.Intent
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import dagger.Lazy
-import java.io.PrintWriter
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Implementation of [UserSwitcherController]. */
-@SysUISingleton
-class UserSwitcherControllerImpl
-@Inject
-constructor(
- @Application private val applicationContext: Context,
- flags: FeatureFlags,
- @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
- private val userInteractorLazy: Lazy<UserInteractor>,
- private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
- private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
- private val activityStarter: ActivityStarter,
-) : UserSwitcherController {
-
- private val useInteractor: Boolean =
- flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) &&
- !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
- private val _oldImpl: UserSwitcherControllerOldImpl
- get() = oldImpl.get()
- private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
- private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
- private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
-
- private val callbackCompatMap =
- mutableMapOf<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
-
- private fun notSupported(): Nothing {
- error("Not supported in the new implementation!")
- }
-
- override val users: ArrayList<UserRecord>
- get() =
- if (useInteractor) {
- userInteractor.userRecords.value
- } else {
- _oldImpl.users
- }
-
- override val isSimpleUserSwitcher: Boolean
- get() =
- if (useInteractor) {
- userInteractor.isSimpleUserSwitcher
- } else {
- _oldImpl.isSimpleUserSwitcher
- }
-
- override fun init(view: View) {
- if (!useInteractor) {
- _oldImpl.init(view)
- }
- }
-
- override val currentUserRecord: UserRecord?
- get() =
- if (useInteractor) {
- userInteractor.selectedUserRecord.value
- } else {
- _oldImpl.currentUserRecord
- }
-
- override val currentUserName: String?
- get() =
- if (useInteractor) {
- currentUserRecord?.let {
- LegacyUserUiHelper.getUserRecordName(
- context = applicationContext,
- record = it,
- isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
- isGuestUserResetting = userInteractor.isGuestUserResetting,
- )
- }
- } else {
- _oldImpl.currentUserName
- }
-
- override fun onUserSelected(
- userId: Int,
- dialogShower: UserSwitchDialogController.DialogShower?
- ) {
- if (useInteractor) {
- userInteractor.selectUser(userId, dialogShower)
- } else {
- _oldImpl.onUserSelected(userId, dialogShower)
- }
- }
-
- override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
- get() =
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.isAddUsersFromLockScreenEnabled
- }
-
- override val isGuestUserAutoCreated: Boolean
- get() =
- if (useInteractor) {
- userInteractor.isGuestUserAutoCreated
- } else {
- _oldImpl.isGuestUserAutoCreated
- }
-
- override val isGuestUserResetting: Boolean
- get() =
- if (useInteractor) {
- userInteractor.isGuestUserResetting
- } else {
- _oldImpl.isGuestUserResetting
- }
-
- override fun createAndSwitchToGuestUser(
- dialogShower: UserSwitchDialogController.DialogShower?,
- ) {
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.createAndSwitchToGuestUser(dialogShower)
- }
- }
-
- override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.showAddUserDialog(dialogShower)
- }
- }
-
- override fun startSupervisedUserActivity() {
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.startSupervisedUserActivity()
- }
- }
-
- override fun onDensityOrFontScaleChanged() {
- if (!useInteractor) {
- _oldImpl.onDensityOrFontScaleChanged()
- }
- }
-
- override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
- if (useInteractor) {
- userInteractor.addCallback(
- object : UserInteractor.UserCallback {
- override fun isEvictable(): Boolean {
- return adapter.get() == null
- }
-
- override fun onUserStateChanged() {
- adapter.get()?.notifyDataSetChanged()
- }
- }
- )
- } else {
- _oldImpl.addAdapter(adapter)
- }
- }
-
- override fun onUserListItemClicked(
- record: UserRecord,
- dialogShower: UserSwitchDialogController.DialogShower?,
- ) {
- if (useInteractor) {
- userInteractor.onRecordSelected(record, dialogShower)
- } else {
- _oldImpl.onUserListItemClicked(record, dialogShower)
- }
- }
-
- override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
- if (useInteractor) {
- userInteractor.removeGuestUser(
- guestUserId = guestUserId,
- targetUserId = targetUserId,
- )
- } else {
- _oldImpl.removeGuestUser(guestUserId, targetUserId)
- }
- }
-
- override fun exitGuestUser(
- guestUserId: Int,
- targetUserId: Int,
- forceRemoveGuestOnExit: Boolean
- ) {
- if (useInteractor) {
- userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
- } else {
- _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
- }
- }
-
- override fun schedulePostBootGuestCreation() {
- if (useInteractor) {
- guestUserInteractor.onDeviceBootCompleted()
- } else {
- _oldImpl.schedulePostBootGuestCreation()
- }
- }
-
- override val isKeyguardShowing: Boolean
- get() =
- if (useInteractor) {
- keyguardInteractor.isKeyguardShowing()
- } else {
- _oldImpl.isKeyguardShowing
- }
-
- override fun startActivity(intent: Intent) {
- if (useInteractor) {
- activityStarter.startActivity(intent, /* dismissShade= */ true)
- } else {
- _oldImpl.startActivity(intent)
- }
- }
-
- override fun refreshUsers(forcePictureLoadForId: Int) {
- if (useInteractor) {
- userInteractor.refreshUsers()
- } else {
- _oldImpl.refreshUsers(forcePictureLoadForId)
- }
- }
-
- override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
- if (useInteractor) {
- val interactorCallback =
- object : UserInteractor.UserCallback {
- override fun onUserStateChanged() {
- callback.onUserSwitched()
- }
- }
- callbackCompatMap[callback] = interactorCallback
- userInteractor.addCallback(interactorCallback)
- } else {
- _oldImpl.addUserSwitchCallback(callback)
- }
- }
-
- override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
- if (useInteractor) {
- val interactorCallback = callbackCompatMap.remove(callback)
- if (interactorCallback != null) {
- userInteractor.removeCallback(interactorCallback)
- }
- } else {
- _oldImpl.removeUserSwitchCallback(callback)
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- if (useInteractor) {
- userInteractor.dump(pw)
- } else {
- _oldImpl.dump(pw, args)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
deleted file mode 100644
index c294c37..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ /dev/null
@@ -1,1063 +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.systemui.statusbar.policy;
-
-import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
-
-import android.annotation.UserIdInt;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyCallback;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.view.View;
-import android.view.WindowManagerGlobal;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.systemui.GuestResetOrExitSessionReceiver;
-import com.android.systemui.GuestResumeSessionReceiver;
-import com.android.systemui.SystemUISecondaryUserService;
-import com.android.systemui.animation.DialogCuj;
-import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.LongRunning;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.qs.QSUserSwitcherEvent;
-import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.user.data.source.UserRecord;
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
-import com.android.systemui.user.shared.model.UserActionModel;
-import com.android.systemui.user.ui.dialog.AddUserDialog;
-import com.android.systemui.user.ui.dialog.ExitGuestDialog;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-/**
- * Old implementation. Keeps a list of all users on the device for user switching.
- *
- * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController}
- * instead.
- */
-@Deprecated
-@SysUISingleton
-public class UserSwitcherControllerOldImpl implements UserSwitcherController {
-
- private static final String TAG = "UserSwitcherController";
- private static final boolean DEBUG = false;
- private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
- "lockscreenSimpleUserSwitcher";
- private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
-
- private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
- private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L;
-
- private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user";
- private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode";
-
- protected final Context mContext;
- protected final UserTracker mUserTracker;
- protected final UserManager mUserManager;
- private final ContentObserver mSettingsObserver;
- private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>();
- @VisibleForTesting
- final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
- @VisibleForTesting
- final GuestResetOrExitSessionReceiver mGuestResetOrExitSessionReceiver;
- private final KeyguardStateController mKeyguardStateController;
- private final DeviceProvisionedController mDeviceProvisionedController;
- private final DevicePolicyManager mDevicePolicyManager;
- protected final Handler mHandler;
- private final ActivityStarter mActivityStarter;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final BroadcastSender mBroadcastSender;
- private final TelephonyListenerManager mTelephonyListenerManager;
- private final InteractionJankMonitor mInteractionJankMonitor;
- private final LatencyTracker mLatencyTracker;
- private final DialogLaunchAnimator mDialogLaunchAnimator;
-
- private ArrayList<UserRecord> mUsers = new ArrayList<>();
- @VisibleForTesting
- AlertDialog mExitGuestDialog;
- @VisibleForTesting
- Dialog mAddUserDialog;
- private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
- private boolean mSimpleUserSwitcher;
- // When false, there won't be any visual affordance to add a new user from the keyguard even if
- // the user is unlocked
- private final MutableStateFlow<Boolean> mAddUsersFromLockScreen =
- StateFlowKt.MutableStateFlow(false);
- private boolean mUserSwitcherEnabled;
- @VisibleForTesting
- boolean mPauseRefreshUsers;
- private int mSecondaryUser = UserHandle.USER_NULL;
- private Intent mSecondaryUserServiceIntent;
- private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
- private final UiEventLogger mUiEventLogger;
- private final IActivityManager mActivityManager;
- private final Executor mBgExecutor;
- private final Executor mUiExecutor;
- private final Executor mLongRunningExecutor;
- private final boolean mGuestUserAutoCreated;
- private final AtomicBoolean mGuestIsResetting;
- private final AtomicBoolean mGuestCreationScheduled;
- private FalsingManager mFalsingManager;
- @Nullable
- private View mView;
- private String mCreateSupervisedUserPackage;
- private GlobalSettings mGlobalSettings;
- private List<UserSwitchCallback> mUserSwitchCallbacks =
- Collections.synchronizedList(new ArrayList<>());
-
- @Inject
- public UserSwitcherControllerOldImpl(
- Context context,
- IActivityManager activityManager,
- UserManager userManager,
- UserTracker userTracker,
- KeyguardStateController keyguardStateController,
- DeviceProvisionedController deviceProvisionedController,
- DevicePolicyManager devicePolicyManager,
- @Main Handler handler,
- ActivityStarter activityStarter,
- BroadcastDispatcher broadcastDispatcher,
- BroadcastSender broadcastSender,
- UiEventLogger uiEventLogger,
- FalsingManager falsingManager,
- TelephonyListenerManager telephonyListenerManager,
- SecureSettings secureSettings,
- GlobalSettings globalSettings,
- @Background Executor bgExecutor,
- @LongRunning Executor longRunningExecutor,
- @Main Executor uiExecutor,
- InteractionJankMonitor interactionJankMonitor,
- LatencyTracker latencyTracker,
- DumpManager dumpManager,
- DialogLaunchAnimator dialogLaunchAnimator,
- GuestResumeSessionReceiver guestResumeSessionReceiver,
- GuestResetOrExitSessionReceiver guestResetOrExitSessionReceiver) {
- mContext = context;
- mActivityManager = activityManager;
- mUserTracker = userTracker;
- mBroadcastDispatcher = broadcastDispatcher;
- mBroadcastSender = broadcastSender;
- mTelephonyListenerManager = telephonyListenerManager;
- mUiEventLogger = uiEventLogger;
- mFalsingManager = falsingManager;
- mInteractionJankMonitor = interactionJankMonitor;
- mLatencyTracker = latencyTracker;
- mGlobalSettings = globalSettings;
- mGuestResumeSessionReceiver = guestResumeSessionReceiver;
- mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver;
- mBgExecutor = bgExecutor;
- mLongRunningExecutor = longRunningExecutor;
- mUiExecutor = uiExecutor;
- mGuestResumeSessionReceiver.register();
- mGuestResetOrExitSessionReceiver.register();
- mGuestUserAutoCreated = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_guestUserAutoCreated);
- mGuestIsResetting = new AtomicBoolean();
- mGuestCreationScheduled = new AtomicBoolean();
- mKeyguardStateController = keyguardStateController;
- mDeviceProvisionedController = deviceProvisionedController;
- mDevicePolicyManager = devicePolicyManager;
- mHandler = handler;
- mActivityStarter = activityStarter;
- mUserManager = userManager;
- mDialogLaunchAnimator = dialogLaunchAnimator;
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_ADDED);
- filter.addAction(Intent.ACTION_USER_REMOVED);
- filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_STOPPED);
- filter.addAction(Intent.ACTION_USER_UNLOCKED);
- mBroadcastDispatcher.registerReceiver(
- mReceiver, filter, null /* executor */,
- UserHandle.SYSTEM, Context.RECEIVER_EXPORTED, null /* permission */);
-
- mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-
- mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class);
-
- filter = new IntentFilter();
- mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
- PERMISSION_SELF, null /* scheduler */,
- Context.RECEIVER_EXPORTED_UNAUDITED);
-
- mSettingsObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
- mAddUsersFromLockScreen.setValue(
- mGlobalSettings.getIntForUser(
- Settings.Global.ADD_USERS_WHEN_LOCKED,
- 0,
- UserHandle.USER_SYSTEM) != 0);
- mUserSwitcherEnabled = mGlobalSettings.getIntForUser(
- Settings.Global.USER_SWITCHER_ENABLED, 0, UserHandle.USER_SYSTEM) != 0;
- refreshUsers(UserHandle.USER_NULL);
- };
- };
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED),
- true, mSettingsObserver);
- // Fetch initial values.
- mSettingsObserver.onChange(false);
-
- keyguardStateController.addCallback(mCallback);
- listenForCallState();
-
- mCreateSupervisedUserPackage = mContext.getString(
- com.android.internal.R.string.config_supervisedUserCreationPackage);
-
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
- refreshUsers(UserHandle.USER_NULL);
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void refreshUsers(int forcePictureLoadForId) {
- if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")");
- if (forcePictureLoadForId != UserHandle.USER_NULL) {
- mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
- }
-
- if (mPauseRefreshUsers) {
- return;
- }
-
- boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL);
- SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
- final int userCount = mUsers.size();
- for (int i = 0; i < userCount; i++) {
- UserRecord r = mUsers.get(i);
- if (r == null || r.picture == null || r.info == null || forceAllUsers
- || mForcePictureLoadForUserId.get(r.info.id)) {
- continue;
- }
- bitmaps.put(r.info.id, r.picture);
- }
- mForcePictureLoadForUserId.clear();
-
- mBgExecutor.execute(() -> {
- List<UserInfo> infos = mUserManager.getAliveUsers();
- if (infos == null) {
- return;
- }
- ArrayList<UserRecord> records = new ArrayList<>(infos.size());
- int currentId = mUserTracker.getUserId();
- // Check user switchability of the foreground user since SystemUI is running in
- // User 0
- boolean canSwitchUsers = mUserManager.getUserSwitchability(
- UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
- UserRecord guestRecord = null;
-
- for (UserInfo info : infos) {
- boolean isCurrent = currentId == info.id;
- if (!mUserSwitcherEnabled && !info.isPrimary()) {
- continue;
- }
-
- if (info.isEnabled()) {
- if (info.isGuest()) {
- // Tapping guest icon triggers remove and a user switch therefore
- // the icon shouldn't be enabled even if the user is current
- guestRecord = LegacyUserDataHelper.createRecord(
- mContext,
- mUserManager,
- null /* picture */,
- info,
- isCurrent,
- canSwitchUsers);
- } else if (info.supportsSwitchToByUser()) {
- records.add(
- LegacyUserDataHelper.createRecord(
- mContext,
- mUserManager,
- bitmaps.get(info.id),
- info,
- isCurrent,
- canSwitchUsers));
- }
- }
- }
-
- if (guestRecord == null) {
- if (mGuestUserAutoCreated) {
- // If mGuestIsResetting=true, the switch should be disabled since
- // we will just use it as an indicator for "Resetting guest...".
- // Otherwise, default to canSwitchUsers.
- boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
- guestRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ENTER_GUEST_MODE,
- false /* isRestricted */,
- isSwitchToGuestEnabled);
- records.add(guestRecord);
- } else if (canCreateGuest(guestRecord != null)) {
- guestRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ENTER_GUEST_MODE,
- false /* isRestricted */,
- canSwitchUsers);
- records.add(guestRecord);
- }
- } else {
- records.add(guestRecord);
- }
-
- if (canCreateUser()) {
- final UserRecord userRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ADD_USER,
- createIsRestricted(),
- canSwitchUsers);
- records.add(userRecord);
- }
-
- if (canCreateSupervisedUser()) {
- final UserRecord userRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ADD_SUPERVISED_USER,
- createIsRestricted(),
- canSwitchUsers);
- records.add(userRecord);
- }
-
- if (canManageUsers()) {
- records.add(LegacyUserDataHelper.createRecord(
- mContext,
- KeyguardUpdateMonitor.getCurrentUser(),
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- /* isRestricted= */ false,
- /* isSwitchToEnabled= */ true
- ));
- }
-
- mUiExecutor.execute(() -> {
- if (records != null) {
- mUsers = records;
- notifyAdapters();
- }
- });
- });
- }
-
- private boolean systemCanCreateUsers() {
- return !mUserManager.hasBaseUserRestriction(
- UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
- }
-
- private boolean currentUserCanCreateUsers() {
- UserInfo currentUser = mUserTracker.getUserInfo();
- return currentUser != null
- && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
- && systemCanCreateUsers();
- }
-
- private boolean anyoneCanCreateUsers() {
- return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue();
- }
-
- @VisibleForTesting
- boolean canCreateGuest(boolean hasExistingGuest) {
- return mUserSwitcherEnabled
- && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
- && !hasExistingGuest;
- }
-
- @VisibleForTesting
- boolean canCreateUser() {
- return mUserSwitcherEnabled
- && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
- && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
- }
-
- @VisibleForTesting
- boolean canManageUsers() {
- UserInfo currentUser = mUserTracker.getUserInfo();
- return mUserSwitcherEnabled
- && ((currentUser != null && currentUser.isAdmin())
- || mAddUsersFromLockScreen.getValue());
- }
-
- private boolean createIsRestricted() {
- return !mAddUsersFromLockScreen.getValue();
- }
-
- @VisibleForTesting
- boolean canCreateSupervisedUser() {
- return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
- }
-
- private void pauseRefreshUsers() {
- if (!mPauseRefreshUsers) {
- mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
- mPauseRefreshUsers = true;
- }
- }
-
- private void notifyAdapters() {
- for (int i = mAdapters.size() - 1; i >= 0; i--) {
- BaseUserSwitcherAdapter adapter = mAdapters.get(i).get();
- if (adapter != null) {
- adapter.notifyDataSetChanged();
- } else {
- mAdapters.remove(i);
- }
- }
- }
-
- @Override
- public boolean isSimpleUserSwitcher() {
- return mSimpleUserSwitcher;
- }
-
- /**
- * Returns whether the current user is a system user.
- */
- @VisibleForTesting
- boolean isSystemUser() {
- return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
- }
-
- @Override
- public @Nullable UserRecord getCurrentUserRecord() {
- for (int i = 0; i < mUsers.size(); ++i) {
- UserRecord userRecord = mUsers.get(i);
- if (userRecord.isCurrent) {
- return userRecord;
- }
- }
- return null;
- }
-
- @Override
- public void onUserSelected(int userId, @Nullable DialogShower dialogShower) {
- UserRecord userRecord = mUsers.stream()
- .filter(x -> x.resolveId() == userId)
- .findFirst()
- .orElse(null);
- if (userRecord == null) {
- return;
- }
-
- onUserListItemClicked(userRecord, dialogShower);
- }
-
- @Override
- public Flow<Boolean> isAddUsersFromLockScreenEnabled() {
- return mAddUsersFromLockScreen;
- }
-
- @Override
- public boolean isGuestUserAutoCreated() {
- return mGuestUserAutoCreated;
- }
-
- @Override
- public boolean isGuestUserResetting() {
- return mGuestIsResetting.get();
- }
-
- @Override
- public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
- if (record.isGuest && record.info == null) {
- createAndSwitchToGuestUser(dialogShower);
- } else if (record.isAddUser) {
- showAddUserDialog(dialogShower);
- } else if (record.isAddSupervisedUser) {
- startSupervisedUserActivity();
- } else if (record.isManageUsers) {
- startActivity(new Intent(Settings.ACTION_USER_SETTINGS));
- } else {
- onUserListItemClicked(record.info.id, record, dialogShower);
- }
- }
-
- private void onUserListItemClicked(int id, UserRecord record, DialogShower dialogShower) {
- int currUserId = mUserTracker.getUserId();
- // If switching from guest and guest is ephemeral, then follow the flow
- // of showExitGuestDialog to remove current guest,
- // and switch to selected user
- UserInfo currUserInfo = mUserTracker.getUserInfo();
- if (currUserId == id) {
- if (record.isGuest) {
- showExitGuestDialog(id, currUserInfo.isEphemeral(), dialogShower);
- }
- return;
- }
-
- if (currUserInfo != null && currUserInfo.isGuest()) {
- showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
- record.resolveId(), dialogShower);
- return;
- }
-
- if (dialogShower != null) {
- // If we haven't morphed into another dialog, it means we have just switched users.
- // Then, dismiss the dialog.
- dialogShower.dismiss();
- }
- switchToUserId(id);
- }
-
- private void switchToUserId(int id) {
- try {
- if (mView != null) {
- mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
- .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
- .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
- }
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
- pauseRefreshUsers();
- mActivityManager.switchUser(id);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't switch user.", e);
- }
- }
-
- private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
- int newId = UserHandle.USER_SYSTEM;
- if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
- UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
- if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
- newId = info.id;
- }
- }
- showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
- }
-
- private void showExitGuestDialog(
- int id,
- boolean isGuestEphemeral,
- int targetId,
- DialogShower dialogShower) {
- if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
- mExitGuestDialog.cancel();
- }
- mExitGuestDialog = new ExitGuestDialog(
- mContext,
- id,
- isGuestEphemeral,
- targetId,
- mKeyguardStateController.isShowing(),
- mFalsingManager,
- mDialogLaunchAnimator,
- this::exitGuestUser);
- if (dialogShower != null) {
- dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
- InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
- INTERACTION_JANK_EXIT_GUEST_MODE_TAG));
- } else {
- mExitGuestDialog.show();
- }
- }
-
- @Override
- public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) {
- createGuestAsync(guestId -> {
- // guestId may be USER_NULL if we haven't reloaded the user list yet.
- if (guestId != UserHandle.USER_NULL) {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
- onUserListItemClicked(guestId, UserRecord.createForGuest(), dialogShower);
- }
- });
- }
-
- @Override
- public void showAddUserDialog(@Nullable DialogShower dialogShower) {
- if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
- mAddUserDialog.cancel();
- }
- final UserInfo currentUser = mUserTracker.getUserInfo();
- mAddUserDialog = new AddUserDialog(
- mContext,
- currentUser.getUserHandle(),
- mKeyguardStateController.isShowing(),
- /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(),
- mFalsingManager,
- mBroadcastSender,
- mDialogLaunchAnimator);
- if (dialogShower != null) {
- dialogShower.showDialog(mAddUserDialog,
- new DialogCuj(
- InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
- INTERACTION_JANK_ADD_NEW_USER_TAG
- ));
- } else {
- mAddUserDialog.show();
- }
- }
-
- @Override
- public void startSupervisedUserActivity() {
- final Intent intent = new Intent()
- .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
- .setPackage(mCreateSupervisedUserPackage)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- mContext.startActivity(intent);
- }
-
- private void listenForCallState() {
- mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
- }
-
- private final TelephonyCallback.CallStateListener mPhoneStateListener =
- new TelephonyCallback.CallStateListener() {
- private int mCallState;
-
- @Override
- public void onCallStateChanged(int state) {
- if (mCallState == state) return;
- if (DEBUG) Log.v(TAG, "Call state changed: " + state);
- mCallState = state;
- refreshUsers(UserHandle.USER_NULL);
- }
- };
-
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Log.v(TAG, "Broadcast: a=" + intent.getAction()
- + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
- }
-
- boolean unpauseRefreshUsers = false;
- int forcePictureLoadForId = UserHandle.USER_NULL;
-
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
- if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
- mExitGuestDialog.cancel();
- mExitGuestDialog = null;
- }
-
- final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- final UserInfo userInfo = mUserManager.getUserInfo(currentId);
- final int userCount = mUsers.size();
- for (int i = 0; i < userCount; i++) {
- UserRecord record = mUsers.get(i);
- if (record.info == null) continue;
- boolean shouldBeCurrent = record.info.id == currentId;
- if (record.isCurrent != shouldBeCurrent) {
- mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
- }
- if (shouldBeCurrent && !record.isGuest) {
- mLastNonGuestUser = record.info.id;
- }
- if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) {
- // Immediately remove restricted records in case the AsyncTask is too slow.
- mUsers.remove(i);
- i--;
- }
- }
- notifyUserSwitchCallbacks();
- notifyAdapters();
-
- // Disconnect from the old secondary user's service
- if (mSecondaryUser != UserHandle.USER_NULL) {
- context.stopServiceAsUser(mSecondaryUserServiceIntent,
- UserHandle.of(mSecondaryUser));
- mSecondaryUser = UserHandle.USER_NULL;
- }
- // Connect to the new secondary user's service (purely to ensure that a persistent
- // SystemUI application is created for that user)
- if (userInfo != null && userInfo.id != UserHandle.USER_SYSTEM) {
- context.startServiceAsUser(mSecondaryUserServiceIntent,
- UserHandle.of(userInfo.id));
- mSecondaryUser = userInfo.id;
- }
- unpauseRefreshUsers = true;
- if (mGuestUserAutoCreated) {
- // Guest user must be scheduled for creation AFTER switching to the target user.
- // This avoids lock contention which will produce UX bugs on the keyguard
- // (b/193933686).
- // TODO(b/191067027): Move guest user recreation to system_server
- guaranteeGuestPresent();
- }
- } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
- forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL);
- } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
- // Unlocking the system user may require a refresh
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId != UserHandle.USER_SYSTEM) {
- return;
- }
- }
- refreshUsers(forcePictureLoadForId);
- if (unpauseRefreshUsers) {
- mUnpauseRefreshUsers.run();
- }
- }
- };
-
- private final Runnable mUnpauseRefreshUsers = new Runnable() {
- @Override
- public void run() {
- mHandler.removeCallbacks(this);
- mPauseRefreshUsers = false;
- refreshUsers(UserHandle.USER_NULL);
- }
- };
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println("UserSwitcherController state:");
- pw.println(" mLastNonGuestUser=" + mLastNonGuestUser);
- pw.print(" mUsers.size="); pw.println(mUsers.size());
- for (int i = 0; i < mUsers.size(); i++) {
- final UserRecord u = mUsers.get(i);
- pw.print(" "); pw.println(u.toString());
- }
- pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher);
- pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated);
- }
-
- @Override
- public String getCurrentUserName() {
- if (mUsers.isEmpty()) return null;
- UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null);
- if (item == null || item.info == null) return null;
- if (item.isGuest) return mContext.getString(com.android.internal.R.string.guest_name);
- return item.info.name;
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- refreshUsers(UserHandle.USER_ALL);
- }
-
- @Override
- public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) {
- mAdapters.add(adapter);
- }
-
- @Override
- public ArrayList<UserRecord> getUsers() {
- return mUsers;
- }
-
- @Override
- public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) {
- UserInfo currentUser = mUserTracker.getUserInfo();
- if (currentUser.id != guestUserId) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not current user (" + currentUser.id + ")");
- return;
- }
- if (!currentUser.isGuest()) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not a guest");
- return;
- }
-
- boolean marked = mUserManager.markGuestForDeletion(currentUser.id);
- if (!marked) {
- Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId);
- return;
- }
-
- if (targetUserId == UserHandle.USER_NULL) {
- // Create a new guest in the foreground, and then immediately switch to it
- createGuestAsync(newGuestId -> {
- if (newGuestId == UserHandle.USER_NULL) {
- Log.e(TAG, "Could not create new guest, switching back to system user");
- switchToUserId(UserHandle.USER_SYSTEM);
- mUserManager.removeUser(currentUser.id);
- try {
- WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't remove guest because ActivityManager "
- + "or WindowManager is dead");
- }
- return;
- }
- switchToUserId(newGuestId);
- mUserManager.removeUser(currentUser.id);
- });
- } else {
- if (mGuestUserAutoCreated) {
- mGuestIsResetting.set(true);
- }
- switchToUserId(targetUserId);
- mUserManager.removeUser(currentUser.id);
- }
- }
-
- @Override
- public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
- boolean forceRemoveGuestOnExit) {
- UserInfo currentUser = mUserTracker.getUserInfo();
- if (currentUser.id != guestUserId) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not current user (" + currentUser.id + ")");
- return;
- }
- if (!currentUser.isGuest()) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not a guest");
- return;
- }
-
- int newUserId = UserHandle.USER_SYSTEM;
- if (targetUserId == UserHandle.USER_NULL) {
- // when target user is not specified switch to last non guest user
- if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
- UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
- if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
- newUserId = info.id;
- }
- }
- } else {
- newUserId = targetUserId;
- }
-
- if (currentUser.isEphemeral() || forceRemoveGuestOnExit) {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
- removeGuestUser(currentUser.id, newUserId);
- } else {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
- switchToUserId(newUserId);
- }
- }
-
- private void scheduleGuestCreation() {
- if (!mGuestCreationScheduled.compareAndSet(false, true)) {
- return;
- }
-
- mLongRunningExecutor.execute(() -> {
- int newGuestId = createGuest();
- mGuestCreationScheduled.set(false);
- mGuestIsResetting.set(false);
- if (newGuestId == UserHandle.USER_NULL) {
- Log.w(TAG, "Could not create new guest while exiting existing guest");
- // Refresh users so that we still display "Guest" if
- // config_guestUserAutoCreated=true
- refreshUsers(UserHandle.USER_NULL);
- }
- });
-
- }
-
- @Override
- public void schedulePostBootGuestCreation() {
- if (isDeviceAllowedToAddGuest()) {
- guaranteeGuestPresent();
- } else {
- mDeviceProvisionedController.addCallback(mGuaranteeGuestPresentAfterProvisioned);
- }
- }
-
- private boolean isDeviceAllowedToAddGuest() {
- return mDeviceProvisionedController.isDeviceProvisioned()
- && !mDevicePolicyManager.isDeviceManaged();
- }
-
- /**
- * If there is no guest on the device, schedule creation of a new guest user in the background.
- */
- private void guaranteeGuestPresent() {
- if (isDeviceAllowedToAddGuest() && mUserManager.findCurrentGuestUser() == null) {
- scheduleGuestCreation();
- }
- }
-
- private void createGuestAsync(Consumer<Integer> callback) {
- final Dialog guestCreationProgressDialog =
- new UserCreatingDialog(mContext, /* isGuest= */true);
- guestCreationProgressDialog.show();
-
- // userManager.createGuest will block the thread so post is needed for the dialog to show
- mBgExecutor.execute(() -> {
- final int guestId = createGuest();
- mUiExecutor.execute(() -> {
- guestCreationProgressDialog.dismiss();
- if (guestId == UserHandle.USER_NULL) {
- Toast.makeText(mContext,
- com.android.settingslib.R.string.add_guest_failed,
- Toast.LENGTH_SHORT).show();
- }
- callback.accept(guestId);
- });
- });
- }
-
- /**
- * Creates a guest user and return its multi-user user ID.
- *
- * This method does not check if a guest already exists before it makes a call to
- * {@link UserManager} to create a new one.
- *
- * @return The multi-user user ID of the newly created guest user, or
- * {@link UserHandle#USER_NULL} if the guest couldn't be created.
- */
- private @UserIdInt int createGuest() {
- UserInfo guest;
- try {
- guest = mUserManager.createGuest(mContext);
- } catch (UserManager.UserOperationException e) {
- Log.e(TAG, "Couldn't create guest user", e);
- return UserHandle.USER_NULL;
- }
- if (guest == null) {
- Log.e(TAG, "Couldn't create guest, most likely because there already exists one");
- return UserHandle.USER_NULL;
- }
- return guest.id;
- }
-
- @Override
- public void init(View view) {
- mView = view;
- }
-
- @Override
- public boolean isKeyguardShowing() {
- return mKeyguardStateController.isShowing();
- }
-
- private boolean shouldUseSimpleUserSwitcher() {
- int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
- return mGlobalSettings.getIntForUser(SIMPLE_USER_SWITCHER_GLOBAL_SETTING,
- defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0;
- }
-
- @Override
- public void startActivity(Intent intent) {
- mActivityStarter.startActivity(intent, /* dismissShade= */ true);
- }
-
- @Override
- public void addUserSwitchCallback(UserSwitchCallback callback) {
- mUserSwitchCallbacks.add(callback);
- }
-
- @Override
- public void removeUserSwitchCallback(UserSwitchCallback callback) {
- mUserSwitchCallbacks.remove(callback);
- }
-
- /**
- * Notify user switch callbacks that user has switched.
- */
- private void notifyUserSwitchCallbacks() {
- List<UserSwitchCallback> temp;
- synchronized (mUserSwitchCallbacks) {
- temp = new ArrayList<>(mUserSwitchCallbacks);
- }
- for (UserSwitchCallback callback : temp) {
- callback.onUserSwitched();
- }
- }
-
- private final KeyguardStateController.Callback mCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardShowingChanged() {
-
- // When Keyguard is going away, we don't need to update our items immediately
- // which
- // helps making the transition faster.
- if (!mKeyguardStateController.isShowing()) {
- mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters);
- } else {
- notifyAdapters();
- }
- }
- };
-
- private final DeviceProvisionedController.DeviceProvisionedListener
- mGuaranteeGuestPresentAfterProvisioned =
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- if (isDeviceAllowedToAddGuest()) {
- mBgExecutor.execute(
- () -> mDeviceProvisionedController.removeCallback(
- mGuaranteeGuestPresentAfterProvisioned));
- guaranteeGuestPresent();
- }
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 9866389..b135d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -28,6 +27,7 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Global;
@@ -46,7 +46,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.qs.SettingObserver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.Utils;
import com.android.systemui.util.settings.GlobalSettings;
@@ -58,14 +58,15 @@
/** Platform implementation of the zen mode controller. **/
@SysUISingleton
-public class ZenModeControllerImpl extends CurrentUserTracker
- implements ZenModeController, Dumpable {
+public class ZenModeControllerImpl implements ZenModeController, Dumpable {
private static final String TAG = "ZenModeController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private final Object mCallbacksLock = new Object();
private final Context mContext;
+ private final UserTracker mUserTracker;
+ private final BroadcastDispatcher mBroadcastDispatcher;
private final SettingObserver mModeSetting;
private final SettingObserver mConfigSetting;
private final NotificationManager mNoMan;
@@ -80,23 +81,45 @@
private long mZenUpdateTime;
private NotificationManager.Policy mConsolidatedNotificationPolicy;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, Context userContext) {
+ mUserId = newUser;
+ if (mRegistered) {
+ mBroadcastDispatcher.unregisterReceiver(mReceiver);
+ }
+ final IntentFilter filter = new IntentFilter(
+ AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+ mBroadcastDispatcher.registerReceiver(mReceiver, filter, null,
+ UserHandle.of(mUserId));
+ mRegistered = true;
+ mSetupObserver.register();
+ }
+ };
+
@Inject
public ZenModeControllerImpl(
Context context,
@Main Handler handler,
BroadcastDispatcher broadcastDispatcher,
DumpManager dumpManager,
- GlobalSettings globalSettings) {
- super(broadcastDispatcher);
+ GlobalSettings globalSettings,
+ UserTracker userTracker) {
mContext = context;
- mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE) {
+ mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
+ mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE,
+ userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
updateZenMode(value);
fireZenChanged(value);
}
};
- mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG) {
+ mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG,
+ userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
updateZenModeConfig();
@@ -112,7 +135,7 @@
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
mUserManager = context.getSystemService(UserManager.class);
- startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
@@ -120,7 +143,7 @@
@Override
public boolean isVolumeRestricted() {
return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME,
- new UserHandle(mUserId));
+ UserHandle.of(mUserId));
}
@Override
@@ -183,19 +206,6 @@
}
@Override
- public void onUserSwitched(int userId) {
- mUserId = userId;
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- }
- final IntentFilter filter = new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
- mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId), filter, null, null);
- mRegistered = true;
- mSetupObserver.register();
- }
-
- @Override
public ComponentName getEffectsSuppressor() {
return NotificationManager.from(mContext).getEffectsSuppressor();
}
@@ -208,7 +218,7 @@
@Override
public int getCurrentUser() {
- return ActivityManager.getCurrentUser();
+ return mUserTracker.getUserId();
}
private void fireNextAlarmChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b1b45b5..1b73539 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -58,8 +58,6 @@
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.statusbar.policy.WalletControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -198,8 +196,4 @@
static DataSaverController provideDataSaverController(NetworkController networkController) {
return networkController.getDataSaverController();
}
-
- /** Binds {@link UserSwitcherController} to its implementation. */
- @Binds
- UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
index 48df15c..93e78ac 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import androidx.annotation.VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
index c7f0b7e..f558fee 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.content.Context
import android.graphics.Canvas
@@ -31,11 +31,21 @@
class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
internal val ripples = ArrayList<RippleAnimation>()
+ private val listeners = ArrayList<RipplesFinishedListener>()
private val ripplePaint = Paint()
private var isWarningLogged = false
companion object {
- const val TAG = "MultiRippleView"
+ private const val TAG = "MultiRippleView"
+
+ interface RipplesFinishedListener {
+ /** Triggered when all the ripples finish running. */
+ fun onRipplesFinish()
+ }
+ }
+
+ fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
+ listeners.add(listener)
}
override fun onDraw(canvas: Canvas?) {
@@ -62,6 +72,10 @@
shouldInvalidate = shouldInvalidate || anim.isPlaying()
}
- if (shouldInvalidate) invalidate()
+ if (shouldInvalidate) {
+ invalidate()
+ } else { // Nothing is playing.
+ listeners.forEach { listener -> listener.onRipplesFinish() }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index aca9e25..b2f8994 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
index 8812254..ae73df2 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.Color
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index d2f3a6a..a950d34 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.PointF
import android.graphics.RuntimeShader
import android.util.MathUtils
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
/**
* Shader class that renders an expanding ripple effect. The ripple contains three elements:
@@ -31,7 +33,7 @@
* Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
*/
class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
- RuntimeShader(buildShader(rippleShape)) {
+ RuntimeShader(buildShader(rippleShape)) {
/** Shapes that the [RippleShader] supports. */
enum class RippleShape {
@@ -39,25 +41,30 @@
ROUNDED_BOX,
ELLIPSE
}
- //language=AGSL
+ // language=AGSL
companion object {
- private const val SHADER_UNIFORMS = """uniform vec2 in_center;
- uniform vec2 in_size;
- uniform float in_progress;
- uniform float in_cornerRadius;
- uniform float in_thickness;
- uniform float in_time;
- uniform float in_distort_radial;
- uniform float in_distort_xy;
- uniform float in_fadeSparkle;
- uniform float in_fadeFill;
- uniform float in_fadeRing;
- uniform float in_blur;
- uniform float in_pixelDensity;
- layout(color) uniform vec4 in_color;
- uniform float in_sparkle_strength;"""
+ private const val SHADER_UNIFORMS =
+ """
+ uniform vec2 in_center;
+ uniform vec2 in_size;
+ uniform float in_progress;
+ uniform float in_cornerRadius;
+ uniform float in_thickness;
+ uniform float in_time;
+ uniform float in_distort_radial;
+ uniform float in_distort_xy;
+ uniform float in_fadeSparkle;
+ uniform float in_fadeFill;
+ uniform float in_fadeRing;
+ uniform float in_blur;
+ uniform float in_pixelDensity;
+ layout(color) uniform vec4 in_color;
+ uniform float in_sparkle_strength;
+ """
- private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
+ private const val SHADER_CIRCLE_MAIN =
+ """
+ vec4 main(vec2 p) {
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float radius = in_size.x * 0.5;
float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
@@ -73,7 +80,9 @@
}
"""
- private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
+ private const val SHADER_ROUNDED_BOX_MAIN =
+ """
+ vec4 main(vec2 p) {
float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
in_thickness), in_blur);
float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
@@ -89,7 +98,9 @@
}
"""
- private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
+ private const val SHADER_ELLIPSE_MAIN =
+ """
+ vec4 main(vec2 p) {
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
@@ -105,22 +116,31 @@
}
"""
- private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
- SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
+ private const val CIRCLE_SHADER =
+ SHADER_UNIFORMS +
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.CIRCLE_SDF +
SHADER_CIRCLE_MAIN
- private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
- RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
- SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
- private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
- SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
+ private const val ROUNDED_BOX_SHADER =
+ SHADER_UNIFORMS +
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.ROUNDED_BOX_SDF +
+ SHADER_ROUNDED_BOX_MAIN
+ private const val ELLIPSE_SHADER =
+ SHADER_UNIFORMS +
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.ELLIPSE_SDF +
SHADER_ELLIPSE_MAIN
private fun buildShader(rippleShape: RippleShape): String =
- when (rippleShape) {
- RippleShape.CIRCLE -> CIRCLE_SHADER
- RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
- RippleShape.ELLIPSE -> ELLIPSE_SHADER
- }
+ when (rippleShape) {
+ RippleShape.CIRCLE -> CIRCLE_SHADER
+ RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
+ RippleShape.ELLIPSE -> ELLIPSE_SHADER
+ }
private fun subProgress(start: Float, end: Float, progress: Float): Float {
val min = Math.min(start, end)
@@ -130,9 +150,7 @@
}
}
- /**
- * Sets the center position of the ripple.
- */
+ /** Sets the center position of the ripple. */
fun setCenter(x: Float, y: Float) {
setFloatUniform("in_center", x, y)
}
@@ -144,21 +162,21 @@
maxSize.y = height
}
- /**
- * Progress of the ripple. Float value between [0, 1].
- */
+ /** Progress of the ripple. Float value between [0, 1]. */
var progress: Float = 0.0f
set(value) {
field = value
setFloatUniform("in_progress", value)
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
- setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
- /* height= */ maxSize.y * curvedProg)
+ setFloatUniform(
+ "in_size",
+ /* width= */ maxSize.x * curvedProg,
+ /* height= */ maxSize.y * curvedProg
+ )
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
// radius should not exceed width and height values.
- setFloatUniform("in_cornerRadius",
- Math.min(maxSize.x, maxSize.y) * curvedProg)
+ setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
@@ -175,18 +193,14 @@
setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
}
- /**
- * Play time since the start of the effect.
- */
+ /** Play time since the start of the effect. */
var time: Float = 0.0f
set(value) {
field = value
setFloatUniform("in_time", value)
}
- /**
- * A hex value representing the ripple color, in the format of ARGB
- */
+ /** A hex value representing the ripple color, in the format of ARGB */
var color: Int = 0xffffff
set(value) {
field = value
@@ -194,9 +208,9 @@
}
/**
- * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus
- * with strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1
- * it's opaque white and looks the most grainy.
+ * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with
+ * strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's
+ * opaque white and looks the most grainy.
*/
var sparkleStrength: Float = 0.0f
set(value) {
@@ -204,9 +218,7 @@
setFloatUniform("in_sparkle_strength", value)
}
- /**
- * Distortion strength of the ripple. Expected value between[0, 1].
- */
+ /** Distortion strength of the ripple. Expected value between[0, 1]. */
var distortionStrength: Float = 0.0f
set(value) {
field = value
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index a6d7930..2994694 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -26,7 +26,7 @@
import android.util.AttributeSet
import android.view.View
import androidx.core.graphics.ColorUtils
-import com.android.systemui.ripple.RippleShader.RippleShape
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape
/**
* A generic expanding ripple effect.
@@ -98,15 +98,18 @@
rippleShader.time = now.toFloat()
invalidate()
}
- animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- onAnimationEnd?.run()
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ onAnimationEnd?.run()
+ }
}
- })
+ )
animator.start()
}
- /** Set the color to be used for the ripple.
+ /**
+ * Set the color to be used for the ripple.
*
* The alpha value of the color will be applied to the ripple. The alpha range is [0-100].
*/
@@ -123,9 +126,7 @@
rippleShader.rippleFill = rippleFill
}
- /**
- * Set the intensity of the sparkles.
- */
+ /** Set the intensity of the sparkles. */
fun setSparkleStrength(strength: Float) {
rippleShader.sparkleStrength = strength
}
@@ -143,20 +144,30 @@
// active effect area. Values here should be kept in sync with the animation implementation
// in the ripple shader.
if (rippleShape == RippleShape.CIRCLE) {
- val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxWidth
+ val maskRadius =
+ (1 -
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth
canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
} else {
- val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxWidth * 2
- val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxHeight * 2
+ val maskWidth =
+ (1 -
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth * 2
+ val maskHeight =
+ (1 -
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxHeight * 2
canvas.drawRect(
- /* left= */ centerX - maskWidth,
- /* top= */ centerY - maskHeight,
- /* right= */ centerX + maskWidth,
- /* bottom= */ centerY + maskHeight,
- ripplePaint)
+ /* left= */ centerX - maskWidth,
+ /* top= */ centerY - maskHeight,
+ /* right= */ centerX + maskWidth,
+ /* bottom= */ centerY + maskHeight,
+ ripplePaint
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
index 5e256c6..8b2f466 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.shaderutil
/** Library class that contains 2D signed distance functions. */
class SdfShaderLibrary {
- //language=AGSL
+ // language=AGSL
companion object {
- const val CIRCLE_SDF = """
+ const val CIRCLE_SDF =
+ """
float sdCircle(vec2 p, float r) {
return (length(p)-r) / r;
}
@@ -34,7 +35,8 @@
}
"""
- const val ROUNDED_BOX_SDF = """
+ const val ROUNDED_BOX_SDF =
+ """
float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
size *= 0.5;
cornerRadius *= 0.5;
@@ -58,7 +60,8 @@
// Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
// This is more expensive than the regular circle SDF, recommend to use the circle SDF if
// possible.
- const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
+ const val ELLIPSE_SDF =
+ """float sdEllipse(vec2 p, vec2 wh) {
wh *= 0.5;
// symmetry
@@ -98,7 +101,8 @@
}
"""
- const val SHADER_SDF_OPERATION_LIB = """
+ const val SHADER_SDF_OPERATION_LIB =
+ """
float soften(float d, float blur) {
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
new file mode 100644
index 0000000..d78e0c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 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.surfaceeffects.shaderutil
+
+/** A common utility functions that are used for computing shaders. */
+class ShaderUtilLibrary {
+ // language=AGSL
+ companion object {
+ const val SHADER_LIB =
+ """
+ float triangleNoise(vec2 n) {
+ n = fract(n * vec2(5.3987, 5.4421));
+ n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
+ float xy = n.x * n.y;
+ // compute in [0..2[ and remap to [-1.0..1.0[
+ return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+ }
+
+ const float PI = 3.1415926535897932384626;
+
+ float sparkles(vec2 uv, float t) {
+ float n = triangleNoise(uv);
+ float s = 0.0;
+ for (float i = 0; i < 4; i += 1) {
+ float l = i * 0.01;
+ float h = l + 0.1;
+ float o = smoothstep(n - l, h, n);
+ o *= abs(sin(PI * o * (t + 0.55 * i)));
+ s += o;
+ }
+ return s;
+ }
+
+ vec2 distort(vec2 p, float time, float distort_amount_radial,
+ float distort_amount_xy) {
+ float angle = atan(p.y, p.x);
+ return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+ cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+ + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+ cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
+ }
+
+ // Return range [-1, 1].
+ vec3 hash(vec3 p) {
+ p = fract(p * vec3(.3456, .1234, .9876));
+ p += dot(p, p.yxz + 43.21);
+ p = (p.xxy + p.yxx) * p.zyx;
+ return (fract(sin(p) * 4567.1234567) - .5) * 2.;
+ }
+
+ // Skew factors (non-uniform).
+ const float SKEW = 0.3333333; // 1/3
+ const float UNSKEW = 0.1666667; // 1/6
+
+ // Return range roughly [-1,1].
+ // It's because the hash function (that returns a random gradient vector) returns
+ // different magnitude of vectors. Noise doesn't have to be in the precise range thus
+ // skipped normalize.
+ float simplex3d(vec3 p) {
+ // Skew the input coordinate, so that we get squashed cubical grid
+ vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
+
+ // Unskew back
+ vec3 u = s - (s.x + s.y + s.z) * UNSKEW;
+
+ // Unskewed coordinate that is relative to p, to compute the noise contribution
+ // based on the distance.
+ vec3 c0 = p - u;
+
+ // We have six simplices (in this case tetrahedron, since we are in 3D) that we
+ // could possibly in.
+ // Here, we are finding the correct tetrahedron (simplex shape), and traverse its
+ // four vertices (c0..3) when computing noise contribution.
+ // The way we find them is by comparing c0's x,y,z values.
+ // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in
+ // by comparing x and y values. i.e. x>y lower, x<y, upper triangle.
+ // Same applies in 3D.
+ //
+ // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0)
+ // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1)
+ // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1)
+ // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1)
+ // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1)
+ // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1)
+ // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1)
+ //
+ // The rule is:
+ // * For offset1, set 1 at the max component, otherwise 0.
+ // * For offset2, set 0 at the min component, otherwise 1.
+ // * For offset3, set 1 for all.
+ //
+ // Encode x0-y0, y0-z0, z0-x0 in a vec3
+ vec3 en = c0 - c0.yzx;
+ // Each represents whether x0>y0, y0>z0, z0>x0
+ en = step(vec3(0.), en);
+ // en.zxy encodes z0>x0, x0>y0, y0>x0
+ vec3 offset1 = en * (1. - en.zxy); // find max
+ vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min)
+ vec3 offset3 = vec3(1.);
+
+ vec3 c1 = c0 - offset1 + UNSKEW;
+ vec3 c2 = c0 - offset2 + UNSKEW * 2.;
+ vec3 c3 = c0 - offset3 + UNSKEW * 3.;
+
+ // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution)
+ //
+ // First compute d^2, squared distance to the point.
+ vec4 w; // w = max(0, r^2 - d^2))
+ w.x = dot(c0, c0);
+ w.y = dot(c1, c1);
+ w.z = dot(c2, c2);
+ w.w = dot(c3, c3);
+
+ // Noise contribution should decay to zero before they cross the simplex boundary.
+ // Usually r^2 is 0.5 or 0.6;
+ // 0.5 ensures continuity but 0.6 increases the visual quality for the application
+ // where discontinuity isn't noticeable.
+ w = max(0.6 - w, 0.);
+
+ // Noise contribution from each point.
+ vec4 nc;
+ nc.x = dot(hash(s), c0);
+ nc.y = dot(hash(s + offset1), c1);
+ nc.z = dot(hash(s + offset2), c2);
+ nc.w = dot(hash(s + offset3), c3);
+
+ nc *= w*w*w*w;
+
+ // Add all the noise contributions.
+ // Should multiply by the possible max contribution to adjust the range in [-1,1].
+ return dot(vec4(32.), nc);
+ }
+ """
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
new file mode 100644
index 0000000..5ac3aad7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.surfaceeffects.turbulencenoise
+
+import android.graphics.BlendMode
+import android.graphics.Color
+
+/** Turbulence noise animation configuration. */
+data class TurbulenceNoiseAnimationConfig(
+ /** The number of grids that is used to generate noise. */
+ val gridCount: Float = DEFAULT_NOISE_GRID_COUNT,
+
+ /** Multiplier for the noise luma matte. Increase this for brighter effects. */
+ val luminosityMultiplier: Float = DEFAULT_LUMINOSITY_MULTIPLIER,
+
+ /**
+ * Noise move speed variables.
+ *
+ * Its sign determines the direction; magnitude determines the speed. <ul>
+ * ```
+ * <li> [noiseMoveSpeedX] positive: right to left; negative: left to right.
+ * <li> [noiseMoveSpeedY] positive: bottom to top; negative: top to bottom.
+ * <li> [noiseMoveSpeedZ] its sign doesn't matter much, as it moves in Z direction. Use it
+ * to add turbulence in place.
+ * ```
+ * </ul>
+ */
+ val noiseMoveSpeedX: Float = 0f,
+ val noiseMoveSpeedY: Float = 0f,
+ val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z,
+
+ /** Color of the effect. */
+ var color: Int = DEFAULT_COLOR,
+ /** Background color of the effect. */
+ val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
+ val opacity: Int = DEFAULT_OPACITY,
+ val width: Float = 0f,
+ val height: Float = 0f,
+ val duration: Float = DEFAULT_NOISE_DURATION_IN_MILLIS,
+ val pixelDensity: Float = 1f,
+ val blendMode: BlendMode = DEFAULT_BLEND_MODE,
+ val onAnimationEnd: Runnable? = null
+) {
+ companion object {
+ const val DEFAULT_NOISE_DURATION_IN_MILLIS = 7500F
+ const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f
+ const val DEFAULT_NOISE_GRID_COUNT = 1.2f
+ const val DEFAULT_NOISE_SPEED_Z = 0.3f
+ const val DEFAULT_OPACITY = 150 // full opacity is 255.
+ const val DEFAULT_COLOR = Color.WHITE
+ const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
+ val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
new file mode 100644
index 0000000..4c7e5f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.surfaceeffects.turbulencenoise
+
+/** A controller that plays [TurbulenceNoiseView]. */
+class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
+ /** Updates the color of the noise. */
+ fun updateNoiseColor(color: Int) {
+ turbulenceNoiseView.updateColor(color)
+ }
+
+ // TODO: add cancel and/ or pause once design requirements become clear.
+ /** Plays [TurbulenceNoiseView] with the given config. */
+ fun play(turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig) {
+ turbulenceNoiseView.play(turbulenceNoiseAnimationConfig)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
new file mode 100644
index 0000000..19c114d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 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.surfaceeffects.turbulencenoise
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+import java.lang.Float.max
+
+/** Shader that renders turbulence simplex noise, with no octave. */
+class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
+ // language=AGSL
+ companion object {
+ private const val UNIFORMS =
+ """
+ uniform float in_gridNum;
+ uniform vec3 in_noiseMove;
+ uniform vec2 in_size;
+ uniform float in_aspectRatio;
+ uniform float in_opacity;
+ uniform float in_pixelDensity;
+ layout(color) uniform vec4 in_color;
+ layout(color) uniform vec4 in_backgroundColor;
+ """
+
+ private const val SHADER_LIB =
+ """
+ float getLuminosity(vec3 c) {
+ return 0.3*c.r + 0.59*c.g + 0.11*c.b;
+ }
+
+ vec3 maskLuminosity(vec3 dest, float lum) {
+ dest.rgb *= vec3(lum);
+ // Clip back into the legal range
+ dest = clamp(dest, vec3(0.), vec3(1.0));
+ return dest;
+ }
+ """
+
+ private const val MAIN_SHADER =
+ """
+ vec4 main(vec2 p) {
+ vec2 uv = p / in_size.xy;
+ uv.x *= in_aspectRatio;
+
+ vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+ float luma = simplex3d(noiseP) * in_opacity;
+ vec3 mask = maskLuminosity(in_color.rgb, luma);
+ vec3 color = in_backgroundColor.rgb + mask * 0.6;
+
+ // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+ // shader here as we are in gamma space.
+ float dither = triangleNoise(p * in_pixelDensity) / 255.;
+
+ // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
+ // multiply rgb with a to get the correct result.
+ color = (color + dither.rrr) * in_color.a;
+ return vec4(color, in_color.a);
+ }
+ """
+
+ private const val TURBULENCE_NOISE_SHADER =
+ ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
+ }
+
+ /** Sets the number of grid for generating noise. */
+ fun setGridCount(gridNumber: Float = 1.0f) {
+ setFloatUniform("in_gridNum", gridNumber)
+ }
+
+ /**
+ * Sets the pixel density of the screen.
+ *
+ * Used it for noise dithering.
+ */
+ fun setPixelDensity(pixelDensity: Float) {
+ setFloatUniform("in_pixelDensity", pixelDensity)
+ }
+
+ /** Sets the noise color of the effect. */
+ fun setColor(color: Int) {
+ setColorUniform("in_color", color)
+ }
+
+ /** Sets the background color of the effect. */
+ fun setBackgroundColor(color: Int) {
+ setColorUniform("in_backgroundColor", color)
+ }
+
+ /**
+ * Sets the opacity to achieve fade in/ out of the animation.
+ *
+ * Expected value range is [1, 0].
+ */
+ fun setOpacity(opacity: Float) {
+ setFloatUniform("in_opacity", opacity)
+ }
+
+ /** Sets the size of the shader. */
+ fun setSize(width: Float, height: Float) {
+ setFloatUniform("in_size", width, height)
+ setFloatUniform("in_aspectRatio", width / max(height, 0.001f))
+ }
+
+ /** Sets noise move speed in x, y, and z direction. */
+ fun setNoiseMove(x: Float, y: Float, z: Float) {
+ setFloatUniform("in_noiseMove", x, y, z)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
new file mode 100644
index 0000000..8649d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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.surfaceeffects.turbulencenoise
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.ColorUtils
+import java.util.Random
+import kotlin.math.sin
+
+/** View that renders turbulence noise effect. */
+class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+
+ companion object {
+ private const val MS_TO_SEC = 0.001f
+ private const val TWO_PI = Math.PI.toFloat() * 2f
+ }
+
+ @VisibleForTesting val turbulenceNoiseShader = TurbulenceNoiseShader()
+ private val paint = Paint().apply { this.shader = turbulenceNoiseShader }
+ private val random = Random()
+ private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+ private var config: TurbulenceNoiseAnimationConfig? = null
+
+ val isPlaying: Boolean
+ get() = animator.isRunning
+
+ init {
+ // Only visible during the animation.
+ visibility = INVISIBLE
+ }
+
+ /** Updates the color during the animation. No-op if there's no animation playing. */
+ fun updateColor(color: Int) {
+ config?.let {
+ it.color = color
+ applyConfig(it)
+ }
+ }
+
+ override fun onDraw(canvas: Canvas?) {
+ if (canvas == null || !canvas.isHardwareAccelerated) {
+ // Drawing with the turbulence noise shader requires hardware acceleration, so skip
+ // if it's unsupported.
+ return
+ }
+
+ canvas.drawPaint(paint)
+ }
+
+ internal fun play(config: TurbulenceNoiseAnimationConfig) {
+ if (isPlaying) {
+ return // Ignore if the animation is playing.
+ }
+ visibility = VISIBLE
+ applyConfig(config)
+
+ // Add random offset to avoid same patterned noise.
+ val offsetX = random.nextFloat()
+ val offsetY = random.nextFloat()
+
+ animator.duration = config.duration.toLong()
+ animator.addUpdateListener { updateListener ->
+ val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
+ // Remap [0,1] to [0, 2*PI]
+ val progress = TWO_PI * updateListener.animatedValue as Float
+
+ turbulenceNoiseShader.setNoiseMove(
+ offsetX + timeInSec * config.noiseMoveSpeedX,
+ offsetY + timeInSec * config.noiseMoveSpeedY,
+ timeInSec * config.noiseMoveSpeedZ
+ )
+
+ // Fade in and out the noise as the animation progress.
+ // TODO: replace it with a better curve
+ turbulenceNoiseShader.setOpacity(sin(TWO_PI - progress) * config.luminosityMultiplier)
+
+ invalidate()
+ }
+
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ visibility = INVISIBLE
+ config.onAnimationEnd?.run()
+ }
+ }
+ )
+ animator.start()
+ }
+
+ private fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
+ this.config = config
+ with(turbulenceNoiseShader) {
+ setGridCount(config.gridCount)
+ setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+ setBackgroundColor(config.backgroundColor)
+ setSize(config.width, config.height)
+ setPixelDensity(config.pixelDensity)
+ }
+ paint.blendMode = config.blendMode
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 3507cb7..095718b 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -46,6 +46,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.CoreStartable;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.util.NotificationChannels;
@@ -61,15 +62,24 @@
private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME";
private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD";
private final Context mContext;
+ private final BroadcastDispatcher mBroadcastDispatcher;
// TODO: delay some notifications to avoid bumpy fast operations
- private NotificationManager mNotificationManager;
- private StorageManager mStorageManager;
+ private final NotificationManager mNotificationManager;
+ private final StorageManager mStorageManager;
@Inject
- public StorageNotification(Context context) {
+ public StorageNotification(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ NotificationManager notificationManager,
+ StorageManager storageManager
+ ) {
mContext = context;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mNotificationManager = notificationManager;
+ mStorageManager = storageManager;
}
private static class MoveInfo {
@@ -168,17 +178,22 @@
@Override
public void start() {
- mNotificationManager = mContext.getSystemService(NotificationManager.class);
-
- mStorageManager = mContext.getSystemService(StorageManager.class);
mStorageManager.registerListener(mListener);
- mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
- android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
- Context.RECEIVER_EXPORTED_UNAUDITED);
- mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),
- android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
- Context.RECEIVER_EXPORTED_UNAUDITED);
+ mBroadcastDispatcher.registerReceiver(
+ mSnoozeReceiver,
+ new IntentFilter(ACTION_SNOOZE_VOLUME),
+ null,
+ null,
+ Context.RECEIVER_EXPORTED_UNAUDITED,
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ mBroadcastDispatcher.registerReceiver(
+ mFinishReceiver,
+ new IntentFilter(ACTION_FINISH_WIZARD),
+ null,
+ null,
+ Context.RECEIVER_EXPORTED_UNAUDITED,
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
// Kick current state into place
final List<DiskInfo> disks = mStorageManager.getDisks();
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index ffaf524..ed53de7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -19,31 +19,18 @@
import android.content.Context
import android.content.pm.UserInfo
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.internal.util.UserIcons
-import com.android.systemui.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.util.concurrent.atomic.AtomicBoolean
@@ -55,7 +42,6 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -72,15 +58,6 @@
* upstream changes.
*/
interface UserRepository {
- /** List of all users on the device. */
- val users: Flow<List<UserModel>>
-
- /** The currently-selected user. */
- val selectedUser: Flow<UserModel>
-
- /** List of available user-related actions. */
- val actions: Flow<List<UserActionModel>>
-
/** User switcher related settings. */
val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
@@ -93,9 +70,6 @@
/** User ID of the last non-guest selected user. */
val lastSelectedNonGuestUserId: Int
- /** Whether actions are available even when locked. */
- val isActionableWhenLocked: Flow<Boolean>
-
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean
@@ -125,18 +99,13 @@
constructor(
@Application private val appContext: Context,
private val manager: UserManager,
- private val controller: UserSwitcherController,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val globalSettings: GlobalSettings,
private val tracker: UserTracker,
- private val featureFlags: FeatureFlags,
) : UserRepository {
- private val isNewImpl: Boolean
- get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
_userSwitcherSettings.asStateFlow().filterNotNull()
@@ -150,58 +119,11 @@
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
private set
- private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
- fun send() {
- trySendWithFailureLogging(
- controller.users,
- TAG,
- )
- }
-
- val callback = UserSwitcherController.UserSwitchCallback { send() }
-
- controller.addUserSwitchCallback(callback)
- send()
-
- awaitClose { controller.removeUserSwitchCallback(callback) }
- }
-
- override val users: Flow<List<UserModel>> =
- userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } }
-
- override val selectedUser: Flow<UserModel> =
- users.map { users -> users.first { user -> user.isSelected } }
-
- override val actions: Flow<List<UserActionModel>> =
- userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
-
- override val isActionableWhenLocked: Flow<Boolean> =
- if (isNewImpl) {
- emptyFlow()
- } else {
- controller.isAddUsersFromLockScreenEnabled
- }
-
override val isGuestUserAutoCreated: Boolean =
- if (isNewImpl) {
- appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
- } else {
- controller.isGuestUserAutoCreated
- }
+ appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
private var _isGuestUserResetting: Boolean = false
- override var isGuestUserResetting: Boolean =
- if (isNewImpl) {
- _isGuestUserResetting
- } else {
- controller.isGuestUserResetting
- }
- set(value) =
- if (isNewImpl) {
- _isGuestUserResetting = value
- } else {
- error("Not supported in the old implementation!")
- }
+ override var isGuestUserResetting: Boolean = _isGuestUserResetting
override val isGuestUserCreationScheduled = AtomicBoolean()
@@ -210,10 +132,8 @@
override var isRefreshUsersPaused: Boolean = false
init {
- if (isNewImpl) {
- observeSelectedUser()
- observeUserSettings()
- }
+ observeSelectedUser()
+ observeUserSettings()
}
override fun refreshUsers() {
@@ -327,64 +247,6 @@
}
}
- private fun UserRecord.isUser(): Boolean {
- return when {
- isAddUser -> false
- isAddSupervisedUser -> false
- isManageUsers -> false
- isGuest -> info != null
- else -> true
- }
- }
-
- private fun UserRecord.isNotUser(): Boolean {
- return !isUser()
- }
-
- private fun UserRecord.toUserModel(): UserModel {
- return UserModel(
- id = resolveId(),
- name = getUserName(this),
- image = getUserImage(this),
- isSelected = isCurrent,
- isSelectable = isSwitchToEnabled || isGuest,
- isGuest = isGuest,
- )
- }
-
- private fun UserRecord.toActionModel(): UserActionModel {
- return when {
- isAddUser -> UserActionModel.ADD_USER
- isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
- isGuest -> UserActionModel.ENTER_GUEST_MODE
- isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
- else -> error("Don't know how to convert to UserActionModel: $this")
- }
- }
-
- private fun getUserName(record: UserRecord): Text {
- val resourceId: Int? = LegacyUserUiHelper.getGuestUserRecordNameResourceId(record)
- return if (resourceId != null) {
- Text.Resource(resourceId)
- } else {
- Text.Loaded(checkNotNull(record.info).name)
- }
- }
-
- private fun getUserImage(record: UserRecord): Drawable {
- if (record.isGuest) {
- return checkNotNull(
- AppCompatResources.getDrawable(appContext, R.drawable.ic_account_circle)
- )
- }
-
- val userId = checkNotNull(record.info?.id)
- return manager.getUserIcon(userId)?.let { userSelectedIcon ->
- BitmapDrawable(userSelectedIcon)
- }
- ?: UserIcons.getDefaultUserIcon(appContext.resources, userId, /* light= */ false)
- }
-
companion object {
private const val TAG = "UserRepository"
@VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index dda78aa..6b81bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -39,12 +39,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -64,8 +61,6 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -82,10 +77,8 @@
constructor(
@Application private val applicationContext: Context,
private val repository: UserRepository,
- private val controller: UserSwitcherController,
private val activityStarter: ActivityStarter,
private val keyguardInteractor: KeyguardInteractor,
- private val featureFlags: FeatureFlags,
private val manager: UserManager,
@Application private val applicationScope: CoroutineScope,
telephonyInteractor: TelephonyInteractor,
@@ -107,9 +100,6 @@
fun onUserStateChanged()
}
- private val isNewImpl: Boolean
- get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
private val supervisedUserPackageName: String?
get() =
applicationContext.getString(
@@ -122,181 +112,146 @@
/** List of current on-device users to select from. */
val users: Flow<List<UserModel>>
get() =
- if (isNewImpl) {
- combine(
- repository.userInfos,
- repository.selectedUserInfo,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, settings ->
- toUserModels(
- userInfos = userInfos,
- selectedUserId = selectedUserInfo.id,
- isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
- )
- }
- } else {
- repository.users
+ combine(
+ repository.userInfos,
+ repository.selectedUserInfo,
+ repository.userSwitcherSettings,
+ ) { userInfos, selectedUserInfo, settings ->
+ toUserModels(
+ userInfos = userInfos,
+ selectedUserId = selectedUserInfo.id,
+ isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+ )
}
/** The currently-selected user. */
val selectedUser: Flow<UserModel>
get() =
- if (isNewImpl) {
- combine(
- repository.selectedUserInfo,
- repository.userSwitcherSettings,
- ) { selectedUserInfo, settings ->
- val selectedUserId = selectedUserInfo.id
- checkNotNull(
- toUserModel(
- userInfo = selectedUserInfo,
- selectedUserId = selectedUserId,
- canSwitchUsers = canSwitchUsers(selectedUserId),
- isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
- )
+ combine(
+ repository.selectedUserInfo,
+ repository.userSwitcherSettings,
+ ) { selectedUserInfo, settings ->
+ val selectedUserId = selectedUserInfo.id
+ checkNotNull(
+ toUserModel(
+ userInfo = selectedUserInfo,
+ selectedUserId = selectedUserId,
+ canSwitchUsers = canSwitchUsers(selectedUserId),
+ isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
)
- }
- } else {
- repository.selectedUser
+ )
}
/** List of user-switcher related actions that are available. */
val actions: Flow<List<UserActionModel>>
get() =
- if (isNewImpl) {
- combine(
- repository.selectedUserInfo,
- repository.userInfos,
- repository.userSwitcherSettings,
- keyguardInteractor.isKeyguardShowing,
- ) { _, userInfos, settings, isDeviceLocked ->
- buildList {
- val hasGuestUser = userInfos.any { it.isGuest }
- if (
- !hasGuestUser &&
- (guestUserInteractor.isGuestUserAutoCreated ||
- UserActionsUtil.canCreateGuest(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- ))
- ) {
- add(UserActionModel.ENTER_GUEST_MODE)
- }
-
- if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
- // The device is locked and our setting to allow actions that add users
- // from the lock-screen is not enabled. The guest action from above is
- // always allowed, even when the device is locked, but the various "add
- // user" actions below are not. We can finish building the list here.
-
- val canCreateUsers =
- UserActionsUtil.canCreateUser(
+ combine(
+ repository.selectedUserInfo,
+ repository.userInfos,
+ repository.userSwitcherSettings,
+ keyguardInteractor.isKeyguardShowing,
+ ) { _, userInfos, settings, isDeviceLocked ->
+ buildList {
+ val hasGuestUser = userInfos.any { it.isGuest }
+ if (
+ !hasGuestUser &&
+ (guestUserInteractor.isGuestUserAutoCreated ||
+ UserActionsUtil.canCreateGuest(
manager,
repository,
settings.isUserSwitcherEnabled,
settings.isAddUsersFromLockscreen,
- )
+ ))
+ ) {
+ add(UserActionModel.ENTER_GUEST_MODE)
+ }
- if (canCreateUsers) {
- add(UserActionModel.ADD_USER)
- }
+ if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
+ // The device is locked and our setting to allow actions that add users
+ // from the lock-screen is not enabled. The guest action from above is
+ // always allowed, even when the device is locked, but the various "add
+ // user" actions below are not. We can finish building the list here.
- if (
- UserActionsUtil.canCreateSupervisedUser(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- supervisedUserPackageName,
- )
- ) {
- add(UserActionModel.ADD_SUPERVISED_USER)
- }
- }
-
- if (
- UserActionsUtil.canManageUsers(
+ val canCreateUsers =
+ UserActionsUtil.canCreateUser(
+ manager,
repository,
settings.isUserSwitcherEnabled,
settings.isAddUsersFromLockscreen,
)
- ) {
- add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+ if (canCreateUsers) {
+ add(UserActionModel.ADD_USER)
}
+
+ if (
+ UserActionsUtil.canCreateSupervisedUser(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ supervisedUserPackageName,
+ )
+ ) {
+ add(UserActionModel.ADD_SUPERVISED_USER)
+ }
+ }
+
+ if (
+ UserActionsUtil.canManageUsers(
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
+ ) {
+ add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
}
}
- } else {
- combine(
- repository.isActionableWhenLocked,
- keyguardInteractor.isKeyguardShowing,
- ) { isActionableWhenLocked, isLocked ->
- isActionableWhenLocked || !isLocked
- }
- .flatMapLatest { isActionable ->
- if (isActionable) {
- repository.actions
- } else {
- // If not actionable it means that we're not allowed to show actions
- // when
- // locked and we are locked. Therefore, we should show no actions.
- flowOf(emptyList())
- }
- }
}
val userRecords: StateFlow<ArrayList<UserRecord>> =
- if (isNewImpl) {
- combine(
- repository.userInfos,
- repository.selectedUserInfo,
- actions,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, actionModels, settings ->
- ArrayList(
- userInfos.map {
+ combine(
+ repository.userInfos,
+ repository.selectedUserInfo,
+ actions,
+ repository.userSwitcherSettings,
+ ) { userInfos, selectedUserInfo, actionModels, settings ->
+ ArrayList(
+ userInfos.map {
+ toRecord(
+ userInfo = it,
+ selectedUserId = selectedUserInfo.id,
+ )
+ } +
+ actionModels.map {
toRecord(
- userInfo = it,
+ action = it,
selectedUserId = selectedUserInfo.id,
+ isRestricted =
+ it != UserActionModel.ENTER_GUEST_MODE &&
+ it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
+ !settings.isAddUsersFromLockscreen,
)
- } +
- actionModels.map {
- toRecord(
- action = it,
- selectedUserId = selectedUserInfo.id,
- isRestricted =
- it != UserActionModel.ENTER_GUEST_MODE &&
- it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
- !settings.isAddUsersFromLockscreen,
- )
- }
- )
- }
- .onEach { notifyCallbacks() }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = ArrayList(),
+ }
)
- } else {
- MutableStateFlow(ArrayList())
- }
+ }
+ .onEach { notifyCallbacks() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = ArrayList(),
+ )
val selectedUserRecord: StateFlow<UserRecord?> =
- if (isNewImpl) {
- repository.selectedUserInfo
- .map { selectedUserInfo ->
- toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = null,
- )
- } else {
- MutableStateFlow(null)
- }
+ repository.selectedUserInfo
+ .map { selectedUserInfo ->
+ toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
@@ -311,44 +266,37 @@
val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
val isSimpleUserSwitcher: Boolean
- get() =
- if (isNewImpl) {
- repository.isSimpleUserSwitcher()
- } else {
- error("Not supported in the old implementation!")
- }
+ get() = repository.isSimpleUserSwitcher()
init {
- if (isNewImpl) {
- refreshUsersScheduler.refreshIfNotPaused()
- telephonyInteractor.callState
- .distinctUntilChanged()
- .onEach { refreshUsersScheduler.refreshIfNotPaused() }
- .launchIn(applicationScope)
+ refreshUsersScheduler.refreshIfNotPaused()
+ telephonyInteractor.callState
+ .distinctUntilChanged()
+ .onEach { refreshUsersScheduler.refreshIfNotPaused() }
+ .launchIn(applicationScope)
- combine(
- broadcastDispatcher.broadcastFlow(
- filter =
- IntentFilter().apply {
- addAction(Intent.ACTION_USER_ADDED)
- addAction(Intent.ACTION_USER_REMOVED)
- addAction(Intent.ACTION_USER_INFO_CHANGED)
- addAction(Intent.ACTION_USER_SWITCHED)
- addAction(Intent.ACTION_USER_STOPPED)
- addAction(Intent.ACTION_USER_UNLOCKED)
- },
- user = UserHandle.SYSTEM,
- map = { intent, _ -> intent },
- ),
- repository.selectedUserInfo.pairwise(null),
- ) { intent, selectedUserChange ->
- Pair(intent, selectedUserChange.previousValue)
- }
- .onEach { (intent, previousSelectedUser) ->
- onBroadcastReceived(intent, previousSelectedUser)
- }
- .launchIn(applicationScope)
- }
+ combine(
+ broadcastDispatcher.broadcastFlow(
+ filter =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_USER_ADDED)
+ addAction(Intent.ACTION_USER_REMOVED)
+ addAction(Intent.ACTION_USER_INFO_CHANGED)
+ addAction(Intent.ACTION_USER_SWITCHED)
+ addAction(Intent.ACTION_USER_STOPPED)
+ addAction(Intent.ACTION_USER_UNLOCKED)
+ },
+ user = UserHandle.SYSTEM,
+ map = { intent, _ -> intent },
+ ),
+ repository.selectedUserInfo.pairwise(null),
+ ) { intent, selectedUserChange ->
+ Pair(intent, selectedUserChange.previousValue)
+ }
+ .onEach { (intent, previousSelectedUser) ->
+ onBroadcastReceived(intent, previousSelectedUser)
+ }
+ .launchIn(applicationScope)
}
fun addCallback(callback: UserCallback) {
@@ -414,48 +362,43 @@
newlySelectedUserId: Int,
dialogShower: UserSwitchDialogController.DialogShower? = null,
) {
- if (isNewImpl) {
- val currentlySelectedUserInfo = repository.getSelectedUserInfo()
- if (
- newlySelectedUserId == currentlySelectedUserInfo.id &&
- currentlySelectedUserInfo.isGuest
- ) {
- // Here when clicking on the currently-selected guest user to leave guest mode
- // and return to the previously-selected non-guest user.
- showDialog(
- ShowDialogRequestModel.ShowExitGuestDialog(
- guestUserId = currentlySelectedUserInfo.id,
- targetUserId = repository.lastSelectedNonGuestUserId,
- isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- onExitGuestUser = this::exitGuestUser,
- dialogShower = dialogShower,
- )
+ val currentlySelectedUserInfo = repository.getSelectedUserInfo()
+ if (
+ newlySelectedUserId == currentlySelectedUserInfo.id && currentlySelectedUserInfo.isGuest
+ ) {
+ // Here when clicking on the currently-selected guest user to leave guest mode
+ // and return to the previously-selected non-guest user.
+ showDialog(
+ ShowDialogRequestModel.ShowExitGuestDialog(
+ guestUserId = currentlySelectedUserInfo.id,
+ targetUserId = repository.lastSelectedNonGuestUserId,
+ isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
)
- return
- }
-
- if (currentlySelectedUserInfo.isGuest) {
- // Here when switching from guest to a non-guest user.
- showDialog(
- ShowDialogRequestModel.ShowExitGuestDialog(
- guestUserId = currentlySelectedUserInfo.id,
- targetUserId = newlySelectedUserId,
- isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- onExitGuestUser = this::exitGuestUser,
- dialogShower = dialogShower,
- )
- )
- return
- }
-
- dialogShower?.dismiss()
-
- switchUser(newlySelectedUserId)
- } else {
- controller.onUserSelected(newlySelectedUserId, dialogShower)
+ )
+ return
}
+
+ if (currentlySelectedUserInfo.isGuest) {
+ // Here when switching from guest to a non-guest user.
+ showDialog(
+ ShowDialogRequestModel.ShowExitGuestDialog(
+ guestUserId = currentlySelectedUserInfo.id,
+ targetUserId = newlySelectedUserId,
+ isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
+ )
+ )
+ return
+ }
+
+ dialogShower?.dismiss()
+
+ switchUser(newlySelectedUserId)
}
/** Executes the given action. */
@@ -463,51 +406,38 @@
action: UserActionModel,
dialogShower: UserSwitchDialogController.DialogShower? = null,
) {
- if (isNewImpl) {
- when (action) {
- UserActionModel.ENTER_GUEST_MODE ->
- guestUserInteractor.createAndSwitchTo(
- this::showDialog,
- this::dismissDialog,
- ) { userId ->
- selectUser(userId, dialogShower)
- }
- UserActionModel.ADD_USER -> {
- val currentUser = repository.getSelectedUserInfo()
- showDialog(
- ShowDialogRequestModel.ShowAddUserDialog(
- userHandle = currentUser.userHandle,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
- dialogShower = dialogShower,
- )
- )
+ when (action) {
+ UserActionModel.ENTER_GUEST_MODE ->
+ guestUserInteractor.createAndSwitchTo(
+ this::showDialog,
+ this::dismissDialog,
+ ) { userId ->
+ selectUser(userId, dialogShower)
}
- UserActionModel.ADD_SUPERVISED_USER ->
- activityStarter.startActivity(
- Intent()
- .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
- .setPackage(supervisedUserPackageName)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
- /* dismissShade= */ true,
+ UserActionModel.ADD_USER -> {
+ val currentUser = repository.getSelectedUserInfo()
+ showDialog(
+ ShowDialogRequestModel.ShowAddUserDialog(
+ userHandle = currentUser.userHandle,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+ dialogShower = dialogShower,
)
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
- activityStarter.startActivity(
- Intent(Settings.ACTION_USER_SETTINGS),
- /* dismissShade= */ true,
- )
+ )
}
- } else {
- when (action) {
- UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
- UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
- UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
- activityStarter.startActivity(
- Intent(Settings.ACTION_USER_SETTINGS),
- /* dismissShade= */ false,
- )
- }
+ UserActionModel.ADD_SUPERVISED_USER ->
+ activityStarter.startActivity(
+ Intent()
+ .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+ .setPackage(supervisedUserPackageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ /* dismissShade= */ true,
+ )
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+ activityStarter.startActivity(
+ Intent(Settings.ACTION_USER_SETTINGS),
+ /* dismissShade= */ true,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index ad09ee3..e137107 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -133,7 +133,9 @@
launch {
viewModel.users.collect { users ->
val viewPool =
- view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList()
+ gridContainerView.children
+ .filter { it.tag == USER_VIEW_TAG }
+ .toMutableList()
viewPool.forEach {
gridContainerView.removeView(it)
flowWidget.removeView(it)
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index e921720..58a4473 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -27,15 +27,12 @@
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@@ -50,16 +47,11 @@
private val broadcastSender: Lazy<BroadcastSender>,
private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
private val interactor: Lazy<UserInteractor>,
- private val featureFlags: Lazy<FeatureFlags>,
) : CoreStartable {
private var currentDialog: Dialog? = null
override fun start() {
- if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
- return
- }
-
startHandlingDialogShowRequests()
startHandlingDialogDismissRequests()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index d857e85..0910ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -20,8 +20,6 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
@@ -41,12 +39,8 @@
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
private val powerInteractor: PowerInteractor,
- private val featureFlags: FeatureFlags,
) : ViewModel() {
- private val isNewImpl: Boolean
- get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
/** On-device users. */
val users: Flow<List<UserViewModel>> =
userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -216,7 +210,6 @@
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
private val powerInteractor: PowerInteractor,
- private val featureFlags: FeatureFlags,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
@@ -224,7 +217,6 @@
userInteractor = userInteractor,
guestUserInteractor = guestUserInteractor,
powerInteractor = powerInteractor,
- featureFlags = featureFlags,
)
as T
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
index d7c4e93..3c57081 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
@@ -16,10 +16,11 @@
package com.android.systemui.util.time;
-import android.app.ActivityManager;
import android.content.Context;
import android.text.format.DateFormat;
+import com.android.systemui.settings.UserTracker;
+
import javax.inject.Inject;
/**
@@ -27,14 +28,16 @@
*/
public class DateFormatUtil {
private final Context mContext;
+ private final UserTracker mUserTracker;
@Inject
- public DateFormatUtil(Context context) {
+ public DateFormatUtil(Context context, UserTracker userTracker) {
mContext = context;
+ mUserTracker = userTracker;
}
/** Returns true if the phone is in 24 hour format. */
public boolean is24HourFormat() {
- return DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
+ return DateFormat.is24HourFormat(mContext, mUserTracker.getUserId());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 52b6b38..e8f8e25 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -155,7 +155,8 @@
verify(configurationController).addCallback(capture(captor))
captor.value.onDensityOrFontScaleChanged()
- verify(events).onFontSettingChanged()
+ verify(smallClockEvents, times(2)).onFontSettingChanged(anyFloat())
+ verify(largeClockEvents, times(2)).onFontSettingChanged(anyFloat())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 27094c0..5514fd0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -113,6 +113,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -157,6 +158,8 @@
private static final int FINGERPRINT_SENSOR_ID = 1;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private DumpManager mDumpManager;
@Mock
private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
@@ -306,8 +309,7 @@
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.when(SubscriptionManager::getDefaultSubscriptionId);
KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
- ExtendedMockito.doReturn(KeyguardUpdateMonitor.getCurrentUser())
- .when(ActivityManager::getCurrentUser);
+ when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
@@ -1032,6 +1034,7 @@
@Test
public void testSecondaryLockscreenRequirement() {
KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
+ when(mUserTracker.getUserId()).thenReturn(UserHandle.myUserId());
int user = KeyguardUpdateMonitor.getCurrentUser();
String packageName = "fake.test.package";
String cls = "FakeService";
@@ -1886,7 +1889,7 @@
AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
protected TestableKeyguardUpdateMonitor(Context context) {
- super(context,
+ super(context, mUserTracker,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index ff4412e9..27701be 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -30,15 +31,15 @@
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
-import androidx.lifecycle.MutableLiveData;
-
import com.android.systemui.SysuiTestCase;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.settings.CurrentUserObservable;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -52,8 +53,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-// Need to run tests on main looper because LiveData operations such as setData, observe,
-// removeObserver cannot be invoked on a background thread.
+// Need to run tests on main looper to allow for onClockChanged operation to happen synchronously.
@RunWithLooper(setAsMainLooper = true)
public final class ClockManagerTest extends SysuiTestCase {
@@ -63,14 +63,16 @@
private static final int SECONDARY_USER_ID = 11;
private static final Uri SETTINGS_URI = null;
+ private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
private ClockManager mClockManager;
private ContentObserver mContentObserver;
private DockManagerFake mFakeDockManager;
- private MutableLiveData<Integer> mCurrentUser;
+ private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallbackCaptor;
@Mock PluginManager mMockPluginManager;
@Mock SysuiColorExtractor mMockColorExtractor;
@Mock ContentResolver mMockContentResolver;
- @Mock CurrentUserObservable mMockCurrentUserObserable;
+ @Mock UserTracker mUserTracker;
@Mock SettingsWrapper mMockSettingsWrapper;
@Mock ClockManager.ClockChangedListener mMockListener1;
@Mock ClockManager.ClockChangedListener mMockListener2;
@@ -83,18 +85,18 @@
mFakeDockManager = new DockManagerFake();
- mCurrentUser = new MutableLiveData<>();
- mCurrentUser.setValue(MAIN_USER_ID);
- when(mMockCurrentUserObserable.getCurrentUser()).thenReturn(mCurrentUser);
+ when(mUserTracker.getUserId()).thenReturn(MAIN_USER_ID);
+ mUserTrackerCallbackCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class);
mClockManager = new ClockManager(getContext(), inflater,
mMockPluginManager, mMockColorExtractor, mMockContentResolver,
- mMockCurrentUserObserable, mMockSettingsWrapper, mFakeDockManager);
+ mUserTracker, mMainExecutor, mMockSettingsWrapper, mFakeDockManager);
mClockManager.addBuiltinClock(() -> new BubbleClockController(
getContext().getResources(), inflater, mMockColorExtractor));
mClockManager.addOnClockChangedListener(mMockListener1);
mClockManager.addOnClockChangedListener(mMockListener2);
+ verify(mUserTracker).addCallback(mUserTrackerCallbackCaptor.capture(), any());
reset(mMockListener1, mMockListener2);
mContentObserver = mClockManager.getContentObserver();
@@ -221,7 +223,7 @@
@Test
public void onUserChanged_defaultClock() {
// WHEN the user changes
- mCurrentUser.setValue(SECONDARY_USER_ID);
+ switchUser(SECONDARY_USER_ID);
// THEN the plugin is null for the default clock face
assertThat(mClockManager.getCurrentClock()).isNull();
}
@@ -232,7 +234,7 @@
when(mMockSettingsWrapper.getLockScreenCustomClockFace(SECONDARY_USER_ID)).thenReturn(
BUBBLE_CLOCK);
// WHEN the user changes
- mCurrentUser.setValue(SECONDARY_USER_ID);
+ switchUser(SECONDARY_USER_ID);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
@@ -244,8 +246,13 @@
// AND the second user as selected the bubble clock for the dock
when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(BUBBLE_CLOCK);
// WHEN the user changes
- mCurrentUser.setValue(SECONDARY_USER_ID);
+ switchUser(SECONDARY_USER_ID);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
+
+ private void switchUser(int newUser) {
+ when(mUserTracker.getUserId()).thenReturn(newUser);
+ mUserTrackerCallbackCaptor.getValue().onUserChanged(newUser, mContext);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 69ccc8b..57ca9c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -903,6 +903,97 @@
}
@Test
+ public void changeMagnificationSize_expectedWindowSize() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final float magnificationScaleLarge = 2.5f;
+ final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
+ final int magnificationSize = (int) (initSize * magnificationScaleLarge);
+
+ final int expectedWindowHeight = magnificationSize;
+ final int expectedWindowWidth = magnificationSize;
+
+ mInstrumentation.runOnMainSync(
+ () ->
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.changeMagnificationSize(
+ WindowMagnificationSettings.MagnificationSize.LARGE);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+
+ assertEquals(expectedWindowHeight, actualWindowHeight.get());
+ assertEquals(expectedWindowWidth, actualWindowWidth.get());
+ }
+
+ @Test
+ public void editModeOnDragCorner_resizesWindow() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final int startingSize = (int) (bounds.width() / 2);
+
+ mInstrumentation.runOnMainSync(
+ () ->
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ waitForIdleSync();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController
+ .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+
+ assertEquals(startingSize + 1, actualWindowHeight.get());
+ assertEquals(startingSize + 2, actualWindowWidth.get());
+ }
+
+ @Test
+ public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+ final int startingSize = (int) (bounds.width() / 2f);
+
+ mInstrumentation.runOnMainSync(
+ () ->
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ Float.NaN, Float.NaN, Float.NaN));
+
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ mWindowMagnificationController
+ .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+ assertEquals(startingSize + 1, actualWindowHeight.get());
+ assertEquals(startingSize, actualWindowWidth.get());
+ }
+
+ @Test
public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
final int minimumWindowSize = mResources.getDimensionPixelSize(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index d20eeaf..2d5188f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -19,12 +19,22 @@
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -35,6 +45,7 @@
import com.android.systemui.SysuiTestCase;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -43,12 +54,23 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+import java.util.List;
+
/** Tests for {@link MenuViewLayer}. */
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class MenuViewLayerTest extends SysuiTestCase {
+ private static final String SELECT_TO_SPEAK_PACKAGE_NAME = "com.google.android.marvin.talkback";
+ private static final String SELECT_TO_SPEAK_SERVICE_NAME =
+ "com.google.android.accessibility.selecttospeak.SelectToSpeakService";
+ private static final ComponentName TEST_SELECT_TO_SPEAK_COMPONENT_NAME = new ComponentName(
+ SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME);
+
private MenuViewLayer mMenuViewLayer;
+ private String mLastAccessibilityButtonTargets;
+ private String mLastEnabledAccessibilityServices;
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@@ -56,13 +78,31 @@
@Mock
private IAccessibilityFloatingMenu mFloatingMenu;
+ @Mock
+ private AccessibilityManager mStubAccessibilityManager;
+
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- final AccessibilityManager stubAccessibilityManager = mContext.getSystemService(
- AccessibilityManager.class);
- mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, stubAccessibilityManager,
+ mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, mStubAccessibilityManager,
mFloatingMenu);
+
+ mLastAccessibilityButtonTargets =
+ Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+ mLastEnabledAccessibilityServices =
+ Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastAccessibilityButtonTargets,
+ UserHandle.USER_CURRENT);
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices,
+ UserHandle.USER_CURRENT);
}
@Test
@@ -87,4 +127,45 @@
verify(mFloatingMenu).hide();
}
+
+ @Test
+ public void tiggerDismissMenuAction_matchA11yButtonTargetsResult() {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ MAGNIFICATION_COMPONENT_NAME.flattenToString(), UserHandle.USER_CURRENT);
+
+ mMenuViewLayer.mDismissMenuAction.run();
+ final String value =
+ Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
+
+ assertThat(value).isEqualTo("");
+ }
+
+ @Test
+ public void tiggerDismissMenuAction_matchEnabledA11yServicesResult() {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ serviceInfo.applicationInfo = applicationInfo;
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+ final AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
+ accessibilityServiceInfo.setResolveInfo(resolveInfo);
+ accessibilityServiceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ final List<AccessibilityServiceInfo> serviceInfoList = new ArrayList<>();
+ accessibilityServiceInfo.setComponentName(TEST_SELECT_TO_SPEAK_COMPONENT_NAME);
+ serviceInfoList.add(accessibilityServiceInfo);
+ when(mStubAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK)).thenReturn(serviceInfoList);
+
+ mMenuViewLayer.mDismissMenuAction.run();
+ final String value = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+ assertThat(value).isEqualTo("");
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
new file mode 100644
index 0000000..982f033
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class AccessorizedBatteryDrawableTest : SysuiTestCase() {
+ @Test
+ fun intrinsicSize_shieldFalse_isBatterySize() {
+ val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+ drawable.displayShield = false
+
+ val density = context.resources.displayMetrics.density
+ assertThat(drawable.intrinsicHeight).isEqualTo((BATTERY_HEIGHT * density).toInt())
+ assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH * density).toInt())
+ }
+
+ @Test
+ fun intrinsicSize_shieldTrue_isBatteryPlusShieldSize() {
+ val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+ drawable.displayShield = true
+
+ val density = context.resources.displayMetrics.density
+ assertThat(drawable.intrinsicHeight)
+ .isEqualTo((BATTERY_HEIGHT_WITH_SHIELD * density).toInt())
+ assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH_WITH_SHIELD * density).toInt())
+ }
+
+ // TODO(b/255625888): Screenshot tests for this drawable would be amazing!
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 1d038a4..1482f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -34,7 +34,9 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
@@ -50,15 +52,16 @@
private BatteryMeterView mBatteryMeterView;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private ConfigurationController mConfigurationController;
@Mock
private TunerService mTunerService;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
private Handler mHandler;
@Mock
private ContentResolver mContentResolver;
+ private FakeFeatureFlags mFeatureFlags;
@Mock
private BatteryController mBatteryController;
@@ -71,19 +74,13 @@
when(mBatteryMeterView.getContext()).thenReturn(mContext);
when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
- mController = new BatteryMeterViewController(
- mBatteryMeterView,
- mConfigurationController,
- mTunerService,
- mBroadcastDispatcher,
- mHandler,
- mContentResolver,
- mBatteryController
- );
+ mFeatureFlags = new FakeFeatureFlags();
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
}
@Test
public void onViewAttached_callbacksRegistered() {
+ initController();
mController.onViewAttached();
verify(mConfigurationController).addCallback(any());
@@ -101,6 +98,7 @@
@Test
public void onViewDetached_callbacksUnregistered() {
+ initController();
// Set everything up first.
mController.onViewAttached();
@@ -114,6 +112,7 @@
@Test
public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() {
+ initController();
// Start out receiving tuner updates
mController.onViewAttached();
@@ -124,10 +123,43 @@
@Test
public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() {
+ initController();
+
mController.ignoreTunerUpdates();
mController.onViewAttached();
verify(mTunerService, never()).addTunable(any(), any());
}
+
+ @Test
+ public void shieldFlagDisabled_viewNotified() {
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+
+ initController();
+
+ verify(mBatteryMeterView).setDisplayShieldEnabled(false);
+ }
+
+ @Test
+ public void shieldFlagEnabled_viewNotified() {
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
+
+ initController();
+
+ verify(mBatteryMeterView).setDisplayShieldEnabled(true);
+ }
+
+ private void initController() {
+ mController = new BatteryMeterViewController(
+ mBatteryMeterView,
+ mUserTracker,
+ mConfigurationController,
+ mTunerService,
+ mHandler,
+ mContentResolver,
+ mFeatureFlags,
+ mBatteryController
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index b4ff2a5..eb7d9c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -17,7 +17,9 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.widget.ImageView
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
@@ -58,6 +60,182 @@
// No assert needed
}
+ @Test
+ fun contentDescription_unknown() {
+ mBatteryMeterView.onBatteryUnknownStateChanged(true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_unknown)
+ )
+ }
+
+ @Test
+ fun contentDescription_estimate() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ )
+ )
+ }
+
+ @Test
+ fun contentDescription_estimateAndOverheated() {
+ mBatteryMeterView.onBatteryLevelChanged(17, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 17,
+ ESTIMATE,
+ )
+ )
+ }
+
+ @Test
+ fun contentDescription_overheated() {
+ mBatteryMeterView.onBatteryLevelChanged(90, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+ )
+ }
+
+ @Test
+ fun contentDescription_charging() {
+ mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging, 45)
+ )
+ }
+
+ @Test
+ fun contentDescription_notCharging() {
+ mBatteryMeterView.onBatteryLevelChanged(45, false)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 45)
+ )
+ }
+
+ @Test
+ fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ )
+ )
+
+ // Update the show mode from estimate to percent
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+
+ assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%")
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 15)
+ )
+ }
+
+ @Test
+ fun contentDescription_manyUpdates_alwaysUpdated() {
+ // Overheated
+ mBatteryMeterView.onBatteryLevelChanged(90, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+ )
+
+ // Overheated & estimate
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+ mBatteryMeterView.updatePercentText()
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 90,
+ ESTIMATE,
+ )
+ )
+
+ // Just estimate
+ mBatteryMeterView.onIsOverheatedChanged(false)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate,
+ 90,
+ ESTIMATE,
+ )
+ )
+
+ // Just percent
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 90)
+ )
+
+ // Charging
+ mBatteryMeterView.onBatteryLevelChanged(90, true)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging, 90)
+ )
+ }
+
+ @Test
+ fun isOverheatedChanged_true_drawableGetsTrue() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(drawable.displayShield).isTrue()
+ }
+
+ @Test
+ fun isOverheatedChanged_false_drawableGetsFalse() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+ val drawable = getBatteryDrawable()
+
+ // Start as true
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ // Update to false
+ mBatteryMeterView.onIsOverheatedChanged(false)
+
+ assertThat(drawable.displayShield).isFalse()
+ }
+
+ @Test
+ fun isOverheatedChanged_true_featureflagOff_drawableGetsFalse() {
+ mBatteryMeterView.setDisplayShieldEnabled(false)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(drawable.displayShield).isFalse()
+ }
+
+ private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
+ return (mBatteryMeterView.getChildAt(0) as ImageView)
+ .drawable as AccessorizedBatteryDrawable
+ }
+
private class Fetcher : BatteryEstimateFetcher {
override fun fetchBatteryTimeRemainingEstimate(
completion: EstimateFetchCompletion) {
@@ -68,4 +246,4 @@
private companion object {
const val ESTIMATE = "2 hours 2 minutes"
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
new file mode 100644
index 0000000..39cb047
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 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.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class BatterySpecsTest : SysuiTestCase() {
+ @Test
+ fun getFullBatteryHeight_shieldFalse_returnsMainHeight() {
+ val fullHeight = BatterySpecs.getFullBatteryHeight(56f, displayShield = false)
+
+ assertThat(fullHeight).isEqualTo(56f)
+ }
+
+ @Test
+ fun getFullBatteryHeight_shieldTrue_returnsMainHeightPlusShield() {
+ val mainHeight = BATTERY_HEIGHT * 5
+ val fullHeight = BatterySpecs.getFullBatteryHeight(mainHeight, displayShield = true)
+
+ // Since the main battery was scaled 5x, the output height should also be scaled 5x
+ val expectedFullHeight = BATTERY_HEIGHT_WITH_SHIELD * 5
+
+ assertThat(fullHeight).isWithin(.0001f).of(expectedFullHeight)
+ }
+
+ @Test
+ fun getFullBatteryWidth_shieldFalse_returnsMainWidth() {
+ val fullWidth = BatterySpecs.getFullBatteryWidth(33f, displayShield = false)
+
+ assertThat(fullWidth).isEqualTo(33f)
+ }
+
+ @Test
+ fun getFullBatteryWidth_shieldTrue_returnsMainWidthPlusShield() {
+ val mainWidth = BATTERY_WIDTH * 3.3f
+
+ val fullWidth = BatterySpecs.getFullBatteryWidth(mainWidth, displayShield = true)
+
+ // Since the main battery was scaled 3.3x, the output width should also be scaled 5x
+ val expectedFullWidth = BATTERY_WIDTH_WITH_SHIELD * 3.3f
+ assertThat(fullWidth).isWithin(.0001f).of(expectedFullWidth)
+ }
+
+ @Test
+ fun getMainBatteryHeight_shieldFalse_returnsFullHeight() {
+ val mainHeight = BatterySpecs.getMainBatteryHeight(89f, displayShield = false)
+
+ assertThat(mainHeight).isEqualTo(89f)
+ }
+
+ @Test
+ fun getMainBatteryHeight_shieldTrue_returnsNotFullHeight() {
+ val fullHeight = BATTERY_HEIGHT_WITH_SHIELD * 7.7f
+
+ val mainHeight = BatterySpecs.getMainBatteryHeight(fullHeight, displayShield = true)
+
+ // Since the full height was scaled 7.7x, the main height should also be scaled 7.7x.
+ val expectedHeight = BATTERY_HEIGHT * 7.7f
+ assertThat(mainHeight).isWithin(.0001f).of(expectedHeight)
+ }
+
+ @Test
+ fun getMainBatteryWidth_shieldFalse_returnsFullWidth() {
+ val mainWidth = BatterySpecs.getMainBatteryWidth(2345f, displayShield = false)
+
+ assertThat(mainWidth).isEqualTo(2345f)
+ }
+
+ @Test
+ fun getMainBatteryWidth_shieldTrue_returnsNotFullWidth() {
+ val fullWidth = BATTERY_WIDTH_WITH_SHIELD * 0.6f
+
+ val mainWidth = BatterySpecs.getMainBatteryWidth(fullWidth, displayShield = true)
+
+ // Since the full width was scaled 0.6x, the main height should also be scaled 0.6x.
+ val expectedWidth = BATTERY_WIDTH * 0.6f
+ assertThat(mainWidth).isWithin(.0001f).of(expectedWidth)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 12c2bbf..898f370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -125,6 +125,21 @@
}
@Test
+ fun testCredentialPasswordDismissesOnBack() {
+ val container = initializeCredentialPasswordContainer(addToView = true)
+ assertThat(container.parent).isNotNull()
+ val root = container.rootView
+
+ // Simulate back invocation
+ container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
+ container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+ waitForIdleSync()
+
+ assertThat(container.parent).isNull()
+ assertThat(root.isAttachedToWindow).isFalse()
+ }
+
+ @Test
fun testIgnoresAnimatedInWhenDismissed() {
val container = initializeFingerprintContainer(addToView = false)
container.dismissFromSystemServer()
@@ -369,20 +384,7 @@
@Test
fun testCredentialUI_disablesClickingOnBackground() {
- whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
- whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- )
-
- // In the credential view, clicking on the background (to cancel authentication) is not
- // valid. Thus, the listener should be null, and it should not be in the accessibility
- // hierarchy.
- val container = initializeFingerprintContainer(
- authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
- )
- waitForIdleSync()
-
- assertThat(container.hasCredentialPasswordView()).isTrue()
+ val container = initializeCredentialPasswordContainer()
assertThat(container.hasBiometricPrompt()).isFalse()
assertThat(
container.findViewById<View>(R.id.background)?.isImportantForAccessibility
@@ -442,6 +444,27 @@
verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
}
+ private fun initializeCredentialPasswordContainer(
+ addToView: Boolean = true,
+ ): TestAuthContainerView {
+ whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+ whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ )
+
+ // In the credential view, clicking on the background (to cancel authentication) is not
+ // valid. Thus, the listener should be null, and it should not be in the accessibility
+ // hierarchy.
+ val container = initializeFingerprintContainer(
+ authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+ addToView = addToView,
+ )
+ waitForIdleSync()
+
+ assertThat(container.hasCredentialPasswordView()).isTrue()
+ return container
+ }
+
private fun initializeFingerprintContainer(
authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
addToView: Boolean = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index 2af0557..d159714 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -24,7 +24,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index f8579ff..0fadc13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -120,6 +120,7 @@
mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
+ mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 0b72a68..3b6f7d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -10,10 +10,12 @@
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
import java.util.concurrent.CountDownLatch
import org.junit.Before
import org.junit.Rule
@@ -30,9 +32,11 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class ControlsEditingActivityTest : SysuiTestCase() {
+ private val uiExecutor = FakeExecutor(FakeSystemClock())
+
@Mock lateinit var controller: ControlsControllerImpl
- @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userTracker: UserTracker
@Mock lateinit var customIconCache: CustomIconCache
@@ -54,8 +58,9 @@
) {
override fun create(intent: Intent?): TestableControlsEditingActivity {
return TestableControlsEditingActivity(
+ uiExecutor,
controller,
- broadcastDispatcher,
+ userTracker,
customIconCache,
uiController,
mockDispatcher,
@@ -92,13 +97,14 @@
}
public class TestableControlsEditingActivity(
+ private val executor: FakeExecutor,
private val controller: ControlsControllerImpl,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val customIconCache: CustomIconCache,
private val uiController: ControlsUiController,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
- ) : ControlsEditingActivity(controller, broadcastDispatcher, customIconCache, uiController) {
+ ) : ControlsEditingActivity(executor, controller, userTracker, customIconCache, uiController) {
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
return mockDispatcher
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index 4b0f7e6..0f06de2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -9,10 +9,10 @@
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
@@ -37,7 +37,7 @@
@Mock lateinit var listingController: ControlsListingController
- @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userTracker: UserTracker
@Mock lateinit var uiController: ControlsUiController
@@ -60,7 +60,7 @@
executor,
controller,
listingController,
- broadcastDispatcher,
+ userTracker,
uiController,
mockDispatcher,
latch
@@ -97,7 +97,7 @@
executor: Executor,
controller: ControlsControllerImpl,
listingController: ControlsListingController,
- broadcastDispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
uiController: ControlsUiController,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
@@ -106,7 +106,7 @@
executor,
controller,
listingController,
- broadcastDispatcher,
+ userTracker,
uiController
) {
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index acc6222..56c3efe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -25,11 +25,11 @@
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
@@ -56,7 +56,7 @@
@Mock lateinit var controlsController: ControlsController
- @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userTracker: UserTracker
@Mock lateinit var uiController: ControlsUiController
@@ -80,7 +80,7 @@
backExecutor,
listingController,
controlsController,
- broadcastDispatcher,
+ userTracker,
uiController,
mockDispatcher,
latch
@@ -118,7 +118,7 @@
backExecutor: Executor,
listingController: ControlsListingController,
controlsController: ControlsController,
- broadcastDispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
uiController: ControlsUiController,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
@@ -128,7 +128,7 @@
backExecutor,
listingController,
controlsController,
- broadcastDispatcher,
+ userTracker,
uiController
) {
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index efb3db7..314b176 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import org.junit.After
@@ -46,9 +47,10 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
@MediumTest
@RunWith(AndroidTestingRunner::class)
@@ -67,6 +69,10 @@
private lateinit var controller: ControlsController
@Mock
+ private lateinit var mainExecutor: Executor
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
private lateinit var listingController: ControlsListingController
@Mock
private lateinit var iIntentSender: IIntentSender
@@ -81,8 +87,9 @@
) {
override fun create(intent: Intent?): TestControlsRequestDialog {
return TestControlsRequestDialog(
+ mainExecutor,
controller,
- fakeBroadcastDispatcher,
+ userTracker,
listingController
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
index 3f6308b..ec239f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
@@ -16,11 +16,13 @@
package com.android.systemui.controls.management
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
class TestControlsRequestDialog(
+ mainExecutor: Executor,
controller: ControlsController,
- dispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
listingController: ControlsListingController
-) : ControlsRequestDialog(controller, dispatcher, listingController)
\ No newline at end of file
+) : ControlsRequestDialog(mainExecutor, controller, userTracker, listingController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 2f206ad..07d7e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeSensors.TriggerSensor;
import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -99,6 +100,8 @@
@Mock
private DevicePostureController mDevicePostureController;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private ProximitySensor mProximitySensor;
// Capture listeners so that they can be used to send events
@@ -428,7 +431,7 @@
DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController);
+ mDevicePostureController, mUserTracker);
for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
assertFalse(sensor.mIgnoresSetting);
@@ -440,7 +443,7 @@
super(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController);
+ mDevicePostureController, mUserTracker);
for (TriggerSensor sensor : mTriggerSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 6091d3a..82432ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -98,6 +99,8 @@
@Mock
private DevicePostureController mDevicePostureController;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private SessionTracker mSessionTracker;
private DozeTriggers mTriggers;
@@ -131,7 +134,7 @@
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
- mDevicePostureController);
+ mDevicePostureController, mUserTracker);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
new file mode 100644
index 0000000..99406ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.statusbar.BlurUtils
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
+
+ companion object {
+ private const val DREAM_IN_BLUR_ANIMATION_DURATION = 1L
+ private const val DREAM_IN_BLUR_ANIMATION_DELAY = 2L
+ private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
+ private const val DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = 4L
+ private const val DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = 5L
+ private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
+ private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
+ private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
+ private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L
+ private const val DREAM_OUT_ALPHA_DURATION = 10L
+ private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L
+ private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L
+ private const val DREAM_OUT_BLUR_DURATION = 13L
+ }
+
+ @Mock private lateinit var mockAnimator: AnimatorSet
+ @Mock private lateinit var blurUtils: BlurUtils
+ @Mock private lateinit var hostViewController: ComplicationHostViewController
+ @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
+ @Mock private lateinit var stateController: DreamOverlayStateController
+ private lateinit var controller: DreamOverlayAnimationsController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ controller =
+ DreamOverlayAnimationsController(
+ blurUtils,
+ hostViewController,
+ statusBarViewController,
+ stateController,
+ DREAM_IN_BLUR_ANIMATION_DURATION,
+ DREAM_IN_BLUR_ANIMATION_DELAY,
+ DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
+ DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY,
+ DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY,
+ DREAM_OUT_TRANSLATION_Y_DISTANCE,
+ DREAM_OUT_TRANSLATION_Y_DURATION,
+ DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
+ DREAM_OUT_TRANSLATION_Y_DELAY_TOP,
+ DREAM_OUT_ALPHA_DURATION,
+ DREAM_OUT_ALPHA_DELAY_BOTTOM,
+ DREAM_OUT_ALPHA_DELAY_TOP,
+ DREAM_OUT_BLUR_DURATION
+ )
+ }
+
+ @Test
+ fun testExitAnimationOnEnd() {
+ val mockCallback: () -> Unit = mock()
+
+ controller.startExitAnimations(
+ view = mock(),
+ doneCallback = mockCallback,
+ animatorBuilder = { mockAnimator }
+ )
+
+ val captor = argumentCaptor<Animator.AnimatorListener>()
+ verify(mockAnimator).addListener(captor.capture())
+ val listener = captor.value
+
+ verify(mockCallback, never()).invoke()
+ listener.onAnimationEnd(mockAnimator)
+ verify(mockCallback, times(1)).invoke()
+ }
+
+ @Test
+ fun testCancellation() {
+ controller.startExitAnimations(
+ view = mock(),
+ doneCallback = mock(),
+ animatorBuilder = { mockAnimator }
+ )
+
+ verify(mockAnimator, never()).cancel()
+ controller.cancelAnimations()
+ verify(mockAnimator, times(1)).cancel()
+ }
+
+ @Test
+ fun testExitAfterStartWillCancel() {
+ val mockStartAnimator: AnimatorSet = mock()
+ val mockExitAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator })
+
+ verify(mockStartAnimator, never()).cancel()
+
+ controller.startExitAnimations(
+ view = mock(),
+ doneCallback = mock(),
+ animatorBuilder = { mockExitAnimator }
+ )
+
+ // Verify that we cancelled the start animator in favor of the exit
+ // animator.
+ verify(mockStartAnimator, times(1)).cancel()
+ verify(mockExitAnimator, never()).cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 517804d..73c226d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -204,7 +204,7 @@
mController.onViewAttached();
verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
- verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+ verify(mAnimationsController, never()).cancelAnimations();
}
@Test
@@ -221,6 +221,6 @@
mController.onViewAttached();
mController.onViewDetached();
- verify(mAnimationsController).cancelRunningEntryAnimations();
+ verify(mAnimationsController).cancelAnimations();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index f04a37f..ffb8342 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -337,4 +338,28 @@
verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
}
+
+ @Test
+ public void testWakeUp() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ true /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ final Runnable callback = mock(Runnable.class);
+ mService.onWakeUp(callback);
+ mMainExecutor.runAllReady();
+ verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
+ }
+
+ @Test
+ public void testWakeUpBeforeStartDoesNothing() {
+ final Runnable callback = mock(Runnable.class);
+ mService.onWakeUp(callback);
+ mMainExecutor.runAllReady();
+ verify(mDreamOverlayContainerViewController, never()).wakeUp(callback, mMainExecutor);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index aa8c93e..30ad485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -90,7 +90,10 @@
private ActivityStarter mActivityStarter;
@Mock
- UiEventLogger mUiEventLogger;
+ private UiEventLogger mUiEventLogger;
+
+ @Captor
+ private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor;
@Before
public void setup() {
@@ -164,6 +167,29 @@
verify(mDreamOverlayStateController).addComplication(mComplication);
}
+ @Test
+ public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setServiceAvailable(true);
+ setHaveFavorites(false);
+
+ // Complication not available on start.
+ verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+
+ // Favorite controls added, complication should be available now.
+ setHaveFavorites(true);
+
+ // Dream overlay becomes active.
+ setDreamOverlayActive(true);
+
+ // Verify complication is added.
+ verify(mDreamOverlayStateController).addComplication(mComplication);
+ }
+
/**
* Ensures clicking home controls chip logs UiEvent.
*/
@@ -196,10 +222,17 @@
private void setServiceAvailable(boolean value) {
final List<ControlsServiceInfo> serviceInfos = mock(List.class);
+ when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos);
when(serviceInfos.isEmpty()).thenReturn(!value);
triggerControlsListingCallback(serviceInfos);
}
+ private void setDreamOverlayActive(boolean value) {
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value);
+ verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture());
+ mStateCallbackCaptor.getValue().onStateChanged();
+ }
+
private void triggerControlsListingCallback(List<ControlsServiceInfo> serviceInfos) {
verify(mControlsListingController).addCallback(mCallbackCaptor.capture());
mCallbackCaptor.getValue().onServicesUpdated(serviceInfos);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
index 14a5702..4e3aca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.dreams.touch;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -33,6 +31,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -52,6 +51,7 @@
@RunWith(AndroidTestingRunner.class)
public class HideComplicationTouchHandlerTest extends SysuiTestCase {
private static final int RESTORE_TIMEOUT = 1000;
+ private static final int HIDE_DELAY = 500;
@Mock
Complication.VisibilityController mVisibilityController;
@@ -71,11 +71,18 @@
@Mock
DreamTouchHandler.TouchSession mSession;
- FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ @Mock
+ DreamOverlayStateController mStateController;
+
+ FakeSystemClock mClock;
+
+ FakeExecutor mFakeExecutor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mClock = new FakeSystemClock();
+ mFakeExecutor = new FakeExecutor(mClock);
}
/**
@@ -86,10 +93,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report multiple active sessions.
when(mSession.getActiveSessionCount()).thenReturn(2);
@@ -103,8 +111,10 @@
// Verify session end.
verify(mSession).pop();
+ mClock.advanceTime(HIDE_DELAY);
+
// Verify no interaction with visibility controller.
- verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -115,10 +125,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report one session.
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -132,8 +143,10 @@
// Verify session end.
verify(mSession).pop();
+ mClock.advanceTime(HIDE_DELAY);
+
// Verify no interaction with visibility controller.
- verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -144,10 +157,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -177,8 +191,10 @@
// Verify session ended.
verify(mSession).pop();
+ mClock.advanceTime(HIDE_DELAY);
+
// Verify no interaction with visibility controller.
- verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -189,10 +205,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -221,11 +238,11 @@
inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent);
mFakeExecutor.runAllReady();
- // Verify callback to restore visibility cancelled.
- verify(mHandler).removeCallbacks(any());
-
+ // Verify visibility controller doesn't hide until after timeout
+ verify(mVisibilityController, never()).setVisibility(eq(View.INVISIBLE));
+ mClock.advanceTime(HIDE_DELAY);
// Verify visibility controller told to hide complications.
- verify(mVisibilityController).setVisibility(eq(View.INVISIBLE), anyBoolean());
+ verify(mVisibilityController).setVisibility(eq(View.INVISIBLE));
Mockito.clearInvocations(mVisibilityController, mHandler);
@@ -235,11 +252,8 @@
mFakeExecutor.runAllReady();
// Verify visibility controller told to show complications.
- ArgumentCaptor<Runnable> delayRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
- verify(mHandler).postDelayed(delayRunnableCaptor.capture(),
- eq(Long.valueOf(RESTORE_TIMEOUT)));
- delayRunnableCaptor.getValue().run();
- verify(mVisibilityController).setVisibility(eq(View.VISIBLE), anyBoolean());
+ mClock.advanceTime(RESTORE_TIMEOUT);
+ verify(mVisibilityController).setVisibility(eq(View.VISIBLE));
// Verify session ended.
verify(mSession).pop();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 8b1554c..d52616b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -63,6 +63,7 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -103,6 +104,7 @@
@Mock private SecureSettings mSecureSettings;
@Mock private Resources mResources;
@Mock private ConfigurationController mConfigurationController;
+ @Mock private UserTracker mUserTracker;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private UserManager mUserManager;
@Mock private TrustManager mTrustManager;
@@ -152,6 +154,7 @@
mVibratorHelper,
mResources,
mConfigurationController,
+ mUserTracker,
mKeyguardStateController,
mUserManager,
mTrustManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
deleted file mode 100644
index 4d66a16..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard
-
-import android.content.pm.PackageManager
-import android.content.pm.ProviderInfo
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SystemUIAppComponentFactoryBase
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderClient as Client
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
-
- @Mock private lateinit var lockPatternUtils: LockPatternUtils
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var activityStarter: ActivityStarter
-
- private lateinit var underTest: KeyguardQuickAffordanceProvider
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest = KeyguardQuickAffordanceProvider()
- val quickAffordanceRepository =
- KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
- configs =
- setOf(
- FakeKeyguardQuickAffordanceConfig(
- key = AFFORDANCE_1,
- pickerIconResourceId = 1,
- ),
- FakeKeyguardQuickAffordanceConfig(
- key = AFFORDANCE_2,
- pickerIconResourceId = 2,
- ),
- ),
- )
- underTest.interactor =
- KeyguardQuickAffordanceInteractor(
- keyguardInteractor =
- KeyguardInteractor(
- repository = FakeKeyguardRepository(),
- ),
- registry = mock(),
- lockPatternUtils = lockPatternUtils,
- keyguardStateController = keyguardStateController,
- userTracker = userTracker,
- activityStarter = activityStarter,
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
- },
- repository = { quickAffordanceRepository },
- )
-
- underTest.attachInfoForTesting(
- context,
- ProviderInfo().apply { authority = Contract.AUTHORITY },
- )
- context.contentResolver.addProvider(Contract.AUTHORITY, underTest)
- context.testablePermissions.setPermission(
- Contract.PERMISSION,
- PackageManager.PERMISSION_GRANTED,
- )
- }
-
- @Test
- fun `onAttachInfo - reportsContext`() {
- val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock()
- underTest.setContextAvailableCallback(callback)
-
- underTest.attachInfo(context, null)
-
- verify(callback).onContextAvailable(context)
- }
-
- @Test
- fun getType() {
- assertThat(underTest.getType(Contract.AffordanceTable.URI))
- .isEqualTo(
- "vnd.android.cursor.dir/vnd." +
- "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}"
- )
- assertThat(underTest.getType(Contract.SlotTable.URI))
- .isEqualTo(
- "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}"
- )
- assertThat(underTest.getType(Contract.SelectionTable.URI))
- .isEqualTo(
- "vnd.android.cursor.dir/vnd." +
- "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}"
- )
- }
-
- @Test
- fun `insert and query selection`() =
- runBlocking(IMMEDIATE) {
- val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
- val affordanceId = AFFORDANCE_2
-
- Client.insertSelection(
- context = context,
- slotId = slotId,
- affordanceId = affordanceId,
- dispatcher = IMMEDIATE,
- )
-
- assertThat(
- Client.querySelections(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Selection(
- slotId = slotId,
- affordanceId = affordanceId,
- )
- )
- )
- }
-
- @Test
- fun `query slots`() =
- runBlocking(IMMEDIATE) {
- assertThat(
- Client.querySlots(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Slot(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- capacity = 1,
- ),
- Client.Slot(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- capacity = 1,
- ),
- )
- )
- }
-
- @Test
- fun `query affordances`() =
- runBlocking(IMMEDIATE) {
- assertThat(
- Client.queryAffordances(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Affordance(
- id = AFFORDANCE_1,
- name = AFFORDANCE_1,
- iconResourceId = 1,
- ),
- Client.Affordance(
- id = AFFORDANCE_2,
- name = AFFORDANCE_2,
- iconResourceId = 2,
- ),
- )
- )
- }
-
- @Test
- fun `delete and query selection`() =
- runBlocking(IMMEDIATE) {
- Client.insertSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- affordanceId = AFFORDANCE_1,
- dispatcher = IMMEDIATE,
- )
- Client.insertSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- affordanceId = AFFORDANCE_2,
- dispatcher = IMMEDIATE,
- )
-
- Client.deleteSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- affordanceId = AFFORDANCE_2,
- dispatcher = IMMEDIATE,
- )
-
- assertThat(
- Client.querySelections(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Selection(
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- affordanceId = AFFORDANCE_1,
- )
- )
- )
- }
-
- @Test
- fun `delete all selections in a slot`() =
- runBlocking(IMMEDIATE) {
- Client.insertSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- affordanceId = AFFORDANCE_1,
- dispatcher = IMMEDIATE,
- )
- Client.insertSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- affordanceId = AFFORDANCE_2,
- dispatcher = IMMEDIATE,
- )
-
- Client.deleteAllSelections(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- dispatcher = IMMEDIATE,
- )
-
- assertThat(
- Client.querySelections(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Selection(
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- affordanceId = AFFORDANCE_1,
- )
- )
- )
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private const val AFFORDANCE_1 = "affordance_1"
- private const val AFFORDANCE_2 = "affordance_2"
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 23516c9..729a1cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -48,6 +48,7 @@
import com.android.systemui.SystemUIInitializerImpl;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -93,6 +94,8 @@
private NextAlarmController mNextAlarmController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private UserTracker mUserTracker;
private TestableKeyguardSliceProvider mProvider;
private boolean mIsZenMode;
@@ -105,6 +108,7 @@
mProvider.attachInfo(getContext(), null);
reset(mContentResolver);
SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST)));
+ when(mUserTracker.getUserId()).thenReturn(100);
}
@After
@@ -267,6 +271,7 @@
mKeyguardBypassController = KeyguardSliceProviderTest.this.mKeyguardBypassController;
mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
+ mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b6780a1..45aaaa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -57,6 +57,7 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -86,6 +87,7 @@
public class KeyguardViewMediatorTest extends SysuiTestCase {
private KeyguardViewMediator mViewMediator;
+ private @Mock UserTracker mUserTracker;
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock KeyguardUpdateMonitor mUpdateMonitor;
@@ -286,6 +288,7 @@
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
+ mUserTracker,
mFalsingCollector,
mLockPatternUtils,
mBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 21e5068..3269f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -93,7 +93,6 @@
@Test
fun testShow_isScrimmed() {
mPrimaryBouncerInteractor.show(true)
- verify(repository).setShowMessage(null)
verify(repository).setOnScreenTurnedOff(false)
verify(repository).setKeyguardAuthenticated(null)
verify(repository).setPrimaryHide(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
index 7cd8e74..56c91bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
@@ -42,6 +42,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -464,7 +465,7 @@
fun onFalseTapOrTouch() {
whenever(mockController.getTransportControls()).thenReturn(mockTransport)
whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
- whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
viewModel.updateController(mockController)
val pos = 169
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 575b1c6..9d33e6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -22,13 +22,13 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.ui.MediaPlayerData
import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -64,7 +64,7 @@
class MediaDataFilterTest : SysuiTestCase() {
@Mock private lateinit var listener: MediaDataManager.Listener
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var broadcastSender: BroadcastSender
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@@ -85,7 +85,7 @@
mediaDataFilter =
MediaDataFilter(
context,
- broadcastDispatcher,
+ userTracker,
broadcastSender,
lockscreenUserManager,
executor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
index a8f4138..a943746 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
@@ -25,7 +25,8 @@
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.monet.ColorScheme
-import com.android.systemui.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -62,6 +63,7 @@
@Mock private lateinit var mediaViewHolder: MediaViewHolder
@Mock private lateinit var gutsViewHolder: GutsViewHolder
@Mock private lateinit var multiRippleController: MultiRippleController
+ @Mock private lateinit var turbulenceNoiseController: TurbulenceNoiseController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
@@ -76,6 +78,7 @@
context,
mediaViewHolder,
multiRippleController,
+ turbulenceNoiseController,
animatingColorTransitionFactory
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 1ad2ca9b..761773b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -78,9 +78,10 @@
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.ripple.MultiRippleView
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -178,6 +179,7 @@
private lateinit var dismiss: FrameLayout
private lateinit var dismissText: TextView
private lateinit var multiRippleView: MultiRippleView
+ private lateinit var turbulenceNoiseView: TurbulenceNoiseView
private lateinit var session: MediaSession
private lateinit var device: MediaDeviceData
@@ -210,7 +212,10 @@
private lateinit var recSubtitle3: TextView
private var shouldShowBroadcastButton: Boolean = false
private val fakeFeatureFlag =
- FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) }
+ FakeFeatureFlags().apply {
+ this.set(Flags.UMO_SURFACE_RIPPLE, false)
+ this.set(Flags.MEDIA_FALSING_PENALTY, true)
+ }
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -382,6 +387,7 @@
}
multiRippleView = MultiRippleView(context, null)
+ turbulenceNoiseView = TurbulenceNoiseView(context, null)
whenever(viewHolder.player).thenReturn(view)
whenever(viewHolder.appIcon).thenReturn(appIcon)
@@ -425,6 +431,7 @@
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
+ whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
}
/** Initialize elements for the recommendation view holder */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 939af16..d35a212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -4,6 +4,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -11,11 +12,11 @@
import com.android.wm.shell.util.GroupedRecentTaskInfo
import com.google.common.truth.Truth.assertThat
import java.util.*
+import java.util.function.Consumer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
-import java.util.function.Consumer
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -23,8 +24,14 @@
private val dispatcher = Dispatchers.Unconfined
private val recentTasks: RecentTasks = mock()
+ private val userTracker: UserTracker = mock()
private val recentTaskListProvider =
- ShellRecentTaskListProvider(dispatcher, Runnable::run, Optional.of(recentTasks))
+ ShellRecentTaskListProvider(
+ dispatcher,
+ Runnable::run,
+ Optional.of(recentTasks),
+ userTracker
+ )
@Test
fun loadRecentTasks_oneTask_returnsTheSameTask() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 5abc0e1..35c8cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -27,6 +27,8 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.Context;
+import android.content.res.Resources;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -42,16 +44,22 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TileLayoutTest extends SysuiTestCase {
- private TileLayout mTileLayout;
+ private Resources mResources;
private int mLayoutSizeForOneTile;
+ private TileLayout mTileLayout; // under test
@Before
public void setUp() throws Exception {
- mTileLayout = new TileLayout(mContext);
+ Context context = Mockito.spy(mContext);
+ mResources = Mockito.spy(context.getResources());
+ Mockito.when(mContext.getResources()).thenReturn(mResources);
+
+ mTileLayout = new TileLayout(context);
// Layout needs to leave space for the tile margins. Three times the margin size is
// sufficient for any number of columns.
mLayoutSizeForOneTile =
@@ -203,4 +211,21 @@
verify(tileRecord1.tileView).setPosition(0);
verify(tileRecord2.tileView).setPosition(1);
}
+
+ @Test
+ public void resourcesChanged_updateResources_returnsTrue() {
+ Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+ mTileLayout.updateResources(); // setup with 1
+ Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
+
+ assertEquals(true, mTileLayout.updateResources());
+ }
+
+ @Test
+ public void resourcesSame_updateResources_returnsFalse() {
+ Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+ mTileLayout.updateResources(); // setup with 1
+
+ assertEquals(false, mTileLayout.updateResources());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 73a0cbc..030c59f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.GlobalSettings
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
@@ -64,6 +65,8 @@
private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
@Mock
private lateinit var mGlobalSettings: GlobalSettings
+ @Mock
+ private lateinit var mUserTracker: UserTracker
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: AirplaneModeTile
@@ -87,7 +90,8 @@
mQsLogger,
mBroadcastDispatcher,
mConnectivityManager,
- mGlobalSettings)
+ mGlobalSettings,
+ mUserTracker)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 3131f60..08a90b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -42,27 +42,21 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UserDetailViewAdapterTest : SysuiTestCase() {
- @Mock
- private lateinit var mUserSwitcherController: UserSwitcherController
- @Mock
- private lateinit var mParent: ViewGroup
- @Mock
- private lateinit var mUserDetailItemView: UserDetailItemView
- @Mock
- private lateinit var mOtherView: View
- @Mock
- private lateinit var mInflatedUserDetailItemView: UserDetailItemView
- @Mock
- private lateinit var mLayoutInflater: LayoutInflater
+ @Mock private lateinit var mUserSwitcherController: UserSwitcherController
+ @Mock private lateinit var mParent: ViewGroup
+ @Mock private lateinit var mUserDetailItemView: UserDetailItemView
+ @Mock private lateinit var mOtherView: View
+ @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+ @Mock private lateinit var mLayoutInflater: LayoutInflater
private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake()
private lateinit var adapter: UserDetailView.Adapter
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -77,10 +71,13 @@
`when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
.thenReturn(mInflatedUserDetailItemView)
`when`(mParent.context).thenReturn(mContext)
- adapter = UserDetailView.Adapter(
- mContext, mUserSwitcherController, uiEventLogger,
- falsingManagerFake
- )
+ adapter =
+ UserDetailView.Adapter(
+ mContext,
+ mUserSwitcherController,
+ uiEventLogger,
+ falsingManagerFake
+ )
mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 2ef7312..48a53bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -60,8 +60,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.UnreleasedFlag;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -169,8 +169,8 @@
private WifiStateWorker mWifiStateWorker;
@Mock
private SignalStrength mSignalStrength;
- @Mock
- private FeatureFlags mFlags;
+
+ private FakeFeatureFlags mFlags = new FakeFeatureFlags();
private TestableResources mTestableResources;
private InternetDialogController mInternetDialogController;
@@ -221,6 +221,7 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mInternetDialogController.mActivityStarter = mActivityStarter;
mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, false);
}
@After
@@ -410,7 +411,7 @@
@Test
public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
fakeAirplaneModeEnabled(false);
when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
@@ -767,7 +768,7 @@
@Test
public void getSignalStrengthIcon_differentSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
Drawable icons = spyController.getSignalStrengthIcon(SUB_ID, mContext, 1, 1, 0, false);
Drawable icons2 = spyController.getSignalStrengthIcon(SUB_ID2, mContext, 1, 1, 0, false);
@@ -777,7 +778,7 @@
@Test
public void getActiveAutoSwitchNonDdsSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
// active on non-DDS
SubscriptionInfo info = mock(SubscriptionInfo.class);
doReturn(SUB_ID2).when(info).getSubscriptionId();
@@ -813,7 +814,7 @@
@Test
public void getMobileNetworkSummary() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
doReturn(true).when(spyController).isMobileDataEnabled();
@@ -837,7 +838,7 @@
@Test
public void launchMobileNetworkSettings_validSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
spyController.launchMobileNetworkSettings(mDialogLaunchView);
@@ -848,7 +849,7 @@
@Test
public void launchMobileNetworkSettings_invalidSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.when(spyController).getActiveAutoSwitchNonDdsSubId();
@@ -860,7 +861,7 @@
@Test
public void setAutoDataSwitchMobileDataPolicy() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
mInternetDialogController.setAutoDataSwitchMobileDataPolicy(SUB_ID, true);
verify(mTelephonyManager).setMobileDataPolicyEnabled(eq(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index f4bc232..df3a62f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -75,7 +75,7 @@
private static final byte[] EXIF_FILE_TAG = "Exif\u0000\u0000".getBytes(US_ASCII);
private static final ZonedDateTime CAPTURE_TIME =
- ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("EST"));
+ ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("America/New_York"));
private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
deleted file mode 100644
index 1b515c6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.settings;
-
-import android.content.Intent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Testing functionality of the current user tracker
- */
-@SmallTest
-public class CurrentUserTrackerTest extends SysuiTestCase {
-
- private CurrentUserTracker mTracker;
- private CurrentUserTracker.UserReceiver mReceiver;
- @Mock
- private BroadcastDispatcher mBroadcastDispatcher;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mReceiver = new CurrentUserTracker.UserReceiver(mBroadcastDispatcher);
- mTracker = new CurrentUserTracker(mReceiver) {
- @Override
- public void onUserSwitched(int newUserId) {
- stopTracking();
- }
- };
- }
-
- @Test
- public void testBroadCastDoesntCrashOnConcurrentModification() {
- mTracker.startTracking();
- CurrentUserTracker secondTracker = new CurrentUserTracker(mReceiver) {
- @Override
- public void onUserSwitched(int newUserId) {
- stopTracking();
- }
- };
- secondTracker.startTracking();
- triggerUserSwitch();
- }
- /**
- * Simulates a user switch event.
- */
- private void triggerUserSwitch() {
- Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, 1);
- mReceiver.onReceive(getContext(), intent);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 1130bda..9d1802a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -28,9 +28,10 @@
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -45,7 +46,9 @@
@TestableLooper.RunWithLooper
class BrightnessDialogTest : SysuiTestCase() {
+ @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
+ @Mock private lateinit var mainExecutor: Executor
@Mock private lateinit var backgroundHandler: Handler
@Mock private lateinit var brightnessSliderController: BrightnessSliderController
@@ -56,8 +59,9 @@
object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
override fun create(intent: Intent?): TestDialog {
return TestDialog(
- fakeBroadcastDispatcher,
+ userTracker,
brightnessSliderControllerFactory,
+ mainExecutor,
backgroundHandler
)
}
@@ -100,8 +104,15 @@
}
class TestDialog(
- broadcastDispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+ mainExecutor: Executor,
backgroundHandler: Handler
- ) : BrightnessDialog(broadcastDispatcher, brightnessSliderControllerFactory, backgroundHandler)
+ ) :
+ BrightnessDialog(
+ userTracker,
+ brightnessSliderControllerFactory,
+ mainExecutor,
+ backgroundHandler
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 0ce9056..bc17c19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -320,6 +321,64 @@
assertThat(changes.largeScreenConstraintsChanges).isNull()
}
+ @Test
+ fun testRelevantViewsAreNotMatchConstraints() {
+ val views = mapOf(
+ R.id.clock to "clock",
+ R.id.date to "date",
+ R.id.statusIcons to "icons",
+ R.id.privacy_container to "privacy",
+ R.id.carrier_group to "carriers",
+ R.id.batteryRemainingIcon to "battery",
+ )
+ views.forEach { (id, name) ->
+ assertWithMessage("$name has 0 height in qqs")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+ assertWithMessage("$name has 0 width in qqs")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+ assertWithMessage("$name has 0 height in qs")
+ .that(qsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+ assertWithMessage("$name has 0 width in qs")
+ .that(qsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+ }
+ }
+
+ @Test
+ fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
+ val views = mapOf(
+ R.id.clock to "clock",
+ R.id.date to "date",
+ R.id.statusIcons to "icons",
+ R.id.privacy_container to "privacy",
+ R.id.carrier_group to "carriers",
+ R.id.batteryRemainingIcon to "battery",
+ )
+ views.forEach { (id, name) ->
+ assertWithMessage("$name changes height")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight)
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight)
+ assertWithMessage("$name changes width")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth)
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth)
+ }
+ }
+
+ @Test
+ fun testEmptyCutoutDateIconsAreConstrainedWidth() {
+ CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
+
+ assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+ assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+ }
+
+ @Test
+ fun testCenterCutoutDateIconsAreConstrainedWidth() {
+ CombinedShadeHeadersConstraintManagerImpl.centerCutoutConstraints(false, 10)()
+
+ assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+ assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+ }
+
private operator fun ConstraintsChanges.invoke() {
qqsConstraintsChanges?.invoke(qqsConstraint)
qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 539a54b..f5bed79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -139,12 +139,19 @@
}
@Test
- fun defaultClock_events_onFontSettingChanged() {
+ fun defaultSmallClock_events_onFontSettingChanged() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
- clock.events.onFontSettingChanged()
+ clock.smallClock.events.onFontSettingChanged(100f)
- verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
- verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
+ verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(100f))
+ }
+
+ @Test
+ fun defaultLargeClock_events_onFontSettingChanged() {
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ clock.largeClock.events.onFontSettingChanged(200f)
+
+ verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f))
verify(mockLargeClockView).setLayoutParams(any())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index fe4da47..e8a7ec8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -87,6 +87,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -271,7 +272,7 @@
mUserManager, mExecutor, mExecutor, mFalsingManager,
mAuthController, mLockPatternUtils, mScreenLifecycle,
mKeyguardBypassController, mAccessibilityManager,
- mFaceHelpMessageDeferral);
+ mFaceHelpMessageDeferral, mock(KeyguardLogger.class));
mController.init();
mController.setIndicationArea(mIndicationArea);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index bdafa48..15a687d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -53,6 +53,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -78,6 +79,8 @@
private NotificationPresenter mPresenter;
@Mock
private UserManager mUserManager;
+ @Mock
+ private UserTracker mUserTracker;
// Dependency mocks:
@Mock
@@ -115,6 +118,7 @@
MockitoAnnotations.initMocks(this);
int currentUserId = ActivityManager.getCurrentUser();
+ when(mUserTracker.getUserId()).thenReturn(currentUserId);
mSettings = new FakeSettings();
mSettings.setUserId(ActivityManager.getCurrentUser());
mCurrentUser = new UserInfo(currentUserId, "", 0);
@@ -344,6 +348,7 @@
mBroadcastDispatcher,
mDevicePolicyManager,
mUserManager,
+ mUserTracker,
(() -> mVisibilityProvider),
(() -> mNotifCollection),
mClickNotifier,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 9c870b5..faf4592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -71,6 +71,7 @@
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -116,6 +117,7 @@
protected TelephonyManager mMockTm;
protected TelephonyListenerManager mTelephonyListenerManager;
protected BroadcastDispatcher mMockBd;
+ protected UserTracker mUserTracker;
protected Config mConfig;
protected CallbackHandler mCallbackHandler;
protected SubscriptionDefaults mMockSubDefaults;
@@ -172,6 +174,7 @@
mMockSm = mock(SubscriptionManager.class);
mMockCm = mock(ConnectivityManager.class);
mMockBd = mock(BroadcastDispatcher.class);
+ mUserTracker = mock(UserTracker.class);
mMockNsm = mock(NetworkScoreManager.class);
mMockSubDefaults = mock(SubscriptionDefaults.class);
mCarrierConfigTracker = mock(CarrierConfigTracker.class);
@@ -246,6 +249,7 @@
mMockSubDefaults,
mMockProvisionController,
mMockBd,
+ mUserTracker,
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 1d11226..ca75a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -154,6 +154,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index d5f5105..84c242c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -82,6 +82,7 @@
mMockSubDefaults,
mMockProvisionController,
mMockBd,
+ mUserTracker,
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
@@ -118,6 +119,7 @@
mMockSubDefaults,
mMockProvisionController,
mMockBd,
+ mUserTracker,
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
@@ -152,6 +154,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
@@ -189,6 +192,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
@@ -274,6 +278,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
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 808abc8..de71e2c 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
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
@@ -59,7 +58,6 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -119,7 +117,6 @@
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock private KeyguardViewMediator mKeyguardViewMediator;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -233,8 +230,7 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager,
- mKeyguardViewMediator);
+ mStatusBarKeyguardViewManager);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -243,8 +239,6 @@
mScrimController.setWallpaperSupportsAmbientMode(false);
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
-
- mScrimController.setLaunchingAffordanceWithPreview(false);
}
@After
@@ -858,8 +852,7 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager,
- mKeyguardViewMediator);
+ mStatusBarKeyguardViewManager);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1638,30 +1631,6 @@
assertScrimAlpha(mScrimBehind, 0);
}
- @Test
- public void keyguardAlpha_whenUnlockedForOcclusion_ifPlayingOcclusionAnimation() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
-
- when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
-
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- finishAnimationsImmediately();
-
- assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
- }
-
- @Test
- public void keyguardAlpha_whenUnlockedForLaunch_ifLaunchingAffordance() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
- when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
- mScrimController.setLaunchingAffordanceWithPreview(true);
-
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- finishAnimationsImmediately();
-
- assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
- }
-
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 49c3a21..9f70565 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -222,9 +222,16 @@
}
@Test
- public void onPanelExpansionChanged_neverHidesScrimmedBouncer() {
+ public void onPanelExpansionChanged_neverHidesFullscreenBouncer() {
when(mPrimaryBouncer.isShowing()).thenReturn(true);
- when(mPrimaryBouncer.isScrimmed()).thenReturn(true);
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPuk);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
+
+ reset(mPrimaryBouncer);
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPin);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
}
@@ -271,13 +278,6 @@
}
@Test
- public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
- mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animate */);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
- }
-
- @Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
when(mBiometricUnlockController.getMode())
.thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
index f304647..0a3da0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
@@ -237,7 +237,7 @@
fun refresh() {
underTest.refresh()
- verify(controller).refreshUsers(UserHandle.USER_NULL)
+ verify(controller).refreshUsers()
}
private fun createUserRecord(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 43d0fe9..1eee08c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -221,4 +221,33 @@
Assert.assertFalse(mBatteryController.isChargingSourceDock());
}
+
+ @Test
+ public void batteryStateChanged_healthNotOverheated_outputsFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isOverheated());
+ }
+
+ @Test
+ public void batteryStateChanged_healthOverheated_outputsTrue() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isOverheated());
+ }
+
+ @Test
+ public void batteryStateChanged_noHealthGiven_outputsFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isOverheated());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index d0391ac..833cabb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bluetooth.BluetoothLogger;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Test;
@@ -56,6 +57,7 @@
@SmallTest
public class BluetoothControllerImplTest extends SysuiTestCase {
+ private UserTracker mUserTracker;
private LocalBluetoothManager mMockBluetoothManager;
private CachedBluetoothDeviceManager mMockDeviceManager;
private LocalBluetoothAdapter mMockAdapter;
@@ -70,6 +72,7 @@
mTestableLooper = TestableLooper.get(this);
mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
mDevices = new ArrayList<>();
+ mUserTracker = mock(UserTracker.class);
mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager);
@@ -81,6 +84,7 @@
mMockDumpManager = mock(DumpManager.class);
mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+ mUserTracker,
mMockDumpManager,
mock(BluetoothLogger.class),
mTestableLooper.getLooper(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 26df03f..dc08aba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +62,8 @@
public class HotspotControllerImplTest extends SysuiTestCase {
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private DumpManager mDumpManager;
@Mock
private TetheringManager mTetheringManager;
@@ -104,7 +107,8 @@
Handler handler = new Handler(mLooper.getLooper());
- mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+ mController = new HotspotControllerImpl(mContext, mUserTracker, handler, handler,
+ mDumpManager);
verify(mTetheringManager)
.registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture());
}
@@ -191,7 +195,7 @@
Handler handler = new Handler(mLooper.getLooper());
HotspotController controller =
- new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+ new HotspotControllerImpl(mContext, mUserTracker, handler, handler, mDumpManager);
verifyNoMoreInteractions(mTetheringManager);
assertFalse(controller.isHotspotSupported());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index d44cdb2..15235b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -50,6 +50,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -72,10 +73,12 @@
private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
private final IKeyChainService.Stub mKeyChainService = mock(IKeyChainService.Stub.class);
private final UserManager mUserManager = mock(UserManager.class);
+ private final UserTracker mUserTracker = mock(UserTracker.class);
private final BroadcastDispatcher mBroadcastDispatcher = mock(BroadcastDispatcher.class);
private final Handler mHandler = mock(Handler.class);
private SecurityControllerImpl mSecurityController;
private ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class);
+ private FakeExecutor mMainExecutor;
private FakeExecutor mBgExecutor;
private BroadcastReceiver mBroadcastReceiver;
@@ -102,11 +105,14 @@
ArgumentCaptor<BroadcastReceiver> brCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
+ mMainExecutor = new FakeExecutor(new FakeSystemClock());
mBgExecutor = new FakeExecutor(new FakeSystemClock());
mSecurityController = new SecurityControllerImpl(
mContext,
+ mUserTracker,
mHandler,
mBroadcastDispatcher,
+ mMainExecutor,
mBgExecutor,
Mockito.mock(DumpManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index 169f4fb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- * Copyright (C) 2021 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.policy
-
-import android.app.IActivityManager
-import android.app.NotificationManager
-import android.app.admin.DevicePolicyManager
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.hardware.face.FaceManager
-import android.hardware.fingerprint.FingerprintManager
-import android.os.Handler
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.ThreadedRenderer
-import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.LatencyTracker
-import com.android.internal.util.UserIcons
-import com.android.systemui.GuestResetOrExitSessionReceiver
-import com.android.systemui.GuestResumeSessionReceiver
-import com.android.systemui.GuestSessionNotification
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogCuj
-import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.QSUserSwitcherEvent
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.NotificationShadeWindowView
-import com.android.systemui.telephony.TelephonyListenerManager
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-class UserSwitcherControllerOldImplTest : SysuiTestCase() {
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var activityManager: IActivityManager
- @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var handler: Handler
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var userManager: UserManager
- @Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var broadcastSender: BroadcastSender
- @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
- @Mock private lateinit var secureSettings: SecureSettings
- @Mock private lateinit var falsingManager: FalsingManager
- @Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
- @Mock private lateinit var latencyTracker: LatencyTracker
- @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
- @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
- @Mock private lateinit var threadedRenderer: ThreadedRenderer
- @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
- @Mock private lateinit var globalSettings: GlobalSettings
- @Mock private lateinit var guestSessionNotification: GuestSessionNotification
- @Mock private lateinit var guestResetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
- private lateinit var resetSessionDialogFactory:
- GuestResumeSessionReceiver.ResetSessionDialog.Factory
- private lateinit var guestResumeSessionReceiver: GuestResumeSessionReceiver
- private lateinit var testableLooper: TestableLooper
- private lateinit var bgExecutor: FakeExecutor
- private lateinit var longRunningExecutor: FakeExecutor
- private lateinit var uiExecutor: FakeExecutor
- private lateinit var uiEventLogger: UiEventLoggerFake
- private lateinit var userSwitcherController: UserSwitcherControllerOldImpl
- private lateinit var picture: Bitmap
- private val ownerId = UserHandle.USER_SYSTEM
- private val ownerInfo = UserInfo(ownerId, "Owner", null,
- UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or
- UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN,
- UserManager.USER_TYPE_FULL_SYSTEM)
- private val guestId = 1234
- private val guestInfo = UserInfo(guestId, "Guest", null,
- UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST)
- private val secondaryUser =
- UserInfo(10, "Secondary", null, 0, UserManager.USER_TYPE_FULL_SECONDARY)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- testableLooper = TestableLooper.get(this)
- bgExecutor = FakeExecutor(FakeSystemClock())
- longRunningExecutor = FakeExecutor(FakeSystemClock())
- uiExecutor = FakeExecutor(FakeSystemClock())
- uiEventLogger = UiEventLoggerFake()
-
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_guestUserAutoCreated, false)
-
- mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java))
- mContext.addMockSystemService(Context.NOTIFICATION_SERVICE,
- mock(NotificationManager::class.java))
- mContext.addMockSystemService(Context.FINGERPRINT_SERVICE,
- mock(FingerprintManager::class.java))
-
- resetSessionDialogFactory = object : GuestResumeSessionReceiver.ResetSessionDialog.Factory {
- override fun create(userId: Int): GuestResumeSessionReceiver.ResetSessionDialog {
- return GuestResumeSessionReceiver.ResetSessionDialog(
- mContext,
- mock(UserSwitcherController::class.java),
- uiEventLogger,
- userId
- )
- }
- }
-
- guestResumeSessionReceiver = GuestResumeSessionReceiver(userTracker,
- secureSettings,
- broadcastDispatcher,
- guestSessionNotification,
- resetSessionDialogFactory)
-
- `when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY)))
- .thenReturn(true)
- `when`(notificationShadeWindowView.context).thenReturn(context)
-
- // Since userSwitcherController involves InteractionJankMonitor.
- // Let's fulfill the dependencies.
- val mockedContext = mock(Context::class.java)
- doReturn(mockedContext).`when`(notificationShadeWindowView).context
- doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
- doNothing().`when`(threadedRenderer).addObserver(any())
- doNothing().`when`(threadedRenderer).removeObserver(any())
- doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
-
- picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
-
- // Create defaults for the current user
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
-
- setupController()
- }
-
- private fun setupController() {
- userSwitcherController =
- UserSwitcherControllerOldImpl(
- mContext,
- activityManager,
- userManager,
- userTracker,
- keyguardStateController,
- deviceProvisionedController,
- devicePolicyManager,
- handler,
- activityStarter,
- broadcastDispatcher,
- broadcastSender,
- uiEventLogger,
- falsingManager,
- telephonyListenerManager,
- secureSettings,
- globalSettings,
- bgExecutor,
- longRunningExecutor,
- uiExecutor,
- interactionJankMonitor,
- latencyTracker,
- dumpManager,
- dialogLaunchAnimator,
- guestResumeSessionReceiver,
- guestResetOrExitSessionReceiver
- )
- userSwitcherController.init(notificationShadeWindowView)
- }
-
- @Test
- fun testSwitchUser_parentDialogDismissed() {
- val otherUserRecord = UserRecord(
- secondaryUser,
- picture,
- false /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- userSwitcherController.onUserListItemClicked(otherUserRecord, dialogShower)
- testableLooper.processAllMessages()
-
- verify(dialogShower).dismiss()
- }
-
- @Test
- fun testAddGuest_okButtonPressed() {
- val emptyGuestUserRecord =
- UserRecord(
- null,
- null,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
- bgExecutor.runAllReady()
- uiExecutor.runAllReady()
- testableLooper.processAllMessages()
- verify(interactionJankMonitor).begin(any())
- verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
- verify(activityManager).switchUser(guestInfo.id)
- assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
- }
-
- @Test
- fun testAddGuest_parentDialogDismissed() {
- val emptyGuestUserRecord =
- UserRecord(
- null,
- null,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
- bgExecutor.runAllReady()
- uiExecutor.runAllReady()
- testableLooper.processAllMessages()
- verify(dialogShower).dismiss()
- }
-
- @Test
- fun testRemoveGuest_removeButtonPressed_isLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestInfo.id)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
- testableLooper.processAllMessages()
- assertEquals(1, uiEventLogger.numLogs())
- assertTrue(
- QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id == uiEventLogger.eventId(0) ||
- QSUserSwitcherEvent.QS_USER_SWITCH.id == uiEventLogger.eventId(0)
- )
- }
-
- @Test
- fun testRemoveGuest_removeButtonPressed_dialogDismissed() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestInfo.id)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
- testableLooper.processAllMessages()
- assertFalse(userSwitcherController.mExitGuestDialog.isShowing)
- }
-
- @Test
- fun testRemoveGuest_dialogShowerUsed() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestInfo.id)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- testableLooper.processAllMessages()
- verify(dialogShower)
- .showDialog(
- userSwitcherController.mExitGuestDialog,
- DialogCuj(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, "exit_guest_mode"))
- }
-
- @Test
- fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestId)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_NEUTRAL).performClick()
- testableLooper.processAllMessages()
- assertEquals(0, uiEventLogger.numLogs())
- }
-
- @Test
- fun testWipeGuest_startOverButtonPressed_isLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestId)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- // Simulate that guest user has already logged in
- `when`(secureSettings.getIntForUser(
- eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
- .thenReturn(1)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
- // Simulate a user switch event
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
- userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
- userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
- .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_WIPE).performClick()
- testableLooper.processAllMessages()
- assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_WIPE.id, uiEventLogger.eventId(0))
- }
-
- @Test
- fun testWipeGuest_continueButtonPressed_isLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestId)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- // Simulate that guest user has already logged in
- `when`(secureSettings.getIntForUser(
- eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
- .thenReturn(1)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
- // Simulate a user switch event
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
- userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
- userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
- .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_DONTWIPE)
- .performClick()
- testableLooper.processAllMessages()
- assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE.id, uiEventLogger.eventId(0))
- }
-
- @Test
- fun test_getCurrentUserName_shouldReturnNameOfTheCurrentUser() {
- fun addUser(id: Int, name: String, isCurrent: Boolean) {
- userSwitcherController.users.add(
- UserRecord(
- UserInfo(id, name, 0),
- null, false, isCurrent, false,
- false, false, false
- )
- )
- }
- val bgUserName = "background_user"
- val fgUserName = "foreground_user"
-
- addUser(1, bgUserName, false)
- addUser(2, fgUserName, true)
-
- assertEquals(fgUserName, userSwitcherController.currentUserName)
- }
-
- @Test
- fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
- `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
- assertEquals(true, userSwitcherController.isSystemUser)
- }
-
- @Test
- fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
- `when`(userTracker.userId).thenReturn(1)
- assertEquals(false, userSwitcherController.isSystemUser)
- }
-
- @Test
- fun testCanCreateSupervisedUserWithConfiguredPackage() {
- // GIVEN the supervised user creation package is configured
- `when`(context.getString(
- com.android.internal.R.string.config_supervisedUserCreationPackage))
- .thenReturn("some_pkg")
-
- // AND the current user is allowed to create new users
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- // WHEN the controller is started with the above config
- setupController()
- testableLooper.processAllMessages()
-
- // THEN a supervised user can be constructed
- assertTrue(userSwitcherController.canCreateSupervisedUser())
- }
-
- @Test
- fun testCannotCreateSupervisedUserWithConfiguredPackage() {
- // GIVEN the supervised user creation package is NOT configured
- `when`(context.getString(
- com.android.internal.R.string.config_supervisedUserCreationPackage))
- .thenReturn(null)
-
- // AND the current user is allowed to create new users
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- // WHEN the controller is started with the above config
- setupController()
- testableLooper.processAllMessages()
-
- // THEN a supervised user can NOT be constructed
- assertFalse(userSwitcherController.canCreateSupervisedUser())
- }
-
- @Test
- fun testCannotCreateUserWhenUserSwitcherDisabled() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
- setupController()
- assertFalse(userSwitcherController.canCreateUser())
- }
-
- @Test
- fun testCannotCreateGuestUserWhenUserSwitcherDisabled() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
- setupController()
- assertFalse(userSwitcherController.canCreateGuest(false))
- }
-
- @Test
- fun testCannotCreateSupervisedUserWhenUserSwitcherDisabled() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
- setupController()
- assertFalse(userSwitcherController.canCreateSupervisedUser())
- }
-
- @Test
- fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
- setupController()
- assertTrue(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
- setupController()
- assertFalse(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun testCanManageUser_userSwitcherEnabled_isAdmin() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
-
- setupController()
- assertTrue(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun testCanManageUser_userSwitcherDisabled_isAdmin() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
-
- setupController()
- assertFalse(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun addUserSwitchCallback() {
- val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
- verify(broadcastDispatcher).registerReceiver(
- capture(broadcastReceiverCaptor),
- any(),
- nullable(), nullable(), anyInt(), nullable())
-
- val cb = mock(UserSwitcherController.UserSwitchCallback::class.java)
- userSwitcherController.addUserSwitchCallback(cb)
-
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
- broadcastReceiverCaptor.value.onReceive(context, intent)
- verify(cb).onUserSwitched()
- }
-
- @Test
- fun onUserItemClicked_guest_runsOnBgThread() {
- val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
- val guestUserRecord = UserRecord(
- null,
- picture,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
-
- userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
- assertTrue(bgExecutor.numPending() > 0)
- verify(userManager, never()).createGuest(context)
- bgExecutor.runAllReady()
- verify(userManager).createGuest(context)
- }
-
- @Test
- fun onUserItemClicked_manageUsers() {
- val manageUserRecord = LegacyUserDataHelper.createRecord(
- mContext,
- ownerId,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- isRestricted = false,
- isSwitchToEnabled = true
- )
-
- userSwitcherController.onUserListItemClicked(manageUserRecord, null)
- val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(),
- eq(true)
- )
- Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 3fe1a9f..c06dbdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -35,6 +35,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.ZenModeController.Callback;
import com.android.systemui.util.settings.FakeSettings;
@@ -58,6 +59,8 @@
BroadcastDispatcher mBroadcastDispatcher;
@Mock
DumpManager mDumpManager;
+ @Mock
+ UserTracker mUserTracker;
private ZenModeControllerImpl mController;
@@ -72,7 +75,8 @@
Handler.createAsync(Looper.myLooper()),
mBroadcastDispatcher,
mDumpManager,
- new FakeSettings());
+ new FakeSettings(),
+ mUserTracker);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
index 05512e5..0d19ab1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.Color
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
new file mode 100644
index 0000000..2024d53
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.surfaceeffects.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiRippleViewTest : SysuiTestCase() {
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun onRippleFinishes_triggersRippleFinished() {
+ val multiRippleView = MultiRippleView(context, null)
+ val multiRippleController = MultiRippleController(multiRippleView)
+ val rippleAnimationConfig = RippleAnimationConfig(duration = 1000L)
+
+ var isTriggered = false
+ val listener =
+ object : MultiRippleView.Companion.RipplesFinishedListener {
+ override fun onRipplesFinish() {
+ isTriggered = true
+ }
+ }
+ multiRippleView.addRipplesFinishedListener(listener)
+
+ fakeExecutor.execute {
+ val rippleAnimation = RippleAnimation(rippleAnimationConfig)
+ multiRippleController.play(rippleAnimation)
+
+ fakeSystemClock.advanceTime(rippleAnimationConfig.duration)
+
+ assertThat(isTriggered).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
index 7662282..756397a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.Color
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
index 2d2f4cc..1e5ab7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -21,12 +21,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
@SmallTest
@RunWith(AndroidTestingRunner::class)
class RippleViewTest : SysuiTestCase() {
- @Mock
private lateinit var rippleView: RippleView
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
new file mode 100644
index 0000000..d25c8c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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.surfaceeffects.turbulencenoise
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseControllerTest : SysuiTestCase() {
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun play_playsTurbulenceNoise() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ fakeExecutor.execute {
+ turbulenceNoiseController.play(config)
+
+ assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(turbulenceNoiseView.isPlaying).isFalse()
+ }
+ }
+
+ @Test
+ fun updateColor_updatesCorrectColor() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f, color = Color.WHITE)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+ val expectedColor = Color.RED
+
+ val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ fakeExecutor.execute {
+ turbulenceNoiseController.play(config)
+
+ turbulenceNoiseView.updateColor(expectedColor)
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(config.color).isEqualTo(expectedColor)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
new file mode 100644
index 0000000..633aac0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 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.surfaceeffects.turbulencenoise
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseViewTest : SysuiTestCase() {
+
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun play_viewHasCorrectVisibility() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+
+ fakeExecutor.execute {
+ turbulenceNoiseView.play(config)
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.VISIBLE)
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+ }
+ }
+
+ @Test
+ fun play_playsAnimation() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ fakeExecutor.execute {
+ turbulenceNoiseView.play(config)
+
+ assertThat(turbulenceNoiseView.isPlaying).isTrue()
+ }
+ }
+
+ @Test
+ fun play_onEnd_triggersOnAnimationEnd() {
+ var animationEnd = false
+ val config =
+ TurbulenceNoiseAnimationConfig(
+ duration = 1000f,
+ onAnimationEnd = { animationEnd = true }
+ )
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ fakeExecutor.execute {
+ turbulenceNoiseView.play(config)
+
+ assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(animationEnd).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
deleted file mode 100644
index 7c7f0e1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2022 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.user.data.repository
-
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
-
- @Before
- fun setUp() {
- super.setUp(isRefactored = true)
- }
-
- @Test
- fun userSwitcherSettings() = runSelfCancelingTest {
- setUpGlobalSettings(
- isSimpleUserSwitcher = true,
- isAddUsersFromLockscreen = true,
- isUserSwitcherEnabled = true,
- )
- underTest = create(this)
-
- var value: UserSwitcherSettingsModel? = null
- underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
-
- assertUserSwitcherSettings(
- model = value,
- expectedSimpleUserSwitcher = true,
- expectedAddUsersFromLockscreen = true,
- expectedUserSwitcherEnabled = true,
- )
-
- setUpGlobalSettings(
- isSimpleUserSwitcher = false,
- isAddUsersFromLockscreen = true,
- isUserSwitcherEnabled = true,
- )
- assertUserSwitcherSettings(
- model = value,
- expectedSimpleUserSwitcher = false,
- expectedAddUsersFromLockscreen = true,
- expectedUserSwitcherEnabled = true,
- )
- }
-
- @Test
- fun refreshUsers() = runSelfCancelingTest {
- underTest = create(this)
- val initialExpectedValue =
- setUpUsers(
- count = 3,
- selectedIndex = 0,
- )
- var userInfos: List<UserInfo>? = null
- var selectedUserInfo: UserInfo? = null
- underTest.userInfos.onEach { userInfos = it }.launchIn(this)
- underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
-
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(initialExpectedValue)
- assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
- assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
- val secondExpectedValue =
- setUpUsers(
- count = 4,
- selectedIndex = 1,
- )
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(secondExpectedValue)
- assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
- assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
- val selectedNonGuestUserId = selectedUserInfo?.id
- val thirdExpectedValue =
- setUpUsers(
- count = 2,
- isLastGuestUser = true,
- selectedIndex = 1,
- )
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(thirdExpectedValue)
- assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
- assertThat(selectedUserInfo?.isGuest).isTrue()
- assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
- }
-
- @Test
- fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
- underTest = create(this)
- val unsortedUsers =
- setUpUsers(
- count = 3,
- selectedIndex = 0,
- isLastGuestUser = true,
- )
- unsortedUsers[0].creationTime = 999
- unsortedUsers[1].creationTime = 900
- unsortedUsers[2].creationTime = 950
- val expectedUsers =
- listOf(
- unsortedUsers[1],
- unsortedUsers[0],
- unsortedUsers[2], // last because this is the guest
- )
- var userInfos: List<UserInfo>? = null
- underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(expectedUsers)
- }
-
- @Test
- fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
- underTest = create(this)
- var selectedUserInfo: UserInfo? = null
- underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
- setUpUsers(
- count = 2,
- selectedIndex = 0,
- )
- tracker.onProfileChanged()
- assertThat(selectedUserInfo?.id == 0)
- setUpUsers(
- count = 2,
- selectedIndex = 1,
- )
- tracker.onProfileChanged()
- assertThat(selectedUserInfo?.id == 1)
- }
-
- private fun setUpUsers(
- count: Int,
- isLastGuestUser: Boolean = false,
- selectedIndex: Int = 0,
- ): List<UserInfo> {
- val userInfos =
- (0 until count).map { index ->
- createUserInfo(
- index,
- isGuest = isLastGuestUser && index == count - 1,
- )
- }
- whenever(manager.aliveUsers).thenReturn(userInfos)
- tracker.set(userInfos, selectedIndex)
- return userInfos
- }
-
- private fun createUserInfo(
- id: Int,
- isGuest: Boolean,
- ): UserInfo {
- val flags = 0
- return UserInfo(
- id,
- "user_$id",
- /* iconPath= */ "",
- flags,
- if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
- )
- }
-
- private fun setUpGlobalSettings(
- isSimpleUserSwitcher: Boolean = false,
- isAddUsersFromLockscreen: Boolean = false,
- isUserSwitcherEnabled: Boolean = true,
- ) {
- context.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
- true,
- )
- globalSettings.putIntForUser(
- UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
- if (isSimpleUserSwitcher) 1 else 0,
- UserHandle.USER_SYSTEM,
- )
- globalSettings.putIntForUser(
- Settings.Global.ADD_USERS_WHEN_LOCKED,
- if (isAddUsersFromLockscreen) 1 else 0,
- UserHandle.USER_SYSTEM,
- )
- globalSettings.putIntForUser(
- Settings.Global.USER_SWITCHER_ENABLED,
- if (isUserSwitcherEnabled) 1 else 0,
- UserHandle.USER_SYSTEM,
- )
- }
-
- private fun assertUserSwitcherSettings(
- model: UserSwitcherSettingsModel?,
- expectedSimpleUserSwitcher: Boolean,
- expectedAddUsersFromLockscreen: Boolean,
- expectedUserSwitcherEnabled: Boolean,
- ) {
- checkNotNull(model)
- assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
- assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
- assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
- }
-
- /**
- * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
- * is then automatically canceled and cleaned-up.
- */
- private fun runSelfCancelingTest(
- block: suspend CoroutineScope.() -> Unit,
- ) =
- runBlocking(Dispatchers.Main.immediate) {
- val scope = CoroutineScope(coroutineContext + Job())
- block(scope)
- scope.cancel()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index dcea83a..2e527be1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,54 +17,263 @@
package com.android.systemui.user.data.repository
+import android.content.pm.UserInfo
+import android.os.UserHandle
import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
-abstract class UserRepositoryImplTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplTest : SysuiTestCase() {
- @Mock protected lateinit var manager: UserManager
- @Mock protected lateinit var controller: UserSwitcherController
+ @Mock private lateinit var manager: UserManager
- protected lateinit var underTest: UserRepositoryImpl
+ private lateinit var underTest: UserRepositoryImpl
- protected lateinit var globalSettings: FakeSettings
- protected lateinit var tracker: FakeUserTracker
- protected lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var globalSettings: FakeSettings
+ private lateinit var tracker: FakeUserTracker
- protected fun setUp(isRefactored: Boolean) {
+ @Before
+ fun setUp() {
MockitoAnnotations.initMocks(this)
globalSettings = FakeSettings()
tracker = FakeUserTracker()
- featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored)
}
- protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+ @Test
+ fun userSwitcherSettings() = runSelfCancelingTest {
+ setUpGlobalSettings(
+ isSimpleUserSwitcher = true,
+ isAddUsersFromLockscreen = true,
+ isUserSwitcherEnabled = true,
+ )
+ underTest = create(this)
+
+ var value: UserSwitcherSettingsModel? = null
+ underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+ assertUserSwitcherSettings(
+ model = value,
+ expectedSimpleUserSwitcher = true,
+ expectedAddUsersFromLockscreen = true,
+ expectedUserSwitcherEnabled = true,
+ )
+
+ setUpGlobalSettings(
+ isSimpleUserSwitcher = false,
+ isAddUsersFromLockscreen = true,
+ isUserSwitcherEnabled = true,
+ )
+ assertUserSwitcherSettings(
+ model = value,
+ expectedSimpleUserSwitcher = false,
+ expectedAddUsersFromLockscreen = true,
+ expectedUserSwitcherEnabled = true,
+ )
+ }
+
+ @Test
+ fun refreshUsers() = runSelfCancelingTest {
+ underTest = create(this)
+ val initialExpectedValue =
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ )
+ var userInfos: List<UserInfo>? = null
+ var selectedUserInfo: UserInfo? = null
+ underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(initialExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+ val secondExpectedValue =
+ setUpUsers(
+ count = 4,
+ selectedIndex = 1,
+ )
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(secondExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+ val selectedNonGuestUserId = selectedUserInfo?.id
+ val thirdExpectedValue =
+ setUpUsers(
+ count = 2,
+ isLastGuestUser = true,
+ selectedIndex = 1,
+ )
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(thirdExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+ assertThat(selectedUserInfo?.isGuest).isTrue()
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+ }
+
+ @Test
+ fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
+ underTest = create(this)
+ val unsortedUsers =
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ isLastGuestUser = true,
+ )
+ unsortedUsers[0].creationTime = 999
+ unsortedUsers[1].creationTime = 900
+ unsortedUsers[2].creationTime = 950
+ val expectedUsers =
+ listOf(
+ unsortedUsers[1],
+ unsortedUsers[0],
+ unsortedUsers[2], // last because this is the guest
+ )
+ var userInfos: List<UserInfo>? = null
+ underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(expectedUsers)
+ }
+
+ private fun setUpUsers(
+ count: Int,
+ isLastGuestUser: Boolean = false,
+ selectedIndex: Int = 0,
+ ): List<UserInfo> {
+ val userInfos =
+ (0 until count).map { index ->
+ createUserInfo(
+ index,
+ isGuest = isLastGuestUser && index == count - 1,
+ )
+ }
+ whenever(manager.aliveUsers).thenReturn(userInfos)
+ tracker.set(userInfos, selectedIndex)
+ return userInfos
+ }
+ @Test
+ fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
+ underTest = create(this)
+ var selectedUserInfo: UserInfo? = null
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+ setUpUsers(
+ count = 2,
+ selectedIndex = 0,
+ )
+ tracker.onProfileChanged()
+ assertThat(selectedUserInfo?.id).isEqualTo(0)
+ setUpUsers(
+ count = 2,
+ selectedIndex = 1,
+ )
+ tracker.onProfileChanged()
+ assertThat(selectedUserInfo?.id).isEqualTo(1)
+ }
+
+ private fun createUserInfo(
+ id: Int,
+ isGuest: Boolean,
+ ): UserInfo {
+ val flags = 0
+ return UserInfo(
+ id,
+ "user_$id",
+ /* iconPath= */ "",
+ flags,
+ if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+ )
+ }
+
+ private fun setUpGlobalSettings(
+ isSimpleUserSwitcher: Boolean = false,
+ isAddUsersFromLockscreen: Boolean = false,
+ isUserSwitcherEnabled: Boolean = true,
+ ) {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
+ true,
+ )
+ globalSettings.putIntForUser(
+ UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
+ if (isSimpleUserSwitcher) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ globalSettings.putIntForUser(
+ Settings.Global.ADD_USERS_WHEN_LOCKED,
+ if (isAddUsersFromLockscreen) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ globalSettings.putIntForUser(
+ Settings.Global.USER_SWITCHER_ENABLED,
+ if (isUserSwitcherEnabled) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ }
+
+ private fun assertUserSwitcherSettings(
+ model: UserSwitcherSettingsModel?,
+ expectedSimpleUserSwitcher: Boolean,
+ expectedAddUsersFromLockscreen: Boolean,
+ expectedUserSwitcherEnabled: Boolean,
+ ) {
+ checkNotNull(model)
+ assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
+ assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
+ assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
+ }
+
+ /**
+ * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+ * is then automatically canceled and cleaned-up.
+ */
+ private fun runSelfCancelingTest(
+ block: suspend CoroutineScope.() -> Unit,
+ ) =
+ runBlocking(Dispatchers.Main.immediate) {
+ val scope = CoroutineScope(coroutineContext + Job())
+ block(scope)
+ scope.cancel()
+ }
+
+ private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
return UserRepositoryImpl(
appContext = context,
manager = manager,
- controller = controller,
applicationScope = scope,
mainDispatcher = IMMEDIATE,
backgroundDispatcher = IMMEDIATE,
globalSettings = globalSettings,
tracker = tracker,
- featureFlags = featureFlags,
)
}
companion object {
- @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate
+ @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
deleted file mode 100644
index a363a03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2022 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.user.data.repository
-
-import android.content.pm.UserInfo
-import androidx.test.filters.SmallTest
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
-
- @Captor
- private lateinit var userSwitchCallbackCaptor:
- ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
-
- @Before
- fun setUp() {
- super.setUp(isRefactored = false)
-
- whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
- whenever(controller.isGuestUserAutoCreated).thenReturn(false)
- whenever(controller.isGuestUserResetting).thenReturn(false)
-
- underTest = create()
- }
-
- @Test
- fun `users - registers for updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.users.onEach {}.launchIn(this)
-
- verify(controller).addUserSwitchCallback(any())
-
- job.cancel()
- }
-
- @Test
- fun `users - unregisters from updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.users.onEach {}.launchIn(this)
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
- job.cancel()
-
- verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
- }
-
- @Test
- fun `users - does not include actions`() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createActionRecord(UserActionModel.ADD_USER),
- createUserRecord(1),
- createUserRecord(2),
- createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
- createActionRecord(UserActionModel.ENTER_GUEST_MODE),
- createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
- )
- )
- var models: List<UserModel>? = null
- val job = underTest.users.onEach { models = it }.launchIn(this)
-
- assertThat(models).hasSize(3)
- assertThat(models?.get(0)?.id).isEqualTo(0)
- assertThat(models?.get(0)?.isSelected).isTrue()
- assertThat(models?.get(1)?.id).isEqualTo(1)
- assertThat(models?.get(1)?.isSelected).isFalse()
- assertThat(models?.get(2)?.id).isEqualTo(2)
- assertThat(models?.get(2)?.isSelected).isFalse()
- job.cancel()
- }
-
- @Test
- fun selectedUser() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createUserRecord(1),
- createUserRecord(2),
- )
- )
- var id: Int? = null
- val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
-
- assertThat(id).isEqualTo(0)
-
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0),
- createUserRecord(1),
- createUserRecord(2, isSelected = true),
- )
- )
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
- userSwitchCallbackCaptor.value.onUserSwitched()
- assertThat(id).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
- fun `actions - unregisters from updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.actions.onEach {}.launchIn(this)
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
- job.cancel()
-
- verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
- }
-
- @Test
- fun `actions - registers for updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.actions.onEach {}.launchIn(this)
-
- verify(controller).addUserSwitchCallback(any())
-
- job.cancel()
- }
-
- @Test
- fun `actions - does not include users`() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createActionRecord(UserActionModel.ADD_USER),
- createUserRecord(1),
- createUserRecord(2),
- createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
- createActionRecord(UserActionModel.ENTER_GUEST_MODE),
- createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
- )
- )
- var models: List<UserActionModel>? = null
- val job = underTest.actions.onEach { models = it }.launchIn(this)
-
- assertThat(models).hasSize(4)
- assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
- assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
- assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
- assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
- job.cancel()
- }
-
- private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
- return UserRecord(
- info = UserInfo(id, "name$id", 0),
- isCurrent = isSelected,
- )
- }
-
- private fun createActionRecord(action: UserActionModel): UserRecord {
- return UserRecord(
- isAddUser = action == UserActionModel.ADD_USER,
- isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
- isGuest = action == UserActionModel.ENTER_GUEST_MODE,
- isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
deleted file mode 100644
index f682e31..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ /dev/null
@@ -1,740 +0,0 @@
-/*
- * Copyright (C) 2022 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.user.domain.interactor
-
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.internal.R.drawable.ic_account_circle
-import com.android.systemui.R
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.model.ShowDialogRequestModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.advanceUntilIdle
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserInteractorRefactoredTest : UserInteractorTest() {
-
- override fun isRefactored(): Boolean {
- return true
- }
-
- @Before
- override fun setUp() {
- super.setUp()
-
- overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
- overrideResource(R.dimen.max_avatar_size, 10)
- overrideResource(
- com.android.internal.R.string.config_supervisedUserCreationPackage,
- SUPERVISED_USER_CREATION_APP_PACKAGE,
- )
- whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
- whenever(manager.canAddMoreUsers(any())).thenReturn(true)
- }
-
- @Test
- fun `onRecordSelected - user`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
-
- verify(dialogShower).dismiss()
- verify(activityManager).switchUser(userInfos[1].id)
- Unit
- }
-
- @Test
- fun `onRecordSelected - switch to guest user`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- underTest.onRecordSelected(UserRecord(info = userInfos.last()))
-
- verify(activityManager).switchUser(userInfos.last().id)
- Unit
- }
-
- @Test
- fun `onRecordSelected - enter guest mode`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
- whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
-
- underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
-
- verify(dialogShower).dismiss()
- verify(manager).createGuest(any())
- Unit
- }
-
- @Test
- fun `onRecordSelected - action`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
-
- verify(dialogShower, never()).dismiss()
- verify(activityStarter).startActivity(any(), anyBoolean())
- }
-
- @Test
- fun `users - switcher enabled`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 3, includeGuest = true)
-
- job.cancel()
- }
-
- @Test
- fun `users - switches to second user`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- userRepository.setSelectedUserInfo(userInfos[1])
-
- assertUsers(models = value, count = 2, selectedIndex = 1)
- job.cancel()
- }
-
- @Test
- fun `users - switcher not enabled`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
-
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 1)
-
- job.cancel()
- }
-
- @Test
- fun selectedUser() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- var value: UserModel? = null
- val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
- assertUser(value, id = 0, isSelected = true)
-
- userRepository.setSelectedUserInfo(userInfos[1])
- assertUser(value, id = 1, isSelected = true)
-
- job.cancel()
- }
-
- @Test
- fun `actions - device unlocked`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
-
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `actions - device unlocked user not primary - empty list`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[1])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
- }
-
- @Test
- fun `actions - device unlocked user is guest - empty list`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = true)
- assertThat(userInfos[1].isGuest).isTrue()
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[1])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
- }
-
- @Test
- fun `actions - device locked add from lockscreen set - full list`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(
- UserSwitcherSettingsModel(
- isUserSwitcherEnabled = true,
- isAddUsersFromLockscreen = true,
- )
- )
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `actions - device locked - only guest action and manage user is shown`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(true)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `executeAction - add user - dialog shown`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- keyguardRepository.setKeyguardShowing(false)
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
- val dialogShower: UserSwitchDialogController.DialogShower = mock()
-
- underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
- assertThat(dialogRequest)
- .isEqualTo(
- ShowDialogRequestModel.ShowAddUserDialog(
- userHandle = userInfos[0].userHandle,
- isKeyguardShowing = false,
- showEphemeralMessage = false,
- dialogShower = dialogShower,
- )
- )
-
- underTest.onDialogShown()
- assertThat(dialogRequest).isNull()
-
- job.cancel()
- }
-
- @Test
- fun `executeAction - add supervised user - starts activity`() =
- runBlocking(IMMEDIATE) {
- underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
- val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
- assertThat(intentCaptor.value.action)
- .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
- assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
- }
-
- @Test
- fun `executeAction - navigate to manage users`() =
- runBlocking(IMMEDIATE) {
- underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
- val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
- assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
- }
-
- @Test
- fun `executeAction - guest mode`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
- whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
- val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
- val showDialogsJob =
- underTest.dialogShowRequests
- .onEach {
- dialogRequests.add(it)
- if (it != null) {
- underTest.onDialogShown()
- }
- }
- .launchIn(this)
- val dismissDialogsJob =
- underTest.dialogDismissRequests
- .onEach {
- if (it != null) {
- underTest.onDialogDismissed()
- }
- }
- .launchIn(this)
-
- underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
- assertThat(dialogRequests)
- .contains(
- ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
- )
- verify(activityManager).switchUser(guestUserInfo.id)
-
- showDialogsJob.cancel()
- dismissDialogsJob.cancel()
- }
-
- @Test
- fun `selectUser - already selected guest re-selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = true)
- val guestUserInfo = userInfos[1]
- assertThat(guestUserInfo.isGuest).isTrue()
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(guestUserInfo)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
- underTest.selectUser(
- newlySelectedUserId = guestUserInfo.id,
- dialogShower = dialogShower,
- )
-
- assertThat(dialogRequest)
- .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
- verify(dialogShower, never()).dismiss()
- job.cancel()
- }
-
- @Test
- fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = true)
- val guestUserInfo = userInfos[1]
- assertThat(guestUserInfo.isGuest).isTrue()
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(guestUserInfo)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
- underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
-
- assertThat(dialogRequest)
- .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
- verify(dialogShower, never()).dismiss()
- job.cancel()
- }
-
- @Test
- fun `selectUser - not currently guest - switches users`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
- underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
-
- assertThat(dialogRequest).isNull()
- verify(activityManager).switchUser(userInfos[1].id)
- verify(dialogShower).dismiss()
- job.cancel()
- }
-
- @Test
- fun `Telephony call state changes - refreshes users`() =
- runBlocking(IMMEDIATE) {
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- telephonyRepository.setCallState(1)
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `User switched broadcast`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val callback1: UserInteractor.UserCallback = mock()
- val callback2: UserInteractor.UserCallback = mock()
- underTest.addCallback(callback1)
- underTest.addCallback(callback2)
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- userRepository.setSelectedUserInfo(userInfos[1])
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_SWITCHED)
- .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
- )
- }
-
- verify(callback1).onUserStateChanged()
- verify(callback2).onUserStateChanged()
- assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `User info changed broadcast`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_INFO_CHANGED),
- )
- }
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `System user unlocked broadcast - refresh users`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_UNLOCKED)
- .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
- )
- }
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `Non-system user unlocked broadcast - do not refresh users`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
- )
- }
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
- }
-
- @Test
- fun userRecords() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = false)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- keyguardRepository.setKeyguardShowing(false)
-
- testCoroutineScope.advanceUntilIdle()
-
- assertRecords(
- records = underTest.userRecords.value,
- userIds = listOf(0, 1, 2),
- selectedUserIndex = 0,
- includeGuest = false,
- expectedActions =
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- ),
- )
- }
-
- @Test
- fun selectedUserRecord() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- keyguardRepository.setKeyguardShowing(false)
-
- assertRecordForUser(
- record = underTest.selectedUserRecord.value,
- id = 0,
- hasPicture = true,
- isCurrent = true,
- isSwitchToEnabled = true,
- )
- }
-
- private fun assertUsers(
- models: List<UserModel>?,
- count: Int,
- selectedIndex: Int = 0,
- includeGuest: Boolean = false,
- ) {
- checkNotNull(models)
- assertThat(models.size).isEqualTo(count)
- models.forEachIndexed { index, model ->
- assertUser(
- model = model,
- id = index,
- isSelected = index == selectedIndex,
- isGuest = includeGuest && index == count - 1
- )
- }
- }
-
- private fun assertUser(
- model: UserModel?,
- id: Int,
- isSelected: Boolean = false,
- isGuest: Boolean = false,
- ) {
- checkNotNull(model)
- assertThat(model.id).isEqualTo(id)
- assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
- assertThat(model.isSelected).isEqualTo(isSelected)
- assertThat(model.isSelectable).isTrue()
- assertThat(model.isGuest).isEqualTo(isGuest)
- }
-
- private fun assertRecords(
- records: List<UserRecord>,
- userIds: List<Int>,
- selectedUserIndex: Int = 0,
- includeGuest: Boolean = false,
- expectedActions: List<UserActionModel> = emptyList(),
- ) {
- assertThat(records.size >= userIds.size).isTrue()
- userIds.indices.forEach { userIndex ->
- val record = records[userIndex]
- assertThat(record.info).isNotNull()
- val isGuest = includeGuest && userIndex == userIds.size - 1
- assertRecordForUser(
- record = record,
- id = userIds[userIndex],
- hasPicture = !isGuest,
- isCurrent = userIndex == selectedUserIndex,
- isGuest = isGuest,
- isSwitchToEnabled = true,
- )
- }
-
- assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
- (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
- val record = records[actionIndex]
- assertThat(record.info).isNull()
- assertRecordForAction(
- record = record,
- type = expectedActions[actionIndex - userIds.size],
- )
- }
- }
-
- private fun assertRecordForUser(
- record: UserRecord?,
- id: Int? = null,
- hasPicture: Boolean = false,
- isCurrent: Boolean = false,
- isGuest: Boolean = false,
- isSwitchToEnabled: Boolean = false,
- ) {
- checkNotNull(record)
- assertThat(record.info?.id).isEqualTo(id)
- assertThat(record.picture != null).isEqualTo(hasPicture)
- assertThat(record.isCurrent).isEqualTo(isCurrent)
- assertThat(record.isGuest).isEqualTo(isGuest)
- assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
- }
-
- private fun assertRecordForAction(
- record: UserRecord,
- type: UserActionModel,
- ) {
- assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
- assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
- assertThat(record.isAddSupervisedUser)
- .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
- }
-
- private fun createUserInfos(
- count: Int,
- includeGuest: Boolean,
- ): List<UserInfo> {
- return (0 until count).map { index ->
- val isGuest = includeGuest && index == count - 1
- createUserInfo(
- id = index,
- name =
- if (isGuest) {
- "guest"
- } else {
- "user_$index"
- },
- isPrimary = !isGuest && index == 0,
- isGuest = isGuest,
- )
- }
- }
-
- private fun createUserInfo(
- id: Int,
- name: String,
- isPrimary: Boolean = false,
- isGuest: Boolean = false,
- ): UserInfo {
- return UserInfo(
- id,
- name,
- /* iconPath= */ "",
- /* flags= */ if (isPrimary) {
- UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
- } else {
- 0
- },
- if (isGuest) {
- UserManager.USER_TYPE_FULL_GUEST
- } else {
- UserManager.USER_TYPE_FULL_SYSTEM
- },
- )
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
- private val GUEST_ICON: Drawable = mock()
- private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 58f5531..8fb98c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,51 +19,90 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.internal.R.drawable.ic_account_circle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.common.shared.model.Text
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-abstract class UserInteractorTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserInteractorTest : SysuiTestCase() {
- @Mock protected lateinit var controller: UserSwitcherController
- @Mock protected lateinit var activityStarter: ActivityStarter
- @Mock protected lateinit var manager: UserManager
- @Mock protected lateinit var activityManager: ActivityManager
- @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
- @Mock protected lateinit var uiEventLogger: UiEventLogger
- @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var activityManager: ActivityManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
- protected lateinit var underTest: UserInteractor
+ private lateinit var underTest: UserInteractor
- protected lateinit var testCoroutineScope: TestCoroutineScope
- protected lateinit var userRepository: FakeUserRepository
- protected lateinit var keyguardRepository: FakeKeyguardRepository
- protected lateinit var telephonyRepository: FakeTelephonyRepository
+ private lateinit var testCoroutineScope: TestCoroutineScope
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var telephonyRepository: FakeTelephonyRepository
- abstract fun isRefactored(): Boolean
-
- open fun setUp() {
+ @Before
+ fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+
+ overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
+ overrideResource(R.dimen.max_avatar_size, 10)
+ overrideResource(
+ com.android.internal.R.string.config_supervisedUserCreationPackage,
+ SUPERVISED_USER_CREATION_APP_PACKAGE,
+ )
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
@@ -79,16 +118,11 @@
UserInteractor(
applicationContext = context,
repository = userRepository,
- controller = controller,
activityStarter = activityStarter,
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
),
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored())
- },
manager = manager,
applicationScope = testCoroutineScope,
telephonyInteractor =
@@ -117,7 +151,665 @@
)
}
+ @Test
+ fun `onRecordSelected - user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(activityManager).switchUser(userInfos[1].id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - switch to guest user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+ verify(activityManager).switchUser(userInfos.last().id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - enter guest mode`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+ whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+
+ underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(manager).createGuest(any())
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - action`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+
+ verify(dialogShower, never()).dismiss()
+ verify(activityStarter).startActivity(any(), anyBoolean())
+ }
+
+ @Test
+ fun `users - switcher enabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ assertUsers(models = value, count = 3, includeGuest = true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `users - switches to second user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ assertUsers(models = value, count = 2, selectedIndex = 1)
+ job.cancel()
+ }
+
+ @Test
+ fun `users - switcher not enabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ assertUsers(models = value, count = 1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun selectedUser() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: UserModel? = null
+ val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
+ assertUser(value, id = 0, isSelected = true)
+
+ userRepository.setSelectedUserInfo(userInfos[1])
+ assertUser(value, id = 1, isSelected = true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked user not primary - empty list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked user is guest - empty list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ assertThat(userInfos[1].isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device locked add from lockscreen set - full list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ isAddUsersFromLockscreen = true,
+ )
+ )
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device locked - only guest action and manage user is shown`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(true)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `executeAction - add user - dialog shown`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogShower: UserSwitchDialogController.DialogShower = mock()
+
+ underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
+ assertThat(dialogRequest)
+ .isEqualTo(
+ ShowDialogRequestModel.ShowAddUserDialog(
+ userHandle = userInfos[0].userHandle,
+ isKeyguardShowing = false,
+ showEphemeralMessage = false,
+ dialogShower = dialogShower,
+ )
+ )
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `executeAction - add supervised user - starts activity`() =
+ runBlocking(IMMEDIATE) {
+ underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+ assertThat(intentCaptor.value.action)
+ .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
+ assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
+ }
+
+ @Test
+ fun `executeAction - navigate to manage users`() =
+ runBlocking(IMMEDIATE) {
+ underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+ assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+ }
+
+ @Test
+ fun `executeAction - guest mode`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+ whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+ val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
+ val showDialogsJob =
+ underTest.dialogShowRequests
+ .onEach {
+ dialogRequests.add(it)
+ if (it != null) {
+ underTest.onDialogShown()
+ }
+ }
+ .launchIn(this)
+ val dismissDialogsJob =
+ underTest.dialogDismissRequests
+ .onEach {
+ if (it != null) {
+ underTest.onDialogDismissed()
+ }
+ }
+ .launchIn(this)
+
+ underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+ assertThat(dialogRequests)
+ .contains(
+ ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
+ )
+ verify(activityManager).switchUser(guestUserInfo.id)
+
+ showDialogsJob.cancel()
+ dismissDialogsJob.cancel()
+ }
+
+ @Test
+ fun `selectUser - already selected guest re-selected - exit guest dialog`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ val guestUserInfo = userInfos[1]
+ assertThat(guestUserInfo.isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(guestUserInfo)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(
+ newlySelectedUserId = guestUserInfo.id,
+ dialogShower = dialogShower,
+ )
+
+ assertThat(dialogRequest)
+ .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ val guestUserInfo = userInfos[1]
+ assertThat(guestUserInfo.isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(guestUserInfo)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
+
+ assertThat(dialogRequest)
+ .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `selectUser - not currently guest - switches users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
+
+ assertThat(dialogRequest).isNull()
+ verify(activityManager).switchUser(userInfos[1].id)
+ verify(dialogShower).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `Telephony call state changes - refreshes users`() =
+ runBlocking(IMMEDIATE) {
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ telephonyRepository.setCallState(1)
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `User switched broadcast`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val callback1: UserInteractor.UserCallback = mock()
+ val callback2: UserInteractor.UserCallback = mock()
+ underTest.addCallback(callback1)
+ underTest.addCallback(callback2)
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ userRepository.setSelectedUserInfo(userInfos[1])
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_SWITCHED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+ )
+ }
+
+ verify(callback1).onUserStateChanged()
+ verify(callback2).onUserStateChanged()
+ assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `User info changed broadcast`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_INFO_CHANGED),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `System user unlocked broadcast - refresh users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_UNLOCKED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `Non-system user unlocked broadcast - do not refresh users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
+ }
+
+ @Test
+ fun userRecords() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+
+ testCoroutineScope.advanceUntilIdle()
+
+ assertRecords(
+ records = underTest.userRecords.value,
+ userIds = listOf(0, 1, 2),
+ selectedUserIndex = 0,
+ includeGuest = false,
+ expectedActions =
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ ),
+ )
+ }
+
+ @Test
+ fun selectedUserRecord() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertRecordForUser(
+ record = underTest.selectedUserRecord.value,
+ id = 0,
+ hasPicture = true,
+ isCurrent = true,
+ isSwitchToEnabled = true,
+ )
+ }
+
+ private fun assertUsers(
+ models: List<UserModel>?,
+ count: Int,
+ selectedIndex: Int = 0,
+ includeGuest: Boolean = false,
+ ) {
+ checkNotNull(models)
+ assertThat(models.size).isEqualTo(count)
+ models.forEachIndexed { index, model ->
+ assertUser(
+ model = model,
+ id = index,
+ isSelected = index == selectedIndex,
+ isGuest = includeGuest && index == count - 1
+ )
+ }
+ }
+
+ private fun assertUser(
+ model: UserModel?,
+ id: Int,
+ isSelected: Boolean = false,
+ isGuest: Boolean = false,
+ ) {
+ checkNotNull(model)
+ assertThat(model.id).isEqualTo(id)
+ assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
+ assertThat(model.isSelected).isEqualTo(isSelected)
+ assertThat(model.isSelectable).isTrue()
+ assertThat(model.isGuest).isEqualTo(isGuest)
+ }
+
+ private fun assertRecords(
+ records: List<UserRecord>,
+ userIds: List<Int>,
+ selectedUserIndex: Int = 0,
+ includeGuest: Boolean = false,
+ expectedActions: List<UserActionModel> = emptyList(),
+ ) {
+ assertThat(records.size >= userIds.size).isTrue()
+ userIds.indices.forEach { userIndex ->
+ val record = records[userIndex]
+ assertThat(record.info).isNotNull()
+ val isGuest = includeGuest && userIndex == userIds.size - 1
+ assertRecordForUser(
+ record = record,
+ id = userIds[userIndex],
+ hasPicture = !isGuest,
+ isCurrent = userIndex == selectedUserIndex,
+ isGuest = isGuest,
+ isSwitchToEnabled = true,
+ )
+ }
+
+ assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
+ (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
+ val record = records[actionIndex]
+ assertThat(record.info).isNull()
+ assertRecordForAction(
+ record = record,
+ type = expectedActions[actionIndex - userIds.size],
+ )
+ }
+ }
+
+ private fun assertRecordForUser(
+ record: UserRecord?,
+ id: Int? = null,
+ hasPicture: Boolean = false,
+ isCurrent: Boolean = false,
+ isGuest: Boolean = false,
+ isSwitchToEnabled: Boolean = false,
+ ) {
+ checkNotNull(record)
+ assertThat(record.info?.id).isEqualTo(id)
+ assertThat(record.picture != null).isEqualTo(hasPicture)
+ assertThat(record.isCurrent).isEqualTo(isCurrent)
+ assertThat(record.isGuest).isEqualTo(isGuest)
+ assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
+ }
+
+ private fun assertRecordForAction(
+ record: UserRecord,
+ type: UserActionModel,
+ ) {
+ assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
+ assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
+ assertThat(record.isAddSupervisedUser)
+ .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+ }
+
+ private fun createUserInfos(
+ count: Int,
+ includeGuest: Boolean,
+ ): List<UserInfo> {
+ return (0 until count).map { index ->
+ val isGuest = includeGuest && index == count - 1
+ createUserInfo(
+ id = index,
+ name =
+ if (isGuest) {
+ "guest"
+ } else {
+ "user_$index"
+ },
+ isPrimary = !isGuest && index == 0,
+ isGuest = isGuest,
+ )
+ }
+ }
+
+ private fun createUserInfo(
+ id: Int,
+ name: String,
+ isPrimary: Boolean = false,
+ isGuest: Boolean = false,
+ ): UserInfo {
+ return UserInfo(
+ id,
+ name,
+ /* iconPath= */ "",
+ /* flags= */ if (isPrimary) {
+ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
+ } else {
+ 0
+ },
+ if (isGuest) {
+ UserManager.USER_TYPE_FULL_GUEST
+ } else {
+ UserManager.USER_TYPE_FULL_SYSTEM
+ },
+ )
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
+ private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val GUEST_ICON: Drawable = mock()
+ private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
deleted file mode 100644
index 6a17c8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2022 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.user.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-open class UserInteractorUnrefactoredTest : UserInteractorTest() {
-
- override fun isRefactored(): Boolean {
- return false
- }
-
- @Before
- override fun setUp() {
- super.setUp()
- }
-
- @Test
- fun `actions - not actionable when locked and locked - no actions`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- userRepository.setActionableWhenLocked(false)
- keyguardRepository.setKeyguardShowing(true)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions).isEmpty()
- job.cancel()
- }
-
- @Test
- fun `actions - not actionable when locked and not locked`() =
- runBlocking(IMMEDIATE) {
- setActions()
- userRepository.setActionableWhenLocked(false)
- keyguardRepository.setKeyguardShowing(false)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun `actions - actionable when locked and not locked`() =
- runBlocking(IMMEDIATE) {
- setActions()
- userRepository.setActionableWhenLocked(true)
- keyguardRepository.setKeyguardShowing(false)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun `actions - actionable when locked and locked`() =
- runBlocking(IMMEDIATE) {
- setActions()
- userRepository.setActionableWhenLocked(true)
- keyguardRepository.setKeyguardShowing(true)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun selectUser() {
- val userId = 3
-
- underTest.selectUser(userId)
-
- verify(controller).onUserSelected(eq(userId), nullable())
- }
-
- @Test
- fun `executeAction - guest`() {
- underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
- verify(controller).createAndSwitchToGuestUser(nullable())
- }
-
- @Test
- fun `executeAction - add user`() {
- underTest.executeAction(UserActionModel.ADD_USER)
-
- verify(controller).showAddUserDialog(nullable())
- }
-
- @Test
- fun `executeAction - add supervised user`() {
- underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
- verify(controller).startSupervisedUserActivity()
- }
-
- @Test
- fun `executeAction - manage users`() {
- underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
- verify(activityStarter).startActivity(any(), anyBoolean())
- }
-
- private fun setActions() {
- userRepository.setActions(UserActionModel.values().toList())
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 116023a..db13680 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -19,7 +19,7 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
-import android.graphics.drawable.Drawable
+import android.content.pm.UserInfo
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
@@ -27,32 +27,37 @@
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Text
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,11 +65,11 @@
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class UserSwitcherViewModelTest : SysuiTestCase() {
- @Mock private lateinit var controller: UserSwitcherController
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var manager: UserManager
@@ -80,28 +85,47 @@
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var powerRepository: FakePowerRepository
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var testScope: TestScope
+ private lateinit var injectedScope: CoroutineScope
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+ whenever(manager.getUserSwitchability(any()))
+ .thenReturn(UserManager.SWITCHABILITY_STATUS_OK)
+ overrideResource(
+ com.android.internal.R.string.config_supervisedUserCreationPackage,
+ SUPERVISED_USER_CREATION_PACKAGE,
+ )
+ testDispatcher = UnconfinedTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ injectedScope = CoroutineScope(testScope.coroutineContext + SupervisorJob())
userRepository = FakeUserRepository()
+ runBlocking {
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ )
+ )
+ }
+
keyguardRepository = FakeKeyguardRepository()
powerRepository = FakePowerRepository()
- val featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true)
- val scope = TestCoroutineScope()
val refreshUsersScheduler =
RefreshUsersScheduler(
- applicationScope = scope,
- mainDispatcher = IMMEDIATE,
+ applicationScope = injectedScope,
+ mainDispatcher = testDispatcher,
repository = userRepository,
)
val guestUserInteractor =
GuestUserInteractor(
applicationContext = context,
- applicationScope = scope,
- mainDispatcher = IMMEDIATE,
- backgroundDispatcher = IMMEDIATE,
+ applicationScope = injectedScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
manager = manager,
repository = userRepository,
deviceProvisionedController = deviceProvisionedController,
@@ -118,21 +142,19 @@
UserInteractor(
applicationContext = context,
repository = userRepository,
- controller = controller,
activityStarter = activityStarter,
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
),
- featureFlags = featureFlags,
manager = manager,
- applicationScope = scope,
+ applicationScope = injectedScope,
telephonyInteractor =
TelephonyInteractor(
repository = FakeTelephonyRepository(),
),
broadcastDispatcher = fakeBroadcastDispatcher,
- backgroundDispatcher = IMMEDIATE,
+ backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
@@ -141,222 +163,216 @@
PowerInteractor(
repository = powerRepository,
),
- featureFlags = featureFlags,
guestUserInteractor = guestUserInteractor,
)
.create(UserSwitcherViewModel::class.java)
}
@Test
- fun users() =
- runBlocking(IMMEDIATE) {
- userRepository.setUsers(
+ fun users() = selfCancelingTest {
+ val userInfos =
+ listOf(
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 1,
+ /* name= */ "one",
+ /* iconPath= */ "",
+ /* flags= */ 0,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 2,
+ /* name= */ "two",
+ /* iconPath= */ "",
+ /* flags= */ 0,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ )
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+
+ val userViewModels = mutableListOf<List<UserViewModel>>()
+ val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+ assertThat(userViewModels.last()).hasSize(3)
+ assertUserViewModel(
+ viewModel = userViewModels.last()[0],
+ viewKey = 0,
+ name = "zero",
+ isSelectionMarkerVisible = true,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[1],
+ viewKey = 1,
+ name = "one",
+ isSelectionMarkerVisible = false,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[2],
+ viewKey = 2,
+ name = "two",
+ isSelectionMarkerVisible = false,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `maximumUserColumns - few users`() = selfCancelingTest {
+ setUsers(count = 2)
+ val values = mutableListOf<Int>()
+ val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+ assertThat(values.last()).isEqualTo(4)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `maximumUserColumns - many users`() = selfCancelingTest {
+ setUsers(count = 5)
+ val values = mutableListOf<Int>()
+ val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+ assertThat(values.last()).isEqualTo(3)
+ job.cancel()
+ }
+
+ @Test
+ fun `isOpenMenuButtonVisible - has actions - true`() = selfCancelingTest {
+ setUsers(2)
+
+ val isVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+ assertThat(isVisible.last()).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun `isOpenMenuButtonVisible - no actions - false`() = selfCancelingTest {
+ val userInfos = setUsers(2)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ keyguardRepository.setKeyguardShowing(true)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(false)
+
+ val isVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+ assertThat(isVisible.last()).isFalse()
+ job.cancel()
+ }
+
+ @Test
+ fun menu() = selfCancelingTest {
+ val isMenuVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
+ assertThat(isMenuVisible.last()).isFalse()
+
+ underTest.onOpenMenuButtonClicked()
+ assertThat(isMenuVisible.last()).isTrue()
+
+ underTest.onMenuClosed()
+ assertThat(isMenuVisible.last()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `menu actions`() = selfCancelingTest {
+ setUsers(2)
+ val actions = mutableListOf<List<UserActionViewModel>>()
+ val job = launch(testDispatcher) { underTest.menu.toList(actions) }
+
+ assertThat(actions.last().map { it.viewKey })
+ .isEqualTo(
listOf(
- UserModel(
- id = 0,
- name = Text.Loaded("zero"),
- image = USER_IMAGE,
- isSelected = true,
- isSelectable = true,
- isGuest = false,
- ),
- UserModel(
- id = 1,
- name = Text.Loaded("one"),
- image = USER_IMAGE,
- isSelected = false,
- isSelectable = true,
- isGuest = false,
- ),
- UserModel(
- id = 2,
- name = Text.Loaded("two"),
- image = USER_IMAGE,
- isSelected = false,
- isSelectable = false,
- isGuest = false,
- ),
+ UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+ UserActionModel.ADD_USER.ordinal.toLong(),
+ UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
)
)
- var userViewModels: List<UserViewModel>? = null
- val job = underTest.users.onEach { userViewModels = it }.launchIn(this)
-
- assertThat(userViewModels).hasSize(3)
- assertUserViewModel(
- viewModel = userViewModels?.get(0),
- viewKey = 0,
- name = "zero",
- isSelectionMarkerVisible = true,
- alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
- isClickable = true,
- )
- assertUserViewModel(
- viewModel = userViewModels?.get(1),
- viewKey = 1,
- name = "one",
- isSelectionMarkerVisible = false,
- alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
- isClickable = true,
- )
- assertUserViewModel(
- viewModel = userViewModels?.get(2),
- viewKey = 2,
- name = "two",
- isSelectionMarkerVisible = false,
- alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA,
- isClickable = false,
- )
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun `maximumUserColumns - few users`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- var value: Int? = null
- val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+ fun `isFinishRequested - finishes when user is switched`() = selfCancelingTest {
+ val userInfos = setUsers(count = 2)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
- assertThat(value).isEqualTo(4)
- job.cancel()
- }
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ assertThat(isFinishRequested.last()).isTrue()
+
+ job.cancel()
+ }
@Test
- fun `maximumUserColumns - many users`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 5)
- var value: Int? = null
- val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+ fun `isFinishRequested - finishes when the screen turns off`() = selfCancelingTest {
+ setUsers(count = 2)
+ powerRepository.setInteractive(true)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
- assertThat(value).isEqualTo(3)
- job.cancel()
- }
+ powerRepository.setInteractive(false)
+
+ assertThat(isFinishRequested.last()).isTrue()
+
+ job.cancel()
+ }
@Test
- fun `isOpenMenuButtonVisible - has actions - true`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
+ fun `isFinishRequested - finishes when cancel button is clicked`() = selfCancelingTest {
+ setUsers(count = 2)
+ powerRepository.setInteractive(true)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
- var isVisible: Boolean? = null
- val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+ underTest.onCancelButtonClicked()
- assertThat(isVisible).isTrue()
- job.cancel()
- }
+ assertThat(isFinishRequested.last()).isTrue()
- @Test
- fun `isOpenMenuButtonVisible - no actions - false`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(emptyList())
+ underTest.onFinished()
- var isVisible: Boolean? = null
- val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+ assertThat(isFinishRequested.last()).isFalse()
- assertThat(isVisible).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
- @Test
- fun menu() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- var isMenuVisible: Boolean? = null
- val job = underTest.isMenuVisible.onEach { isMenuVisible = it }.launchIn(this)
- assertThat(isMenuVisible).isFalse()
-
- underTest.onOpenMenuButtonClicked()
- assertThat(isMenuVisible).isTrue()
-
- underTest.onMenuClosed()
- assertThat(isMenuVisible).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun `menu actions`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- var actions: List<UserActionViewModel>? = null
- val job = underTest.menu.onEach { actions = it }.launchIn(this)
-
- assertThat(actions?.map { it.viewKey })
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
- UserActionModel.ADD_USER.ordinal.toLong(),
- UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when user is switched`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- var isFinishRequested: Boolean? = null
- val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
- assertThat(isFinishRequested).isFalse()
-
- userRepository.setSelectedUser(1)
- yield()
- assertThat(isFinishRequested).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when the screen turns off`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- var isFinishRequested: Boolean? = null
- val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
- assertThat(isFinishRequested).isFalse()
-
- powerRepository.setInteractive(false)
- yield()
- assertThat(isFinishRequested).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when cancel button is clicked`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- var isFinishRequested: Boolean? = null
- val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
- assertThat(isFinishRequested).isFalse()
-
- underTest.onCancelButtonClicked()
- yield()
- assertThat(isFinishRequested).isTrue()
-
- underTest.onFinished()
- yield()
- assertThat(isFinishRequested).isFalse()
-
- job.cancel()
- }
-
- private suspend fun setUsers(count: Int) {
- userRepository.setUsers(
+ private suspend fun setUsers(count: Int): List<UserInfo> {
+ val userInfos =
(0 until count).map { index ->
- UserModel(
- id = index,
- name = Text.Loaded("$index"),
- image = USER_IMAGE,
- isSelected = index == 0,
- isSelectable = true,
- isGuest = false,
+ UserInfo(
+ /* id= */ index,
+ /* name= */ "$index",
+ /* iconPath= */ "",
+ /* flags= */ if (index == 0) {
+ // This is the primary user.
+ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
+ } else {
+ // This isn't the primary user.
+ 0
+ },
+ UserManager.USER_TYPE_FULL_SYSTEM,
)
}
- )
+ userRepository.setUserInfos(userInfos)
+
+ if (userInfos.isNotEmpty()) {
+ userRepository.setSelectedUserInfo(userInfos[0])
+ }
+ return userInfos
}
private fun assertUserViewModel(
@@ -364,19 +380,25 @@
viewKey: Int,
name: String,
isSelectionMarkerVisible: Boolean,
- alpha: Float,
- isClickable: Boolean,
) {
checkNotNull(viewModel)
assertThat(viewModel.viewKey).isEqualTo(viewKey)
assertThat(viewModel.name).isEqualTo(Text.Loaded(name))
assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible)
- assertThat(viewModel.alpha).isEqualTo(alpha)
- assertThat(viewModel.onClicked != null).isEqualTo(isClickable)
+ assertThat(viewModel.alpha)
+ .isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA)
+ assertThat(viewModel.onClicked).isNotNull()
}
+ private fun selfCancelingTest(
+ block: suspend TestScope.() -> Unit,
+ ): TestResult =
+ testScope.runTest {
+ block()
+ injectedScope.coroutineContext[Job.Key]?.cancelAndJoin()
+ }
+
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private val USER_IMAGE = mock<Drawable>()
+ private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index d42f757..db3b9b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -1331,7 +1331,7 @@
spyOn(mContext);
mBubbleController.updateBubble(mBubbleEntry);
verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
@@ -1351,7 +1351,7 @@
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
@@ -1365,7 +1365,7 @@
mBubbleData.setExpanded(true);
verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
i.putExtra("reason", "gestureNav");
mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
@@ -1379,7 +1379,7 @@
mBubbleData.setExpanded(true);
verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 4df8aa4..b7c8cbf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -20,26 +20,15 @@
import android.content.pm.UserInfo
import android.os.UserHandle
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.yield
class FakeUserRepository : UserRepository {
- private val _users = MutableStateFlow<List<UserModel>>(emptyList())
- override val users: Flow<List<UserModel>> = _users.asStateFlow()
- override val selectedUser: Flow<UserModel> =
- users.map { models -> models.first { model -> model.isSelected } }
-
- private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
- override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
-
private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
_userSwitcherSettings.asStateFlow()
@@ -52,9 +41,6 @@
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
- private val _isActionableWhenLocked = MutableStateFlow(false)
- override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
-
private var _isGuestUserAutoCreated: Boolean = false
override val isGuestUserAutoCreated: Boolean
get() = _isGuestUserAutoCreated
@@ -100,35 +86,6 @@
yield()
}
- fun setUsers(models: List<UserModel>) {
- _users.value = models
- }
-
- suspend fun setSelectedUser(userId: Int) {
- check(_users.value.find { it.id == userId } != null) {
- "Cannot select a user with ID $userId - no user with that ID found!"
- }
-
- setUsers(
- _users.value.map { model ->
- when {
- model.isSelected && model.id != userId -> model.copy(isSelected = false)
- !model.isSelected && model.id == userId -> model.copy(isSelected = true)
- else -> model
- }
- }
- )
- yield()
- }
-
- fun setActions(models: List<UserActionModel>) {
- _actions.value = models
- }
-
- fun setActionableWhenLocked(value: Boolean) {
- _isActionableWhenLocked.value = value
- }
-
fun setGuestUserAutoCreated(value: Boolean) {
_isGuestUserAutoCreated = value
}
diff --git a/packages/VpnDialogs/Android.bp b/packages/VpnDialogs/Android.bp
index 05135b2..e4f80e2 100644
--- a/packages/VpnDialogs/Android.bp
+++ b/packages/VpnDialogs/Android.bp
@@ -23,10 +23,15 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+android_library {
+ name: "VpnDialogsLib",
+ srcs: ["src/**/*.java"],
+}
+
android_app {
name: "VpnDialogs",
certificate: "platform",
privileged: true,
- srcs: ["src/**/*.java"],
+ static_libs: ["VpnDialogsLib"],
platform_apis: true,
}
diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml
index f971a09..28e7272 100644
--- a/packages/VpnDialogs/res/values/strings.xml
+++ b/packages/VpnDialogs/res/values/strings.xml
@@ -100,4 +100,33 @@
without any consequences. [CHAR LIMIT=20] -->
<string name="dismiss">Dismiss</string>
+ <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+ into displaying what they want. The system will attempt to sanitize the label, and if the
+ label is deemed dangerous, then this string is used instead. The first argument is the
+ first 30 characters of the label, and the second argument is the package name of the app.
+ Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+ "My VPN app wants to set up a VPN connection...". If the label is very long, then, this
+ will be used to show "VerylongVPNlabel… (com.my.vpn.app) wants to set up a VPN
+ connection...". For this case, the code will refer to sanitized_vpn_label_with_ellipsis.
+ -->
+ <string name="sanitized_vpn_label_with_ellipsis">
+ <xliff:g id="sanitized_vpn_label_with_ellipsis" example="My VPN app">%1$s</xliff:g>… (
+ <xliff:g id="sanitized_vpn_label_with_ellipsis" example="com.my.vpn.app">%2$s</xliff:g>)
+ </string>
+
+ <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+ into displaying what they want. The system will attempt to sanitize the label, and if the
+ label is deemed dangerous, then this string is used instead. The first argument is the
+ label, and the second argument is the package name of the app.
+ Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+ "My VPN app wants to set up a VPN connection...". If the VPN label contains HTML tag but
+ the length is not very long, the dialog will show "VpnLabelWith<br>HtmlTag
+ (com.my.vpn.app) wants to set up a VPN connection...". For this case, the code will refer
+ to sanitized_vpn_label.
+ -->
+ <string name="sanitized_vpn_label">
+ <xliff:g id="sanitized_vpn_label" example="My VPN app">%1$s</xliff:g> (
+ <xliff:g id="sanitized_vpn_label" example="com.my.vpn.app">%2$s</xliff:g>)
+ </string>
+
</resources>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
index fb23678..a98d6d8 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
@@ -33,6 +33,7 @@
import android.widget.Button;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AlertActivity;
import com.android.internal.net.VpnConfig;
@@ -40,12 +41,19 @@
implements DialogInterface.OnClickListener, ImageGetter {
private static final String TAG = "VpnConfirm";
+ // Usually the label represents the app name, 150 code points might be enough to display the app
+ // name, and 150 code points won't cover the warning message from VpnDialog.
+ @VisibleForTesting
+ static final int MAX_VPN_LABEL_LENGTH = 150;
+
@VpnManager.VpnType private final int mVpnType;
private String mPackage;
private VpnManager mVm;
+ private View mView;
+
public ConfirmDialog() {
this(VpnManager.TYPE_VPN_SERVICE);
}
@@ -54,6 +62,43 @@
mVpnType = vpnType;
}
+ /**
+ * This function will use the string resource to combine the VPN label and the package name.
+ *
+ * If the VPN label violates the length restriction, the first 30 code points of VPN label and
+ * the package name will be returned. Or return the VPN label and the package name directly if
+ * the VPN label doesn't violate the length restriction.
+ *
+ * The result will be something like,
+ * - ThisIsAVeryLongVpnAppNameWhich... (com.vpn.app)
+ * if the VPN label violates the length restriction.
+ * or
+ * - VpnLabelWith<br>HtmlTag (com.vpn.app)
+ * if the VPN label doesn't violate the length restriction.
+ *
+ */
+ private String getSimplifiedLabel(String vpnLabel, String packageName) {
+ if (vpnLabel.codePointCount(0, vpnLabel.length()) > 30) {
+ return getString(R.string.sanitized_vpn_label_with_ellipsis,
+ vpnLabel.substring(0, vpnLabel.offsetByCodePoints(0, 30)),
+ packageName);
+ }
+
+ return getString(R.string.sanitized_vpn_label, vpnLabel, packageName);
+ }
+
+ @VisibleForTesting
+ protected String getSanitizedVpnLabel(String vpnLabel, String packageName) {
+ final String sanitizedVpnLabel = Html.escapeHtml(vpnLabel);
+ final boolean exceedMaxVpnLabelLength = sanitizedVpnLabel.codePointCount(0,
+ sanitizedVpnLabel.length()) > MAX_VPN_LABEL_LENGTH;
+ if (exceedMaxVpnLabelLength || !vpnLabel.equals(sanitizedVpnLabel)) {
+ return getSimplifiedLabel(sanitizedVpnLabel, packageName);
+ }
+
+ return sanitizedVpnLabel;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -75,15 +120,16 @@
finish();
return;
}
- View view = View.inflate(this, R.layout.confirm, null);
- ((TextView) view.findViewById(R.id.warning)).setText(
- Html.fromHtml(getString(R.string.warning, getVpnLabel()),
- this, null /* tagHandler */));
+ mView = View.inflate(this, R.layout.confirm, null);
+ ((TextView) mView.findViewById(R.id.warning)).setText(
+ Html.fromHtml(getString(R.string.warning, getSanitizedVpnLabel(
+ getVpnLabel().toString(), mPackage)),
+ this /* imageGetter */, null /* tagHandler */));
mAlertParams.mTitle = getText(R.string.prompt);
mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
mAlertParams.mPositiveButtonListener = this;
mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
- mAlertParams.mView = view;
+ mAlertParams.mView = mView;
setupAlert();
getWindow().setCloseOnTouchOutside(false);
@@ -92,6 +138,11 @@
button.setFilterTouchesWhenObscured(true);
}
+ @VisibleForTesting
+ public CharSequence getWarningText() {
+ return ((TextView) mView.findViewById(R.id.warning)).getText();
+ }
+
private CharSequence getVpnLabel() {
try {
return VpnConfig.getVpnLabel(this, mPackage);
diff --git a/packages/VpnDialogs/tests/Android.bp b/packages/VpnDialogs/tests/Android.bp
new file mode 100644
index 0000000..68639bd
--- /dev/null
+++ b/packages/VpnDialogs/tests/Android.bp
@@ -0,0 +1,36 @@
+// Copyright 2022, 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "VpnDialogsTests",
+ // Use platform certificate because the test will invoke a hidden API.
+ // (e.g. VpnManager#prepareVpn()).
+ certificate: "platform",
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-target-minus-junit4",
+ "VpnDialogsLib",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/packages/VpnDialogs/tests/AndroidManifest.xml b/packages/VpnDialogs/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f26c1fe
--- /dev/null
+++ b/packages/VpnDialogs/tests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.vpndialogs.tests">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="com.android.vpndialogs.VpnDialogTest$InstrumentedConfirmDialog"/>
+ </application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.vpndialogs.tests"
+ android:label="Vpn dialog tests">
+ </instrumentation>
+</manifest>
diff --git a/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java b/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java
new file mode 100644
index 0000000..7cfa466
--- /dev/null
+++ b/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 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.vpndialogs;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.VpnManager;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class VpnDialogTest {
+ private ActivityScenario<ConfirmDialog> mActivityScenario;
+
+ @SuppressWarnings("StaticMockMember")
+ @Mock
+ private static PackageManager sPm;
+
+ @SuppressWarnings("StaticMockMember")
+ @Mock
+ private static VpnManager sVm;
+
+ @Mock
+ private ApplicationInfo mAi;
+
+ private static final String VPN_APP_NAME = "VpnApp";
+ private static final String VPN_APP_PACKAGE_NAME = "com.android.vpndialogs.VpnDialogTest";
+ private static final String VPN_LABEL_CONTAINS_HTML_TAG =
+ "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a>";
+ private static final String VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION =
+ "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a></b>"
+ + " Wants to connect the network. <br></br><br></br><br></br><br></br><br></br>"
+ + " <br></br><br></br><br></br><br></br><br></br><br></br><br></br><br></br> Deny it?";
+ private static final String VPN_LABEL_VIOLATES_LENGTH_RESTRICTION = "This is a VPN label"
+ + " which violates the length restriction. The length restriction here are 150 code"
+ + " points. So the VPN label should be sanitized, and shows the package name to the"
+ + " user.";
+
+ public static class InstrumentedConfirmDialog extends ConfirmDialog {
+ @Override
+ public PackageManager getPackageManager() {
+ return sPm;
+ }
+
+ @Override
+ public @Nullable Object getSystemService(@ServiceName @NonNull String name) {
+ switch (name) {
+ case Context.VPN_MANAGEMENT_SERVICE:
+ return sVm;
+ default:
+ return super.getSystemService(name);
+ }
+ }
+
+ @Override
+ public String getCallingPackage() {
+ return VPN_APP_PACKAGE_NAME;
+ }
+ }
+
+ private void launchActivity() {
+ final Context context = getInstrumentation().getContext();
+ mActivityScenario = ActivityScenario.launch(
+ new Intent(context, InstrumentedConfirmDialog.class));
+ }
+
+ @Test
+ public void testGetSanitizedVpnLabel_withNormalCase() throws Exception {
+ // Test the normal case that the VPN label showed in the VpnDialog is the app name.
+ doReturn(VPN_APP_NAME).when(mAi).loadLabel(sPm);
+ launchActivity();
+ mActivityScenario.onActivity(activity -> {
+ assertTrue(activity.getWarningText().toString().contains(VPN_APP_NAME));
+ });
+ }
+
+ private void verifySanitizedVpnLabel(String originalLabel) {
+ doReturn(originalLabel).when(mAi).loadLabel(sPm);
+ launchActivity();
+ mActivityScenario.onActivity(activity -> {
+ // The VPN label was sanitized because violating length restriction or having a html
+ // tag, so the warning message will contain the package name.
+ assertTrue(activity.getWarningText().toString().contains(activity.getCallingPackage()));
+ // Also, the length of sanitized VPN label shouldn't longer than MAX_VPN_LABEL_LENGTH
+ // and it shouldn't contain html tag.
+ final String sanitizedVpnLabel =
+ activity.getSanitizedVpnLabel(originalLabel, VPN_APP_PACKAGE_NAME);
+ assertTrue(sanitizedVpnLabel.codePointCount(0, sanitizedVpnLabel.length())
+ < ConfirmDialog.MAX_VPN_LABEL_LENGTH);
+ assertFalse(sanitizedVpnLabel.contains("<b>"));
+ });
+ }
+
+ @Test
+ public void testGetSanitizedVpnLabel_withHtmlTag() throws Exception {
+ // Test the case that the VPN label was sanitized because there is a html tag.
+ verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG);
+ }
+
+ @Test
+ public void testGetSanitizedVpnLabel_withHtmlTagAndViolateLengthRestriction() throws Exception {
+ // Test the case that the VPN label was sanitized because there is a html tag.
+ verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION);
+ }
+
+ @Test
+ public void testGetSanitizedVpnLabel_withLengthRestriction() throws Exception {
+ // Test the case that the VPN label was sanitized because hitting the length restriction.
+ verifySanitizedVpnLabel(VPN_LABEL_VIOLATES_LENGTH_RESTRICTION);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(false).when(sVm).prepareVpn(anyString(), anyString(), anyInt());
+ doReturn(null).when(sPm).queryIntentServices(any(), anyInt());
+ doReturn(mAi).when(sPm).getApplicationInfo(anyString(), anyInt());
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index f35de17..c77b597 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityService.ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS;
@@ -83,6 +84,7 @@
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.inputmethod.EditorInfo;
+import android.window.ScreenCapture;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import com.android.internal.annotations.GuardedBy;
@@ -211,6 +213,11 @@
/** The timestamp of requesting to take screenshot in milliseconds */
private long mRequestTakeScreenshotTimestampMs;
+ /**
+ * The timestamps of requesting to take a window screenshot in milliseconds,
+ * mapping from accessibility window id -> timestamp.
+ */
+ private SparseArray<Long> mRequestTakeScreenshotOfWindowTimestampMs = new SparseArray<>();
public interface SystemSupport {
/**
@@ -1252,6 +1259,51 @@
}
@Override
+ public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId,
+ ScreenCapture.ScreenCaptureListener listener,
+ IAccessibilityInteractionConnectionCallback callback) throws RemoteException {
+ final long currentTimestamp = SystemClock.uptimeMillis();
+ if ((currentTimestamp
+ - mRequestTakeScreenshotOfWindowTimestampMs.get(accessibilityWindowId, 0L))
+ <= ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT, interactionId);
+ return;
+ }
+ mRequestTakeScreenshotOfWindowTimestampMs.put(accessibilityWindowId, currentTimestamp);
+
+ synchronized (mLock) {
+ if (!hasRightsToCurrentUserLocked()) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, interactionId);
+ return;
+ }
+ if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+ interactionId);
+ return;
+ }
+ }
+ if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+ interactionId);
+ return;
+ }
+
+ RemoteAccessibilityConnection connection = mA11yWindowManager.getConnectionLocked(
+ mSystemSupport.getCurrentUserIdLocked(),
+ resolveAccessibilityWindowIdLocked(accessibilityWindowId));
+ if (connection == null) {
+ callback.sendTakeScreenshotOfWindowError(
+ AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_WINDOW, interactionId);
+ return;
+ }
+ connection.getRemote().takeScreenshotOfWindow(interactionId, listener, callback);
+ }
+
+ @Override
public void takeScreenshot(int displayId, RemoteCallback callback) {
if (svcConnTracingEnabled()) {
logTraceSvcConn("takeScreenshot", "displayId=" + displayId + ";callback=" + callback);
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index 6958b66..c08b6ab 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -167,6 +167,12 @@
mServiceCallback.setPerformAccessibilityActionResult(succeeded, interactionId);
}
+ @Override
+ public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
+ throws RemoteException {
+ mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
+ }
+
private void replaceInfoActionsAndCallService() {
final AccessibilityNodeInfo nodeToReturn;
boolean doCallback = false;
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 8525e36..592045c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -245,6 +245,7 @@
});
}
+ @SuppressWarnings("ReturnValueIgnored")
private void maybeRequestShowInlineSuggestions(int sessionId,
@Nullable InlineSuggestionsRequest request,
@Nullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState,
diff --git a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
new file mode 100644
index 0000000..042bcbd
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.backup;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.provider.DeviceConfig;
+
+/**
+ * Retrieves values of feature flags.
+ *
+ * <p>These flags are intended to be configured server-side and their values should be set in {@link
+ * DeviceConfig} by a service that periodically syncs with the server.
+ *
+ * <p>This class must ensure that the namespace, flag name, and default value passed into {@link
+ * DeviceConfig} matches what's declared on the server. The namespace is shared for all backup and
+ * restore flags.
+ */
+public class BackupAndRestoreFeatureFlags {
+ private static final String NAMESPACE = "backup_and_restore";
+
+ private BackupAndRestoreFeatureFlags() {}
+
+ /** Retrieves the value of the flag "backup_transport_future_timeout_millis". */
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ public static long getBackupTransportFutureTimeoutMillis() {
+ return DeviceConfig.getLong(
+ NAMESPACE,
+ /* name= */ "backup_transport_future_timeout_millis",
+ /* defaultValue= */ 600000); // 10 minutes
+ }
+
+ /** Retrieves the value of the flag "backup_transport_callback_timeout_millis". */
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ public static long getBackupTransportCallbackTimeoutMillis() {
+ return DeviceConfig.getLong(
+ NAMESPACE,
+ /* name= */ "backup_transport_callback_timeout_millis",
+ /* defaultValue= */ 300000); // 5 minutes
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/OperationStorage.java b/services/backup/java/com/android/server/backup/OperationStorage.java
index 466f647..8f73436 100644
--- a/services/backup/java/com/android/server/backup/OperationStorage.java
+++ b/services/backup/java/com/android/server/backup/OperationStorage.java
@@ -153,4 +153,4 @@
* @return a set of operation tokens for operations in that state.
*/
Set<Integer> operationTokensForOpState(@OpState int state);
-};
+}
diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
index 6908c60..a94167e 100644
--- a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
+++ b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
@@ -353,4 +353,4 @@
op.callback.handleCancel(cancelAll);
}
}
-};
+}
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index 40d7cad..21005bb 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -30,6 +30,7 @@
import com.android.internal.backup.IBackupTransport;
import com.android.internal.infra.AndroidFuture;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
import java.util.ArrayDeque;
import java.util.HashSet;
@@ -385,7 +386,8 @@
private <T> T getFutureResult(AndroidFuture<T> future) {
try {
- return future.get(600, TimeUnit.SECONDS);
+ return future.get(BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis(),
+ TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException
| CancellationException e) {
Slog.w(TAG, "Failed to get result from transport:", e);
diff --git a/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
index fb98825..deaa86c 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportStatusCallback.java
@@ -23,13 +23,13 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.ITransportStatusCallback;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
public class TransportStatusCallback extends ITransportStatusCallback.Stub {
private static final String TAG = "TransportStatusCallback";
- private static final int TIMEOUT_MILLIS = 300 * 1000; // 5 minutes.
private static final int OPERATION_STATUS_DEFAULT = 0;
- private final int mOperationTimeout;
+ private final long mOperationTimeout;
@GuardedBy("this")
private int mOperationStatus = OPERATION_STATUS_DEFAULT;
@@ -37,7 +37,7 @@
private boolean mHasCompletedOperation = false;
public TransportStatusCallback() {
- mOperationTimeout = TIMEOUT_MILLIS;
+ mOperationTimeout = BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis();
}
@VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 000bafe..ce7854d 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -86,6 +86,15 @@
}
/**
+ * For communicating when activities are blocked from entering PIP on the display by this
+ * policy controller.
+ */
+ public interface PipBlockedCallback {
+ /** Called when an activity is blocked from entering PIP. */
+ void onEnteringPipBlocked(int uid);
+ }
+
+ /**
* If required, allow the secure activity to display on remote device since
* {@link android.os.Build.VERSION_CODES#TIRAMISU}.
*/
@@ -112,6 +121,7 @@
@GuardedBy("mGenericWindowPolicyControllerLock")
final ArraySet<Integer> mRunningUids = new ArraySet<>();
@Nullable private final ActivityListener mActivityListener;
+ @Nullable private final PipBlockedCallback mPipBlockedCallback;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull
@GuardedBy("mGenericWindowPolicyControllerLock")
@@ -155,6 +165,7 @@
@NonNull Set<ComponentName> blockedActivities,
@ActivityPolicy int defaultActivityPolicy,
@NonNull ActivityListener activityListener,
+ @NonNull PipBlockedCallback pipBlockedCallback,
@NonNull ActivityBlockedCallback activityBlockedCallback,
@NonNull SecureWindowCallback secureWindowCallback,
@AssociationRequest.DeviceProfile String deviceProfile) {
@@ -169,6 +180,7 @@
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
mDeviceProfile = deviceProfile;
+ mPipBlockedCallback = pipBlockedCallback;
mSecureWindowCallback = secureWindowCallback;
}
@@ -317,6 +329,17 @@
}
}
+ @Override
+ public boolean isEnteringPipAllowed(int uid) {
+ if (super.isEnteringPipAllowed(uid)) {
+ return true;
+ }
+ mHandler.post(() -> {
+ mPipBlockedCallback.onEnteringPipBlocked(uid);
+ });
+ return false;
+ }
+
/**
* Returns true if an app with the given UID has an activity running on the virtual display for
* this controller.
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5ebbf07..be21075 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -624,6 +624,7 @@
mParams.getBlockedActivities(),
mParams.getDefaultActivityPolicy(),
createListenerAdapter(),
+ this::onEnteringPipBlocked,
this::onActivityBlocked,
this::onSecureWindowShown,
mAssociationInfo.getDeviceProfile());
@@ -779,6 +780,11 @@
return mVirtualDisplayIds.contains(displayId);
}
+ void onEnteringPipBlocked(int uid) {
+ showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_pip_blocked,
+ Toast.LENGTH_LONG, mContext.getMainLooper());
+ }
+
interface OnDeviceCloseListener {
void onClose(int associationId);
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 0f101b0..08ee6d7 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -658,7 +658,6 @@
int sessionId, int flags, @NonNull IResultReceiver result) {
Objects.requireNonNull(activityToken);
Objects.requireNonNull(shareableActivityToken);
- Objects.requireNonNull(sessionId);
final int userId = UserHandle.getCallingUserId();
final ActivityPresentationInfo activityPresentationInfo = getAmInternal()
@@ -677,7 +676,6 @@
@Override
public void finishSession(int sessionId) {
- Objects.requireNonNull(sessionId);
final int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 8055afc..2662e03 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -685,7 +685,9 @@
FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
packageInfo.packageName,
packageInfo.getLongVersionCode(),
- mBinaryHashes.get(packageInfo.packageName));
+ mBinaryHashes.get(packageInfo.packageName),
+ 4, // indicating that the digest is SHA256
+ null); // TODO: This is to comform to the extended schema.
}
}
} catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d0f245f..86bb699 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -23,18 +23,29 @@
import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE;
+import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_UNKNOWN;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
+import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
import static android.os.PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_CARRIER_PRIVILEGED_APP;
import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
import static android.os.PowerExemptionManager.REASON_CURRENT_INPUT_METHOD;
import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.os.PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
import static android.os.PowerExemptionManager.REASON_DEVICE_OWNER;
+import static android.os.PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL;
+import static android.os.PowerExemptionManager.REASON_DPO_PROTECTED_APP;
import static android.os.PowerExemptionManager.REASON_FGS_BINDING;
import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMISSION;
@@ -45,10 +56,12 @@
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
import static android.os.PowerExemptionManager.REASON_PROFILE_OWNER;
+import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY;
import static android.os.PowerExemptionManager.REASON_SERVICE_LAUNCH;
import static android.os.PowerExemptionManager.REASON_START_ACTIVITY_FLAG;
import static android.os.PowerExemptionManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
@@ -63,6 +76,9 @@
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT;
@@ -94,6 +110,11 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.ForegroundServiceStartNotAllowedException;
+import android.app.ForegroundServiceTypeNotAllowedException;
+import android.app.ForegroundServiceTypePolicy;
+import android.app.ForegroundServiceTypePolicy.ForegroundServicePolicyCheckCode;
+import android.app.ForegroundServiceTypePolicy.ForegroundServiceTypePermission;
+import android.app.ForegroundServiceTypePolicy.ForegroundServiceTypePolicyInfo;
import android.app.IApplicationThread;
import android.app.IForegroundServiceObserver;
import android.app.IServiceConnection;
@@ -122,6 +143,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -576,6 +598,7 @@
getAppStateTracker().addBackgroundRestrictedAppListener(new BackgroundRestrictedListener());
mAppWidgetManagerInternal = LocalServices.getService(AppWidgetManagerInternal.class);
setAllowListWhileInUsePermissionInFgs();
+ initSystemExemptedFgsTypePermission();
}
private AppStateTracker getAppStateTracker() {
@@ -724,7 +747,7 @@
ServiceRecord r = res.record;
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
- allowBackgroundActivityStarts);
+ allowBackgroundActivityStarts, false /* isBindService */);
if (!mAm.mUserController.exists(r.userId)) {
Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -757,8 +780,8 @@
Slog.w(TAG, msg);
showFgsBgRestrictedNotificationLocked(r);
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
- 0, FGS_STOP_REASON_UNKNOWN);
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+ 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) {
throw new ForegroundServiceStartNotAllowedException(msg);
}
@@ -1911,6 +1934,7 @@
ignoreForeground = true;
}
+ int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
if (!ignoreForeground) {
if (r.mStartForegroundCount == 0) {
/*
@@ -1931,7 +1955,9 @@
if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
resetFgsRestrictionLocked(r);
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
- r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+ false /* allowBackgroundActivityStarts */,
+ false /* isBindService */);
final String temp = "startForegroundDelayMs:" + delayMs;
if (r.mInfoAllowStartForeground != null) {
r.mInfoAllowStartForeground += "; " + temp;
@@ -1945,7 +1971,9 @@
// The second or later time startForeground() is called after service is
// started. Check for app state again.
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
- r.appInfo.uid, r.intent.getIntent(), r, r.userId, false);
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+ false /* allowBackgroundActivityStarts */,
+ false /* isBindService */);
}
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
@@ -1965,13 +1993,49 @@
updateServiceForegroundLocked(psr, true);
ignoreForeground = true;
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
- 0, FGS_STOP_REASON_UNKNOWN);
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+ 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
r.appInfo.uid)) {
throw new ForegroundServiceStartNotAllowedException(msg);
}
}
+
+ if (!ignoreForeground) {
+ Pair<Integer, RuntimeException> fgsTypeResult = null;
+ if (foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ fgsTypeResult = validateForegroundServiceType(r,
+ foregroundServiceType,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
+ } else {
+ int fgsTypes = foregroundServiceType;
+ // If the service has declared some unknown types which might be coming
+ // from future releases, and if it also comes with the "specialUse",
+ // then it'll be deemed as the "specialUse" and we ignore this
+ // unknown type. Otherwise, it'll be treated as an invalid type.
+ int defaultFgsTypes = (foregroundServiceType
+ & ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE) != 0
+ ? ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+ : ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+ for (int serviceType = Integer.highestOneBit(fgsTypes);
+ serviceType != 0;
+ serviceType = Integer.highestOneBit(fgsTypes)) {
+ fgsTypeResult = validateForegroundServiceType(r,
+ serviceType, defaultFgsTypes);
+ fgsTypes &= ~serviceType;
+ if (fgsTypeResult.first != FGS_TYPE_POLICY_CHECK_OK) {
+ break;
+ }
+ }
+ }
+ fgsTypeCheckCode = fgsTypeResult.first;
+ if (fgsTypeResult.second != null) {
+ logFGSStateChangeLocked(r,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+ 0, FGS_STOP_REASON_UNKNOWN, fgsTypeResult.first);
+ throw fgsTypeResult.second;
+ }
+ }
}
// Apps under strict background restrictions simply don't get to have foreground
@@ -2040,8 +2104,8 @@
registerAppOpCallbackLocked(r);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
- 0, FGS_STOP_REASON_UNKNOWN);
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+ 0, FGS_STOP_REASON_UNKNOWN, fgsTypeCheckCode);
updateNumForegroundServicesLocked();
}
// Even if the service is already a FGS, we need to update the notification,
@@ -2122,10 +2186,11 @@
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
unregisterAppOpCallbackLocked(r);
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
r.mFgsExitTime > r.mFgsEnterTime
? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0,
- FGS_STOP_REASON_STOP_FOREGROUND);
+ FGS_STOP_REASON_STOP_FOREGROUND,
+ FGS_TYPE_POLICY_CHECK_UNKNOWN);
r.mFgsNotificationWasDeferred = false;
signalForegroundServiceObserversLocked(r);
resetFgsRestrictionLocked(r);
@@ -2161,6 +2226,118 @@
return now < eligible;
}
+ /**
+ * Validate if the given service can start a foreground service with given type.
+ *
+ * @return A pair, where the first parameter is the result code and second is the exception
+ * object if it fails to start a foreground service with given type.
+ */
+ @NonNull
+ private Pair<Integer, RuntimeException> validateForegroundServiceType(ServiceRecord r,
+ @ForegroundServiceType int type,
+ @ForegroundServiceType int defaultToType) {
+ final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
+ final ForegroundServiceTypePolicyInfo policyInfo =
+ policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
+ final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
+ mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
+ r.mAllowWhileInUsePermissionInFgs, policyInfo);
+ RuntimeException exception = null;
+ switch (code) {
+ case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
+ final String msg = "Starting FGS with type "
+ + ServiceInfo.foregroundServiceTypeToLabel(type)
+ + " code=" + code
+ + " callerApp=" + r.app
+ + " targetSDK=" + r.app.info.targetSdkVersion;
+ Slog.wtfQuiet(TAG, msg);
+ Slog.w(TAG, msg);
+ } break;
+ case FGS_TYPE_POLICY_CHECK_DISABLED: {
+ exception = new ForegroundServiceTypeNotAllowedException(
+ "Starting FGS with type "
+ + ServiceInfo.foregroundServiceTypeToLabel(type)
+ + " callerApp=" + r.app
+ + " targetSDK=" + r.app.info.targetSdkVersion
+ + " has been prohibited");
+ } break;
+ case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE: {
+ final String msg = "Starting FGS with type "
+ + ServiceInfo.foregroundServiceTypeToLabel(type)
+ + " code=" + code
+ + " callerApp=" + r.app
+ + " targetSDK=" + r.app.info.targetSdkVersion
+ + " requiredPermissions=" + policyInfo.toPermissionString();
+ Slog.wtfQuiet(TAG, msg);
+ Slog.w(TAG, msg);
+ } break;
+ case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED: {
+ exception = new SecurityException("Starting FGS with type "
+ + ServiceInfo.foregroundServiceTypeToLabel(type)
+ + " callerApp=" + r.app
+ + " targetSDK=" + r.app.info.targetSdkVersion
+ + " requires permissions: "
+ + policyInfo.toPermissionString());
+ } break;
+ case FGS_TYPE_POLICY_CHECK_OK:
+ default:
+ break;
+ }
+ return Pair.create(code, exception);
+ }
+
+ private class SystemExemptedFgsTypePermission extends ForegroundServiceTypePermission {
+ SystemExemptedFgsTypePermission() {
+ super("System exempted");
+ }
+
+ @Override
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+ @NonNull String packageName, boolean allowWhileInUse) {
+ final AppRestrictionController appRestrictionController = mAm.mAppRestrictionController;
+ @ReasonCode int reason = appRestrictionController
+ .getPotentialSystemExemptionReason(callerUid);
+ if (reason == REASON_DENIED) {
+ reason = appRestrictionController
+ .getPotentialSystemExemptionReason(callerUid, packageName);
+ if (reason == REASON_DENIED) {
+ reason = appRestrictionController
+ .getPotentialUserAllowedExemptionReason(callerUid, packageName);
+ }
+ }
+ switch (reason) {
+ case REASON_SYSTEM_UID:
+ case REASON_SYSTEM_ALLOW_LISTED:
+ case REASON_DEVICE_DEMO_MODE:
+ case REASON_DISALLOW_APPS_CONTROL:
+ case REASON_DEVICE_OWNER:
+ case REASON_PROFILE_OWNER:
+ case REASON_PROC_STATE_PERSISTENT:
+ case REASON_PROC_STATE_PERSISTENT_UI:
+ case REASON_SYSTEM_MODULE:
+ case REASON_CARRIER_PRIVILEGED_APP:
+ case REASON_DPO_PROTECTED_APP:
+ case REASON_ACTIVE_DEVICE_ADMIN:
+ case REASON_ROLE_EMERGENCY:
+ case REASON_ALLOWLISTED_PACKAGE:
+ return PERMISSION_GRANTED;
+ default:
+ return PERMISSION_DENIED;
+ }
+ }
+ }
+
+ private void initSystemExemptedFgsTypePermission() {
+ final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
+ final ForegroundServiceTypePolicyInfo policyInfo =
+ policy.getForegroundServiceTypePolicyInfo(
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
+ if (policyInfo != null) {
+ policyInfo.setCustomPermission(new SystemExemptedFgsTypePermission());
+ }
+ }
+
ServiceNotificationPolicy applyForegroundServiceNotificationLocked(Notification notification,
final String tag, final int id, final String pkg, final int userId) {
// By nature of the FGS API, all FGS notifications have a null tag
@@ -2985,7 +3162,7 @@
}
}
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
- false);
+ false /* allowBackgroundActivityStarts */, true /* isBindService */);
if (s.app != null) {
ProcessServiceRecord servicePsr = s.app.mServices;
@@ -4773,10 +4950,11 @@
unregisterAppOpCallbackLocked(r);
r.mFgsExitTime = SystemClock.uptimeMillis();
logFGSStateChangeLocked(r,
- FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
r.mFgsExitTime > r.mFgsEnterTime
? (int) (r.mFgsExitTime - r.mFgsEnterTime) : 0,
- FGS_STOP_REASON_STOP_SERVICE);
+ FGS_STOP_REASON_STOP_SERVICE,
+ FGS_TYPE_POLICY_CHECK_UNKNOWN);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
}
@@ -6500,7 +6678,7 @@
*/
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
- boolean allowBackgroundActivityStarts) {
+ boolean allowBackgroundActivityStarts, boolean isBindService) {
r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime();
// Check DeviceConfig flag.
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
@@ -6510,14 +6688,15 @@
if (!r.mAllowWhileInUsePermissionInFgs
|| (r.mAllowStartForeground == REASON_DENIED)) {
final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
- callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts);
+ callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts,
+ isBindService);
if (!r.mAllowWhileInUsePermissionInFgs) {
r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
}
if (r.mAllowStartForeground == REASON_DENIED) {
r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
- userId);
+ userId, isBindService);
}
}
}
@@ -6537,9 +6716,10 @@
}
final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, null /* serviceRecord */,
- false /* allowBackgroundActivityStarts */);
+ false /* allowBackgroundActivityStarts */, false);
@ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
- allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */);
+ allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
+ false /* isBindService */);
if (allowStartFgs == REASON_DENIED) {
if (canBindingClientStartFgsLocked(callingUid) != null) {
@@ -6559,7 +6739,7 @@
*/
private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
int callingPid, int callingUid, @Nullable ServiceRecord targetService,
- boolean allowBackgroundActivityStarts) {
+ boolean allowBackgroundActivityStarts, boolean isBindService) {
int ret = REASON_DENIED;
final int uidState = mAm.getUidStateLocked(callingUid);
@@ -6713,12 +6893,12 @@
shouldAllowFgsWhileInUsePermissionLocked(
clientPackageName,
clientPid, clientUid, null /* serviceRecord */,
- false /* allowBackgroundActivityStarts */);
+ false /* allowBackgroundActivityStarts */, false);
final @ReasonCode int allowStartFgs =
shouldAllowFgsStartForegroundNoBindingCheckLocked(
allowWhileInUse2,
clientPid, clientUid, clientPackageName,
- null /* targetService */);
+ null /* targetService */, false);
if (allowStartFgs != REASON_DENIED) {
return new Pair<>(allowStartFgs, clientPackageName);
} else {
@@ -6750,11 +6930,11 @@
*/
private @ReasonCode int shouldAllowFgsStartForegroundWithBindingCheckLocked(
@ReasonCode int allowWhileInUse, String callingPackage, int callingPid,
- int callingUid, Intent intent, ServiceRecord r, int userId) {
+ int callingUid, Intent intent, ServiceRecord r, int userId, boolean isBindService) {
ActivityManagerService.FgsTempAllowListItem tempAllowListReason =
r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid);
int ret = shouldAllowFgsStartForegroundNoBindingCheckLocked(allowWhileInUse, callingPid,
- callingUid, callingPackage, r);
+ callingUid, callingPackage, r, isBindService);
String bindFromPackage = null;
if (ret == REASON_DENIED) {
@@ -6789,6 +6969,7 @@
+ "; callerTargetSdkVersion:" + callerTargetSdkVersion
+ "; startForegroundCount:" + r.mStartForegroundCount
+ "; bindFromPackage:" + bindFromPackage
+ + ": isBindService:" + isBindService
+ "]";
if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
r.mLoggedInfoAllowStartForeground = false;
@@ -6799,7 +6980,7 @@
private @ReasonCode int shouldAllowFgsStartForegroundNoBindingCheckLocked(
@ReasonCode int allowWhileInUse, int callingPid, int callingUid, String callingPackage,
- @Nullable ServiceRecord targetService) {
+ @Nullable ServiceRecord targetService, boolean isBindService) {
int ret = allowWhileInUse;
if (ret == REASON_DENIED) {
@@ -6981,10 +7162,12 @@
}
private void logFgsBackgroundStart(ServiceRecord r) {
+ /*
// Only log if FGS is started from background.
if (!isFgsBgStart(r.mAllowStartForeground)) {
return;
}
+ */
if (!r.mLoggedInfoAllowStartForeground) {
final String msg = "Background started FGS: "
+ ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
@@ -6996,10 +7179,10 @@
}
Slog.i(TAG, msg);
} else {
- if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
- mAm.mConstants.mFgsStartDeniedLogSampleRate)) {
+ //if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
+ // mAm.mConstants.mFgsStartDeniedLogSampleRate)) {
Slog.wtfQuiet(TAG, msg);
- }
+ //}
Slog.w(TAG, msg);
}
r.mLoggedInfoAllowStartForeground = true;
@@ -7011,17 +7194,20 @@
* @param r ServiceRecord
* @param state one of ENTER/EXIT/DENIED event.
* @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state.
+ * @param fgsStopReason why was this FGS stopped.
+ * @param fgsTypeCheckCode The FGS type policy check result.
*/
private void logFGSStateChangeLocked(ServiceRecord r, int state, int durationMs,
- @FgsStopReason int fgsStopReason) {
+ @FgsStopReason int fgsStopReason,
+ @ForegroundServicePolicyCheckCode int fgsTypeCheckCode) {
if (!ActivityManagerUtils.shouldSamplePackageForAtom(
r.packageName, mAm.mConstants.mFgsAtomSampleRate)) {
return;
}
boolean allowWhileInUsePermissionInFgs;
@PowerExemptionManager.ReasonCode int fgsStartReasonCode;
- if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
- || state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+ if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
+ || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
} else {
@@ -7047,14 +7233,15 @@
r.mStartForegroundCount,
ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
r.mFgsHasNotificationPermission,
- r.foregroundServiceType);
+ r.foregroundServiceType,
+ fgsTypeCheckCode);
int event = 0;
- if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
+ if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
event = EventLogTags.AM_FOREGROUND_SERVICE_START;
- } else if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+ } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
event = EventLogTags.AM_FOREGROUND_SERVICE_STOP;
- } else if (state == FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
+ } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED;
} else {
// Unknown event.
@@ -7082,7 +7269,7 @@
String callingPackage) {
return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
/* targetService */ null,
- /* allowBackgroundActivityStarts */ false)
+ /* allowBackgroundActivityStarts */ false, false)
!= REASON_DENIED;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 16fe121..003f7f0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -249,6 +249,18 @@
private static final String KEY_MAX_PHANTOM_PROCESSES = "max_phantom_processes";
/**
+ * Enables proactive killing of cached apps
+ */
+ private static final String KEY_PROACTIVE_KILLS_ENABLED = "proactive_kills_enabled";
+
+ /**
+ * Trim LRU cached app when swap falls below this minimum percentage.
+ *
+ * Depends on KEY_PROACTIVE_KILLS_ENABLED
+ */
+ private static final String KEY_LOW_SWAP_THRESHOLD_PERCENT = "low_swap_threshold_percent";
+
+ /**
* Default value for mFlagBackgroundActivityStartsEnabled if not explicitly set in
* Settings.Global. This allows it to be set experimentally unless it has been
* enabled/disabled in developer options. Defaults to false.
@@ -874,6 +886,10 @@
*/
private static final long DEFAULT_MIN_ASSOC_LOG_DURATION = 5 * 60 * 1000; // 5 mins
+ private static final boolean DEFAULT_PROACTIVE_KILLS_ENABLED = false;
+
+ private static final float DEFAULT_LOW_SWAP_THRESHOLD_PERCENT = 0.10f;
+
private static final String KEY_MIN_ASSOC_LOG_DURATION = "min_assoc_log_duration";
public static long MIN_ASSOC_LOG_DURATION = DEFAULT_MIN_ASSOC_LOG_DURATION;
@@ -904,6 +920,8 @@
public static boolean BINDER_HEAVY_HITTER_AUTO_SAMPLER_ENABLED;
public static int BINDER_HEAVY_HITTER_AUTO_SAMPLER_BATCHSIZE;
public static float BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD;
+ public static boolean PROACTIVE_KILLS_ENABLED = DEFAULT_PROACTIVE_KILLS_ENABLED;
+ public static float LOW_SWAP_THRESHOLD_PERCENT = DEFAULT_LOW_SWAP_THRESHOLD_PERCENT;
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
new OnPropertiesChangedListener() {
@@ -1040,6 +1058,12 @@
case KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS:
updateMaxServiceConnectionsPerProcess();
break;
+ case KEY_PROACTIVE_KILLS_ENABLED:
+ updateProactiveKillsEnabled();
+ break;
+ case KEY_LOW_SWAP_THRESHOLD_PERCENT:
+ updateLowSwapThresholdPercent();
+ break;
default:
break;
}
@@ -1660,6 +1684,20 @@
CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
}
+ private void updateProactiveKillsEnabled() {
+ PROACTIVE_KILLS_ENABLED = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_PROACTIVE_KILLS_ENABLED,
+ DEFAULT_PROACTIVE_KILLS_ENABLED);
+ }
+
+ private void updateLowSwapThresholdPercent() {
+ LOW_SWAP_THRESHOLD_PERCENT = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_LOW_SWAP_THRESHOLD_PERCENT,
+ DEFAULT_LOW_SWAP_THRESHOLD_PERCENT);
+ }
+
private void updateMinAssocLogDuration() {
MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
@@ -1860,6 +1898,10 @@
pw.print("="); pw.println(mNetworkAccessTimeoutMs);
pw.print(" "); pw.print(KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS);
pw.print("="); pw.println(mMaxServiceConnectionsPerProcess);
+ pw.print(" "); pw.print(KEY_PROACTIVE_KILLS_ENABLED);
+ pw.print("="); pw.println(PROACTIVE_KILLS_ENABLED);
+ pw.print(" "); pw.print(KEY_LOW_SWAP_THRESHOLD_PERCENT);
+ pw.print("="); pw.println(LOW_SWAP_THRESHOLD_PERCENT);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index af6eaf4..d44b727 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -378,12 +378,10 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.UndecFunction;
import com.android.server.AlarmManagerInternal;
import com.android.server.BootReceiver;
@@ -11173,9 +11171,9 @@
pw.printf("%s%s: %-60s (%s in swap)\n", prefix, stringifyKBSize(mi.pss),
mi.label, stringifyKBSize(mi.swapPss));
} else {
- pw.printf("%s%s: %s %s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
+ pw.printf("%s%s: %s%s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
mi.label,
- mi.userId != UserHandle.USER_SYSTEM ? "(user " + mi.userId + ")" : "");
+ mi.userId != UserHandle.USER_SYSTEM ? " (user " + mi.userId + ")" : "");
}
} else if (mi.isProc) {
pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel);
@@ -18904,19 +18902,20 @@
}
@Override
- public SyncNotedAppOp startProxyOperation(int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId,
- @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
- Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+ @NonNull UndecFunction<IBinder, Integer, AttributionSource,
+ Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
+ SyncNotedAppOp> superImpl) {
if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply(code, new AttributionSource(shellUid,
+ return superImpl.apply(clientId, code, new AttributionSource(shellUid,
"com.android.shell", attributionSource.getAttributionTag(),
attributionSource.getToken(), attributionSource.getNext()),
startIfModeDefault, shouldCollectAsyncNotedOp, message,
@@ -18926,21 +18925,22 @@
Binder.restoreCallingIdentity(identity);
}
}
- return superImpl.apply(code, attributionSource, startIfModeDefault,
+ return superImpl.apply(clientId, code, attributionSource, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
@Override
- public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
- Boolean, Void> superImpl) {
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
+ @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
+ Void> superImpl) {
if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- superImpl.apply(code, new AttributionSource(shellUid,
+ superImpl.apply(clientId, code, new AttributionSource(shellUid,
"com.android.shell", attributionSource.getAttributionTag(),
attributionSource.getToken(), attributionSource.getNext()),
skipProxyOperation);
@@ -18948,7 +18948,7 @@
Binder.restoreCallingIdentity(identity);
}
}
- superImpl.apply(code, attributionSource, skipProxyOperation);
+ superImpl.apply(clientId, code, attributionSource, skipProxyOperation);
}
private boolean isTargetOp(int code) {
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 50515cd..1f98aba 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -16,10 +16,10 @@
package com.android.server.am;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPES_MAX_INDEX;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
-import static android.content.pm.ServiceInfo.NUM_OF_FOREGROUND_SERVICE_TYPES;
import static android.content.pm.ServiceInfo.foregroundServiceTypeToLabel;
import static android.os.PowerExemptionManager.REASON_DENIED;
@@ -645,7 +645,7 @@
PackageDurations(int uid, String packageName,
MaxTrackingDurationConfig maxTrackingDurationConfig, AppFGSTracker tracker) {
- super(uid, packageName, NUM_OF_FOREGROUND_SERVICE_TYPES + 1, TAG,
+ super(uid, packageName, FOREGROUND_SERVICE_TYPES_MAX_INDEX + 1, TAG,
maxTrackingDurationConfig);
mEvents[DEFAULT_INDEX] = new LinkedList<>();
mTracker = tracker;
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index ba1c3b3..6abf6d8 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -2784,6 +2784,37 @@
*/
@ReasonCode
int getBackgroundRestrictionExemptionReason(int uid) {
+ @ReasonCode int reason = getPotentialSystemExemptionReason(uid);
+ if (reason != REASON_DENIED) {
+ return reason;
+ }
+ final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
+ if (packages != null) {
+ // Check each packages to see if any of them is in the "fixed" exemption cases.
+ for (String pkg : packages) {
+ reason = getPotentialSystemExemptionReason(uid, pkg);
+ if (reason != REASON_DENIED) {
+ return reason;
+ }
+ }
+ // Loop the packages again, and check the user-configurable exemptions.
+ for (String pkg : packages) {
+ reason = getPotentialUserAllowedExemptionReason(uid, pkg);
+ if (reason != REASON_DENIED) {
+ return reason;
+ }
+ }
+ }
+ return REASON_DENIED;
+ }
+
+ /**
+ * @param uid The uid to check.
+ * @return The potential exemption reason of the given uid. The caller must decide
+ * whether or not it should be exempted.
+ */
+ @ReasonCode
+ int getPotentialSystemExemptionReason(int uid) {
if (UserHandle.isCore(uid)) {
return REASON_SYSTEM_UID;
}
@@ -2811,37 +2842,51 @@
} else if (uidProcState <= PROCESS_STATE_PERSISTENT_UI) {
return REASON_PROC_STATE_PERSISTENT_UI;
}
- final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
- if (packages != null) {
- final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
- final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
- final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
- // Check each packages to see if any of them is in the "fixed" exemption cases.
- for (String pkg : packages) {
- if (isSystemModule(pkg)) {
- return REASON_SYSTEM_MODULE;
- } else if (isCarrierApp(pkg)) {
- return REASON_CARRIER_PRIVILEGED_APP;
- } else if (isExemptedFromSysConfig(pkg)) {
- return REASON_SYSTEM_ALLOW_LISTED;
- } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
- return REASON_SYSTEM_ALLOW_LISTED;
- } else if (pm.isPackageStateProtected(pkg, userId)) {
- return REASON_DPO_PROTECTED_APP;
- } else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
- return REASON_ACTIVE_DEVICE_ADMIN;
- }
- }
- // Loop the packages again, and check the user-configurable exemptions.
- for (String pkg : packages) {
- if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
- uid, pkg) == AppOpsManager.MODE_ALLOWED) {
- return REASON_OP_ACTIVATE_VPN;
- } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
- uid, pkg) == AppOpsManager.MODE_ALLOWED) {
- return REASON_OP_ACTIVATE_PLATFORM_VPN;
- }
- }
+ return REASON_DENIED;
+ }
+
+ /**
+ * @param uid The uid to check.
+ * @param pkgName The package name to check.
+ * @return The potential system-fixed exemption reason of the given uid/package. The caller
+ * must decide whether or not it should be exempted.
+ */
+ @ReasonCode
+ int getPotentialSystemExemptionReason(int uid, String pkg) {
+ final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+ final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+ final int userId = UserHandle.getUserId(uid);
+ if (isSystemModule(pkg)) {
+ return REASON_SYSTEM_MODULE;
+ } else if (isCarrierApp(pkg)) {
+ return REASON_CARRIER_PRIVILEGED_APP;
+ } else if (isExemptedFromSysConfig(pkg)) {
+ return REASON_SYSTEM_ALLOW_LISTED;
+ } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
+ return REASON_SYSTEM_ALLOW_LISTED;
+ } else if (pm.isPackageStateProtected(pkg, userId)) {
+ return REASON_DPO_PROTECTED_APP;
+ } else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
+ return REASON_ACTIVE_DEVICE_ADMIN;
+ }
+ return REASON_DENIED;
+ }
+
+ /**
+ * @param uid The uid to check.
+ * @param pkgName The package name to check.
+ * @return The potential user-allowed exemption reason of the given uid/package. The caller
+ * must decide whether or not it should be exempted.
+ */
+ @ReasonCode
+ int getPotentialUserAllowedExemptionReason(int uid, String pkg) {
+ final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
+ if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_VPN;
+ } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_PLATFORM_VPN;
}
if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
return REASON_ROLE_DIALER;
@@ -2852,6 +2897,7 @@
if (isOnDeviceIdleAllowlist(uid)) {
return REASON_ALLOWLISTED_PACKAGE;
}
+ final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
return REASON_COMPANION_DEVICE_MANAGER;
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 56909e3..dfac82c 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -170,7 +170,7 @@
*/
public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis";
- private static final long DEFAULT_DELAY_NORMAL_MILLIS = 0;
+ private static final long DEFAULT_DELAY_NORMAL_MILLIS = +500;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts
@@ -178,7 +178,7 @@
*/
public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
- private static final long DEFAULT_DELAY_CACHED_MILLIS = +30_000;
+ private static final long DEFAULT_DELAY_CACHED_MILLIS = +120_000;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to urgent
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 47ca427..739d277 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -164,6 +164,7 @@
private boolean mProcessCached;
private boolean mProcessInstrumented;
+ private boolean mProcessPersistent;
private String mCachedToString;
private String mCachedToShortString;
@@ -323,8 +324,10 @@
this.app = app;
if (app != null) {
setProcessInstrumented(app.getActiveInstrumentation() != null);
+ setProcessPersistent(app.isPersistent());
} else {
setProcessInstrumented(false);
+ setProcessPersistent(false);
}
}
@@ -352,6 +355,17 @@
}
/**
+ * Update if this process is in the "persistent" state, which signals broadcast dispatch should
+ * bypass all pauses or delays to prevent the system from becoming out of sync with itself.
+ */
+ public void setProcessPersistent(boolean persistent) {
+ if (mProcessPersistent != persistent) {
+ mProcessPersistent = persistent;
+ invalidateRunnableAt();
+ }
+ }
+
+ /**
* Return if we know of an actively running "warm" process for this queue.
*/
public boolean isProcessWarm() {
@@ -636,6 +650,7 @@
static final int REASON_MAX_PENDING = 3;
static final int REASON_BLOCKED = 4;
static final int REASON_INSTRUMENTED = 5;
+ static final int REASON_PERSISTENT = 6;
static final int REASON_CONTAINS_FOREGROUND = 10;
static final int REASON_CONTAINS_ORDERED = 11;
static final int REASON_CONTAINS_ALARM = 12;
@@ -652,6 +667,7 @@
REASON_MAX_PENDING,
REASON_BLOCKED,
REASON_INSTRUMENTED,
+ REASON_PERSISTENT,
REASON_CONTAINS_FOREGROUND,
REASON_CONTAINS_ORDERED,
REASON_CONTAINS_ALARM,
@@ -672,6 +688,7 @@
case REASON_MAX_PENDING: return "MAX_PENDING";
case REASON_BLOCKED: return "BLOCKED";
case REASON_INSTRUMENTED: return "INSTRUMENTED";
+ case REASON_PERSISTENT: return "PERSISTENT";
case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
@@ -731,6 +748,9 @@
} else if (mCountManifest > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_MANIFEST;
+ } else if (mProcessPersistent) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_PERSISTENT;
} else if (mProcessCached) {
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 6793876..008d95a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -239,7 +239,7 @@
}
case MSG_DELIVERY_TIMEOUT_SOFT: {
synchronized (mService) {
- deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj);
+ deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1);
}
return true;
}
@@ -746,9 +746,10 @@
if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
- final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
- mLocalHandler.sendMessageDelayed(
- Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, queue), timeout);
+ final int softTimeoutMillis = (int) (r.isForeground() ? mFgConstants.TIMEOUT
+ : mBgConstants.TIMEOUT);
+ mLocalHandler.sendMessageDelayed(Message.obtain(mLocalHandler,
+ MSG_DELIVERY_TIMEOUT_SOFT, softTimeoutMillis, 0, queue), softTimeoutMillis);
}
if (r.allowBackgroundActivityStarts) {
@@ -835,15 +836,17 @@
r.resultTo = null;
}
- private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue) {
+ private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
+ int softTimeoutMillis) {
if (queue.app != null) {
// Instead of immediately triggering an ANR, extend the timeout by
// the amount of time the process was runnable-but-waiting; we're
// only willing to do this once before triggering an hard ANR
final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
- final long timeout = MathUtils.constrain(cpuDelayTime, 0, mConstants.TIMEOUT);
+ final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis);
mLocalHandler.sendMessageDelayed(
- Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), timeout);
+ Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue),
+ hardTimeoutMillis);
} else {
deliveryTimeoutHardLocked(queue);
}
@@ -1453,7 +1456,7 @@
}
BroadcastProcessQueue created = new BroadcastProcessQueue(mConstants, processName, uid);
- created.app = mService.getProcessRecordLocked(processName, uid);
+ created.setProcess(mService.getProcessRecordLocked(processName, uid));
if (leaf == null) {
mProcessQueues.put(uid, created);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 2d7b0dc..e34cd12 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -845,7 +845,7 @@
/**
* Retrieves the free swap percentage.
*/
- static private native double getFreeSwapPercent();
+ static native double getFreeSwapPercent();
/**
* Retrieves the total used physical ZRAM
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 68e5a5d..eb2b7d4 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1118,6 +1118,12 @@
private long mNextNoKillDebugMessageTime;
+ private double mLastFreeSwapPercent = 1.00;
+
+ private static double getFreeSwapPercent() {
+ return CachedAppOptimizer.getFreeSwapPercent();
+ }
+
@GuardedBy({"mService", "mProcLock"})
private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
@@ -1142,6 +1148,11 @@
int numEmpty = 0;
int numTrimming = 0;
+ boolean proactiveKillsEnabled = mConstants.PROACTIVE_KILLS_ENABLED;
+ double lowSwapThresholdPercent = mConstants.LOW_SWAP_THRESHOLD_PERCENT;
+ double freeSwapPercent = proactiveKillsEnabled ? getFreeSwapPercent() : 1.00;
+ ProcessRecord lruCachedApp = null;
+
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
@@ -1179,6 +1190,8 @@
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
true);
+ } else if (proactiveKillsEnabled) {
+ lruCachedApp = app;
}
break;
case PROCESS_STATE_CACHED_EMPTY:
@@ -1198,6 +1211,8 @@
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
true);
+ } else if (proactiveKillsEnabled) {
+ lruCachedApp = app;
}
}
break;
@@ -1229,6 +1244,20 @@
}
}
+ if (proactiveKillsEnabled // Proactive kills enabled?
+ && doKillExcessiveProcesses // Should kill excessive processes?
+ && freeSwapPercent < lowSwapThresholdPercent // Swap below threshold?
+ && lruCachedApp != null // If no cached app, let LMKD decide
+ // If swap is non-decreasing, give reclaim a chance to catch up
+ && freeSwapPercent < mLastFreeSwapPercent) {
+ lruCachedApp.killLocked("swap low and too many cached",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
+ true);
+ }
+
+ mLastFreeSwapPercent = freeSwapPercent;
+
return mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming);
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index d2ef479..2ad2077 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -30,6 +30,7 @@
import android.app.ActivityManager;
import android.content.ComponentName;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.Slog;
import android.util.TimeUtils;
@@ -43,6 +44,9 @@
* The state info of the process, including proc state, oom adj score, et al.
*/
final class ProcessStateRecord {
+ // Enable this to trace all OomAdjuster state transitions
+ private static final boolean TRACE_OOM_ADJ = false;
+
private final ProcessRecord mApp;
private final ActivityManagerService mService;
private final ActivityManagerGlobalLock mProcLock;
@@ -916,6 +920,12 @@
@GuardedBy("mService")
void setAdjType(String adjType) {
+ if (TRACE_OOM_ADJ) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "oom:" + mApp.processName + "/u" + mApp.uid, 0);
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "oom:" + mApp.processName + "/u" + mApp.uid, adjType, 0);
+ }
mAdjType = adjType;
}
@@ -1153,6 +1163,10 @@
@GuardedBy({"mService", "mProcLock"})
void onCleanupApplicationRecordLSP() {
+ if (TRACE_OOM_ADJ) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "oom:" + mApp.processName + "/u" + mApp.uid, 0);
+ }
setHasForegroundActivities(false);
mHasShownUi = false;
mForcingToImportant = null;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 9213327..4d86140 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1090,7 +1090,7 @@
// TODO(b/239982558): for now we're just updating the user's visibility, but most likely
// we'll need to remove this call and handle that as part of the user state workflow
// instead.
- userManagerInternal.unassignUserFromDisplay(userId);
+ userManagerInternal.unassignUserFromDisplayOnStop(userId);
final boolean visibilityChanged;
boolean visibleBefore;
@@ -1650,13 +1650,30 @@
return false;
}
- if (!userInfo.preCreated) {
- // TODO(b/244644281): UMI should return whether the user is visible. And if fails,
- // the user should not be in the mediator's started users structure
- mInjector.getUserManagerInternal().assignUserToDisplay(userId,
- userInfo.profileGroupId, foreground, displayId);
+ t.traceBegin("assignUserToDisplayOnStart");
+ int result = mInjector.getUserManagerInternal().assignUserToDisplayOnStart(userId,
+ userInfo.profileGroupId, foreground, displayId);
+ t.traceEnd();
+
+ boolean visible;
+ switch (result) {
+ case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE:
+ visible = true;
+ break;
+ case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE:
+ visible = false;
+ break;
+ default:
+ Slogf.wtf(TAG, "Wrong result from assignUserToDisplayOnStart(): %d", result);
+ // Fall through
+ case UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE:
+ Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
+ (foreground ? "fg" : "bg"), userId, displayId,
+ UserManagerInternal.userAssignmentResultToString(result));
+ return false;
}
+
// TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
@@ -1751,19 +1768,6 @@
}
t.traceEnd();
- // Need to call UM when user is on background, as there are some cases where the user
- // cannot be started in background on a secondary display (for example, if user is a
- // profile).
- // TODO(b/253103846): it's also explicitly checking if the user is the USER_SYSTEM, as
- // the UM call would return true during boot (when CarService / BootUserInitializer
- // calls AM.startUserInBackground() because the system user is still the current user.
- // TODO(b/244644281): another fragility of this check is that it must wait to call
- // UMI.isUserVisible() until the user state is check, as that method checks if the
- // profile of the current user is started. We should fix that dependency so the logic
- // belongs to just one place (like UserDisplayAssigner)
- boolean visible = foreground
- || userId != UserHandle.USER_SYSTEM
- && mInjector.getUserManagerInternal().isUserVisible(userId);
if (visible) {
synchronized (mLock) {
addVisibleUserLocked(userId);
@@ -1816,8 +1820,8 @@
// user that was started in the background before), so it's necessary to explicitly
// notify the services (while when the user starts from BOOTING, USER_START_MSG
// takes care of that.
- mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId,
- visible ? 1 : 0));
+ mHandler.sendMessage(
+ mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId, 1));
}
t.traceBegin("sendMessages");
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 31d707d..64f2aa3 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -1314,16 +1314,9 @@
void onUserSwitching(TargetUser from, TargetUser to) {
final int toUserId = to.getUserIdentifier();
- if (from != null) {
- synchronized (mLock) {
- final int fromUserId = from.getUserIdentifier();
- if (mSettings.containsKey(fromUserId)) {
- sendUserMessage(fromUserId, REMOVE_SETTINGS, "ON_USER_SWITCHING",
- 0 /*delayMillis*/);
- }
- }
- }
-
+ // we want to re-populate the setting when switching user as the device config may have
+ // changed, which will only update for the previous user, see
+ // DeviceConfigListener#onPropertiesChanged.
sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING",
0 /*delayMillis*/);
@@ -1392,8 +1385,9 @@
Slog.v(TAG, "Package configuration not found for " + packageName);
return;
}
+ } else {
+ updateFps(packageConfig, packageName, gameMode, userId);
}
- updateFps(packageConfig, packageName, gameMode, userId);
updateUseAngle(packageName, gameMode);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a58583c..7e00c32 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2920,18 +2920,18 @@
}
@Override
- public SyncNotedAppOp startProxyOperation(int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
- return mCheckOpsDelegateDispatcher.startProxyOperation(code, attributionSource,
+ return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
attributionChainId);
}
- private SyncNotedAppOp startProxyOperationImpl(int code,
+ private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
@@ -2940,11 +2940,9 @@
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final IBinder proxyToken = attributionSource.getToken();
final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
- final IBinder proxiedToken = attributionSource.getNextToken();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
@@ -2986,7 +2984,7 @@
if (!skipProxyOperation) {
// Test if the proxied operation will succeed before starting the proxy operation
- final SyncNotedAppOp testProxiedOp = startOperationUnchecked(proxiedToken, code,
+ final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
@@ -2998,7 +2996,7 @@
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final SyncNotedAppOp proxyAppOp = startOperationUnchecked(proxyToken, code, proxyUid,
+ final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
shouldCollectMessage, proxyAttributionFlags, attributionChainId,
@@ -3008,7 +3006,7 @@
}
}
- return startOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
+ return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
@@ -3151,22 +3149,20 @@
}
@Override
- public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation) {
- mCheckOpsDelegateDispatcher.finishProxyOperation(code, attributionSource,
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
+ mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation);
}
- private Void finishProxyOperationImpl(int code, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation) {
+ private Void finishProxyOperationImpl(IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final IBinder proxyToken = attributionSource.getToken();
final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
- final IBinder proxiedToken = attributionSource.getNextToken();
skipProxyOperation = skipProxyOperation
&& isCallerAndAttributionTrusted(attributionSource);
@@ -3185,7 +3181,7 @@
}
if (!skipProxyOperation) {
- finishOperationUnchecked(proxyToken, code, proxyUid, resolvedProxyPackageName,
+ finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
proxyAttributionTag);
}
@@ -3195,7 +3191,7 @@
return null;
}
- finishOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName,
+ finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag);
return null;
@@ -6262,7 +6258,6 @@
Objects.requireNonNull(stackTrace);
Preconditions.checkArgument(op >= 0);
Preconditions.checkArgument(op < AppOpsManager._NUM_OP);
- Objects.requireNonNull(version);
NoteOpTrace noteOpTrace = new NoteOpTrace(stackTrace, op, packageName, version);
@@ -6436,42 +6431,42 @@
attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
}
- public SyncNotedAppOp startProxyOperation(int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
- return mPolicy.startProxyOperation(code, attributionSource,
+ return mPolicy.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId,
this::startDelegateProxyOperationImpl);
} else {
- return mPolicy.startProxyOperation(code, attributionSource,
+ return mPolicy.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId,
AppOpsService.this::startProxyOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
- return startDelegateProxyOperationImpl(code, attributionSource,
+ return startDelegateProxyOperationImpl(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId);
}
- return startProxyOperationImpl(code, attributionSource, startIfModeDefault,
+ return startProxyOperationImpl(clientId, code, attributionSource, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
- private SyncNotedAppOp startDelegateProxyOperationImpl(int code,
+ private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlsgs, int attributionChainId) {
- return mCheckOpsDelegate.startProxyOperation(code, attributionSource,
+ return mCheckOpsDelegate.startProxyOperation(clientId, code, attributionSource,
startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlsgs,
attributionChainId, AppOpsService.this::startProxyOperationImpl);
@@ -6500,27 +6495,28 @@
AppOpsService.this::finishOperationImpl);
}
- public void finishProxyOperation(int code,
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
- mPolicy.finishProxyOperation(code, attributionSource,
+ mPolicy.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, this::finishDelegateProxyOperationImpl);
} else {
- mPolicy.finishProxyOperation(code, attributionSource,
+ mPolicy.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
}
} else if (mCheckOpsDelegate != null) {
- finishDelegateProxyOperationImpl(code, attributionSource, skipProxyOperation);
+ finishDelegateProxyOperationImpl(clientId, code, attributionSource,
+ skipProxyOperation);
} else {
- finishProxyOperationImpl(code, attributionSource, skipProxyOperation);
+ finishProxyOperationImpl(clientId, code, attributionSource, skipProxyOperation);
}
}
- private Void finishDelegateProxyOperationImpl(int code,
+ private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
- mCheckOpsDelegate.finishProxyOperation(code, attributionSource, skipProxyOperation,
- AppOpsService.this::finishProxyOperationImpl);
+ mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
+ skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
return null;
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 3c281d1..5114bd5 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -26,6 +26,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
@@ -171,6 +172,7 @@
return MODE_ALLOWED;
}
case OP_RECORD_AUDIO:
+ case OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO:
if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) == 0) {
return MODE_IGNORED;
} else {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
index a9d8054..a6cf72c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java
@@ -34,13 +34,4 @@
public Mutable() {
value = null;
}
-
- /**
- * Initialize value with specific value.
- *
- * @param value initial value.
- */
- public Mutable(E value) {
- this.value = value;
- }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
index f6e90ef..188c25d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
@@ -25,7 +25,7 @@
AM_LW,
AM_MW,
AM_SW,
-};
+}
class Utils {
diff --git a/services/core/java/com/android/server/cpu/OWNERS b/services/core/java/com/android/server/cpu/OWNERS
new file mode 100644
index 0000000..2f42363
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 608533
+
+include platform/packages/services/Car:/OWNERS
+lakshmana@google.com
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index c06101f..a57d802 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -2511,19 +2511,22 @@
float appliedThermalCapNits =
event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
? -1f : convertToNits(event.getThermalMax());
-
- FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
- convertToNits(event.getInitialBrightness()),
- convertToNits(event.getBrightness()),
- event.getSlowAmbientLux(),
- event.getPhysicalDisplayId(),
- event.isShortTermModelActive(),
- appliedLowPowerMode,
- appliedRbcStrength,
- appliedHbmMaxNits,
- appliedThermalCapNits,
- event.isAutomaticBrightnessEnabled(),
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
+ && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
+ .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+ convertToNits(event.getInitialBrightness()),
+ convertToNits(event.getBrightness()),
+ event.getSlowAmbientLux(),
+ event.getPhysicalDisplayId(),
+ event.isShortTermModelActive(),
+ appliedLowPowerMode,
+ appliedRbcStrength,
+ appliedHbmMaxNits,
+ appliedThermalCapNits,
+ event.isAutomaticBrightnessEnabled(),
+ FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ }
}
private final class DisplayControllerHandler extends Handler {
@@ -2691,7 +2694,7 @@
int displayId, SensorManager sensorManager) {
return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
looper, nudgeUpdatePowerState,
- displayId, sensorManager);
+ displayId, sensorManager, /* injector= */ null);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index 5b64dd5..a3433d9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -30,6 +30,7 @@
import android.view.Display;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.utils.SensorUtils;
import java.io.PrintWriter;
@@ -40,16 +41,22 @@
* state changes.
*/
public final class DisplayPowerProximityStateController {
- private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
+ @VisibleForTesting
+ static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
+ @VisibleForTesting
+ static final int PROXIMITY_UNKNOWN = -1;
+ @VisibleForTesting
+ static final int PROXIMITY_POSITIVE = 1;
+ @VisibleForTesting
+ static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+
private static final int MSG_IGNORE_PROXIMITY = 2;
- private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
- private static final int PROXIMITY_POSITIVE = 1;
private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
// Proximity sensor debounce delay in milliseconds for positive transitions.
- private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+
// Proximity sensor debounce delay in milliseconds for negative transitions.
private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
// Trigger proximity if distance is less than 5 cm.
@@ -66,12 +73,13 @@
private final DisplayPowerProximityStateHandler mHandler;
// A runnable to execute the utility to update the power state.
private final Runnable mNudgeUpdatePowerState;
+ private Clock mClock;
// A listener which listen's to the events emitted by the proximity sensor.
private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
if (mProximitySensorEnabled) {
- final long time = SystemClock.uptimeMillis();
+ final long time = mClock.uptimeMillis();
final float distance = event.values[0];
boolean positive = distance >= 0.0f && distance < mProximityThreshold;
handleProximitySensorEvent(time, positive);
@@ -147,7 +155,12 @@
public DisplayPowerProximityStateController(
WakelockController wakeLockController, DisplayDeviceConfig displayDeviceConfig,
Looper looper,
- Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager) {
+ Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager,
+ Injector injector) {
+ if (injector == null) {
+ injector = new Injector();
+ }
+ mClock = injector.createClock();
mWakelockController = wakeLockController;
mHandler = new DisplayPowerProximityStateHandler(looper);
mNudgeUpdatePowerState = nudgeUpdatePowerState;
@@ -239,7 +252,6 @@
setProximitySensorEnabled(false);
mWaitingForNegativeProximity = false;
}
-
if (mScreenOffBecauseOfProximity
&& (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
// The screen *was* off due to prox being near, but now it's "far" so lets turn
@@ -313,7 +325,7 @@
+ mSkipRampBecauseOfProximityChangeToNegative);
}
- private void ignoreProximitySensorUntilChangedInternal() {
+ void ignoreProximitySensorUntilChangedInternal() {
if (!mIgnoreProximityUntilChanged
&& mProximity == PROXIMITY_POSITIVE) {
// Only ignore if it is still reporting positive (near)
@@ -414,7 +426,7 @@
if (mProximitySensorEnabled
&& mPendingProximity != PROXIMITY_UNKNOWN
&& mPendingProximityDebounceTime >= 0) {
- final long now = SystemClock.uptimeMillis();
+ final long now = mClock.uptimeMillis();
if (mPendingProximityDebounceTime <= now) {
if (mProximity != mPendingProximity) {
// if the status of the sensor changed, stop ignoring.
@@ -473,4 +485,66 @@
}
}
+ @VisibleForTesting
+ boolean getPendingWaitForNegativeProximityLocked() {
+ synchronized (mLock) {
+ return mPendingWaitForNegativeProximityLocked;
+ }
+ }
+
+ @VisibleForTesting
+ boolean getWaitingForNegativeProximity() {
+ return mWaitingForNegativeProximity;
+ }
+
+ @VisibleForTesting
+ boolean shouldIgnoreProximityUntilChanged() {
+ return mIgnoreProximityUntilChanged;
+ }
+
+ boolean isProximitySensorEnabled() {
+ return mProximitySensorEnabled;
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ @VisibleForTesting
+ int getPendingProximity() {
+ return mPendingProximity;
+ }
+
+ @VisibleForTesting
+ int getProximity() {
+ return mProximity;
+ }
+
+
+ @VisibleForTesting
+ long getPendingProximityDebounceTime() {
+ return mPendingProximityDebounceTime;
+ }
+
+ @VisibleForTesting
+ SensorEventListener getProximitySensorListener() {
+ return mProximitySensorListener;
+ }
+
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /**
+ * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+ */
+ long uptimeMillis();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ Clock createClock() {
+ return () -> SystemClock.uptimeMillis();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 16f1f23..cb97e28 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -180,12 +180,6 @@
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
@NonNull Handler handler) {
- this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap());
- }
-
- LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
- @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler, DeviceStateToLayoutMap deviceStateToLayoutMap) {
mSyncRoot = syncRoot;
mPowerManager = context.getSystemService(PowerManager.class);
mInteractive = mPowerManager.isInteractive();
@@ -200,7 +194,7 @@
mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray(
com.android.internal.R.array.config_deviceStatesOnWhichToSleep));
mDisplayDeviceRepo.addListener(this);
- mDeviceStateToLayoutMap = deviceStateToLayoutMap;
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
}
@Override
@@ -401,7 +395,9 @@
// the transition is smooth. Plus, on some devices, only one internal displays can be
// on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be
// temporarily turned off.
- resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION);
+ if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
+ resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION);
+ }
mPendingDeviceState = state;
final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
mInteractive, mBootCompleted);
@@ -944,8 +940,8 @@
newDisplay.swapDisplaysLocked(oldDisplay);
}
- if (displayLayout.isEnabled()) {
- setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_ENABLED);
+ if (!displayLayout.isEnabled()) {
+ setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED);
}
}
@@ -965,7 +961,7 @@
final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
display.updateLocked(mDisplayDeviceRepo);
mLogicalDisplays.put(displayId, display);
- setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_DISABLED);
+ setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED);
return display;
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index facc6b2..4a0ba22 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -116,7 +116,7 @@
private final DreamUiEventLogger mDreamUiEventLogger;
private final ComponentName mAmbientDisplayComponent;
private final boolean mDismissDreamOnActivityStart;
- private final boolean mDreamsOnlyEnabledForSystemUser;
+ private final boolean mDreamsOnlyEnabledForDockUser;
private final boolean mDreamsEnabledByDefaultConfig;
private final boolean mDreamsActivatedOnChargeByDefault;
private final boolean mDreamsActivatedOnDockByDefault;
@@ -214,8 +214,8 @@
mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
- mDreamsOnlyEnabledForSystemUser =
- mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForSystemUser);
+ mDreamsOnlyEnabledForDockUser =
+ mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForDockUser);
mDismissDreamOnActivityStart = mContext.getResources().getBoolean(
R.bool.config_dismissDreamOnActivityStart);
@@ -292,10 +292,9 @@
pw.println();
pw.println("mCurrentDream=" + mCurrentDream);
pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
- pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+ pw.println("mDreamsOnlyEnabledForDockUser=" + mDreamsOnlyEnabledForDockUser);
pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting);
pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
- pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault);
pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault);
pw.println("mIsDocked=" + mIsDocked);
@@ -602,7 +601,8 @@
}
private boolean dreamsEnabledForUser(int userId) {
- return !mDreamsOnlyEnabledForSystemUser || (userId == UserHandle.USER_SYSTEM);
+ // TODO(b/257333623): Support non-system Dock Users in HSUM.
+ return !mDreamsOnlyEnabledForDockUser || (userId == UserHandle.USER_SYSTEM);
}
private ServiceInfo getServiceInfo(ComponentName name) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 573bf19..5646e1b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -20,6 +20,8 @@
import static com.android.server.hdmi.Constants.ADDR_BACKUP_2;
import static com.android.server.hdmi.Constants.ADDR_TV;
+import static java.util.Map.entry;
+
import android.annotation.Nullable;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -45,7 +47,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -57,38 +58,34 @@
private static final String TAG = "HdmiUtils";
- private static final Map<Integer, List<Integer>> ADDRESS_TO_TYPE =
- new HashMap<Integer, List<Integer>>() {
- {
- put(Constants.ADDR_TV, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV));
- put(Constants.ADDR_RECORDER_1,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
- put(Constants.ADDR_RECORDER_2,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
- put(Constants.ADDR_TUNER_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
- put(Constants.ADDR_PLAYBACK_1,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
- put(Constants.ADDR_AUDIO_SYSTEM,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM));
- put(Constants.ADDR_TUNER_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
- put(Constants.ADDR_TUNER_3, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
- put(Constants.ADDR_PLAYBACK_2,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
- put(Constants.ADDR_RECORDER_3,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER));
- put(Constants.ADDR_TUNER_4, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER));
- put(Constants.ADDR_PLAYBACK_3,
- Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK));
- put(Constants.ADDR_BACKUP_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
- HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
- HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR));
- put(Constants.ADDR_BACKUP_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
- HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
- HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR));
- put(Constants.ADDR_SPECIFIC_USE, Lists.newArrayList(ADDR_TV));
- put(Constants.ADDR_UNREGISTERED, Collections.emptyList());
- }
- };
+ private static final Map<Integer, List<Integer>> ADDRESS_TO_TYPE = Map.ofEntries(
+ entry(Constants.ADDR_TV, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV)),
+ entry(Constants.ADDR_RECORDER_1,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+ entry(Constants.ADDR_RECORDER_2,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+ entry(Constants.ADDR_TUNER_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+ entry(Constants.ADDR_PLAYBACK_1,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+ entry(Constants.ADDR_AUDIO_SYSTEM,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)),
+ entry(Constants.ADDR_TUNER_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+ entry(Constants.ADDR_TUNER_3, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+ entry(Constants.ADDR_PLAYBACK_2,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+ entry(Constants.ADDR_RECORDER_3,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_RECORDER)),
+ entry(Constants.ADDR_TUNER_4, Lists.newArrayList(HdmiDeviceInfo.DEVICE_TUNER)),
+ entry(Constants.ADDR_PLAYBACK_3,
+ Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK)),
+ entry(Constants.ADDR_BACKUP_1, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
+ HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
+ HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)),
+ entry(Constants.ADDR_BACKUP_2, Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK,
+ HdmiDeviceInfo.DEVICE_RECORDER, HdmiDeviceInfo.DEVICE_TUNER,
+ HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)),
+ entry(Constants.ADDR_SPECIFIC_USE, Lists.newArrayList(ADDR_TV)),
+ entry(Constants.ADDR_UNREGISTERED, Collections.emptyList()));
private static final String[] DEFAULT_NAMES = {
"TV",
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 6234421..298098a 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -17,10 +17,15 @@
package com.android.server.input;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.graphics.PointF;
import android.hardware.display.DisplayViewport;
import android.os.IBinder;
import android.view.InputChannel;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import java.util.List;
@@ -136,6 +141,26 @@
public abstract InputChannel createInputChannel(String inputChannelName);
/**
+ * Pilfer pointers from the input channel with the given token so that ongoing gestures are
+ * canceled for all other channels.
+ */
+ public abstract void pilferPointers(IBinder token);
+
+ /**
+ * Called when the current input method and/or {@link InputMethodSubtype} is updated.
+ *
+ * @param userId User ID to be notified about.
+ * @param subtypeHandle A {@link InputMethodSubtypeHandle} corresponds to {@code subtype}.
+ * @param subtype A {@link InputMethodSubtype} object, or {@code null} when the current
+ * {@link InputMethodSubtype} is not suitable for the physical keyboard layout
+ * mapping.
+ * @see InputMethodSubtype#isSuitableForPhysicalKeyboardLayoutMapping()
+ */
+ public abstract void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
+ @Nullable InputMethodSubtypeHandle subtypeHandle,
+ @Nullable InputMethodSubtype subtype);
+
+ /**
* Increments keyboard backlight level if the device has an associated keyboard backlight
* {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT}
*/
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index d15f68c..8497dfb 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -23,6 +23,7 @@
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.Notification;
import android.app.NotificationManager;
@@ -111,11 +112,13 @@
import android.view.SurfaceControl;
import android.view.VerifiedInputEvent;
import android.view.ViewConfiguration;
+import android.view.inputmethod.InputMethodSubtype;
import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
@@ -1811,8 +1814,8 @@
*/
public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
@NonNull IBinder toChannelToken) {
- Objects.nonNull(fromChannelToken);
- Objects.nonNull(toChannelToken);
+ Objects.requireNonNull(fromChannelToken);
+ Objects.requireNonNull(toChannelToken);
return mNative.transferTouchFocus(fromChannelToken, toChannelToken,
false /* isDragDrop */);
}
@@ -2685,6 +2688,8 @@
@EnforcePermission(Manifest.permission.MONITOR_INPUT)
@Override
public void pilferPointers(IBinder inputChannelToken) {
+ super.pilferPointers_enforcePermission();
+
Objects.requireNonNull(inputChannelToken);
mNative.pilferPointers(inputChannelToken);
}
@@ -3789,6 +3794,21 @@
}
@Override
+ public void pilferPointers(IBinder token) {
+ mNative.pilferPointers(token);
+ }
+
+ @Override
+ public void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
+ @Nullable InputMethodSubtypeHandle subtypeHandle,
+ @Nullable InputMethodSubtype subtype) {
+ if (DEBUG) {
+ Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
+ + " subtypeHandle=" + subtypeHandle);
+ }
+ }
+
+ @Override
public void incrementKeyboardBacklight(int deviceId) {
mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 1a0f6f7..015e576 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -28,6 +28,7 @@
import android.view.InputChannel;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
import android.window.ImeOnBackInvokedDispatcher;
@@ -198,9 +199,10 @@
// TODO(b/192412909): Convert this back to void method
@AnyThread
- boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
+ boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, int flags,
+ ResultReceiver resultReceiver) {
try {
- mTarget.showSoftInput(showInputToken, flags, resultReceiver);
+ mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
} catch (RemoteException e) {
logRemoteException(e);
return false;
@@ -210,9 +212,10 @@
// TODO(b/192412909): Convert this back to void method
@AnyThread
- boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
+ boolean hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, int flags,
+ ResultReceiver resultReceiver) {
try {
- mTarget.hideSoftInput(hideInputToken, flags, resultReceiver);
+ mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver);
} catch (RemoteException e) {
logRemoteException(e);
return false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 4d1c5ae..8b083bd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -129,6 +129,7 @@
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
@@ -151,6 +152,7 @@
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
+import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.IInputMethodSession;
@@ -162,6 +164,7 @@
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
@@ -641,6 +644,10 @@
*/
private boolean mInputShown;
+ /** The token tracking the current IME request or {@code null} otherwise. */
+ @Nullable
+ private ImeTracker.Token mCurStatsToken;
+
/**
* {@code true} if the current input method is in fullscreen mode.
*/
@@ -760,7 +767,7 @@
* <dd>
* If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
* </dd>
- * dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+ * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
* <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
* currently invisible.
* </dd>
@@ -784,8 +791,7 @@
/**
* Internal state snapshot when
- * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IRemoteInputConnection, EditorInfo,
- * boolean)} is about to be called.
+ * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
*
* <p>Calling that IPC endpoint basically means that
* {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
@@ -1070,7 +1076,7 @@
/**
* Add a new entry and discard the oldest entry as needed.
- * @param info {@lin StartInputInfo} to be added.
+ * @param info {@link StartInputInfo} to be added.
*/
void addEntry(@NonNull StartInputInfo info) {
final int index = mNextIndex;
@@ -1188,18 +1194,18 @@
} else if (accessibilityRequestingNoImeUri.equals(uri)) {
final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId);
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0 /* def */, mUserId);
mAccessibilityRequestingNoSoftKeyboard =
(accessibilitySoftKeyboardSetting & AccessibilityService.SHOW_MODE_MASK)
== AccessibilityService.SHOW_MODE_HIDDEN;
if (mAccessibilityRequestingNoSoftKeyboard) {
final boolean showRequested = mShowRequested;
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+ 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
mShowRequested = showRequested;
} else if (mShowRequested) {
- showCurrentInputLocked(mCurFocusedWindow,
- InputMethodManager.SHOW_IMPLICIT, null,
+ showCurrentInputImplicitLocked(mCurFocusedWindow,
SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
}
} else {
@@ -1665,8 +1671,8 @@
}
// Hide soft input before user switch task since switch task may block main handler a while
// and delayed the hideCurrentInputLocked().
- hideCurrentInputLocked(
- mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SWITCH_USER);
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */, SoftInputShowHideReason.HIDE_SWITCH_USER);
final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
clientToBeReset);
mUserSwitchHandlerTask = task;
@@ -2221,7 +2227,7 @@
}
final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
try {
- client.asBinder().linkToDeath(deathRecipient, 0);
+ client.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -2246,7 +2252,7 @@
synchronized (ImfLock.class) {
ClientState cs = mClients.remove(client.asBinder());
if (cs != null) {
- client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0);
+ client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
clearClientSessionLocked(cs);
clearClientSessionForAccessibilityLocked(cs);
@@ -2259,8 +2265,8 @@
}
if (mCurClient == cs) {
- hideCurrentInputLocked(
- mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
if (mBoundToMethod) {
mBoundToMethod = false;
IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -2307,6 +2313,8 @@
mCurClient.mSessionRequestedForAccessibility = false;
mCurClient = null;
mCurVirtualDisplayToScreenMatrix = null;
+ ImeTracker.get().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ mCurStatsToken = null;
mMenuController.hideInputMethodMenuLocked();
}
@@ -2379,8 +2387,11 @@
navButtonFlags, mCurImeDispatcher);
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
- showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
- SoftInputShowHideReason.ATTACH_NEW_INPUT);
+ // Re-use current statsToken, if it exists.
+ final ImeTracker.Token statsToken = mCurStatsToken;
+ mCurStatsToken = null;
+ showCurrentInputLocked(mCurFocusedWindow, statsToken, getAppShowFlagsLocked(),
+ null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
}
String curId = getCurIdLocked();
@@ -2499,7 +2510,8 @@
if (mDisplayIdToShowIme == INVALID_DISPLAY) {
mImeHiddenByDisplayPolicy = true;
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */,
SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
return InputBindResult.NO_IME;
}
@@ -3201,6 +3213,18 @@
}
@GuardedBy("ImfLock.class")
+ private void notifyInputMethodSubtypeChangedLocked(@UserIdInt int userId,
+ @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+ final InputMethodSubtype normalizedSubtype =
+ subtype != null && subtype.isSuitableForPhysicalKeyboardLayoutMapping()
+ ? subtype : null;
+ final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null
+ ? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null;
+ mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
+ userId, newSubtypeHandle, normalizedSubtype);
+ }
+
+ @GuardedBy("ImfLock.class")
void setInputMethodLocked(String id, int subtypeId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
@@ -3209,8 +3233,10 @@
// See if we need to notify a subtype change within the same IME.
if (id.equals(getSelectedMethodIdLocked())) {
+ final int userId = mSettings.getCurrentUserId();
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
+ notifyInputMethodSubtypeChangedLocked(userId, info, null);
return;
}
final InputMethodSubtype oldSubtype = mCurrentSubtype;
@@ -3225,6 +3251,7 @@
if (newSubtype == null || oldSubtype == null) {
Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
+ ", new subtype = " + newSubtype);
+ notifyInputMethodSubtypeChangedLocked(userId, info, null);
return;
}
if (newSubtype != oldSubtype) {
@@ -3262,22 +3289,23 @@
}
@Override
- public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
- int lastClickTooType, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
+ public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
int uid = Binder.getCallingUid();
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput");
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "showSoftInput")) {
+ if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
return false;
}
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
- return showCurrentInputLocked(
- windowToken, flags, lastClickTooType, resultReceiver, reason);
+ return showCurrentInputLocked(windowToken, statsToken, flags, lastClickTooType,
+ resultReceiver, reason);
} finally {
Binder.restoreCallingIdentity(ident);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3294,7 +3322,8 @@
"InputMethodManagerService#startStylusHandwriting");
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting")) {
+ if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
+ null /* statsToken */)) {
return;
}
if (!hasSupportedStylusLocked()) {
@@ -3349,19 +3378,33 @@
}
@GuardedBy("ImfLock.class")
- boolean showCurrentInputLocked(IBinder windowToken, int flags,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- return showCurrentInputLocked(
- windowToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
+ boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+ int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ return showCurrentInputLocked(windowToken, statsToken, flags,
+ MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
}
@GuardedBy("ImfLock.class")
- private boolean showCurrentInputLocked(IBinder windowToken, int flags, int lastClickToolType,
+ private boolean showCurrentInputLocked(IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ // Create statsToken is none exists.
+ if (statsToken == null) {
+ String packageName = null;
+ if (mCurEditorInfo != null) {
+ packageName = mCurEditorInfo.packageName;
+ }
+ statsToken = new ImeTracker.Token(packageName);
+ ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_SERVER_START_INPUT,
+ reason);
+ }
+
mShowRequested = true;
if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
if ((flags & InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
@@ -3371,8 +3414,10 @@
}
if (!mSystemReady) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
mBindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3380,6 +3425,9 @@
// create a placeholder token for IMS so that IMS cannot inject windows into client app.
Binder showInputToken = new Binder();
mShowRequestWindowMap.put(showInputToken, windowToken);
+ ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
+ mCurStatsToken = null;
final int showFlags = getImeShowFlagsLocked();
if (DEBUG) {
Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
@@ -3391,23 +3439,34 @@
curMethod.updateEditorToolType(lastClickToolType);
}
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
- if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
+ if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
onShowHideSoftInputRequested(true /* show */, windowToken, reason);
}
mInputShown = true;
return true;
+ } else {
+ ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ mCurStatsToken = statsToken;
}
return false;
}
@Override
- public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
int uid = Binder.getCallingUid();
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput");
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "hideSoftInput")) {
+ if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
+ if (mInputShown) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ } else {
+ ImeTracker.get().onCancelled(statsToken,
+ ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ }
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -3415,7 +3474,7 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
return InputMethodManagerService.this.hideCurrentInputLocked(windowToken,
- flags, resultReceiver, reason);
+ statsToken, flags, resultReceiver, reason);
} finally {
Binder.restoreCallingIdentity(ident);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3424,17 +3483,32 @@
}
@GuardedBy("ImfLock.class")
- boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
+ boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+ int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ // Create statsToken is none exists.
+ if (statsToken == null) {
+ String packageName = null;
+ if (mCurEditorInfo != null) {
+ packageName = mCurEditorInfo.packageName;
+ }
+ statsToken = new ImeTracker.Token(packageName);
+ ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
+ }
+
if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mShowExplicitlyRequested || mShowForced)) {
if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
+
if (mShowForced && (flags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
return false;
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
// There is a chance that IMM#hideSoftInput() is called in a transient state where
// IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
@@ -3445,8 +3519,8 @@
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
IInputMethodInvoker curMethod = getCurMethodLocked();
- final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
- || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+ final boolean shouldHideSoftInput = (curMethod != null)
+ && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
boolean res;
if (shouldHideSoftInput) {
final Binder hideInputToken = new Binder();
@@ -3455,17 +3529,20 @@
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
// the final state.
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
if (DEBUG) {
Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
+ ", " + resultReceiver + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
}
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
- if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) {
+ if (curMethod.hideSoftInput(hideInputToken, statsToken, 0 /* flags */,
+ resultReceiver)) {
onShowHideSoftInputRequested(false /* show */, windowToken, reason);
}
res = true;
} else {
+ ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
res = false;
}
mBindingController.setCurrentMethodNotVisible();
@@ -3473,6 +3550,9 @@
mShowRequested = false;
mShowExplicitlyRequested = false;
mShowForced = false;
+ // Cancel existing statsToken for show IME as we got a hide request.
+ ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ mCurStatsToken = null;
return res;
}
@@ -3630,8 +3710,8 @@
Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ " a background user, use EditorInfo.targetInputMethodUser with"
+ " INTERACT_ACROSS_USERS_FULL permission.");
- hideCurrentInputLocked(
- mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_INVALID_USER);
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */, SoftInputShowHideReason.HIDE_INVALID_USER);
return InputBindResult.INVALID_USER;
}
@@ -3687,7 +3767,7 @@
boolean didStart = false;
InputBindResult res = null;
- // We shows the IME when the system allows the IME focused target window to restore the
+ // We show the IME when the system allows the IME focused target window to restore the
// IME visibility (e.g. switching to the app task when last time the IME is visible).
// Note that we don't restore IME visibility for some cases (e.g. when the soft input
// state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
@@ -3699,7 +3779,7 @@
res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection,
editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
imeDispatcher);
- showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+ showCurrentInputImplicitLocked(windowToken,
SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
return res;
}
@@ -3712,8 +3792,8 @@
// be behind any soft input window, so hide the
// soft input window if it is shown.
if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
- hideCurrentInputLocked(
- mCurFocusedWindow, InputMethodManager.HIDE_NOT_ALWAYS, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+ InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */,
SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
// If focused display changed, we should unbind current method
@@ -3742,10 +3822,7 @@
imeDispatcher);
didStart = true;
}
- showCurrentInputLocked(
- windowToken,
- InputMethodManager.SHOW_IMPLICIT,
- null,
+ showCurrentInputImplicitLocked(windowToken,
SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
}
break;
@@ -3758,14 +3835,16 @@
case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */,
SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
}
break;
case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
if (!sameWindowFocused) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input");
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */,
SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
}
break;
@@ -3781,7 +3860,7 @@
imeDispatcher);
didStart = true;
}
- showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+ showCurrentInputImplicitLocked(windowToken,
SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
} else {
Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
@@ -3802,7 +3881,7 @@
imeDispatcher);
didStart = true;
}
- showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+ showCurrentInputImplicitLocked(windowToken,
SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
}
} else {
@@ -3824,7 +3903,8 @@
// an editor upon refocusing a window.
if (startInputByWinGainedFocus) {
if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+ 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
}
}
@@ -3838,7 +3918,8 @@
// 2) SOFT_INPUT_STATE_VISIBLE state without an editor
// 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */,
SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
}
res = startInputUncheckedLocked(cs, inputContext,
@@ -3853,8 +3934,15 @@
}
@GuardedBy("ImfLock.class")
- private boolean canInteractWithImeLocked(
- int uid, IInputMethodClient client, String methodName) {
+ private void showCurrentInputImplicitLocked(@NonNull IBinder windowToken,
+ @SoftInputShowHideReason int reason) {
+ showCurrentInputLocked(windowToken, null /* statsToken */, InputMethodManager.SHOW_IMPLICIT,
+ null /* resultReceiver */, reason);
+ }
+
+ @GuardedBy("ImfLock.class")
+ private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName,
+ @Nullable ImeTracker.Token statsToken) {
if (mCurClient == null || client == null
|| mCurClient.mClient.asBinder() != client.asBinder()) {
// We need to check if this is the current client with
@@ -3862,13 +3950,16 @@
// be made before input is started in it.
final ClientState cs = mClients.get(client.asBinder());
if (cs == null) {
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
if (!isImeClientFocused(mCurFocusedWindow, cs)) {
Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
return false;
}
}
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
return true;
}
@@ -4221,7 +4312,7 @@
final int curTokenDisplayId;
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(callingUid, client,
- "getInputMethodWindowVisibleHeight")) {
+ "getInputMethodWindowVisibleHeight", null /* statsToken */)) {
if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) {
EventLog.writeEvent(0x534e4554, "204906124", callingUid, "");
mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true);
@@ -4444,7 +4535,8 @@
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession")) {
+ if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession",
+ null /* statsToken */)) {
return;
}
final long ident = Binder.clearCallingIdentity();
@@ -4471,7 +4563,8 @@
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
- if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest")) {
+ if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest",
+ null /* statsToken */)) {
return;
}
final long ident = Binder.clearCallingIdentity();
@@ -4656,7 +4749,8 @@
}
@BinderThread
- private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) {
+ private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
+ @Nullable ImeTracker.Token statsToken) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
@@ -4664,13 +4758,22 @@
}
if (!setVisible) {
if (mCurClient != null) {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
+
mWindowManagerInternal.hideIme(
mHideRequestWindowMap.get(windowToken),
- mCurClient.mSelfReportedDisplayId);
+ mCurClient.mSelfReportedDisplayId, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
}
} else {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
// Send to window manager to show IME after IME layout finishes.
- mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken));
+ mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken),
+ statsToken);
}
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -4737,7 +4840,8 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- hideCurrentInputLocked(mLastImeTargetWindow, flags, null, reason);
+ hideCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
+ null /* resultReceiver */, reason);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4754,7 +4858,8 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- showCurrentInputLocked(mLastImeTargetWindow, flags, null,
+ showCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
+ null /* resultReceiver */,
SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4845,7 +4950,8 @@
case MSG_HIDE_CURRENT_INPUT_METHOD:
synchronized (ImfLock.class) {
final @SoftInputShowHideReason int reason = (int) msg.obj;
- hideCurrentInputLocked(mCurFocusedWindow, 0, null, reason);
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+ null /* resultReceiver */, reason);
}
return true;
@@ -5297,6 +5403,7 @@
mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
}
}
+ notifyInputMethodSubtypeChangedLocked(mSettings.getCurrentUserId(), imi, mCurrentSubtype);
if (!setSubtypeOnly) {
// Set InputMethod here
@@ -6332,7 +6439,8 @@
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
if (userId == mSettings.getCurrentUserId()) {
- hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+ 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
mBindingController.unbindCurrentMethod();
// Reset the current IME
@@ -6597,8 +6705,9 @@
@BinderThread
@Override
- public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible) {
- mImms.applyImeVisibility(mToken, windowToken, setVisible);
+ public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
+ @Nullable ImeTracker.Token statsToken) {
+ mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken);
}
@BinderThread
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index 4f6d0d4..e46b8c0c 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -523,9 +523,9 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder(100);
- TransactionRecord[] arr;
+ ContextHubServiceTransaction[] arr;
synchronized (this) {
- arr = mTransactionQueue.toArray(new TransactionRecord[0]);
+ arr = mTransactionQueue.toArray(new ContextHubServiceTransaction[0]);
}
for (int i = 0; i < arr.length; i++) {
sb.append(i + ": " + arr[i] + "\n");
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 1435016..77cd673 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -76,6 +76,8 @@
"ENABLE_PSDS_PERIODIC_DOWNLOAD";
private static final String CONFIG_ENABLE_ACTIVE_SIM_EMERGENCY_SUPL =
"ENABLE_ACTIVE_SIM_EMERGENCY_SUPL";
+ private static final String CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION =
+ "ENABLE_NI_SUPL_MESSAGE_INJECTION";
static final String CONFIG_LONGTERM_PSDS_SERVER_1 = "LONGTERM_PSDS_SERVER_1";
static final String CONFIG_LONGTERM_PSDS_SERVER_2 = "LONGTERM_PSDS_SERVER_2";
static final String CONFIG_LONGTERM_PSDS_SERVER_3 = "LONGTERM_PSDS_SERVER_3";
@@ -218,6 +220,14 @@
}
/**
+ * Returns true if NI SUPL message injection is enabled; Returns false otherwise.
+ * Default false if not set.
+ */
+ boolean isNiSuplMessageInjectionEnabled() {
+ return getBooleanConfig(CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION, false);
+ }
+
+ /**
* Returns true if a long-term PSDS server is configured.
*/
boolean isLongTermPsdsServerConfigured() {
@@ -286,26 +296,24 @@
Log.e(TAG, "Unable to set " + CONFIG_ES_EXTENSION_SEC + ": " + mEsExtensionSec);
}
- Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
- {
- put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
- put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
+ Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>();
- if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
- put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
- }
+ map.put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
+ map.put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
- put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
- put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
- GnssConfiguration::native_set_gnss_pos_protocol_select);
- put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
- GnssConfiguration::native_set_emergency_supl_pdn);
+ if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
+ map.put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
+ }
- if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
- put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
- }
- }
- };
+ map.put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
+ map.put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
+ GnssConfiguration::native_set_gnss_pos_protocol_select);
+ map.put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
+ GnssConfiguration::native_set_emergency_supl_pdn);
+
+ if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
+ map.put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
+ }
for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
String propertyName = entry.getKey();
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 6f637b8..6f6b1c9 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -84,6 +84,7 @@
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.provider.Settings;
+import android.provider.Telephony.Sms.Intents;
import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentity;
import android.telephony.CellIdentityGsm;
@@ -95,6 +96,7 @@
import android.telephony.CellInfoLte;
import android.telephony.CellInfoNr;
import android.telephony.CellInfoWcdma;
+import android.telephony.SmsMessage;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -107,6 +109,7 @@
import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.HexDump;
import com.android.server.FgThread;
import com.android.server.location.gnss.GnssSatelliteBlocklistHelper.GnssSatelliteBlocklistCallback;
import com.android.server.location.gnss.NtpTimeHelper.InjectNtpTimeCallback;
@@ -523,23 +526,31 @@
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
- if (action == null) {
- return;
- }
+ mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
- switch (action) {
- case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
- case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
- subscriptionOrCarrierConfigChanged();
- break;
- }
+ if (mNetworkConnectivityHandler.isNativeAgpsRilSupported()
+ && mGnssConfiguration.isNiSuplMessageInjectionEnabled()) {
+ // Listen to WAP PUSH NI SUPL message.
+ // See User Plane Location Protocol Candidate Version 3.0,
+ // OMA-TS-ULP-V3_0-20110920-C, Section 8.3 OMA Push.
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
+ try {
+ intentFilter.addDataType("application/vnd.omaloc-supl-init");
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ Log.w(TAG, "Malformed SUPL init mime type");
}
- }, intentFilter, null, mHandler);
+ mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+
+ // Listen to MT SMS NI SUPL message.
+ // See User Plane Location Protocol Candidate Version 3.0,
+ // OMA-TS-ULP-V3_0-20110920-C, Section 8.4 MT SMS.
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
+ intentFilter.addDataScheme("sms");
+ intentFilter.addDataAuthority("localhost", "7275");
+ mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+ }
mNetworkConnectivityHandler.registerNetworkCallbacks();
@@ -560,6 +571,80 @@
updateEnabled();
}
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
+ if (action == null) {
+ return;
+ }
+
+ switch (action) {
+ case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
+ case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
+ subscriptionOrCarrierConfigChanged();
+ break;
+ case Intents.WAP_PUSH_RECEIVED_ACTION:
+ case Intents.DATA_SMS_RECEIVED_ACTION:
+ injectSuplInit(intent);
+ break;
+ }
+ }
+ };
+
+ private void injectSuplInit(Intent intent) {
+ if (!isNfwLocationAccessAllowed()) {
+ Log.w(TAG, "Reject SUPL INIT as no NFW location access");
+ return;
+ }
+
+ int slotIndex = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+ SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+ if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+ Log.e(TAG, "Invalid slot index");
+ return;
+ }
+
+ byte[] suplInit = null;
+ String action = intent.getAction();
+ if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) {
+ SmsMessage[] messages = Intents.getMessagesFromIntent(intent);
+ if (messages == null) {
+ Log.e(TAG, "Message does not exist in the intent");
+ return;
+ }
+ for (SmsMessage message : messages) {
+ suplInit = message.getUserData();
+ injectSuplInit(suplInit, slotIndex);
+ }
+ } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) {
+ suplInit = intent.getByteArrayExtra("data");
+ injectSuplInit(suplInit, slotIndex);
+ }
+ }
+
+ private void injectSuplInit(byte[] suplInit, int slotIndex) {
+ if (suplInit != null) {
+ if (DEBUG) {
+ Log.d(TAG, "suplInit = "
+ + HexDump.toHexString(suplInit) + " slotIndex = " + slotIndex);
+ }
+ mGnssNative.injectNiSuplMessageData(suplInit, suplInit.length , slotIndex);
+ }
+ }
+
+ private boolean isNfwLocationAccessAllowed() {
+ if (mGnssNative.isInEmergencySession()) {
+ return true;
+ }
+ if (mGnssVisibilityControl != null
+ && mGnssVisibilityControl.hasLocationPermissionEnabledProxyApps()) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Implements {@link InjectNtpTimeCallback#injectTime}
*/
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 02bdfd5..a7fffe2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -762,6 +762,10 @@
return APN_INVALID;
}
+ protected boolean isNativeAgpsRilSupported() {
+ return native_is_agps_ril_supported();
+ }
+
// AGPS support
private native void native_agps_data_conn_open(long networkHandle, String apn, int apnIpType);
diff --git a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
index 631dbbf..4e5e5f8 100644
--- a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
@@ -437,6 +437,10 @@
return locationPermissionEnabledProxyApps;
}
+ public boolean hasLocationPermissionEnabledProxyApps() {
+ return getLocationPermissionEnabledProxyApps().length > 0;
+ }
+
private void handleNfwNotification(NfwNotification nfwNotification) {
if (DEBUG) Log.d(TAG, "Non-framework location access notification: " + nfwNotification);
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 2d015a5d..edb2e5b 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -989,6 +989,14 @@
mGnssHal.injectPsdsData(data, length, psdsType);
}
+ /**
+ * Injects NI SUPL message data into the GNSS HAL.
+ */
+ public void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+ Preconditions.checkState(mRegistered);
+ mGnssHal.injectNiSuplMessageData(data, length, slotIndex);
+ }
+
@NativeEntryPoint
void reportGnssServiceDied() {
// Not necessary to clear (and restore) binder identity since it runs on another thread.
@@ -1278,7 +1286,7 @@
}
@NativeEntryPoint
- boolean isInEmergencySession() {
+ public boolean isInEmergencySession() {
return Binder.withCleanCallingIdentity(
() -> mEmergencyHelper.isInEmergency(
TimeUnit.SECONDS.toMillis(mConfiguration.getEsExtensionSec())));
@@ -1507,6 +1515,10 @@
protected void injectPsdsData(byte[] data, int length, int psdsType) {
native_inject_psds_data(data, length, psdsType);
}
+
+ protected void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+ native_inject_ni_supl_message_data(data, length, slotIndex);
+ }
}
// basic APIs
@@ -1650,6 +1662,9 @@
private static native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc,
int lac, long cid, int tac, int pcid, int arfcn);
+ private static native void native_inject_ni_supl_message_data(byte[] data, int length,
+ int slotIndex);
+
// PSDS APIs
private static native boolean native_supports_psds();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 92b685c7..d02faad 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -248,14 +248,14 @@
// Locking order is mUserCreationAndRemovalLock -> mSpManager.
private final Object mUserCreationAndRemovalLock = new Object();
- // These two arrays are only used at boot time. To save memory, they are set to null when
- // PHASE_BOOT_COMPLETED is reached.
+ // These two arrays are only used at boot time. To save memory, they are set to null near the
+ // end of the boot, when onThirdPartyAppsStarted() is called.
@GuardedBy("mUserCreationAndRemovalLock")
private SparseIntArray mEarlyCreatedUsers = new SparseIntArray();
@GuardedBy("mUserCreationAndRemovalLock")
private SparseIntArray mEarlyRemovedUsers = new SparseIntArray();
@GuardedBy("mUserCreationAndRemovalLock")
- private boolean mBootComplete;
+ private boolean mThirdPartyAppsStarted;
// Current password metrics for all secured users on the device. Updated when user unlocks the
// device or changes password. Removed when user is stopped.
@@ -297,16 +297,9 @@
@Override
public void onBootPhase(int phase) {
super.onBootPhase(phase);
- switch (phase) {
- case PHASE_ACTIVITY_MANAGER_READY:
- mLockSettingsService.migrateOldDataAfterSystemReady();
- mLockSettingsService.loadEscrowData();
- break;
- case PHASE_BOOT_COMPLETED:
- mLockSettingsService.bootCompleted();
- break;
- default:
- break;
+ if (phase == PHASE_ACTIVITY_MANAGER_READY) {
+ mLockSettingsService.migrateOldDataAfterSystemReady();
+ mLockSettingsService.loadEscrowData();
}
}
@@ -749,8 +742,8 @@
* <p>
* This is primarily needed for users that were removed by Android 13 or earlier, which didn't
* guarantee removal of LSS state as it relied on the {@code ACTION_USER_REMOVED} intent. It is
- * also needed because {@link #removeUser()} delays requests to remove LSS state until the
- * {@code PHASE_BOOT_COMPLETED} boot phase, so they can be lost.
+ * also needed because {@link #removeUser()} delays requests to remove LSS state until Weaver is
+ * guaranteed to be available, so they can be lost.
* <p>
* Stale state is detected by checking whether the user serial number changed. This works
* because user serial numbers are never reused.
@@ -931,7 +924,9 @@
return success;
}
- private void bootCompleted() {
+ // This is called when Weaver is guaranteed to be available (if the device supports Weaver).
+ // It does any synthetic password related work that was delayed from earlier in the boot.
+ private void onThirdPartyAppsStarted() {
synchronized (mUserCreationAndRemovalLock) {
// Handle delayed calls to LSS.removeUser() and LSS.createNewUser().
for (int i = 0; i < mEarlyRemovedUsers.size(); i++) {
@@ -976,7 +971,7 @@
setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
}
- mBootComplete = true;
+ mThirdPartyAppsStarted = true;
}
}
@@ -2304,14 +2299,14 @@
private void createNewUser(@UserIdInt int userId, int userSerialNumber) {
synchronized (mUserCreationAndRemovalLock) {
- // Before PHASE_BOOT_COMPLETED, don't actually create the synthetic password yet, but
- // rather automatically delay it to later. We do this because protecting the synthetic
+ // During early boot, don't actually create the synthetic password yet, but rather
+ // automatically delay it to later. We do this because protecting the synthetic
// password requires the Weaver HAL if the device supports it, and some devices don't
// make Weaver available until fairly late in the boot process. This logic ensures a
// consistent flow across all devices, regardless of their Weaver implementation.
- if (!mBootComplete) {
- Slogf.i(TAG, "Delaying locksettings state creation for user %d until boot complete",
- userId);
+ if (!mThirdPartyAppsStarted) {
+ Slogf.i(TAG, "Delaying locksettings state creation for user %d until third-party " +
+ "apps are started", userId);
mEarlyCreatedUsers.put(userId, userSerialNumber);
mEarlyRemovedUsers.delete(userId);
return;
@@ -2325,14 +2320,14 @@
private void removeUser(@UserIdInt int userId) {
synchronized (mUserCreationAndRemovalLock) {
- // Before PHASE_BOOT_COMPLETED, don't actually remove the LSS state yet, but rather
- // automatically delay it to later. We do this because deleting synthetic password
- // protectors requires the Weaver HAL if the device supports it, and some devices don't
- // make Weaver available until fairly late in the boot process. This logic ensures a
- // consistent flow across all devices, regardless of their Weaver implementation.
- if (!mBootComplete) {
- Slogf.i(TAG, "Delaying locksettings state removal for user %d until boot complete",
- userId);
+ // During early boot, don't actually remove the LSS state yet, but rather automatically
+ // delay it to later. We do this because deleting synthetic password protectors
+ // requires the Weaver HAL if the device supports it, and some devices don't make Weaver
+ // available until fairly late in the boot process. This logic ensures a consistent
+ // flow across all devices, regardless of their Weaver implementation.
+ if (!mThirdPartyAppsStarted) {
+ Slogf.i(TAG, "Delaying locksettings state removal for user %d until third-party " +
+ "apps are started", userId);
if (mEarlyCreatedUsers.indexOfKey(userId) >= 0) {
mEarlyCreatedUsers.delete(userId);
} else {
@@ -2634,9 +2629,8 @@
* protects the user's CE key with a key derived from the SP.
* <p>
* This is called just once in the lifetime of the user: at user creation time (possibly delayed
- * until {@code PHASE_BOOT_COMPLETED} to ensure that the Weaver HAL is available if the device
- * supports it), or when upgrading from Android 13 or earlier where users with no LSKF didn't
- * necessarily have an SP.
+ * until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
+ * or earlier where users with no LSKF didn't necessarily have an SP.
*/
@GuardedBy("mSpManager")
@VisibleForTesting
@@ -3159,7 +3153,7 @@
pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size());
synchronized (mUserCreationAndRemovalLock) {
- pw.println("BootComplete: " + mBootComplete);
+ pw.println("ThirdPartyAppsStarted: " + mThirdPartyAppsStarted);
}
}
@@ -3317,6 +3311,11 @@
private final class LocalService extends LockSettingsInternal {
@Override
+ public void onThirdPartyAppsStarted() {
+ LockSettingsService.this.onThirdPartyAppsStarted();
+ }
+
+ @Override
public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index d836df5..807ba3c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -309,6 +309,10 @@
}
private void writeFile(File path, byte[] data) {
+ writeFile(path, data, /* syncParentDir= */ true);
+ }
+
+ private void writeFile(File path, byte[] data, boolean syncParentDir) {
synchronized (mFileWriteLock) {
// Use AtomicFile to guarantee atomicity of the file write, including when an existing
// file is replaced with a new one. This method is usually used to create new files,
@@ -326,9 +330,11 @@
file.failWrite(out);
}
// For performance reasons, AtomicFile only syncs the file itself, not also the parent
- // directory. The latter must be done explicitly here, as some callers need a guarantee
- // that the file really exists on-disk when this returns.
- fsyncDirectory(path.getParentFile());
+ // directory. The latter must be done explicitly when requested here, as some callers
+ // need a guarantee that the file really exists on-disk when this returns.
+ if (syncParentDir) {
+ fsyncDirectory(path.getParentFile());
+ }
mCache.putFile(path, data);
}
}
@@ -378,10 +384,20 @@
}
}
+ /**
+ * Writes the synthetic password state file for the given user ID, protector ID, and state name.
+ * If the file already exists, then it is atomically replaced.
+ * <p>
+ * This doesn't sync the parent directory, and a result the new state file may be lost if the
+ * system crashes. The caller must call {@link syncSyntheticPasswordState()} afterwards to sync
+ * the parent directory if needed, preferably after batching up other state file creations for
+ * the same user. We do it this way because directory syncs are expensive on some filesystems.
+ */
public void writeSyntheticPasswordState(int userId, long protectorId, String name,
byte[] data) {
ensureSyntheticPasswordDirectoryForUser(userId);
- writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data);
+ writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data,
+ /* syncParentDir= */ false);
}
public byte[] readSyntheticPasswordState(int userId, long protectorId, String name) {
@@ -392,6 +408,13 @@
deleteFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name));
}
+ /**
+ * Ensures that all synthetic password state files for the user have really been saved to disk.
+ */
+ public void syncSyntheticPasswordState(int userId) {
+ fsyncDirectory(getSyntheticPasswordDirectoryForUser(userId));
+ }
+
public Map<Integer, List<Long>> listSyntheticPasswordProtectorsForAllUsers(String stateName) {
Map<Integer, List<Long>> result = new ArrayMap<>();
final UserManager um = UserManager.get(mContext);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3fd488e..73a16fd 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -619,12 +619,16 @@
/**
* Creates a new synthetic password (SP) for the given user.
- *
+ * <p>
* Any existing SID for the user is cleared.
- *
+ * <p>
* Also saves the escrow information necessary to re-generate the synthetic password under
* an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
* password escrow should be disabled completely on the given user.
+ * <p>
+ * {@link syncState()} is not called yet; the caller should create a protector afterwards, which
+ * handles this. This makes it so that all the user's initial SP state files, including the
+ * initial LSKF-based protector, are efficiently created with only a single {@link syncState()}.
*/
SyntheticPassword newSyntheticPassword(int userId) {
clearSidForUser(userId);
@@ -668,6 +672,7 @@
private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
saveState(SP_HANDLE_NAME, spHandle, NULL_PROTECTOR_ID, userId);
+ syncState(userId);
}
private boolean loadEscrowData(SyntheticPassword sp, int userId) {
@@ -677,6 +682,11 @@
return e0 != null && p1 != null;
}
+ /**
+ * Saves the escrow data for the synthetic password. The caller is responsible for calling
+ * {@link syncState()} afterwards, once the user's other initial synthetic password state files
+ * have been created.
+ */
private void saveEscrowData(SyntheticPassword sp, int userId) {
saveState(SP_E0_NAME, sp.mEncryptedEscrowSplit0, NULL_PROTECTOR_ID, userId);
saveState(SP_P1_NAME, sp.mEscrowSplit1, NULL_PROTECTOR_ID, userId);
@@ -708,6 +718,10 @@
return buffer.getInt();
}
+ /**
+ * Creates a file that stores the Weaver slot the protector is using. The caller is responsible
+ * for calling {@link syncState()} afterwards, once all the protector's files have been created.
+ */
private void saveWeaverSlot(int slot, long protectorId, int userId) {
ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES);
buffer.put(WEAVER_VERSION);
@@ -837,6 +851,7 @@
}
createSyntheticPasswordBlob(protectorId, PROTECTOR_TYPE_LSKF_BASED, sp, protectorSecret,
sid, userId);
+ syncState(userId); // ensure the new files are really saved to disk
return protectorId;
}
@@ -996,6 +1011,7 @@
saveSecdiscardable(tokenHandle, tokenData.secdiscardableOnDisk, userId);
createSyntheticPasswordBlob(tokenHandle, getTokenBasedProtectorType(tokenData.mType), sp,
tokenData.aggregatedSecret, 0L, userId);
+ syncState(userId); // ensure the new files are really saved to disk
tokenMap.get(userId).remove(tokenHandle);
if (tokenData.mCallback != null) {
tokenData.mCallback.onEscrowTokenActivated(tokenHandle, userId);
@@ -1003,6 +1019,11 @@
return true;
}
+ /**
+ * Creates a synthetic password blob, i.e. the file that stores the encrypted synthetic password
+ * (or encrypted escrow secret) for a protector. The caller is responsible for calling
+ * {@link syncState()} afterwards, once all the protector's files have been created.
+ */
private void createSyntheticPasswordBlob(long protectorId, byte protectorType,
SyntheticPassword sp, byte[] protectorSecret, long sid, int userId) {
final byte[] spSecret;
@@ -1118,6 +1139,7 @@
// (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
pwd.credentialType = credential.getType();
saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
+ syncState(userId);
synchronizeFrpPassword(pwd, 0, userId);
} else {
Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
@@ -1156,6 +1178,7 @@
if (result.syntheticPassword != null && !credential.isNone() &&
!hasPasswordMetrics(protectorId, userId)) {
savePasswordMetrics(credential, result.syntheticPassword, protectorId, userId);
+ syncState(userId); // Not strictly needed as the upgrade can be re-done, but be safe.
}
return result;
}
@@ -1275,6 +1298,7 @@
+ blob.mProtectorType);
createSyntheticPasswordBlob(protectorId, blob.mProtectorType, result, protectorSecret,
sid, userId);
+ syncState(userId); // Not strictly needed as the upgrade can be re-done, but be safe.
}
return result;
}
@@ -1396,12 +1420,21 @@
return ArrayUtils.concat(data, secdiscardable);
}
+ /**
+ * Generates and writes the secdiscardable file for the given protector. The caller is
+ * responsible for calling {@link syncState()} afterwards, once all the protector's files have
+ * been created.
+ */
private byte[] createSecdiscardable(long protectorId, int userId) {
byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
saveSecdiscardable(protectorId, data, userId);
return data;
}
+ /**
+ * Writes the secdiscardable file for the given protector. The caller is responsible for
+ * calling {@link syncState()} afterwards, once all the protector's files have been created.
+ */
private void saveSecdiscardable(long protectorId, byte[] secdiscardable, int userId) {
saveState(SECDISCARDABLE_NAME, secdiscardable, protectorId, userId);
}
@@ -1445,6 +1478,11 @@
return VersionedPasswordMetrics.deserialize(decrypted).getMetrics();
}
+ /**
+ * Creates the password metrics file: the file associated with the LSKF-based protector that
+ * contains the encrypted metrics about the LSKF. The caller is responsible for calling
+ * {@link syncState()} afterwards if needed.
+ */
private void savePasswordMetrics(LockscreenCredential credential, SyntheticPassword sp,
long protectorId, int userId) {
final byte[] encrypted = SyntheticPasswordCrypto.encrypt(sp.deriveMetricsKey(),
@@ -1466,10 +1504,21 @@
return mStorage.readSyntheticPasswordState(userId, protectorId, stateName);
}
+ /**
+ * Persists the given synthetic password state for the given user ID and protector ID.
+ * <p>
+ * For performance reasons, this doesn't sync the user's synthetic password state directory. As
+ * a result, it doesn't guarantee that the file will really be present after a crash. If that
+ * is needed, call {@link syncState()} afterwards, preferably after batching up related updates.
+ */
private void saveState(String stateName, byte[] data, long protectorId, int userId) {
mStorage.writeSyntheticPasswordState(userId, protectorId, stateName, data);
}
+ private void syncState(int userId) {
+ mStorage.syncSyntheticPasswordState(userId);
+ }
+
private void destroyState(String stateName, long protectorId, int userId) {
mStorage.deleteSyntheticPasswordState(userId, protectorId, stateName);
}
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index dcdb881..72ce38b 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -275,6 +275,10 @@
String.valueOf(mComponentType));
}
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
@ComponentType
private static int getComponentType(PendingIntent pendingIntent) {
if (pendingIntent.isBroadcast()) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c3a5558..90135ad 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4967,7 +4967,16 @@
}
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
- return mZenModeHelper.addAutomaticZenRule(pkg, automaticZenRule,
+ // If the calling app is the system (from any user), take the package name from the
+ // rule's owner rather than from the caller's package.
+ String rulePkg = pkg;
+ if (isCallingAppIdSystem()) {
+ if (automaticZenRule.getOwner() != null) {
+ rulePkg = automaticZenRule.getOwner().getPackageName();
+ }
+ }
+
+ return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
"addAutomaticZenRule");
}
@@ -9764,6 +9773,12 @@
return uid == Process.SYSTEM_UID;
}
+ protected boolean isCallingAppIdSystem() {
+ final int uid = Binder.getCallingUid();
+ final int appid = UserHandle.getAppId(uid);
+ return appid == Process.SYSTEM_UID;
+ }
+
protected boolean isUidSystemOrPhone(int uid) {
final int appid = UserHandle.getAppId(uid);
return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index bbbf452..1bbcc83 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -852,7 +852,9 @@
Objects.requireNonNull(pkg);
Objects.requireNonNull(group);
Objects.requireNonNull(group.getId());
- Objects.requireNonNull(!TextUtils.isEmpty(group.getName()));
+ if (TextUtils.isEmpty(group.getName())) {
+ throw new IllegalArgumentException("group.getName() can't be empty");
+ }
boolean needsDndChange = false;
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f2c78ad..4b2c88c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -326,7 +326,7 @@
public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
String reason) {
- if (!isSystemRule(automaticZenRule)) {
+ if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
if (component == null) {
component = getActivityInfo(automaticZenRule.getConfigurationActivity());
@@ -582,11 +582,6 @@
}
}
- private boolean isSystemRule(AutomaticZenRule rule) {
- return rule.getOwner() != null
- && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
- }
-
private ServiceInfo getServiceInfo(ComponentName owner) {
Intent queryIntent = new Intent();
queryIntent.setComponent(owner);
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 5e98cc0..978e436 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -29,6 +29,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Binder;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ShellCommand;
@@ -64,7 +65,8 @@
private final IOverlayManager mInterface;
private static final Map<String, Integer> TYPE_MAP = Map.of(
"color", TypedValue.TYPE_FIRST_COLOR_INT,
- "string", TypedValue.TYPE_STRING);
+ "string", TypedValue.TYPE_STRING,
+ "drawable", -1);
OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
mContext = ctx;
@@ -258,7 +260,7 @@
String name = "";
String filename = null;
String opt;
- String configuration = null;
+ String config = null;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--user":
@@ -277,7 +279,7 @@
filename = getNextArgRequired();
break;
case "--config":
- configuration = getNextArgRequired();
+ config = getNextArgRequired();
break;
default:
err.println("Error: Unknown option: " + opt);
@@ -312,7 +314,9 @@
final String resourceName = getNextArgRequired();
final String typeStr = getNextArgRequired();
final String strData = String.join(" ", peekRemainingArgs());
- addOverlayValue(overlayBuilder, resourceName, typeStr, strData, configuration);
+ if (addOverlayValue(overlayBuilder, resourceName, typeStr, strData, config) != 0) {
+ return 1;
+ }
}
mInterface.commit(new OverlayManagerTransaction.Builder()
@@ -369,8 +373,10 @@
return 1;
}
String config = parser.getAttributeValue(null, "config");
- addOverlayValue(overlayBuilder, targetPackage + ':' + target,
- overlayType, value, config);
+ if (addOverlayValue(overlayBuilder, targetPackage + ':' + target,
+ overlayType, value, config) != 0) {
+ return 1;
+ }
}
}
}
@@ -384,7 +390,7 @@
return 0;
}
- private void addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
+ private int addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
String resourceName, String typeString, String valueString, String configuration) {
final int type;
typeString = typeString.toLowerCase(Locale.getDefault());
@@ -399,6 +405,9 @@
}
if (type == TypedValue.TYPE_STRING) {
overlayBuilder.setResourceValue(resourceName, type, valueString, configuration);
+ } else if (type < 0) {
+ ParcelFileDescriptor pfd = openFileForSystem(valueString, "r");
+ overlayBuilder.setResourceValue(resourceName, pfd, configuration);
} else {
final int intData;
if (valueString.startsWith("0x")) {
@@ -408,6 +417,7 @@
}
overlayBuilder.setResourceValue(resourceName, type, intData, configuration);
}
+ return 0;
}
private int runEnableExclusive() throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 3b676c65..b4792c6 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -44,7 +44,6 @@
import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Watched;
-import com.android.server.utils.WatchedArrayList;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedArraySet;
import com.android.server.utils.WatchedSparseBooleanMatrix;
@@ -179,9 +178,9 @@
@NonNull
@Watched
- protected WatchedArrayList<String> mProtectedBroadcasts;
+ protected WatchedArraySet<String> mProtectedBroadcasts;
@NonNull
- protected SnapshotCache<WatchedArrayList<String>> mProtectedBroadcastsSnapshot;
+ protected SnapshotCache<WatchedArraySet<String>> mProtectedBroadcastsSnapshot;
/**
* This structure maps uid -> uid and indicates whether access from the first should be
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 2e67bf2..c97711b 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -73,7 +73,6 @@
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableImpl;
-import com.android.server.utils.WatchedArrayList;
import com.android.server.utils.WatchedArraySet;
import com.android.server.utils.WatchedSparseBooleanMatrix;
import com.android.server.utils.WatchedSparseSetArray;
@@ -223,7 +222,7 @@
mForceQueryable = new WatchedArraySet<>();
mForceQueryableSnapshot = new SnapshotCache.Auto<>(
mForceQueryable, mForceQueryable, "AppsFilter.mForceQueryable");
- mProtectedBroadcasts = new WatchedArrayList<>();
+ mProtectedBroadcasts = new WatchedArraySet<>();
mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>(
mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts");
mPermissionToUids = new HashMap<>();
@@ -573,13 +572,17 @@
return null;
}
- final boolean protectedBroadcastsChanged;
- synchronized (mProtectedBroadcastsLock) {
- protectedBroadcastsChanged =
- mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts());
- }
- if (protectedBroadcastsChanged) {
- mQueriesViaComponentRequireRecompute.set(true);
+ final List<String> newBroadcasts = newPkg.getProtectedBroadcasts();
+ if (newBroadcasts.size() != 0) {
+ final boolean protectedBroadcastsChanged;
+ synchronized (mProtectedBroadcastsLock) {
+ final int oldSize = mProtectedBroadcasts.size();
+ mProtectedBroadcasts.addAll(newBroadcasts);
+ protectedBroadcastsChanged = mProtectedBroadcasts.size() != oldSize;
+ }
+ if (protectedBroadcastsChanged) {
+ mQueriesViaComponentRequireRecompute.set(true);
+ }
}
final boolean newIsForceQueryable;
@@ -1149,7 +1152,12 @@
final ArrayList<String> protectedBroadcasts = new ArrayList<>(
mProtectedBroadcasts.untrackedStorage());
collectProtectedBroadcasts(settings, removingPackageName);
- protectedBroadcastsChanged = !mProtectedBroadcasts.containsAll(protectedBroadcasts);
+ for (int i = 0; i < protectedBroadcasts.size(); ++i) {
+ if (!mProtectedBroadcasts.contains(protectedBroadcasts.get(i))) {
+ protectedBroadcastsChanged = true;
+ break;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java
index 7daa0b9..483fa8a 100644
--- a/services/core/java/com/android/server/pm/AppsFilterUtils.java
+++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java
@@ -29,7 +29,7 @@
import com.android.server.pm.pkg.component.ParsedIntentInfo;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.utils.WatchedArrayList;
+import com.android.server.utils.WatchedArraySet;
import java.util.List;
import java.util.Set;
@@ -45,7 +45,7 @@
/** Returns true if the querying package may query for the potential target package */
public static boolean canQueryViaComponents(AndroidPackage querying,
- AndroidPackage potentialTarget, WatchedArrayList<String> protectedBroadcasts) {
+ AndroidPackage potentialTarget, WatchedArraySet<String> protectedBroadcasts) {
if (!querying.getQueriesIntents().isEmpty()) {
for (Intent intent : querying.getQueriesIntents()) {
if (matchesPackage(intent, potentialTarget, protectedBroadcasts)) {
@@ -117,7 +117,7 @@
}
private static boolean matchesPackage(Intent intent, AndroidPackage potentialTarget,
- WatchedArrayList<String> protectedBroadcasts) {
+ WatchedArraySet<String> protectedBroadcasts) {
if (matchesAnyComponents(
intent, potentialTarget.getServices(), null /*protectedBroadcasts*/)) {
return true;
@@ -138,7 +138,7 @@
private static boolean matchesAnyComponents(Intent intent,
List<? extends ParsedMainComponent> components,
- WatchedArrayList<String> protectedBroadcasts) {
+ WatchedArraySet<String> protectedBroadcasts) {
for (int i = ArrayUtils.size(components) - 1; i >= 0; i--) {
ParsedMainComponent component = components.get(i);
if (!component.isExported()) {
@@ -152,7 +152,7 @@
}
private static boolean matchesAnyFilter(Intent intent, ParsedComponent component,
- WatchedArrayList<String> protectedBroadcasts) {
+ WatchedArraySet<String> protectedBroadcasts) {
List<ParsedIntentInfo> intents = component.getIntents();
for (int i = ArrayUtils.size(intents) - 1; i >= 0; i--) {
IntentFilter intentFilter = intents.get(i).getIntentFilter();
@@ -164,7 +164,7 @@
}
private static boolean matchesIntentFilter(Intent intent, IntentFilter intentFilter,
- @Nullable WatchedArrayList<String> protectedBroadcasts) {
+ @Nullable WatchedArraySet<String> protectedBroadcasts) {
return intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
intent.getData(), intent.getCategories(), "AppsFilter", true,
protectedBroadcasts != null ? protectedBroadcasts.untrackedStorage() : null) > 0;
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index bf00a33..5b8ee2b 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -125,6 +125,14 @@
ActivityInfo getActivityInfo(ComponentName component, long flags, int userId);
/**
+ * Similar to {@link Computer#getActivityInfo(android.content.ComponentName, long, int)} but
+ * only visible as internal service. This method bypass INTERACT_ACROSS_USERS or
+ * INTERACT_ACROSS_USERS_FULL permission checks and only to be used for intent resolution across
+ * chained cross profiles
+ */
+ ActivityInfo getActivityInfoCrossProfile(ComponentName component, long flags, int userId);
+
+ /**
* Important: The provided filterCallingUid is used exclusively to filter out activities
* that can be seen based on user state. It's typically the original caller uid prior
* to clearing. Because it can only be provided by trusted code, its value can be
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b285136..a8534b0 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -835,6 +835,24 @@
}
/**
+ * Similar to {@link Computer#getActivityInfo(android.content.ComponentName, long, int)} but
+ * only visible as internal service. This method bypass INTERACT_ACROSS_USERS or
+ * INTERACT_ACROSS_USERS_FULL permission checks and only to be used for intent resolution across
+ * chained cross profiles
+ * @param component application's component
+ * @param flags resolve info flags
+ * @param userId user id where activity resides
+ * @return ActivityInfo corresponding to requested component.
+ */
+ public final ActivityInfo getActivityInfoCrossProfile(ComponentName component,
+ @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
+ if (!mUserManager.exists(userId)) return null;
+ flags = updateFlagsForComponent(flags, userId);
+
+ return getActivityInfoInternalBody(component, flags, Binder.getCallingUid(), userId);
+ }
+
+ /**
* Important: The provided filterCallingUid is used exclusively to filter out activities
* that can be seen based on user state. It's typically the original caller uid prior
* to clearing. Because it can only be provided by trusted code, its value can be
@@ -1711,7 +1729,7 @@
ComponentName forwardingActivityComponentName = new ComponentName(
androidApplication().packageName, className);
ActivityInfo forwardingActivityInfo =
- getActivityInfo(forwardingActivityComponentName, 0,
+ getActivityInfoCrossProfile(forwardingActivityComponentName, 0,
sourceUserId);
if (!targetIsProfile) {
forwardingActivityInfo.showUserIcon = targetUserId;
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 798217f..04bd135 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -49,6 +49,15 @@
//flag to decide if intent needs to be resolved cross profile if pkgName is already defined
public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008;
+ /*
+ This flag, denotes if further cross profile resolution is allowed, e.g. if profile#0 is linked
+ to profile#1 and profile#2 . When intent resolution from profile#1 is started we resolve it in
+ profile#1 and profile#0. The profile#0 is also linked to profile#2, we will only resolve in
+ profile#2 if CrossProfileIntentFilter between profile#1 and profile#0 have set flag
+ FLAG_ALLOW_CHAINED_RESOLUTION.
+ */
+ public static final int FLAG_ALLOW_CHAINED_RESOLUTION = 0x00000010;
+
private static final String TAG = "CrossProfileIntentFilter";
/**
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index 5ae4cab..4362956 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -36,14 +36,19 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.server.LocalServices;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationUtils;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Queue;
+import java.util.Set;
import java.util.function.Function;
/**
@@ -115,73 +120,111 @@
Intent intent, String resolvedType, int userId, long flags, String pkgName,
boolean hasNonNegativePriorityResult,
Function<String, PackageStateInternal> pkgSettingFunction) {
-
+ Queue<Integer> pendingUsers = new ArrayDeque<>();
+ Set<Integer> visitedUserIds = new HashSet<>();
+ SparseBooleanArray hasNonNegativePriorityResultFromParent = new SparseBooleanArray();
+ visitedUserIds.add(userId);
+ pendingUsers.add(userId);
+ hasNonNegativePriorityResultFromParent.put(userId, hasNonNegativePriorityResult);
+ UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
+ while (!pendingUsers.isEmpty()) {
+ int currentUserId = pendingUsers.poll();
+ List<CrossProfileIntentFilter> matchingFilters =
+ computer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
+ currentUserId);
- List<CrossProfileIntentFilter> matchingFilters =
- computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
-
- if (matchingFilters == null || matchingFilters.isEmpty()) {
- /** if intent is web intent, checking if parent profile should handle the intent even
- if there is no matching filter. The configuration is based on user profile
- restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/
- if (intent.hasWebURI()) {
- UserInfo parent = computer.getProfileParent(userId);
- if (parent != null) {
- CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer
- .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags, userId,
- parent.id);
- if (generalizedCrossProfileDomainInfo != null) {
- crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo);
+ if (matchingFilters == null || matchingFilters.isEmpty()) {
+ /** if intent is web intent, checking if parent profile should handle the intent
+ * even if there is no matching filter. The configuration is based on user profile
+ * restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/
+ if (currentUserId == userId && intent.hasWebURI()) {
+ UserInfo parent = computer.getProfileParent(currentUserId);
+ if (parent != null) {
+ CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer
+ .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags,
+ currentUserId, parent.id);
+ if (generalizedCrossProfileDomainInfo != null) {
+ crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo);
+ }
}
}
+ continue;
}
- return crossProfileDomainInfos;
- }
- UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
- UserInfo sourceUserInfo = umInternal.getUserInfo(userId);
+ UserInfo sourceUserInfo = umInternal.getUserInfo(currentUserId);
- // Grouping the CrossProfileIntentFilters based on targerId
- SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
- new SparseArray<>();
+ // Grouping the CrossProfileIntentFilters based on targerId
+ SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
+ new SparseArray<>();
- for (int index = 0; index < matchingFilters.size(); index++) {
- CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index);
+ for (int index = 0; index < matchingFilters.size(); index++) {
+ CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index);
- if (!crossProfileIntentFiltersByUser
- .contains(crossProfileIntentFilter.mTargetUserId)) {
- crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId,
- new ArrayList<>());
+ if (!crossProfileIntentFiltersByUser
+ .contains(crossProfileIntentFilter.mTargetUserId)) {
+ crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId,
+ new ArrayList<>());
+ }
+ crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId)
+ .add(crossProfileIntentFilter);
}
- crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId)
- .add(crossProfileIntentFilter);
- }
- /*
- For each target user, we would call their corresponding strategy
- {@link CrossProfileResolver} to resolve intent in corresponding user
- */
- for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) {
+ /*
+ For each target user, we would call their corresponding strategy
+ {@link CrossProfileResolver} to resolve intent in corresponding user
+ */
+ for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) {
- UserInfo targetUserInfo = umInternal.getUserInfo(crossProfileIntentFiltersByUser
- .keyAt(index));
+ int targetUserId = crossProfileIntentFiltersByUser.keyAt(index);
- // Choosing strategy based on source and target user
- CrossProfileResolver crossProfileResolver =
- chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo);
+ //if user is already visited then skip resolution for particular user.
+ if (visitedUserIds.contains(targetUserId)) {
+ continue;
+ }
+
+ UserInfo targetUserInfo = umInternal.getUserInfo(targetUserId);
+
+ // Choosing strategy based on source and target user
+ CrossProfileResolver crossProfileResolver =
+ chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo);
/*
If {@link CrossProfileResolver} is available for source,target pair we will call it to
get {@link CrossProfileDomainInfo}s from that user.
*/
- if (crossProfileResolver != null) {
- List<CrossProfileDomainInfo> crossProfileInfos = crossProfileResolver
- .resolveIntent(computer, intent, resolvedType, userId,
- crossProfileIntentFiltersByUser.keyAt(index), flags, pkgName,
- crossProfileIntentFiltersByUser.valueAt(index),
- hasNonNegativePriorityResult, pkgSettingFunction);
- crossProfileDomainInfos.addAll(crossProfileInfos);
+ if (crossProfileResolver != null) {
+ List<CrossProfileDomainInfo> crossProfileInfos = crossProfileResolver
+ .resolveIntent(computer, intent, resolvedType, currentUserId,
+ targetUserId, flags, pkgName,
+ crossProfileIntentFiltersByUser.valueAt(index),
+ hasNonNegativePriorityResultFromParent.get(currentUserId),
+ pkgSettingFunction);
+ crossProfileDomainInfos.addAll(crossProfileInfos);
+
+ hasNonNegativePriorityResultFromParent.put(targetUserId,
+ hasNonNegativePriority(crossProfileInfos));
+
+ /*
+ Adding target user to queue if flag
+ {@link CrossProfileIntentFilter#FLAG_ALLOW_CHAINED_RESOLUTION} is set for any
+ {@link CrossProfileIntentFilter}
+ */
+ boolean allowChainedResolution = false;
+ for (int filterIndex = 0; filterIndex < crossProfileIntentFiltersByUser
+ .valueAt(index).size(); filterIndex++) {
+ if ((CrossProfileIntentFilter
+ .FLAG_ALLOW_CHAINED_RESOLUTION & crossProfileIntentFiltersByUser
+ .valueAt(index).get(filterIndex).mFlags) != 0) {
+ allowChainedResolution = true;
+ break;
+ }
+ }
+ if (allowChainedResolution) {
+ pendingUsers.add(targetUserId);
+ }
+ visitedUserIds.add(targetUserId);
+ }
}
}
@@ -237,7 +280,7 @@
/**
* Returns true if we source user can reach target user for given intent. The source can
- * directly or indirectly reach to target. This will perform depth first search to check if
+ * directly or indirectly reach to target. This will perform breadth first search to check if
* source can reach target.
* @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
* @param intent request
@@ -251,13 +294,38 @@
@UserIdInt int targetUserId) {
if (sourceUserId == targetUserId) return true;
- List<CrossProfileIntentFilter> matches =
- computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
- if (matches != null) {
- for (int index = 0; index < matches.size(); index++) {
- CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index);
- if (crossProfileIntentFilter.mTargetUserId == targetUserId) {
- return true;
+ Queue<Integer> pendingUsers = new ArrayDeque<>();
+ Set<Integer> visitedUserIds = new HashSet<>();
+ visitedUserIds.add(sourceUserId);
+ pendingUsers.add(sourceUserId);
+
+ while (!pendingUsers.isEmpty()) {
+ int currentUserId = pendingUsers.poll();
+
+ List<CrossProfileIntentFilter> matches =
+ computer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
+ currentUserId);
+ if (matches != null) {
+ for (int index = 0; index < matches.size(); index++) {
+ CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index);
+ if (crossProfileIntentFilter.mTargetUserId == targetUserId) {
+ return true;
+ }
+ if (visitedUserIds.contains(crossProfileIntentFilter.mTargetUserId)) {
+ continue;
+ }
+
+ /*
+ If source cannot directly reach to target, we will add
+ CrossProfileIntentFilter.mTargetUserId user to queue to check if target user
+ can be reached via CrossProfileIntentFilter.mTargetUserId i.e. it can be
+ indirectly reached through chained/linked profiles.
+ */
+ if ((CrossProfileIntentFilter.FLAG_ALLOW_CHAINED_RESOLUTION
+ & crossProfileIntentFilter.mFlags) != 0) {
+ pendingUsers.add(crossProfileIntentFilter.mTargetUserId);
+ visitedUserIds.add(crossProfileIntentFilter.mTargetUserId);
+ }
}
}
}
@@ -605,4 +673,14 @@
return resolveInfoList;
}
+
+ /**
+ * @param crossProfileDomainInfos list of cross profile domain info in descending priority order
+ * @return if the list contains a resolve info with non-negative priority
+ */
+ private boolean hasNonNegativePriority(List<CrossProfileDomainInfo> crossProfileDomainInfos) {
+ return crossProfileDomainInfos.size() > 0
+ && crossProfileDomainInfos.get(0).mResolveInfo != null
+ && crossProfileDomainInfos.get(0).mResolveInfo.priority >= 0;
+ }
}
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index cac9323..ceaaefd 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -319,4 +319,135 @@
HOME,
MOBILE_NETWORK_SETTINGS);
}
+
+ /**
+ * Clone profile's DefaultCrossProfileIntentFilter
+ */
+
+ /*
+ Allowing media capture from clone to parent profile as clone profile would not have camera
+ */
+ private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_MEDIA_CAPTURE =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(MediaStore.ACTION_IMAGE_CAPTURE)
+ .addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
+ .addAction(MediaStore.ACTION_VIDEO_CAPTURE)
+ .addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
+ .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
+ .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
+ .addAction(MediaStore.INTENT_ACTION_VIDEO_CAMERA)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .build();
+
+ /*
+ Allowing send action from clone to parent profile to share content from clone apps to parent
+ apps
+ */
+ private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_SEND_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_SEND)
+ .addAction(Intent.ACTION_SEND_MULTIPLE)
+ .addAction(Intent.ACTION_SENDTO)
+ .addDataType("*/*")
+ .build();
+
+ /*
+ Allowing send action from parent to clone profile to share content from parent apps to clone
+ apps
+ */
+ private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_SEND_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_SEND)
+ .addAction(Intent.ACTION_SEND_MULTIPLE)
+ .addAction(Intent.ACTION_SENDTO)
+ .addDataType("*/*")
+ .build();
+
+ /*
+ Allowing view action from clone to parent profile to open any app-links or web links
+ */
+ private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_VIEW_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_VIEW)
+ .addDataScheme("https")
+ .addDataScheme("http")
+ .build();
+
+ /*
+ Allowing view action from parent to clone profile to open any app-links or web links
+ */
+ private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_VIEW_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_VIEW)
+ .addDataScheme("https")
+ .addDataScheme("http")
+ .build();
+
+ /*
+ Allowing pick,insert and edit action from clone to parent profile to open picker or contacts
+ insert/edit.
+ */
+ private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_PICK_INSERT_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_PICK)
+ .addAction(Intent.ACTION_GET_CONTENT)
+ .addAction(Intent.ACTION_EDIT)
+ .addAction(Intent.ACTION_INSERT)
+ .addAction(Intent.ACTION_INSERT_OR_EDIT)
+ .addDataType("*/*")
+ .build();
+
+ /*
+ Allowing pick,insert and edit action from parent to clone profile to open picker
+ */
+ private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_PICK_INSERT_ACTION =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+ // and FLAG_ALLOW_CHAINED_RESOLUTION set
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_PICK)
+ .addAction(Intent.ACTION_GET_CONTENT)
+ .addAction(Intent.ACTION_EDIT)
+ .addAction(Intent.ACTION_INSERT)
+ .addAction(Intent.ACTION_INSERT_OR_EDIT)
+ .addDataType("*/*")
+ .build();
+
+ public static List<DefaultCrossProfileIntentFilter> getDefaultCloneProfileFilters() {
+ return Arrays.asList(
+ PARENT_TO_CLONE_SEND_ACTION,
+ PARENT_TO_CLONE_VIEW_ACTION,
+ PARENT_TO_CLONE_PICK_INSERT_ACTION,
+ CLONE_TO_PARENT_MEDIA_CAPTURE,
+ CLONE_TO_PARENT_SEND_ACTION,
+ CLONE_TO_PARENT_VIEW_ACTION,
+ CLONE_TO_PARENT_PICK_INSERT_ACTION
+
+ );
+ }
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7dae4c6..70bd24c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -150,7 +150,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.InstallLocationUtils;
import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
@@ -875,7 +874,7 @@
}
}
- Map<String, ReconciledPackage> reconciledPackages;
+ List<ReconciledPackage> reconciledPackages;
synchronized (mPm.mLock) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
@@ -1883,11 +1882,11 @@
}
@GuardedBy("mPm.mLock")
- private void commitPackagesLocked(Map<String, ReconciledPackage> reconciledPackages,
+ private void commitPackagesLocked(List<ReconciledPackage> reconciledPackages,
@NonNull int[] allUsers) {
// TODO: remove any expected failures from this method; this should only be able to fail due
// to unavoidable errors (I/O, etc.)
- for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+ for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
final ParsedPackage parsedPackage = installRequest.getParsedPackage();
final String packageName = parsedPackage.getPackageName();
@@ -2205,9 +2204,9 @@
* locks on {@link com.android.server.pm.PackageManagerService.mLock}.
*/
@GuardedBy("mPm.mInstallLock")
- private void executePostCommitStepsLIF(Map<String, ReconciledPackage> reconciledPackages) {
+ private void executePostCommitStepsLIF(List<ReconciledPackage> reconciledPackages) {
final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
- for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+ for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
@@ -2338,45 +2337,6 @@
incrementalStorages);
}
- public int installLocationPolicy(PackageInfoLite pkgLite, int installFlags) {
- String packageName = pkgLite.packageName;
- int installLocation = pkgLite.installLocation;
- // reader
- synchronized (mPm.mLock) {
- // Currently installed package which the new package is attempting to replace or
- // null if no such package is installed.
- AndroidPackage installedPkg = mPm.mPackages.get(packageName);
-
- if (installedPkg != null) {
- if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
- // Check for updated system application.
- if (installedPkg.isSystem()) {
- return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
- } else {
- // If current upgrade specifies particular preference
- if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
- // Application explicitly specified internal.
- return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
- } else if (
- installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
- // App explicitly prefers external. Let policy decide
- } else {
- // Prefer previous location
- if (installedPkg.isExternalStorage()) {
- return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
- }
- return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
- }
- }
- } else {
- // Invalid install. Return error code
- return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS;
- }
- }
- }
- return pkgLite.recommendedInstallLocation;
- }
-
Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
long requiredInstalledVersionCode, int installFlags) {
if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
@@ -2659,11 +2619,29 @@
}
}
+ Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_UID, request.getUid());
+ if (update) {
+ extras.putBoolean(Intent.EXTRA_REPLACING, true);
+ }
+ extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+
+ // If a package is a static shared library, then only the installer of the package
+ // should get the broadcast.
+ if (installerPackageName != null
+ && request.getPkg().getStaticSharedLibraryName() != null) {
+ mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, 0 /*flags*/,
+ installerPackageName, null /*finishedReceiver*/,
+ request.getNewUsers(), null /* instantUserIds*/,
+ null /* broadcastAllowList */, null);
+ }
+
// Send installed broadcasts if the package is not a static shared lib.
if (request.getPkg().getStaticSharedLibraryName() == null) {
mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
- // Send added for users that see the package for the first time
+ // Send PACKAGE_ADDED broadcast for users that see the package for the first time
// sendPackageAddedForNewUsers also deals with system apps
int appId = UserHandle.getAppId(request.getUid());
boolean isSystem = request.getPkg().isSystem();
@@ -2671,13 +2649,9 @@
isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
firstUserIds, firstInstantUserIds, dataLoaderType);
- // Send added for users that don't see the package for the first time
- Bundle extras = new Bundle();
- extras.putInt(Intent.EXTRA_UID, request.getUid());
- if (update) {
- extras.putBoolean(Intent.EXTRA_REPLACING, true);
- }
- extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+ // Send PACKAGE_ADDED broadcast for users that don't see
+ // the package for the first time
+
// Send to all running apps.
final SparseArray<int[]> newBroadcastAllowList;
synchronized (mPm.mLock) {
@@ -2690,8 +2664,8 @@
extras, 0 /*flags*/,
null /*targetPackage*/, null /*finishedReceiver*/,
updateUserIds, instantUserIds, newBroadcastAllowList, null);
+ // Send to the installer, even if it's not running.
if (installerPackageName != null) {
- // Send to the installer, even if it's not running.
mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
extras, 0 /*flags*/,
installerPackageName, null /*finishedReceiver*/,
@@ -3645,7 +3619,7 @@
boolean appIdCreated = false;
try {
final String pkgName = scanResult.mPkgSetting.getPackageName();
- final Map<String, ReconciledPackage> reconcileResult =
+ final List<ReconciledPackage> reconcileResult =
ReconcilePackageUtils.reconcilePackages(
Collections.singletonList(installRequest),
mPm.mPackages, Collections.singletonMap(pkgName,
@@ -3657,7 +3631,7 @@
} else {
installRequest.setScannedPackageSettingAppId(Process.INVALID_UID);
}
- commitReconciledScanResultLocked(reconcileResult.get(pkgName),
+ commitReconciledScanResultLocked(reconcileResult.get(0),
mPm.mUserManager.getUserIds());
} catch (PackageManagerException e) {
if (appIdCreated) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 4443710..01a8bd0 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -59,8 +59,10 @@
@Nullable
private PackageRemovedInfo mRemovedInfo;
- private @PackageManagerService.ScanFlags int mScanFlags;
- private @ParsingPackageUtils.ParseFlags int mParseFlags;
+ @PackageManagerService.ScanFlags
+ private int mScanFlags;
+ @ParsingPackageUtils.ParseFlags
+ private int mParseFlags;
private boolean mReplace;
@Nullable /* The original Package if it is being replaced, otherwise {@code null} */
@@ -155,7 +157,7 @@
mParseFlags = parseFlags;
mScanFlags = scanFlags;
mScanResult = scanResult;
- mPackageMetrics = null; // No real logging from this code path
+ mPackageMetrics = null; // No logging from this code path
}
@Nullable
@@ -393,11 +395,13 @@
return mParsedPackage;
}
- public @ParsingPackageUtils.ParseFlags int getParseFlags() {
+ @ParsingPackageUtils.ParseFlags
+ public int getParseFlags() {
return mParseFlags;
}
- public @PackageManagerService.ScanFlags int getScanFlags() {
+ @PackageManagerService.ScanFlags
+ public int getScanFlags() {
return mScanFlags;
}
@@ -435,6 +439,11 @@
return mIsInstallForUsers;
}
+ public boolean isInstallFromAdb() {
+ return mInstallArgs != null
+ && (mInstallArgs.mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0;
+ }
+
@Nullable
public PackageSetting getOriginalPackageSetting() {
return mOriginalPs;
@@ -731,10 +740,10 @@
}
}
- public void onInstallCompleted() {
+ public void onInstallCompleted(Computer snapshot) {
if (getReturnCode() == INSTALL_SUCCEEDED) {
if (mPackageMetrics != null) {
- mPackageMetrics.onInstallSucceed();
+ mPackageMetrics.onInstallSucceed(snapshot);
}
}
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index d13822a..e4a0a3a 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -510,7 +510,7 @@
mInstallPackageHelper.installPackagesTraced(installRequests);
for (InstallRequest request : installRequests) {
- request.onInstallCompleted();
+ request.onInstallCompleted(mPm.snapshotComputer());
doPostInstall(request);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index b725325..0391163 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -16,16 +16,26 @@
package com.android.server.pm;
+import static android.os.Process.INVALID_UID;
+
import android.annotation.IntDef;
+import android.content.pm.parsing.ApkLiteParseUtils;
import android.os.UserManager;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.pm.pkg.PackageStateInternal;
+import java.io.File;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Stream;
/**
* Metrics class for reporting stats to logging infrastructures like Westworld
@@ -43,7 +53,8 @@
STEP_COMMIT,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface StepInt {}
+ public @interface StepInt {
+ }
private final long mInstallStartTimestampMillis;
private final SparseArray<InstallStep> mInstallSteps = new SparseArray<>();
@@ -56,16 +67,30 @@
mInstallRequest = installRequest;
}
- public void onInstallSucceed() {
+ public void onInstallSucceed(Computer snapshot) {
+ // TODO(b/239722919): report to SecurityLog if on work profile or managed device
+ reportInstallationStats(snapshot, true /* success */);
+ }
+
+ private void reportInstallationStats(Computer snapshot, boolean success) {
+ // TODO(b/249294752): do not log if adb
final long installDurationMillis =
System.currentTimeMillis() - mInstallStartTimestampMillis;
// write to stats
final Pair<int[], long[]> stepDurations = getInstallStepDurations();
final int[] newUsers = mInstallRequest.getNewUsers();
final int[] originalUsers = mInstallRequest.getOriginUsers();
+ final String packageName = mInstallRequest.getName();
+ final String installerPackageName = mInstallRequest.getInstallerPackageName();
+ final int installerUid = installerPackageName == null ? INVALID_UID
+ : snapshot.getPackageUid(installerPackageName, 0, 0);
+ final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+ final long versionCode = success ? 0 : ps.getVersionCode();
+ final long apksSize = getApksSize(ps.getPath());
+
FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
0 /* session_id */,
- null /* package_name */,
+ success ? null : packageName /* not report package_name on success */,
mInstallRequest.getUid() /* uid */,
newUsers /* user_ids */,
getUserTypes(newUsers) /* user_types */,
@@ -73,13 +98,13 @@
getUserTypes(originalUsers) /* original_user_types */,
mInstallRequest.getReturnCode() /* public_return_code */,
0 /* internal_error_code */,
- 0 /* apks_size_bytes */,
- 0 /* version_code */,
+ apksSize /* apks_size_bytes */,
+ versionCode /* version_code */,
stepDurations.first /* install_steps */,
stepDurations.second /* step_duration_millis */,
installDurationMillis /* total_duration_millis */,
mInstallRequest.getInstallFlags() /* install_flags */,
- -1 /* installer_package_uid */,
+ installerUid /* installer_package_uid */,
-1 /* original_installer_package_uid */,
mInstallRequest.getDataLoaderType() /* data_loader_type */,
0 /* user_action_required_type */,
@@ -93,6 +118,19 @@
);
}
+ private long getApksSize(File apkDir) {
+ // TODO(b/249294752): also count apk sizes for failed installs
+ final AtomicLong apksSize = new AtomicLong();
+ try (Stream<Path> walkStream = Files.walk(apkDir.toPath())) {
+ walkStream.filter(p -> p.toFile().isFile()
+ && ApkLiteParseUtils.isApkFile(p.toFile())).forEach(
+ f -> apksSize.addAndGet(f.toFile().length()));
+ } catch (IOException e) {
+ // ignore
+ }
+ return apksSize.get();
+ }
+
public void onStepStarted(@StepInt int step) {
mInstallSteps.put(step, new InstallStep());
}
@@ -140,12 +178,15 @@
private static class InstallStep {
private final long mStartTimestampMillis;
private long mDurationMillis = -1;
+
InstallStep() {
mStartTimestampMillis = System.currentTimeMillis();
}
+
void finish() {
mDurationMillis = System.currentTimeMillis() - mStartTimestampMillis;
}
+
long getDurationMillis() {
return mDurationMillis;
}
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 3c863d0..4cac115 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -111,12 +111,6 @@
}
private void sendPackageRemovedBroadcastInternal(boolean killApp, boolean removedBySystem) {
- // Don't send static shared library removal broadcasts as these
- // libs are visible only the apps that depend on them an one
- // cannot remove the library if it has a dependency.
- if (mIsStaticSharedLib) {
- return;
- }
Bundle extras = new Bundle();
final int removedUid = mRemovedAppId >= 0 ? mRemovedAppId : mUid;
extras.putInt(Intent.EXTRA_UID, removedUid);
@@ -128,15 +122,22 @@
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers);
+
+ // Send PACKAGE_REMOVED broadcast to the respective installer.
+ if (mRemovedPackage != null && mInstallerPackageName != null) {
+ mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
+ mRemovedPackage, extras, 0 /*flags*/,
+ mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
+ }
+ if (mIsStaticSharedLib) {
+ // When uninstalling static shared libraries, only the package's installer needs to be
+ // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients.
+ return;
+ }
if (mRemovedPackage != null) {
mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
mRemovedPackage, extras, 0, null /*targetPackage*/, null,
mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
- if (mInstallerPackageName != null) {
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
- mRemovedPackage, extras, 0 /*flags*/,
- mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
- }
mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
mRemovedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME,
null /*finishedReceiver*/, mBroadcastUsers, mInstantUserIds,
diff --git a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
index 3b306a8..b310c62a 100644
--- a/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
+++ b/services/core/java/com/android/server/pm/PerPackageReadTimeouts.java
@@ -16,7 +16,7 @@
package com.android.server.pm;
-import android.annotation.NonNull;;
+import android.annotation.NonNull;
import android.text.TextUtils;
import com.android.internal.util.HexDump;
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 01d17f6..99bcbc9 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -35,6 +35,7 @@
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.utils.WatchedLongSparseArray;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -48,14 +49,14 @@
* as install) led to the request.
*/
final class ReconcilePackageUtils {
- public static Map<String, ReconciledPackage> reconcilePackages(
+ public static List<ReconciledPackage> reconcilePackages(
List<InstallRequest> installRequests,
Map<String, AndroidPackage> allPackages,
Map<String, Settings.VersionInfo> versionInfos,
SharedLibrariesImpl sharedLibraries,
KeySetManagerService ksms, Settings settings)
throws ReconcileFailure {
- final Map<String, ReconciledPackage> result = new ArrayMap<>(installRequests.size());
+ final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
// make a copy of the existing set of packages so we can combine them with incoming packages
final ArrayMap<String, AndroidPackage> combinedPackages =
@@ -88,7 +89,6 @@
}
-
final DeletePackageAction deletePackageAction;
// we only want to try to delete for non system apps
if (installRequest.isInstallReplace() && !installRequest.isInstallSystem()) {
@@ -257,13 +257,11 @@
}
}
- result.put(installPackageName,
+ final ReconciledPackage reconciledPackage =
new ReconciledPackage(installRequests, allPackages, installRequest,
deletePackageAction, allowedSharedLibInfos, signingDetails,
- sharedUserSignaturesChanged, removeAppKeySetData));
- }
+ sharedUserSignaturesChanged, removeAppKeySetData);
- for (InstallRequest installRequest : installRequests) {
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
@@ -271,24 +269,21 @@
// library paths after the scan is done. Also during the initial
// scan don't update any libs as we do this wholesale after all
// apps are scanned to avoid dependency based scanning.
- if ((installRequest.getScanFlags() & SCAN_BOOTING) != 0
- || (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
- != 0) {
- continue;
+ if ((installRequest.getScanFlags() & SCAN_BOOTING) == 0
+ && (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+ == 0) {
+ try {
+ reconciledPackage.mCollectedSharedLibraryInfos =
+ sharedLibraries.collectSharedLibraryInfos(
+ installRequest.getParsedPackage(), combinedPackages,
+ incomingSharedLibraries);
+ } catch (PackageManagerException e) {
+ throw new ReconcileFailure(e.error, e.getMessage());
+ }
}
- final String installPackageName = installRequest.getParsedPackage().getPackageName();
- try {
- result.get(installPackageName).mCollectedSharedLibraryInfos =
- sharedLibraries.collectSharedLibraryInfos(
- installRequest.getParsedPackage(), combinedPackages,
- incomingSharedLibraries);
- } catch (PackageManagerException e) {
- throw new ReconcileFailure(e.error, e.getMessage());
- }
- }
- for (InstallRequest installRequest : installRequests) {
installRequest.onReconcileFinished();
+ result.add(reconciledPackage);
}
return result;
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 9155830..9dafcce 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -25,6 +25,7 @@
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.UserManager;
+import android.util.DebugUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -46,6 +47,18 @@
public @interface OwnerType {
}
+ public static final int USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE = 1;
+ public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2;
+ public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1;
+
+ private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT";
+ @IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = {
+ USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE,
+ USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE,
+ USER_ASSIGNMENT_RESULT_FAILURE
+ })
+ public @interface UserAssignmentResult {}
+
public interface UserRestrictionsListener {
/**
* Called when a user restriction changes.
@@ -343,34 +356,28 @@
public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
/**
- * Assigns a user to a display.
- *
- * <p>On most devices this call will be a no-op, but it will be used on devices that support
- * multiple users on multiple displays (like automotives with passenger displays).
+ * Assigns a user to a display when it's starting, returning whether the assignment succeeded
+ * and the user is {@link UserManager#isUserVisible() visible}.
*
* <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
- * is started)
+ * is started). If other clients (like {@code CarService} need to explicitly change the user /
+ * display assignment, we'll need to provide other APIs.
*
* <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
- * check it. In fact, one of the intended clients for this method is
- * {@code DisplayManagerService}, which will call it when a virtual display is created (another
- * client is {@code UserController}, which will call it when a user is started).
+ * pass a valid display id.
*/
- // TODO(b/244644281): rename to assignUserToDisplayOnStart() and make sure it's called on boot
- // as well
- public abstract void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
+ public abstract @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
+ @UserIdInt int profileGroupId,
boolean foreground, int displayId);
/**
- * Unassigns a user from its current display.
- *
- * <p>On most devices this call will be a no-op, but it will be used on devices that support
- * multiple users on multiple displays (like automotives with passenger displays).
+ * Unassigns a user from its current display when it's stopping.
*
* <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
- * is stopped).
+ * is stopped). If other clients (like {@code CarService} need to explicitly change the user /
+ * display assignment, we'll need to provide other APIs.
*/
- public abstract void unassignUserFromDisplay(@UserIdInt int userId);
+ public abstract void unassignUserFromDisplayOnStop(@UserIdInt int userId);
/**
* Returns {@code true} if the user is visible (as defined by
@@ -413,6 +420,15 @@
*/
public abstract @UserIdInt int getUserAssignedToDisplay(int displayId);
+ /**
+ * Gets the user-friendly representation of the {@code result} of a
+ * {@link #assignUserToDisplayOnStart(int, int, boolean, int)} call.
+ */
+ public static String userAssignmentResultToString(@UserAssignmentResult int result) {
+ return DebugUtils.constantToString(UserManagerInternal.class, PREFIX_USER_ASSIGNMENT_RESULT,
+ result);
+ }
+
/** Adds a {@link UserVisibilityListener}. */
public abstract void addUserVisibilityListener(UserVisibilityListener listener);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d255669..4a4a231 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
@@ -264,7 +265,7 @@
@VisibleForTesting
static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
- private static final int USER_VERSION = 10;
+ private static final int USER_VERSION = 11;
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
@@ -2814,7 +2815,8 @@
synchronized (mUsersLock) {
count = getAliveUsersExcludingGuestsCountLU();
}
- return count >= UserManager.getMaxSupportedUsers();
+ return count >= UserManager.getMaxSupportedUsers()
+ && !isCreationOverrideEnabled();
}
/**
@@ -2824,15 +2826,16 @@
* <p>For checking whether more profiles can be added to a particular parent use
* {@link #canAddMoreProfilesToUser}.
*/
- private boolean canAddMoreUsersOfType(UserTypeDetails userTypeDetails) {
- if (!userTypeDetails.isEnabled()) {
+ private boolean canAddMoreUsersOfType(@NonNull UserTypeDetails userTypeDetails) {
+ if (!isUserTypeEnabled(userTypeDetails)) {
return false;
}
final int max = userTypeDetails.getMaxAllowed();
if (max == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
return true; // Indicates that there is no max.
}
- return getNumberOfUsersOfType(userTypeDetails.getName()) < max;
+ return getNumberOfUsersOfType(userTypeDetails.getName()) < max
+ || isCreationOverrideEnabled();
}
/**
@@ -2843,7 +2846,7 @@
public int getRemainingCreatableUserCount(String userType) {
checkQueryOrCreateUsersPermission("get the remaining number of users that can be added.");
final UserTypeDetails type = mUserTypes.get(userType);
- if (type == null || !type.isEnabled()) {
+ if (type == null || !isUserTypeEnabled(type)) {
return 0;
}
synchronized (mUsersLock) {
@@ -2917,7 +2920,21 @@
public boolean isUserTypeEnabled(String userType) {
checkCreateUsersPermission("check if user type is enabled.");
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
- return userTypeDetails != null && userTypeDetails.isEnabled();
+ return userTypeDetails != null && isUserTypeEnabled(userTypeDetails);
+ }
+
+ /** Returns whether the creation of users of the given user type is enabled on this device. */
+ private boolean isUserTypeEnabled(@NonNull UserTypeDetails userTypeDetails) {
+ return userTypeDetails.isEnabled() || isCreationOverrideEnabled();
+ }
+
+ /**
+ * Returns whether to almost-always allow creating users even beyond their limit or if disabled.
+ * For Debug builds only.
+ */
+ private boolean isCreationOverrideEnabled() {
+ return Build.isDebuggable()
+ && SystemProperties.getBoolean(DEV_CREATE_OVERRIDE_PROPERTY, false);
}
@Override
@@ -2930,7 +2947,8 @@
@Override
public boolean canAddMoreProfilesToUser(String userType, @UserIdInt int userId,
boolean allowedToRemoveOne) {
- return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne);
+ return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne)
+ || isCreationOverrideEnabled();
}
@Override
@@ -2948,7 +2966,7 @@
checkQueryOrCreateUsersPermission(
"get the remaining number of profiles that can be added to the given user.");
final UserTypeDetails type = mUserTypes.get(userType);
- if (type == null || !type.isEnabled()) {
+ if (type == null || !isUserTypeEnabled(type)) {
return 0;
}
// Managed profiles have their own specific rules.
@@ -3337,6 +3355,7 @@
final int oldFlags = systemUserData.info.flags;
final int newFlags;
final String newUserType;
+ // TODO(b/256624031): Also handle FLAG_MAIN
if (newHeadlessSystemUserMode) {
newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
newFlags = oldFlags & ~UserInfo.FLAG_FULL;
@@ -3629,6 +3648,22 @@
userVersion = 10;
}
+ if (userVersion < 11) {
+ // Add FLAG_MAIN
+ if (isHeadlessSystemUserMode()) {
+ final UserInfo earliestCreatedUser = getEarliestCreatedFullUser();
+ earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
+ userIdsToWrite.add(earliestCreatedUser.id);
+ } else {
+ synchronized (mUsersLock) {
+ final UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
+ userData.info.flags |= UserInfo.FLAG_MAIN;
+ userIdsToWrite.add(userData.info.id);
+ }
+ }
+ userVersion = 11;
+ }
+
// Reminder: If you add another upgrade, make sure to increment USER_VERSION too.
// Done with userVersion changes, moving on to deal with userTypeVersion upgrades
@@ -3758,6 +3793,21 @@
userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType);
}
+ private UserInfo getEarliestCreatedFullUser() {
+ final List<UserInfo> users = getUsersInternal(true, true, true);
+ UserInfo earliestUser = users.get(0);
+ long earliestCreationTime = earliestUser.creationTime;
+ for (int i = 0; i < users.size(); i++) {
+ final UserInfo info = users.get(i);
+ if (info.isFull() && info.isAdmin() && info.creationTime > 0
+ && info.creationTime < earliestCreationTime) {
+ earliestCreationTime = info.creationTime;
+ earliestUser = info;
+ }
+ }
+ return earliestUser;
+ }
+
@GuardedBy({"mPackagesLock"})
private void fallbackToSingleUserLP() {
int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
@@ -4407,7 +4457,7 @@
+ ") indicated SYSTEM user, which cannot be created.");
return null;
}
- if (!userTypeDetails.isEnabled()) {
+ if (!isUserTypeEnabled(userTypeDetails)) {
throwCheckedUserOperationException(
"Cannot add a user of disabled type " + userType + ".",
UserManager.USER_OPERATION_ERROR_MAX_USERS);
@@ -4479,27 +4529,12 @@
+ " for user " + parentId,
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
- // In legacy mode, restricted profile's parent can only be the owner user
- if (isRestricted && !UserManager.isSplitSystemUser()
- && (parentId != UserHandle.USER_SYSTEM)) {
+ if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
+ && !isCreationOverrideEnabled()) {
throwCheckedUserOperationException(
- "Cannot add restricted profile - parent user must be owner",
+ "Cannot add restricted profile - parent user must be system",
UserManager.USER_OPERATION_ERROR_UNKNOWN);
}
- if (isRestricted && UserManager.isSplitSystemUser()) {
- if (parent == null) {
- throwCheckedUserOperationException(
- "Cannot add restricted profile - parent user must be specified",
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
- }
- if (!parent.info.canHaveProfile()) {
- throwCheckedUserOperationException(
- "Cannot add restricted profile - profiles cannot be created for "
- + "the specified parent user id "
- + parentId,
- UserManager.USER_OPERATION_ERROR_UNKNOWN);
- }
- }
userId = getNextAvailableId();
Slog.i(LOG_TAG, "Creating user " + userId + " of type " + userType);
@@ -6799,15 +6834,14 @@
}
@Override
- public void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
+ public int assignUserToDisplayOnStart(@UserIdInt int userId, @UserIdInt int profileGroupId,
boolean foreground, int displayId) {
- mUserVisibilityMediator.startUser(userId, profileGroupId, foreground, displayId);
- mUserVisibilityMediator.assignUserToDisplay(userId, profileGroupId, displayId);
+ return mUserVisibilityMediator.startUser(userId, profileGroupId, foreground,
+ displayId);
}
@Override
- public void unassignUserFromDisplay(@UserIdInt int userId) {
- mUserVisibilityMediator.unassignUserFromDisplay(userId);
+ public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
mUserVisibilityMediator.stopUser(userId);
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index dbd026e..27d74d5 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -148,7 +148,8 @@
UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
UserManager.DISALLOW_WIFI_DIRECT,
UserManager.DISALLOW_ADD_WIFI_CONFIG,
- UserManager.DISALLOW_CELLULAR_2G
+ UserManager.DISALLOW_CELLULAR_2G,
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
});
public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -197,7 +198,8 @@
UserManager.DISALLOW_WIFI_TETHERING,
UserManager.DISALLOW_WIFI_DIRECT,
UserManager.DISALLOW_ADD_WIFI_CONFIG,
- UserManager.DISALLOW_CELLULAR_2G
+ UserManager.DISALLOW_CELLULAR_2G,
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
);
/**
@@ -237,7 +239,8 @@
UserManager.DISALLOW_WIFI_TETHERING,
UserManager.DISALLOW_WIFI_DIRECT,
UserManager.DISALLOW_ADD_WIFI_CONFIG,
- UserManager.DISALLOW_CELLULAR_2G
+ UserManager.DISALLOW_CELLULAR_2G,
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO
);
/**
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index c35fe17..8fb5773 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -126,11 +126,13 @@
.setCrossProfileIntentFilterAccessControl(
CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
.setIsCredentialSharableWithParent(true)
+ .setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT)
- .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
+ .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
+ .setUseParentsContacts(true));
}
/**
@@ -260,7 +262,8 @@
private static UserTypeDetails.Builder getDefaultTypeFullSystem() {
return new UserTypeDetails.Builder()
.setName(USER_TYPE_FULL_SYSTEM)
- .setBaseType(FLAG_SYSTEM | FLAG_FULL);
+ .setBaseType(FLAG_SYSTEM | FLAG_FULL)
+ .setDefaultUserInfoPropertyFlags(UserInfo.FLAG_MAIN);
}
/**
@@ -310,6 +313,10 @@
return DefaultCrossProfileIntentFiltersUtils.getDefaultManagedProfileFilters();
}
+ private static List<DefaultCrossProfileIntentFilter> getDefaultCloneCrossProfileIntentFilter() {
+ return DefaultCrossProfileIntentFiltersUtils.getDefaultCloneProfileFilters();
+ }
+
/**
* Reads the given xml parser to obtain device user-type customization, and updates the given
* map of {@link UserTypeDetails.Builder}s accordingly.
@@ -391,6 +398,7 @@
}
setIntAttribute(parser, "enabled", builder::setEnabled);
+ setIntAttribute(parser, "max-allowed", builder::setMaxAllowed);
// Process child elements.
final int depth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index bd81062..cbf7dfe 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -20,12 +20,15 @@
import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
-import android.annotation.IntDef;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.UserHandle;
import android.os.UserManager;
-import android.util.DebugUtils;
import android.util.Dumpable;
import android.util.IndentingPrintWriter;
import android.util.SparseIntArray;
@@ -34,6 +37,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.utils.Slogf;
import java.io.PrintWriter;
@@ -52,23 +56,10 @@
private static final String TAG = UserVisibilityMediator.class.getSimpleName();
- private static final String PREFIX_START_USER_RESULT = "START_USER_";
-
// TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
@VisibleForTesting
static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
- public static final int START_USER_RESULT_SUCCESS_VISIBLE = 1;
- public static final int START_USER_RESULT_SUCCESS_INVISIBLE = 2;
- public static final int START_USER_RESULT_FAILURE = -1;
-
- @IntDef(flag = false, prefix = {PREFIX_START_USER_RESULT}, value = {
- START_USER_RESULT_SUCCESS_VISIBLE,
- START_USER_RESULT_SUCCESS_INVISIBLE,
- START_USER_RESULT_FAILURE
- })
- public @interface StartUserResult {}
-
private final Object mLock = new Object();
private final boolean mUsersOnSecondaryDisplaysEnabled;
@@ -97,10 +88,37 @@
}
/**
- * TODO(b/244644281): merge with assignUserToDisplay() or add javadoc.
+ * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, boolean, int)}.
*/
- public @StartUserResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId,
+ public @UserAssignmentResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId,
boolean foreground, int displayId) {
+ // TODO(b/244644281): this method need to perform 4 actions:
+ //
+ // 1. Check if the user can be started given the provided arguments
+ // 2. If it can, decide whether it's visible or not (which is the return value)
+ // 3. Update the current user / profiles state
+ // 4. Update the users on secondary display state (if applicable)
+ //
+ // Ideally, they should be done "atomically" (i.e, only changing state while holding the
+ // mLock), but the initial implementation is just calling the existing methods, as the
+ // focus is to change the UserController startUser() workflow (so it relies on this class
+ // for the logic above).
+ //
+ // The next CL will refactor it (and the unit tests) to achieve that atomicity.
+ int result = startOnly(userId, profileGroupId, foreground, displayId);
+ if (result != USER_ASSIGNMENT_RESULT_FAILURE) {
+ assignUserToDisplay(userId, profileGroupId, displayId);
+ }
+ return result;
+ }
+
+ /**
+ * @deprecated - see comment inside {@link #startUser(int, int, boolean, int)}
+ */
+ @Deprecated
+ @VisibleForTesting
+ @UserAssignmentResult int startOnly(@UserIdInt int userId,
+ @UserIdInt int profileGroupId, boolean foreground, int displayId) {
int actualProfileGroupId = profileGroupId == NO_PROFILE_GROUP_ID
? userId
: profileGroupId;
@@ -111,7 +129,7 @@
if (foreground && displayId != DEFAULT_DISPLAY) {
Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start foreground user on "
+ "secondary display", userId, actualProfileGroupId, foreground, displayId);
- return START_USER_RESULT_FAILURE;
+ return USER_ASSIGNMENT_RESULT_FAILURE;
}
int visibility;
@@ -121,13 +139,12 @@
Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user on "
+ "secondary display", userId, actualProfileGroupId, foreground,
displayId);
- return START_USER_RESULT_FAILURE;
+ return USER_ASSIGNMENT_RESULT_FAILURE;
}
if (foreground) {
Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
- + "foreground", userId, actualProfileGroupId, foreground,
- displayId);
- return START_USER_RESULT_FAILURE;
+ + "foreground", userId, actualProfileGroupId, foreground, displayId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
} else {
boolean isParentRunning = mStartedProfileGroupIds
.get(actualProfileGroupId) == actualProfileGroupId;
@@ -135,18 +152,18 @@
Slogf.d(TAG, "profile parent running: %b", isParentRunning);
}
visibility = isParentRunning
- ? START_USER_RESULT_SUCCESS_VISIBLE
- : START_USER_RESULT_SUCCESS_INVISIBLE;
+ ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
+ : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
}
} else if (foreground) {
mCurrentUserId = userId;
- visibility = START_USER_RESULT_SUCCESS_VISIBLE;
+ visibility = USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
} else {
- visibility = START_USER_RESULT_SUCCESS_INVISIBLE;
+ visibility = USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
}
if (DBG) {
Slogf.d(TAG, "adding user / profile mapping (%d -> %d) and returning %s",
- userId, actualProfileGroupId, startUserResultToString(visibility));
+ userId, actualProfileGroupId, userAssignmentResultToString(visibility));
}
mStartedProfileGroupIds.put(userId, actualProfileGroupId);
}
@@ -154,21 +171,11 @@
}
/**
- * TODO(b/244644281): merge with unassignUserFromDisplay() or add javadoc (and unit tests)
+ * @deprecated - see comment inside {@link #startUser(int, int, boolean, int)}
*/
- public void stopUser(@UserIdInt int userId) {
- if (DBG) {
- Slogf.d(TAG, "stopUser(%d)", userId);
- }
- synchronized (mLock) {
- mStartedProfileGroupIds.delete(userId);
- }
- }
-
- /**
- * See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
- */
- public void assignUserToDisplay(int userId, int profileGroupId, int displayId) {
+ @Deprecated
+ @VisibleForTesting
+ void assignUserToDisplay(int userId, int profileGroupId, int displayId) {
if (DBG) {
Slogf.d(TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplaysEnabled=%b",
userId, displayId, mUsersOnSecondaryDisplaysEnabled);
@@ -246,23 +253,26 @@
}
/**
- * See {@link UserManagerInternal#unassignUserFromDisplay(int)}.
+ * See {@link UserManagerInternal#unassignUserFromDisplayOnStop(int)}.
*/
- public void unassignUserFromDisplay(int userId) {
+ public void stopUser(int userId) {
if (DBG) {
- Slogf.d(TAG, "unassignUserFromDisplay(%d)", userId);
+ Slogf.d(TAG, "stopUser(%d)", userId);
}
- if (!mUsersOnSecondaryDisplaysEnabled) {
- // Don't need to do anything because methods (such as isUserVisible()) already know
- // that the current user (and their profiles) is assigned to the default display.
- if (DBG) {
- Slogf.d(TAG, "ignoring when device doesn't support MUMD");
- }
- return;
- }
-
synchronized (mLock) {
if (DBG) {
+ Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
+ mStartedProfileGroupIds);
+ }
+ mStartedProfileGroupIds.delete(userId);
+
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ // Don't need to do update mUsersOnSecondaryDisplays because methods (such as
+ // isUserVisible()) already know that the current user (and their profiles) is
+ // assigned to the default display.
+ return;
+ }
+ if (DBG) {
Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
mUsersOnSecondaryDisplays);
}
@@ -395,33 +405,40 @@
ipw.print("Current user id: ");
ipw.println(mCurrentUserId);
- ipw.print("Number of started user / profile group mappings: ");
- ipw.println(mStartedProfileGroupIds.size());
- if (mStartedProfileGroupIds.size() > 0) {
- ipw.increaseIndent();
- for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
- ipw.print("User #");
- ipw.print(mStartedProfileGroupIds.keyAt(i));
- ipw.print(" -> profile #");
- ipw.println(mStartedProfileGroupIds.valueAt(i));
- }
- ipw.decreaseIndent();
- }
+ dumpIntArray(ipw, mStartedProfileGroupIds, "started user / profile group", "u", "pg");
- ipw.print("Supports users on secondary displays: ");
+ ipw.print("Supports background users on secondary displays: ");
ipw.println(mUsersOnSecondaryDisplaysEnabled);
if (mUsersOnSecondaryDisplaysEnabled) {
- ipw.print("Users on secondary displays: ");
- synchronized (mLock) {
- ipw.println(mUsersOnSecondaryDisplays);
- }
+ dumpIntArray(ipw, mUsersOnSecondaryDisplays, "background user / secondary display",
+ "u", "d");
}
}
ipw.decreaseIndent();
}
+ private static void dumpIntArray(IndentingPrintWriter ipw, SparseIntArray array,
+ String arrayDescription, String keyName, String valueName) {
+ ipw.print("Number of ");
+ ipw.print(arrayDescription);
+ ipw.print(" mappings: ");
+ ipw.println(array.size());
+ if (array.size() <= 0) {
+ return;
+ }
+ ipw.increaseIndent();
+ for (int i = 0; i < array.size(); i++) {
+ ipw.print(keyName); ipw.print(':');
+ ipw.print(array.keyAt(i));
+ ipw.print(" -> ");
+ ipw.print(valueName); ipw.print(':');
+ ipw.println(array.valueAt(i));
+ }
+ ipw.decreaseIndent();
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
if (pw instanceof IndentingPrintWriter) {
@@ -445,14 +462,6 @@
return map;
}
- /**
- * Gets the user-friendly representation of the {@code result}.
- */
- public static String startUserResultToString(@StartUserResult int result) {
- return DebugUtils.constantToString(UserVisibilityMediator.class, PREFIX_START_USER_RESULT,
- result);
- }
-
// TODO(b/244644281): methods below are needed because some APIs use the current users (full and
// profiles) state to decide whether a user is visible or not. If we decide to always store that
// info into intermediate maps, we should remove them.
@@ -483,6 +492,14 @@
}
@VisibleForTesting
+ boolean isStartedUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return mStartedProfileGroupIds.get(userId,
+ INITIAL_CURRENT_USER_ID) != INITIAL_CURRENT_USER_ID;
+ }
+ }
+
+ @VisibleForTesting
boolean isStartedProfile(@UserIdInt int userId) {
int profileGroupId;
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index 5ba209d..9bca155 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -295,6 +295,7 @@
* NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
* for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
*/
+ @SuppressWarnings("ReturnValueIgnored")
/*package*/ static String encodeClassLoader(String classpath, String classLoaderName) {
classpath.getClass(); // Throw NPE if classpath is null
String classLoaderDexoptEncoding = classLoaderName;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 8588267..83e17a5 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -86,6 +86,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -165,6 +166,11 @@
COARSE_BACKGROUND_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
}
+ private static final Set<String> FINE_LOCATION_PERMISSIONS = new ArraySet<>();
+ static {
+ FINE_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+ }
+
private static final Set<String> ACTIVITY_RECOGNITION_PERMISSIONS = new ArraySet<>();
static {
ACTIVITY_RECOGNITION_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION);
@@ -616,6 +622,10 @@
grantPermissionsToSystemPackage(pm, getDefaultCaptivePortalLoginPackage(), userId,
NOTIFICATION_PERMISSIONS);
+ // Dock Manager
+ grantPermissionsToSystemPackage(pm, getDefaultDockManagerPackage(), userId,
+ NOTIFICATION_PERMISSIONS);
+
// Camera
grantPermissionsToSystemPackage(pm,
getDefaultSystemHandlerActivityPackage(pm, MediaStore.ACTION_IMAGE_CAPTURE, userId),
@@ -783,6 +793,8 @@
CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS, MICROPHONE_PERMISSIONS,
PHONE_PERMISSIONS, SMS_PERMISSIONS, COARSE_BACKGROUND_LOCATION_PERMISSIONS,
NEARBY_DEVICES_PERMISSIONS, NOTIFICATION_PERMISSIONS);
+ revokeRuntimePermissions(pm, voiceInteractPackageName, FINE_LOCATION_PERMISSIONS,
+ false, userId);
}
}
@@ -933,6 +945,10 @@
return mContext.getString(R.string.config_defaultCaptivePortalLoginPackageName);
}
+ private String getDefaultDockManagerPackage() {
+ return mContext.getString(R.string.config_defaultDockManagerPackageName);
+ }
+
@SafeVarargs
private final void grantPermissionToEachSystemPackage(PackageManagerWrapper pm,
ArrayList<String> packages, int userId, Set<String>... permissions) {
@@ -1924,7 +1940,7 @@
mPkgRequestingPerm, newRestrictionExcemptFlags, -1, mUser);
}
- if (newGranted != null && newGranted != mOriginalGranted) {
+ if (newGranted != null && !Objects.equals(newGranted, mOriginalGranted)) {
if (newGranted) {
NO_PM_CACHE.grantPermission(mPermission, mPkgRequestingPerm, mUser);
} else {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5f9ab95..9ec63fc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -50,6 +50,7 @@
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.healthconnect.HealthConnectManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
@@ -1100,7 +1101,7 @@
if (resolvedPackageName == null) {
return;
}
- appOpsManager.finishOp(accessorSource.getToken(), op,
+ appOpsManager.finishOp(attributionSourceState.token, op,
accessorSource.getUid(), resolvedPackageName,
accessorSource.getAttributionTag());
} else {
@@ -1109,8 +1110,9 @@
if (resolvedAttributionSource.getPackageName() == null) {
return;
}
- appOpsManager.finishProxyOp(AppOpsManager.opToPublicName(op),
- resolvedAttributionSource, skipCurrentFinish);
+ appOpsManager.finishProxyOp(attributionSourceState.token,
+ AppOpsManager.opToPublicName(op), resolvedAttributionSource,
+ skipCurrentFinish);
}
RegisteredAttribution registered =
sRunningAttributionSources.remove(current.getToken());
@@ -1156,7 +1158,8 @@
if (permissionInfo == null) {
try {
permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
- if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
+ if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)
+ || HealthConnectManager.isHealthPermission(context, permission)) {
// Double addition due to concurrency is fine - the backing
// store is concurrent.
sPlatformPermissions.put(permission, permissionInfo);
@@ -1225,10 +1228,11 @@
&& next.getNext() == null);
final boolean selfAccess = singleReceiverFromDatasource || next == null;
- final int opMode = performOpTransaction(context, op, current, message,
- forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
- selfAccess, singleReceiverFromDatasource, AppOpsManager.OP_NONE,
- AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+ final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
+ current, message, forDataDelivery, /*startDataDelivery*/ false,
+ skipCurrentChecks, selfAccess, singleReceiverFromDatasource,
+ AppOpsManager.OP_NONE, AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE,
AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
switch (opMode) {
@@ -1331,10 +1335,10 @@
attributionSource, next, fromDatasource, startDataDelivery, selfAccess,
isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;
- final int opMode = performOpTransaction(context, op, current, message,
- forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
- singleReceiverFromDatasource, attributedOp, proxyAttributionFlags,
- proxiedAttributionFlags, attributionChainId);
+ final int opMode = performOpTransaction(context, attributionSource.getToken(), op,
+ current, message, forDataDelivery, startDataDelivery, skipCurrentChecks,
+ selfAccess, singleReceiverFromDatasource, attributedOp,
+ proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
switch (opMode) {
case AppOpsManager.MODE_ERRORED: {
@@ -1479,8 +1483,8 @@
attributionSource, next, /*fromDatasource*/ false, startDataDelivery,
selfAccess, isLinkTrusted) : ATTRIBUTION_FLAGS_NONE;
- final int opMode = performOpTransaction(context, op, current, message,
- forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+ final int opMode = performOpTransaction(context, current.getToken(), op, current,
+ message, forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
/*fromDatasource*/ false, AppOpsManager.OP_NONE, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId);
@@ -1502,7 +1506,8 @@
}
@SuppressWarnings("ConstantConditions")
- private static int performOpTransaction(@NonNull Context context, int op,
+ private static int performOpTransaction(@NonNull Context context,
+ @NonNull IBinder chainStartToken, int op,
@NonNull AttributionSource attributionSource, @Nullable String message,
boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
boolean selfAccess, boolean singleReceiverFromDatasource, int attributedOp,
@@ -1564,7 +1569,7 @@
if (selfAccess) {
try {
startedOpResult = appOpsManager.startOpNoThrow(
- resolvedAttributionSource.getToken(), startedOp,
+ chainStartToken, startedOp,
resolvedAttributionSource.getUid(),
resolvedAttributionSource.getPackageName(),
/*startIfModeDefault*/ false,
@@ -1575,14 +1580,14 @@
+ " platform defined runtime permission "
+ AppOpsManager.opToPermission(op) + " while not having "
+ Manifest.permission.UPDATE_APP_OPS_STATS);
- startedOpResult = appOpsManager.startProxyOpNoThrow(attributedOp,
- attributionSource, message, skipProxyOperation,
+ startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
+ attributedOp, attributionSource, message, skipProxyOperation,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
} else {
try {
- startedOpResult = appOpsManager.startProxyOpNoThrow(startedOp,
- resolvedAttributionSource, message, skipProxyOperation,
+ startedOpResult = appOpsManager.startProxyOpNoThrow(chainStartToken,
+ startedOp, resolvedAttributionSource, message, skipProxyOperation,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
} catch (SecurityException e) {
//TODO 195339480: remove
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index a6d148c..383249f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -45,13 +45,11 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuintConsumer;
import com.android.internal.util.function.QuintFunction;
-import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.UndecFunction;
import com.android.server.LocalServices;
@@ -257,14 +255,14 @@
}
@Override
- public SyncNotedAppOp startProxyOperation(int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId,
- @NonNull DecFunction<Integer, AttributionSource, Boolean, Boolean, String, Boolean,
- Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
- return superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
+ @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean, Boolean, String,
+ Boolean, Boolean, Integer, Integer, Integer, SyncNotedAppOp> superImpl) {
+ return superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
@@ -280,10 +278,10 @@
}
@Override
- public void finishProxyOperation(int code, @NonNull AttributionSource attributionSource,
- boolean skipProxyOperation, @NonNull TriFunction<Integer, AttributionSource,
- Boolean, Void> superImpl) {
- superImpl.apply(resolveDatasourceOp(code, attributionSource.getUid(),
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
+ @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean, Void> superImpl) {
+ superImpl.apply(clientId, resolveDatasourceOp(code, attributionSource.getUid(),
attributionSource.getPackageName(), attributionSource.getAttributionTag()),
attributionSource, skipProxyOperation);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 98b5c1b..3aa333a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3589,7 +3589,12 @@
@Override
public void onKeyguardExitResult(boolean success) {
if (success) {
- startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
}
});
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index d8b1120..cc84c85 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1053,7 +1053,7 @@
super(context);
mContext = context;
- mBinderService = new BinderService();
+ mBinderService = new BinderService(mContext);
mLocalService = new LocalService();
mNativeWrapper = injector.createNativeWrapper();
mSystemProperties = injector.createSystemPropertiesWrapper();
@@ -5485,12 +5485,17 @@
@VisibleForTesting
final class BinderService extends IPowerManager.Stub {
+ private final PowerManagerShellCommand mShellCommand;
+
+ BinderService(Context context) {
+ mShellCommand = new PowerManagerShellCommand(context, this);
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new PowerManagerShellCommand(this)).exec(
- this, in, out, err, args, callback, resultReceiver);
+ mShellCommand.exec(this, in, out, err, args, callback, resultReceiver);
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index a9b33ed..9439b76 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -16,10 +16,15 @@
package com.android.server.power;
+import android.content.Context;
import android.content.Intent;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ShellCommand;
+import android.util.SparseArray;
+import android.view.Display;
import java.io.PrintWriter;
import java.util.List;
@@ -27,9 +32,13 @@
class PowerManagerShellCommand extends ShellCommand {
private static final int LOW_POWER_MODE_ON = 1;
- final PowerManagerService.BinderService mService;
+ private final Context mContext;
+ private final PowerManagerService.BinderService mService;
- PowerManagerShellCommand(PowerManagerService.BinderService service) {
+ private SparseArray<WakeLock> mProxWakelocks = new SparseArray<>();
+
+ PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) {
+ mContext = context;
mService = service;
}
@@ -52,6 +61,8 @@
return runSuppressAmbientDisplay();
case "list-ambient-display-suppression-tokens":
return runListAmbientDisplaySuppressionTokens();
+ case "set-prox":
+ return runSetProx();
default:
return handleDefaultCommands(cmd);
}
@@ -117,6 +128,56 @@
return 0;
}
+
+ /** TODO: Consider updating this code to support all wakelock types. */
+ private int runSetProx() throws RemoteException {
+ PrintWriter pw = getOutPrintWriter();
+ final boolean acquire;
+ switch (getNextArgRequired().toLowerCase()) {
+ case "list":
+ pw.println("Wakelocks:");
+ pw.println(mProxWakelocks);
+ return 0;
+ case "acquire":
+ acquire = true;
+ break;
+ case "release":
+ acquire = false;
+ break;
+ default:
+ pw.println("Error: Allowed options are 'list' 'enable' and 'disable'.");
+ return -1;
+ }
+
+ int displayId = Display.INVALID_DISPLAY;
+ String displayOption = getNextArg();
+ if ("-d".equals(displayOption)) {
+ String idStr = getNextArg();
+ displayId = Integer.parseInt(idStr);
+ if (displayId < 0) {
+ pw.println("Error: Specified displayId (" + idStr + ") must a non-negative int.");
+ return -1;
+ }
+ }
+
+ int wakelockIndex = displayId + 1; // SparseArray doesn't support negative indexes
+ WakeLock wakelock = mProxWakelocks.get(wakelockIndex);
+ if (wakelock == null) {
+ PowerManager pm = mContext.getSystemService(PowerManager.class);
+ wakelock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
+ "PowerManagerShellCommand[" + displayId + "]", displayId);
+ mProxWakelocks.put(wakelockIndex, wakelock);
+ }
+
+ if (acquire) {
+ wakelock.acquire();
+ } else {
+ wakelock.release();
+ }
+ pw.println(wakelock);
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -138,6 +199,11 @@
pw.println(" ambient display");
pw.println(" list-ambient-display-suppression-tokens");
pw.println(" prints the tokens used to suppress ambient display");
+ pw.println(" set-prox [list|acquire|release] (-d <display_id>)");
+ pw.println(" Acquires the proximity sensor wakelock. Wakelock is associated with");
+ pw.println(" a specific display if specified. 'list' lists wakelocks previously");
+ pw.println(" created by set-prox including their held status.");
+
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index d9f504e..ac97038 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -206,16 +206,21 @@
Log.d(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
}
+ mRebinder = null;
+
Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
mBoundServiceInfo.getComponentName());
- if (!mContext.bindServiceAsUser(bindIntent, this,
- BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
- mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
- Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
- mRebinder = this::bind;
- mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
- } else {
- mRebinder = null;
+ try {
+ if (!mContext.bindServiceAsUser(bindIntent, this,
+ BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+ mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
+ Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
+ mRebinder = this::bind;
+ mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
+ }
+ } catch (SecurityException e) {
+ // if anything goes wrong it shouldn't crash the system server
+ Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " bind failed", e);
}
}
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 8d106f7..5801920 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -128,4 +128,5 @@
@Override
public void dumpDebugLog(@NonNull PrintWriter printWriter) {
SystemClockTime.dump(printWriter);
- }}
+ }
+}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index f52f0b7..2c8fd96 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1066,7 +1066,28 @@
} finally {
Binder.restoreCallingIdentity(identity);
}
+ }
+ @Override
+ public void notifyRecordingStopped(IBinder sessionToken, String recordingId, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyRecordingStopped");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyRecordingStopped(recordingId);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyRecordingStopped", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
@@ -2253,6 +2274,23 @@
}
@Override
+ public void onRequestStopRecording(String recordingId) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestStopRecording");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestStopRecording(recordingId, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestStopRecording", e);
+ }
+ }
+ }
+
+ @Override
public void onRequestSigning(String id, String algorithm, String alias, byte[] data) {
synchronized (mLock) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b8cd8d9..c875f4a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1897,12 +1897,9 @@
}
}
- private static final HashMap<Integer, String> sWallpaperType = new HashMap<Integer, String>() {
- {
- put(FLAG_SYSTEM, RECORD_FILE);
- put(FLAG_LOCK, RECORD_LOCK_FILE);
- }
- };
+ private static final Map<Integer, String> sWallpaperType = Map.of(
+ FLAG_SYSTEM, RECORD_FILE,
+ FLAG_LOCK, RECORD_LOCK_FILE);
private void errorCheck(int userID) {
sWallpaperType.forEach((type, filename) -> {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7386a19..3b7bf48 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1211,7 +1211,8 @@
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
- r.reportFullyDrawnLocked(restoredFromBundle);
+ mTaskSupervisor.getActivityMetricsLogger().notifyFullyDrawn(r,
+ restoredFromBundle);
}
}
} finally {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index f0de1d3..e1ab291 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -454,6 +454,7 @@
final int windowsFullyDrawnDelayMs;
final int activityRecordIdHashCode;
final boolean relaunched;
+ final long timestampNs;
private TransitionInfoSnapshot(TransitionInfo info) {
this(info, info.mLastLaunchedActivity, INVALID_DELAY);
@@ -483,6 +484,7 @@
activityRecordIdHashCode = System.identityHashCode(launchedActivity);
this.windowsFullyDrawnDelayMs = windowsFullyDrawnDelayMs;
relaunched = info.mRelaunched;
+ timestampNs = info.mLaunchingState.mStartRealtimeNs;
}
@WaitResult.LaunchState int getLaunchState() {
@@ -498,6 +500,10 @@
}
}
+ boolean isIntresetedToEventLog() {
+ return type == TYPE_TRANSITION_WARM_LAUNCH || type == TYPE_TRANSITION_COLD_LAUNCH;
+ }
+
PackageOptimizationInfo getPackageOptimizationInfo(ArtManagerInternal artManagerInternal) {
return artManagerInternal == null || launchedActivityAppRecordRequiredAbi == null
? PackageOptimizationInfo.createWithNoInfo()
@@ -1022,16 +1028,17 @@
// This will avoid any races with other operations that modify the ActivityRecord.
final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
if (info.isInterestingToLoggerAndObserver()) {
- final long timestampNs = info.mLaunchingState.mStartRealtimeNs;
final long uptimeNs = info.mLaunchingState.mStartUptimeNs;
final int transitionDelay = info.mCurrentTransitionDelayMs;
final int processState = info.mProcessState;
final int processOomAdj = info.mProcessOomAdj;
mLoggerHandler.post(() -> logAppTransition(
- timestampNs, uptimeNs, transitionDelay, infoSnapshot, isHibernating,
+ uptimeNs, transitionDelay, infoSnapshot, isHibernating,
processState, processOomAdj));
}
- mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
+ if (infoSnapshot.isIntresetedToEventLog()) {
+ mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
+ }
if (info.mPendingFullyDrawn != null) {
info.mPendingFullyDrawn.run();
}
@@ -1040,7 +1047,7 @@
}
// This gets called on another thread without holding the activity manager lock.
- private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeNs,
+ private void logAppTransition(long transitionDeviceUptimeNs,
int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
int processState, int processOomAdj) {
final LogMaker builder = new LogMaker(APP_TRANSITION);
@@ -1108,7 +1115,7 @@
isIncremental,
isLoading,
info.launchedActivityName.hashCode(),
- TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
+ TimeUnit.NANOSECONDS.toMillis(info.timestampNs),
processState,
processOomAdj);
@@ -1132,10 +1139,6 @@
}
private void logAppDisplayed(TransitionInfoSnapshot info) {
- if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) {
- return;
- }
-
EventLog.writeEvent(WM_ACTIVITY_LAUNCH_TIME,
info.userId, info.activityRecordIdHashCode, info.launchedActivityShortComponentName,
info.windowsDrawnDelayMs);
@@ -1181,8 +1184,7 @@
}
/** @see android.app.Activity#reportFullyDrawn */
- TransitionInfoSnapshot logAppTransitionReportedDrawn(ActivityRecord r,
- boolean restoredFromBundle) {
+ TransitionInfoSnapshot notifyFullyDrawn(ActivityRecord r, boolean restoredFromBundle) {
final TransitionInfo info = mLastTransitionInfo.get(r);
if (info == null) {
return null;
@@ -1191,7 +1193,7 @@
// There are still undrawn activities, postpone reporting fully drawn until all of its
// windows are drawn. So that is closer to an usable state.
info.mPendingFullyDrawn = () -> {
- logAppTransitionReportedDrawn(r, restoredFromBundle);
+ notifyFullyDrawn(r, restoredFromBundle);
info.mPendingFullyDrawn = null;
};
return null;
@@ -1204,7 +1206,9 @@
currentTimestampNs - info.mLaunchingState.mStartUptimeNs);
final TransitionInfoSnapshot infoSnapshot =
new TransitionInfoSnapshot(info, r, (int) startupTimeMs);
- mLoggerHandler.post(() -> logAppFullyDrawn(infoSnapshot));
+ if (infoSnapshot.isIntresetedToEventLog()) {
+ mLoggerHandler.post(() -> logAppFullyDrawn(infoSnapshot));
+ }
mLastTransitionInfo.remove(r);
if (!info.isInterestingToLoggerAndObserver()) {
@@ -1216,46 +1220,8 @@
// fullfils (handling reportFullyDrawn() callbacks).
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"ActivityManager:ReportingFullyDrawn " + info.mLastLaunchedActivity.packageName);
-
- final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
- builder.setPackageName(r.packageName);
- builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
- builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
- builder.setType(restoredFromBundle
- ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
- : TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE);
- builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING,
- info.mProcessRunning ? 1 : 0);
- mMetricsLogger.write(builder);
- final PackageOptimizationInfo packageOptimizationInfo =
- infoSnapshot.getPackageOptimizationInfo(getArtManagerInternal());
- // Incremental info
- boolean isIncremental = false, isLoading = false;
- final String codePath = info.mLastLaunchedActivity.info.applicationInfo.getCodePath();
- if (codePath != null && IncrementalManager.isIncrementalPath(codePath)) {
- isIncremental = true;
- isLoading = isIncrementalLoading(info.mLastLaunchedActivity.packageName,
- info.mLastLaunchedActivity.mUserId);
- }
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_START_FULLY_DRAWN,
- info.mLastLaunchedActivity.info.applicationInfo.uid,
- info.mLastLaunchedActivity.packageName,
- restoredFromBundle
- ? FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITH_BUNDLE
- : FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITHOUT_BUNDLE,
- info.mLastLaunchedActivity.info.name,
- info.mProcessRunning,
- startupTimeMs,
- packageOptimizationInfo.getCompilationReason(),
- packageOptimizationInfo.getCompilationFilter(),
- info.mSourceType,
- info.mSourceEventDelayMs,
- isIncremental,
- isLoading,
- info.mLastLaunchedActivity.info.name.hashCode(),
- TimeUnit.NANOSECONDS.toMillis(info.mLaunchingState.mStartRealtimeNs));
-
+ mLoggerHandler.post(() -> logAppFullyDrawnMetrics(infoSnapshot, restoredFromBundle,
+ info.mProcessRunning));
// Ends the trace started at the beginning of this function. This is located here to allow
// the trace slice to have a noticable duration.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -1266,11 +1232,48 @@
return infoSnapshot;
}
- private void logAppFullyDrawn(TransitionInfoSnapshot info) {
- if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) {
- return;
+ private void logAppFullyDrawnMetrics(TransitionInfoSnapshot info, boolean restoredFromBundle,
+ boolean processRunning) {
+ final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
+ builder.setPackageName(info.packageName);
+ builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivityName);
+ builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS,
+ (long) info.windowsFullyDrawnDelayMs);
+ builder.setType(restoredFromBundle
+ ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
+ : TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE);
+ builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0);
+ mMetricsLogger.write(builder);
+ final PackageOptimizationInfo packageOptimizationInfo =
+ info.getPackageOptimizationInfo(getArtManagerInternal());
+ // Incremental info
+ boolean isIncremental = false, isLoading = false;
+ final String codePath = info.applicationInfo.getCodePath();
+ if (codePath != null && IncrementalManager.isIncrementalPath(codePath)) {
+ isIncremental = true;
+ isLoading = isIncrementalLoading(info.packageName, info.userId);
}
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.APP_START_FULLY_DRAWN,
+ info.applicationInfo.uid,
+ info.packageName,
+ restoredFromBundle
+ ? FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITH_BUNDLE
+ : FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITHOUT_BUNDLE,
+ info.launchedActivityName,
+ processRunning,
+ info.windowsFullyDrawnDelayMs,
+ packageOptimizationInfo.getCompilationReason(),
+ packageOptimizationInfo.getCompilationFilter(),
+ info.sourceType,
+ info.sourceEventDelayMs,
+ isIncremental,
+ isLoading,
+ info.launchedActivityName.hashCode(),
+ TimeUnit.NANOSECONDS.toMillis(info.timestampNs));
+ }
+ private void logAppFullyDrawn(TransitionInfoSnapshot info) {
StringBuilder sb = mStringBuilder;
sb.setLength(0);
sb.append("Fully drawn ");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2a3946f..8f8b57f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1747,6 +1747,7 @@
}
prevDc.mClosingApps.remove(this);
+ prevDc.getDisplayPolicy().removeRelaunchingApp(this);
if (prevDc.mFocusedApp == this) {
prevDc.setFocusedApp(null);
@@ -3130,8 +3131,8 @@
}
// Check to see if PiP is supported for the display this container is on.
- if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isWindowingModeSupported(
- WINDOWING_MODE_PINNED)) {
+ if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isEnteringPipAllowed(
+ getUid())) {
Slog.w(TAG, "Display " + mDisplayContent.getDisplayId()
+ " doesn't support enter picture-in-picture mode. caller = " + caller);
return false;
@@ -3959,6 +3960,9 @@
void startRelaunching() {
if (mPendingRelaunchCount == 0) {
mRelaunchStartTime = SystemClock.elapsedRealtime();
+ if (mVisibleRequested) {
+ mDisplayContent.getDisplayPolicy().addRelaunchingApp(this);
+ }
}
clearAllDrawn();
@@ -3972,7 +3976,7 @@
mPendingRelaunchCount--;
if (mPendingRelaunchCount == 0 && !isClientVisible()) {
// Don't count if the client won't report drawn.
- mRelaunchStartTime = 0;
+ finishOrAbortReplacingWindow();
}
} else {
// Update keyguard flags upon finishing relaunch.
@@ -3993,7 +3997,12 @@
return;
}
mPendingRelaunchCount = 0;
+ finishOrAbortReplacingWindow();
+ }
+
+ void finishOrAbortReplacingWindow() {
mRelaunchStartTime = 0;
+ mDisplayContent.getDisplayPolicy().removeRelaunchingApp(this);
}
/**
@@ -5102,6 +5111,9 @@
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
logAppCompatState();
+ if (!visible) {
+ finishOrAbortReplacingWindow();
+ }
}
/**
@@ -6485,15 +6497,6 @@
}
}
- void reportFullyDrawnLocked(boolean restoredFromBundle) {
- final TransitionInfoSnapshot info = mTaskSupervisor
- .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
- if (info != null) {
- mTaskSupervisor.reportActivityLaunched(false /* timeout */, this,
- info.windowsFullyDrawnDelayMs, info.getLaunchState());
- }
- }
-
void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
// stop tracking
@@ -6896,11 +6899,8 @@
* {@link android.view.Display#INVALID_DISPLAY} if not attached.
*/
int getDisplayId() {
- final Task rootTask = getRootTask();
- if (rootTask == null) {
- return INVALID_DISPLAY;
- }
- return rootTask.getDisplayId();
+ return task != null && task.mDisplayContent != null
+ ? task.mDisplayContent.mDisplayId : INVALID_DISPLAY;
}
final boolean isDestroyable() {
@@ -7901,8 +7901,8 @@
}
@Override
- float getSizeCompatScale() {
- return hasSizeCompatBounds() ? mSizeCompatScale : super.getSizeCompatScale();
+ float getCompatScale() {
+ return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e6d9492..a857d90 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1989,12 +1989,6 @@
? targetTask.getTopNonFinishingActivity()
: targetTaskTop;
- // At this point we are certain we want the task moved to the front. If we need to dismiss
- // any other always-on-top root tasks, now is the time to do it.
- if (targetTaskTop.canTurnScreenOn() && mService.isDreaming()) {
- targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
- }
-
if (mMovedToFront) {
// We moved the task to front, use starting window to hide initial drawn delay.
targetTaskTop.showStartingWindow(true /* taskSwitch */);
@@ -2006,6 +2000,12 @@
// And for paranoia, make sure we have correctly resumed the top activity.
resumeTargetRootTaskIfNeeded();
+ // This is moving an existing task to front. But since dream activity has a higher z-order
+ // to cover normal activities, it needs the awakening event to be dismissed.
+ if (mService.isDreaming() && targetTaskTop.canTurnScreenOn()) {
+ targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
+ }
+
mLastStartActivityRecord = targetTaskTop;
return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3c457e1..2f70eda 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -27,6 +27,7 @@
import static android.app.ActivityManager.START_FLAG_NATIVE_DEBUGGING;
import static android.app.ActivityManager.START_FLAG_TRACK_ALLOCATION;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.app.WaitResult.INVALID_DELAY;
@@ -2577,6 +2578,10 @@
// Apply options to prevent pendingOptions be taken when scheduling
// activity lifecycle transaction to make sure the override pending app
// transition will be applied immediately.
+ if (activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
+ targetActivity.mPendingRemoteAnimation =
+ activityOptions.getRemoteAnimationAdapter();
+ }
targetActivity.applyOptionsAnimation();
if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 9398bbe..f5da4c8 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -19,6 +19,7 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -831,9 +832,8 @@
}
boolean isWallpaperVisible(WindowState w) {
- if (mBackAnimationInProgress && w.isFocused()) {
- return mShowWallpaper;
- }
- return false;
+ return mAnimationTargets.mComposed && mShowWallpaper
+ && w.mAttrs.type == TYPE_BASE_APPLICATION && w.mActivityRecord != null
+ && mAnimationTargets.isTarget(w.mActivityRecord, true /* open */);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b84b2d8..bedeabe 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -369,6 +369,55 @@
}
@Override
+ ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
+ ActivityRecord boundary) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return null;
+ }
+ return super.getActivity(callback, traverseTopToBottom, boundary);
+ }
+
+ @Override
+ Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return null;
+ }
+ return super.getTask(callback, traverseTopToBottom);
+ }
+
+ @Override
+ boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return false;
+ }
+ return super.forAllActivities(callback, traverseTopToBottom);
+ }
+
+ @Override
+ boolean forAllRootTasks(Predicate<Task> callback, boolean traverseTopToBottom) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return false;
+ }
+ return super.forAllRootTasks(callback, traverseTopToBottom);
+ }
+
+ @Override
+ boolean forAllTasks(Predicate<Task> callback) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return false;
+ }
+ return super.forAllTasks(callback);
+ }
+
+ @Override
+ boolean forAllLeafTasks(Predicate<Task> callback) {
+ if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ return false;
+ }
+ return super.forAllLeafTasks(callback);
+ }
+
+ @Override
void forAllDisplayAreas(Consumer<DisplayArea> callback) {
super.forAllDisplayAreas(callback);
callback.accept(this);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8b34443..12efe0d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -230,6 +230,7 @@
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.view.inputmethod.ImeTracker;
import android.window.DisplayWindowPolicyController;
import android.window.IDisplayAreaOrganizer;
import android.window.ScreenCapture;
@@ -454,7 +455,7 @@
/**
* Compat metrics computed based on {@link #mDisplayMetrics}.
- * @see #updateDisplayAndOrientation(int, Configuration)
+ * @see #updateDisplayAndOrientation(Configuration)
*/
private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
@@ -5031,7 +5032,7 @@
* layer has been assigned since), to facilitate assigning the layer from the IME target, or
* fall back if there is no target.
* - the container doesn't always participate in window traversal, according to
- * {@link #skipImeWindowsDuringTraversal()}
+ * {@link #skipImeWindowsDuringTraversal(DisplayContent)}
*/
private static class ImeContainer extends DisplayArea.Tokens {
boolean mNeedsLayer = false;
@@ -6712,25 +6713,35 @@
mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
stateController.getControlsForDispatch(this));
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to deliver inset state change", e);
+ Slog.w(TAG, "Failed to deliver inset control state change", e);
}
}
@Override
- public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+ public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
try {
- mRemoteInsetsController.showInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
+ mRemoteInsetsController.showInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
}
}
@Override
- public void hideInsets(@InsetsType int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
try {
- mRemoteInsetsController.hideInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
+ mRemoteInsetsController.hideInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to deliver showInsets", e);
+ Slog.w(TAG, "Failed to deliver hideInsets", e);
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a1efd2d..1fef3c2 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -270,6 +270,12 @@
private final ArraySet<WindowState> mInsetsSourceWindowsExceptIme = new ArraySet<>();
+ /** Apps which are controlling the appearance of system bars */
+ private final ArraySet<ActivityRecord> mSystemBarColorApps = new ArraySet<>();
+
+ /** Apps which are relaunching and were controlling the appearance of system bars */
+ private final ArraySet<ActivityRecord> mRelaunchingSystemBarColorApps = new ArraySet<>();
+
private boolean mIsFreeformWindowOverlappingWithNavBar;
private boolean mLastImmersiveMode;
@@ -1449,6 +1455,7 @@
mStatusBarBackgroundWindows.clear();
mStatusBarColorCheckedBounds.setEmpty();
mStatusBarBackgroundCheckedBounds.setEmpty();
+ mSystemBarColorApps.clear();
mAllowLockscreenWhenOn = false;
mShowingDream = false;
@@ -1525,6 +1532,7 @@
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
new Rect(win.getFrame())));
mStatusBarColorCheckedBounds.union(sTmpRect);
+ addSystemBarColorApp(win);
}
}
@@ -1537,6 +1545,7 @@
if (isOverlappingWithNavBar) {
if (mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
+ addSystemBarColorApp(win);
}
if (mNavBarBackgroundWindow == null) {
mNavBarBackgroundWindow = win;
@@ -1555,9 +1564,11 @@
}
} else if (win.isDimming()) {
if (mStatusBar != null) {
- addStatusBarAppearanceRegionsForDimmingWindow(
+ if (addStatusBarAppearanceRegionsForDimmingWindow(
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
- mStatusBar.getFrame(), win.getBounds(), win.getFrame());
+ mStatusBar.getFrame(), win.getBounds(), win.getFrame())) {
+ addSystemBarColorApp(win);
+ }
}
if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
@@ -1565,18 +1576,21 @@
}
}
- private void addStatusBarAppearanceRegionsForDimmingWindow(int appearance, Rect statusBarFrame,
- Rect winBounds, Rect winFrame) {
+ /**
+ * Returns true if mStatusBarAppearanceRegionList is changed.
+ */
+ private boolean addStatusBarAppearanceRegionsForDimmingWindow(
+ int appearance, Rect statusBarFrame, Rect winBounds, Rect winFrame) {
if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) {
- return;
+ return false;
}
if (mStatusBarColorCheckedBounds.contains(sTmpRect)) {
- return;
+ return false;
}
if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) {
mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds)));
mStatusBarColorCheckedBounds.union(sTmpRect);
- return;
+ return true;
}
// A dimming window can divide status bar into different appearance regions (up to 3).
// +---------+-------------+---------+
@@ -1605,6 +1619,14 @@
// We don't have vertical status bar yet, so we don't handle the other orientation.
}
mStatusBarColorCheckedBounds.union(sTmpRect);
+ return true;
+ }
+
+ private void addSystemBarColorApp(WindowState win) {
+ final ActivityRecord app = win.mActivityRecord;
+ if (app != null) {
+ mSystemBarColorApps.add(app);
+ }
}
/**
@@ -2049,7 +2071,8 @@
// Don't show status bar when swiping on already visible navigation bar.
// But restore the position of navigation bar if it has been moved by the control
// target.
- controlTarget.showInsets(Type.navigationBars(), false);
+ controlTarget.showInsets(Type.navigationBars(), false /* fromIme */,
+ null /* statsToken */);
return;
}
@@ -2057,10 +2080,12 @@
// Show transient bars if they are hidden; restore position if they are visible.
mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE,
isGestureOnSystemBar);
- controlTarget.showInsets(restorePositionTypes, false);
+ controlTarget.showInsets(restorePositionTypes, false /* fromIme */,
+ null /* statsToken */);
} else {
// Restore visibilities and positions of system bars.
- controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), false);
+ controlTarget.showInsets(Type.statusBars() | Type.navigationBars(),
+ false /* fromIme */, null /* statsToken */);
// To further allow the pull-down-from-the-top gesture to pull down the notification
// shade as a consistent motion, we reroute the touch events here from the currently
// touched window to the status bar after making it visible.
@@ -2086,6 +2111,25 @@
return mDisplayContent.getInsetsPolicy();
}
+ /**
+ * Called when an app has started replacing its main window.
+ */
+ void addRelaunchingApp(ActivityRecord app) {
+ if (mSystemBarColorApps.contains(app)) {
+ mRelaunchingSystemBarColorApps.add(app);
+ }
+ }
+
+ /**
+ * Called when an app has finished replacing its main window or aborted.
+ */
+ void removeRelaunchingApp(ActivityRecord app) {
+ final boolean removed = mRelaunchingSystemBarColorApps.remove(app);
+ if (removed & mRelaunchingSystemBarColorApps.isEmpty()) {
+ updateSystemBarAttributes();
+ }
+ }
+
void resetSystemBarAttributes() {
mLastDisableFlags = 0;
updateSystemBarAttributes();
@@ -2128,6 +2172,11 @@
final int displayId = getDisplayId();
final int disableFlags = win.getDisableFlags();
final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
+ if (!mRelaunchingSystemBarColorApps.isEmpty()) {
+ // The appearance of system bars might change while relaunching apps. We don't report
+ // the intermediate state to system UI. Otherwise, it might trigger redundant effects.
+ return;
+ }
final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
final boolean isNavbarColorManagedByIme =
@@ -2590,6 +2639,14 @@
pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
pw.println(mTopFullscreenOpaqueWindowState);
}
+ if (!mSystemBarColorApps.isEmpty()) {
+ pw.print(prefix); pw.print("mSystemBarColorApps=");
+ pw.println(mSystemBarColorApps);
+ }
+ if (!mRelaunchingSystemBarColorApps.isEmpty()) {
+ pw.print(prefix); pw.print("mRelaunchingSystemBarColorApps=");
+ pw.println(mRelaunchingSystemBarColorApps);
+ }
if (mNavBarColorWindowCandidate != null) {
pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
pw.println(mNavBarColorWindowCandidate);
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 5d49042..6f821b5 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -162,6 +162,17 @@
return mDisplayWindowPolicyController.canShowTasksInRecents();
}
+ /**
+ * @see DisplayWindowPolicyController#isEnteringPipAllowed(int)
+ */
+ public final boolean isEnteringPipAllowed(int uid) {
+ if (mDisplayWindowPolicyController == null) {
+ return true;
+ }
+ return mDisplayWindowPolicyController.isEnteringPipAllowed(uid);
+ }
+
+
void dump(String prefix, PrintWriter pw) {
if (mDisplayWindowPolicyController != null) {
pw.println();
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index e0644b6..b735b30 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -442,11 +442,12 @@
mRemoveContentMode = other.mRemoveContentMode;
changed = true;
}
- if (other.mShouldShowWithInsecureKeyguard != mShouldShowWithInsecureKeyguard) {
+ if (!Objects.equals(
+ other.mShouldShowWithInsecureKeyguard, mShouldShowWithInsecureKeyguard)) {
mShouldShowWithInsecureKeyguard = other.mShouldShowWithInsecureKeyguard;
changed = true;
}
- if (other.mShouldShowSystemDecors != mShouldShowSystemDecors) {
+ if (!Objects.equals(other.mShouldShowSystemDecors, mShouldShowSystemDecors)) {
mShouldShowSystemDecors = other.mShouldShowSystemDecors;
changed = true;
}
@@ -458,15 +459,15 @@
mFixedToUserRotation = other.mFixedToUserRotation;
changed = true;
}
- if (other.mIgnoreOrientationRequest != mIgnoreOrientationRequest) {
+ if (!Objects.equals(other.mIgnoreOrientationRequest, mIgnoreOrientationRequest)) {
mIgnoreOrientationRequest = other.mIgnoreOrientationRequest;
changed = true;
}
- if (other.mIgnoreDisplayCutout != mIgnoreDisplayCutout) {
+ if (!Objects.equals(other.mIgnoreDisplayCutout, mIgnoreDisplayCutout)) {
mIgnoreDisplayCutout = other.mIgnoreDisplayCutout;
changed = true;
}
- if (other.mDontMoveToTop != mDontMoveToTop) {
+ if (!Objects.equals(other.mDontMoveToTop, mDontMoveToTop)) {
mDontMoveToTop = other.mDontMoveToTop;
changed = true;
}
@@ -522,14 +523,13 @@
mRemoveContentMode = delta.mRemoveContentMode;
changed = true;
}
- if (delta.mShouldShowWithInsecureKeyguard != null
- && delta.mShouldShowWithInsecureKeyguard
- != mShouldShowWithInsecureKeyguard) {
+ if (delta.mShouldShowWithInsecureKeyguard != null && !Objects.equals(
+ delta.mShouldShowWithInsecureKeyguard, mShouldShowWithInsecureKeyguard)) {
mShouldShowWithInsecureKeyguard = delta.mShouldShowWithInsecureKeyguard;
changed = true;
}
- if (delta.mShouldShowSystemDecors != null
- && delta.mShouldShowSystemDecors != mShouldShowSystemDecors) {
+ if (delta.mShouldShowSystemDecors != null && !Objects.equals(
+ delta.mShouldShowSystemDecors, mShouldShowSystemDecors)) {
mShouldShowSystemDecors = delta.mShouldShowSystemDecors;
changed = true;
}
@@ -543,18 +543,18 @@
mFixedToUserRotation = delta.mFixedToUserRotation;
changed = true;
}
- if (delta.mIgnoreOrientationRequest != null
- && delta.mIgnoreOrientationRequest != mIgnoreOrientationRequest) {
+ if (delta.mIgnoreOrientationRequest != null && !Objects.equals(
+ delta.mIgnoreOrientationRequest, mIgnoreOrientationRequest)) {
mIgnoreOrientationRequest = delta.mIgnoreOrientationRequest;
changed = true;
}
- if (delta.mIgnoreDisplayCutout != null
- && delta.mIgnoreDisplayCutout != mIgnoreDisplayCutout) {
+ if (delta.mIgnoreDisplayCutout != null && !Objects.equals(
+ delta.mIgnoreDisplayCutout, mIgnoreDisplayCutout)) {
mIgnoreDisplayCutout = delta.mIgnoreDisplayCutout;
changed = true;
}
- if (delta.mDontMoveToTop != null
- && delta.mDontMoveToTop != mDontMoveToTop) {
+ if (delta.mDontMoveToTop != null && !Objects.equals(
+ delta.mDontMoveToTop, mDontMoveToTop)) {
mDontMoveToTop = delta.mDontMoveToTop;
changed = true;
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 554791a..7fd093f 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -33,9 +33,11 @@
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
+import android.view.InsetsSourceConsumer;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
@@ -49,6 +51,9 @@
*/
final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider {
+ /** The token tracking the current IME request or {@code null} otherwise. */
+ @Nullable
+ private ImeTracker.Token mImeRequesterStatsToken;
private InsetsControlTarget mImeRequester;
private Runnable mShowImeRunner;
private boolean mIsImeLayoutDrawn;
@@ -162,14 +167,20 @@
}
/**
- * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
- * requests to show IME on {@param imeTarget}.
+ * Called from {@link WindowManagerInternal#showImePostLayout}
+ * when {@link android.inputmethodservice.InputMethodService} requests to show IME
+ * on {@param imeTarget}.
*
- * @param imeTarget imeTarget on which IME request is coming from.
+ * @param imeTarget imeTarget on which IME show request is coming from.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*/
- void scheduleShowImePostLayout(InsetsControlTarget imeTarget) {
+ void scheduleShowImePostLayout(InsetsControlTarget imeTarget,
+ @Nullable ImeTracker.Token statsToken) {
boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
mImeRequester = imeTarget;
+ // There was still a stats token, so that request presumably failed.
+ ImeTracker.get().onFailed(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ mImeRequesterStatsToken = statsToken;
if (targetChanged) {
// target changed, check if new target can show IME.
ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
@@ -183,15 +194,20 @@
ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
? mImeRequester : mImeRequester.getWindow().getName());
mShowImeRunner = () -> {
+ ImeTracker.get().onProgress(mImeRequesterStatsToken,
+ ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
// Target should still be the same.
if (isReadyToShowIme()) {
+ ImeTracker.get().onProgress(mImeRequesterStatsToken,
+ ImeTracker.PHASE_WM_SHOW_IME_READY);
final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
target.getWindow() != null ? target.getWindow().getName() : "");
setImeShowing(true);
- target.showInsets(WindowInsets.Type.ime(), true /* fromIme */);
+ target.showInsets(WindowInsets.Type.ime(), true /* fromIme */,
+ mImeRequesterStatsToken);
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
if (target != mImeRequester && mImeRequester != null) {
ProtoLog.w(WM_DEBUG_IME,
@@ -199,7 +215,12 @@
(mImeRequester.getWindow() != null
? mImeRequester.getWindow().getName() : ""));
}
+ } else {
+ ImeTracker.get().onFailed(mImeRequesterStatsToken,
+ ImeTracker.PHASE_WM_SHOW_IME_READY);
}
+ // Clear token here so we don't report an error in abortShowImePostLayout().
+ mImeRequesterStatsToken = null;
abortShowImePostLayout();
};
mDisplayContent.mWmService.requestTraversal();
@@ -234,6 +255,8 @@
mImeRequester = null;
mIsImeLayoutDrawn = false;
mShowImeRunner = null;
+ ImeTracker.get().onCancelled(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ mImeRequesterStatsToken = null;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index d35b7c3..8ecbc17 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -16,9 +16,11 @@
package com.android.server.wm;
+import android.annotation.Nullable;
import android.inputmethodservice.InputMethodService;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
/**
* Generalization of an object that can control insets state.
@@ -57,8 +59,10 @@
*
* @param types to specify which types of insets source window should be shown.
* @param fromIme {@code true} if IME show request originated from {@link InputMethodService}.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*/
- default void showInsets(@InsetsType int types, boolean fromIme) {
+ default void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
}
/**
@@ -66,8 +70,10 @@
*
* @param types to specify which types of insets source window should be hidden.
* @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}.
+ * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
*/
- default void hideInsets(@InsetsType int types, boolean fromIme) {
+ default void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
}
/**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b9fa80c..f66fa0f 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -799,7 +799,7 @@
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show
? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
: LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
- null /* translator */);
+ null /* translator */, null /* statsToken */);
SurfaceAnimationThread.getHandler().post(
() -> mListener.onReady(mAnimationControl, typesReady));
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 74a236b..2dbccae 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -580,9 +580,8 @@
// Rounded corners should be displayed above the taskbar.
bounds.bottom =
Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
- if (mActivityRecord.inSizeCompatMode()
- && mActivityRecord.getSizeCompatScale() < 1.0f) {
- bounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
+ if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) {
+ bounds.scale(1.0f / mActivityRecord.getCompatScale());
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 872542a..dfcad48 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -291,6 +291,12 @@
private final IBinder mFragmentToken;
/**
+ * Whether to delay the call to {@link #updateOrganizedTaskFragmentSurface()} when there is a
+ * configuration change.
+ */
+ private boolean mDelayOrganizedTaskFragmentSurfaceUpdate;
+
+ /**
* Whether to delay the last activity of TaskFragment being immediately removed while finishing.
* This should only be set on a embedded TaskFragment, where the organizer can have the
* opportunity to perform animations and finishing the adjacent TaskFragment.
@@ -2264,35 +2270,41 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
- // Task will animate differently.
- if (mTaskFragmentOrganizer != null) {
- mTmpPrevBounds.set(getBounds());
- }
-
super.onConfigurationChanged(newParentConfig);
- final boolean shouldStartChangeTransition = shouldStartChangeTransition(mTmpPrevBounds);
- if (shouldStartChangeTransition) {
- initializeChangeTransition(mTmpPrevBounds);
- }
if (mTaskFragmentOrganizer != null) {
- if (mTransitionController.isShellTransitionsEnabled()
- && !mTransitionController.isCollecting(this)) {
- // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
- // update the surface here if it is not collected by Shell transition.
- updateOrganizedTaskFragmentSurface();
- } else if (!mTransitionController.isShellTransitionsEnabled()
- && !shouldStartChangeTransition) {
- // Update the surface here instead of in the organizer so that we can make sure
- // it can be synced with the surface freezer for legacy app transition.
- updateOrganizedTaskFragmentSurface();
- }
+ updateOrganizedTaskFragmentSurface();
}
sendTaskFragmentInfoChanged();
}
+ void deferOrganizedTaskFragmentSurfaceUpdate() {
+ mDelayOrganizedTaskFragmentSurfaceUpdate = true;
+ }
+
+ void continueOrganizedTaskFragmentSurfaceUpdate() {
+ mDelayOrganizedTaskFragmentSurfaceUpdate = false;
+ updateOrganizedTaskFragmentSurface();
+ }
+
private void updateOrganizedTaskFragmentSurface() {
+ if (mDelayOrganizedTaskFragmentSurfaceUpdate) {
+ return;
+ }
+ if (mTransitionController.isShellTransitionsEnabled()
+ && !mTransitionController.isCollecting(this)) {
+ // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
+ // update the surface here if it is not collected by Shell transition.
+ updateOrganizedTaskFragmentSurfaceUnchecked();
+ } else if (!mTransitionController.isShellTransitionsEnabled() && !isAnimating()) {
+ // Update the surface here instead of in the organizer so that we can make sure
+ // it can be synced with the surface freezer for legacy app transition.
+ updateOrganizedTaskFragmentSurfaceUnchecked();
+ }
+ }
+
+ private void updateOrganizedTaskFragmentSurfaceUnchecked() {
final SurfaceControl.Transaction t = getSyncTransaction();
updateSurfacePosition(t);
updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
@@ -2346,7 +2358,7 @@
}
/** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
- private boolean shouldStartChangeTransition(Rect startBounds) {
+ boolean shouldStartChangeTransition(Rect startBounds) {
if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
return false;
}
@@ -2366,7 +2378,7 @@
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
if (mTaskFragmentOrganizer != null) {
- updateOrganizedTaskFragmentSurface();
+ updateOrganizedTaskFragmentSurfaceUnchecked();
// If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
// emit the callbacks now.
sendTaskFragmentAppeared();
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 37bef3a..25df511 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -423,7 +423,7 @@
Transition newTransition = null;
if (isCollecting()) {
if (displayChange != null) {
- throw new IllegalArgumentException("Provided displayChange for a non-new request");
+ Slog.e(TAG, "Provided displayChange for a non-new request", new Throwable());
}
// Make the collecting transition wait until this request is ready.
mCollectingTransition.setReady(readyGroupRef, false);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index c206a15..bab3a05 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -43,6 +43,7 @@
import android.view.SurfaceControlViewHost;
import android.view.WindowInfo;
import android.view.WindowManager.DisplayImePolicy;
+import android.view.inputmethod.ImeTracker;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.input.InputManagerService;
@@ -729,16 +730,20 @@
* Show IME on imeTargetWindow once IME has finished layout.
*
* @param imeTargetWindowToken token of the (IME target) window on which IME should be shown.
+ * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*/
- public abstract void showImePostLayout(IBinder imeTargetWindowToken);
+ public abstract void showImePostLayout(IBinder imeTargetWindowToken,
+ @Nullable ImeTracker.Token statsToken);
/**
* Hide IME using imeTargetWindow when requested.
*
* @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden.
* @param displayId the id of the display the IME is on.
+ * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
*/
- public abstract void hideIme(IBinder imeTargetWindowToken, int displayId);
+ public abstract void hideIme(IBinder imeTargetWindowToken, int displayId,
+ @Nullable ImeTracker.Token statsToken);
/**
* Tell window manager about a package that should be running with a restricted range of
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fa6f90a..df343db 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -292,6 +292,7 @@
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import android.window.ITaskFpsCallback;
import android.window.ScreenCapture;
@@ -425,7 +426,7 @@
* @see #ENABLE_SHELL_TRANSITIONS
*/
public static final boolean sEnableShellTransitions =
- SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, true);
+ SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false);
/**
* Allows a fullscreen windowing mode activity to launch in its desired orientation directly
@@ -1842,7 +1843,7 @@
// Make this invalid which indicates a null attached frame.
outAttachedFrame.set(0, 0, -1, -1);
}
- outSizeCompatScale[0] = win.getSizeCompatScaleForClient();
+ outSizeCompatScale[0] = win.getCompatScaleForClient();
}
Binder.restoreCallingIdentity(origId);
@@ -8014,7 +8015,8 @@
}
@Override
- public void showImePostLayout(IBinder imeTargetWindowToken) {
+ public void showImePostLayout(IBinder imeTargetWindowToken,
+ @Nullable ImeTracker.Token statsToken) {
synchronized (mGlobalLock) {
InputTarget imeTarget = getInputTargetFromWindowTokenLocked(imeTargetWindowToken);
if (imeTarget == null) {
@@ -8023,17 +8025,18 @@
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
imeTarget = controlTarget.getWindow();
- // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which
- // is controlled by default display
+ // If InsetsControlTarget doesn't have a window, it's using remoteControlTarget
+ // which is controlled by default display
final DisplayContent dc = imeTarget != null
? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked();
dc.getInsetsStateController().getImeSourceProvider()
- .scheduleShowImePostLayout(controlTarget);
+ .scheduleShowImePostLayout(controlTarget, statsToken);
}
}
@Override
- public void hideIme(IBinder imeTargetWindowToken, int displayId) {
+ public void hideIme(IBinder imeTargetWindowToken, int displayId,
+ @Nullable ImeTracker.Token statsToken) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.hideIme");
synchronized (mGlobalLock) {
WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
@@ -8049,10 +8052,15 @@
dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
}
if (dc != null && dc.getImeTarget(IME_TARGET_CONTROL) != null) {
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
ProtoLog.d(WM_DEBUG_IME, "hideIme Control target: %s ",
dc.getImeTarget(IME_TARGET_CONTROL));
- dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(
- WindowInsets.Type.ime(), true /* fromIme */);
+ dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(WindowInsets.Type.ime(),
+ true /* fromIme */, statsToken);
+ } else {
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
}
if (dc != null) {
dc.getInsetsStateController().getImeSourceProvider().setImeShowing(false);
@@ -8895,7 +8903,7 @@
outInsetsState.set(state, true /* copySources */);
if (WindowState.hasCompatScale(attrs, token, overrideScale)) {
final float compatScale = token != null && token.hasSizeCompatBounds()
- ? token.getSizeCompatScale() * overrideScale
+ ? token.getCompatScale() * overrideScale
: overrideScale;
outInsetsState.scale(1f / compatScale);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 6f2930c..1b70d1d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -21,7 +21,7 @@
import static android.os.Process.myTid;
import static android.os.Process.setThreadPriority;
-import static com.android.server.LockGuard.INDEX_WINDOW;;
+import static com.android.server.LockGuard.INDEX_WINDOW;
import com.android.internal.annotations.GuardedBy;
import com.android.server.AnimationThread;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 7415376..de12a4e 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -48,6 +48,7 @@
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -144,6 +145,8 @@
@VisibleForTesting
final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
+ private final Rect mTmpBounds = new Rect();
+
WindowOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
mGlobalLock = atm.mGlobalLock;
@@ -702,7 +705,7 @@
}
private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
- int effects = 0;
+ int effects = applyChanges(tr, c, null /* errorCallbackToken */);
final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
@@ -717,6 +720,10 @@
effects = TRANSACT_EFFECTS_LIFECYCLE;
}
+ if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
+ tr.setDragResizing(c.getDragResizing(), DRAG_RESIZE_MODE_FREEFORM);
+ }
+
final int childWindowingMode = c.getActivityWindowingMode();
if (childWindowingMode > -1) {
tr.forAllActivities(a -> { a.setWindowingMode(childWindowingMode); });
@@ -759,6 +766,7 @@
private int applyDisplayAreaChanges(DisplayArea displayArea,
WindowContainerTransaction.Change c) {
final int[] effects = new int[1];
+ effects[0] = applyChanges(displayArea, c, null /* errorCallbackToken */);
if ((c.getChangeMask()
& WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
@@ -779,6 +787,27 @@
return effects[0];
}
+ private int applyTaskFragmentChanges(@NonNull TaskFragment taskFragment,
+ @NonNull WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
+ if (taskFragment.isEmbeddedTaskFragmentInPip()) {
+ // No override from organizer for embedded TaskFragment in a PIP Task.
+ return 0;
+ }
+
+ // When the TaskFragment is resized, we may want to create a change transition for it, for
+ // which we want to defer the surface update until we determine whether or not to start
+ // change transition.
+ mTmpBounds.set(taskFragment.getBounds());
+ taskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
+ final int effects = applyChanges(taskFragment, c, errorCallbackToken);
+ if (taskFragment.shouldStartChangeTransition(mTmpBounds)) {
+ taskFragment.initializeChangeTransition(mTmpBounds);
+ }
+ taskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
+ mTmpBounds.set(0, 0, 0, 0);
+ return effects;
+ }
+
private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
@NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,
@@ -1444,20 +1473,15 @@
private int applyWindowContainerChange(WindowContainer wc,
WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
sanitizeWindowContainer(wc);
- if (wc.asTaskFragment() != null && wc.asTaskFragment().isEmbeddedTaskFragmentInPip()) {
- // No override from organizer for embedded TaskFragment in a PIP Task.
- return 0;
+ if (wc.asDisplayArea() != null) {
+ return applyDisplayAreaChanges(wc.asDisplayArea(), c);
+ } else if (wc.asTask() != null) {
+ return applyTaskChanges(wc.asTask(), c);
+ } else if (wc.asTaskFragment() != null) {
+ return applyTaskFragmentChanges(wc.asTaskFragment(), c, errorCallbackToken);
+ } else {
+ return applyChanges(wc, c, errorCallbackToken);
}
-
- int effects = applyChanges(wc, c, errorCallbackToken);
-
- if (wc instanceof DisplayArea) {
- effects |= applyDisplayAreaChanges(wc.asDisplayArea(), c);
- } else if (wc instanceof Task) {
- effects |= applyTaskChanges(wc.asTask(), c);
- }
-
- return effects;
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 17e6499..86dd0b5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -249,6 +249,7 @@
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import android.window.OnBackInvokedCallbackInfo;
@@ -475,7 +476,7 @@
// Current transformation being applied.
float mGlobalScale = 1f;
float mInvGlobalScale = 1f;
- float mSizeCompatScale = 1f;
+ float mCompatScale = 1f;
final float mOverrideScale;
float mHScale = 1f, mVScale = 1f;
float mLastHScale = 1f, mLastVScale = 1f;
@@ -1256,21 +1257,21 @@
void updateGlobalScale() {
if (hasCompatScale()) {
- mSizeCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
- ? mToken.getSizeCompatScale()
+ mCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
+ ? mToken.getCompatScale()
: 1f;
- mGlobalScale = mSizeCompatScale * mOverrideScale;
+ mGlobalScale = mCompatScale * mOverrideScale;
mInvGlobalScale = 1f / mGlobalScale;
return;
}
- mGlobalScale = mInvGlobalScale = mSizeCompatScale = 1f;
+ mGlobalScale = mInvGlobalScale = mCompatScale = 1f;
}
- float getSizeCompatScaleForClient() {
- // If the size compat scale is because of the size compat bounds, we only scale down its
- // coordinates at the server side without letting the client know.
- return mToken.hasSizeCompatBounds() ? 1f : mSizeCompatScale;
+ float getCompatScaleForClient() {
+ // If this window in the size compat mode. The scaling is fully controlled at the server
+ // side. The client doesn't need to take it into account.
+ return mToken.hasSizeCompatBounds() ? 1f : mCompatScale;
}
/**
@@ -1539,10 +1540,11 @@
mWmService.makeWindowFreezingScreenIfNeededLocked(this);
// If the orientation is changing, or we're starting or ending a drag resizing action,
- // then we need to hold off on unfreezing the display until this window has been
- // redrawn; to do that, we need to go through the process of getting informed by the
- // application when it has finished drawing.
- if (getOrientationChanging() || dragResizingChanged) {
+ // or we're resizing an embedded Activity, then we need to hold off on unfreezing the
+ // display until this window has been redrawn; to do that, we need to go through the
+ // process of getting informed by the application when it has finished drawing.
+ if (getOrientationChanging() || dragResizingChanged
+ || isEmbeddedActivityResizeChanged()) {
if (dragResizingChanged) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize start waiting for draw, "
@@ -3868,7 +3870,7 @@
}
}
- outFrames.sizeCompatScale = getSizeCompatScaleForClient();
+ outFrames.compatScale = getCompatScaleForClient();
// Note: in the cases where the window is tied to an activity, we should not send a
// configuration update when the window has requested to be hidden. Doing so can lead to
@@ -4018,7 +4020,7 @@
mClient.insetsControlChanged(getCompatInsetsState(),
stateController.getControlsForDispatch(this));
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to deliver inset state change to w=" + this, e);
+ Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e);
}
}
@@ -4028,20 +4030,30 @@
}
@Override
- public void showInsets(@InsetsType int types, boolean fromIme) {
+ public void showInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
try {
- mClient.showInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
+ mClient.showInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
}
}
@Override
- public void hideInsets(@InsetsType int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme,
+ @Nullable ImeTracker.Token statsToken) {
try {
- mClient.hideInsets(types, fromIme);
+ ImeTracker.get().onProgress(statsToken,
+ ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
+ mClient.hideInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to deliver showInsets", e);
+ Slog.w(TAG, "Failed to deliver hideInsets", e);
+ ImeTracker.get().onFailed(statsToken,
+ ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
}
}
@@ -4149,6 +4161,20 @@
return mActivityRecord == null || mActivityRecord.isFullyTransparentBarAllowed(frame);
}
+ /**
+ * Whether this window belongs to a resizing embedded activity.
+ */
+ private boolean isEmbeddedActivityResizeChanged() {
+ if (mActivityRecord == null || !isVisibleRequested()) {
+ // No need to update if the window is in the background.
+ return false;
+ }
+
+ final TaskFragment embeddedTaskFragment = mActivityRecord.getOrganizedTaskFragment();
+ return embeddedTaskFragment != null
+ && mDisplayContent.mChangingContainers.contains(embeddedTaskFragment);
+ }
+
boolean isDragResizeChanged() {
return mDragResizing != computeDragResizing();
}
@@ -6018,7 +6044,7 @@
final long duration =
SystemClock.elapsedRealtime() - mActivityRecord.mRelaunchStartTime;
Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms");
- mActivityRecord.mRelaunchStartTime = 0;
+ mActivityRecord.finishOrAbortReplacingWindow();
}
if (mActivityRecord != null && mAttrs.type == TYPE_APPLICATION_STARTING) {
mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 7c481f5..f2527b6 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -258,7 +258,7 @@
* @return The scale for applications running in compatibility mode. Multiply the size in the
* application by this scale will be the size in the screen.
*/
- float getSizeCompatScale() {
+ float getCompatScale() {
return mDisplayContent.mCompatibleScreenScale;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3197124..2030347 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -260,10 +260,9 @@
// --- NativeInputManager ---
-class NativeInputManager : public virtual RefBase,
- public virtual InputReaderPolicyInterface,
- public virtual InputDispatcherPolicyInterface,
- public virtual PointerControllerPolicyInterface {
+class NativeInputManager : public virtual InputReaderPolicyInterface,
+ public virtual InputDispatcherPolicyInterface,
+ public virtual PointerControllerPolicyInterface {
protected:
virtual ~NativeInputManager();
@@ -1520,8 +1519,14 @@
return 0;
}
- NativeInputManager* im = new NativeInputManager(serviceObj, messageQueue->getLooper());
- im->incStrong(0);
+ static std::once_flag nativeInitialize;
+ NativeInputManager* im = nullptr;
+ std::call_once(nativeInitialize, [&]() {
+ // Create the NativeInputManager, which should not be destroyed or deallocated for the
+ // lifetime of the process.
+ im = new NativeInputManager(serviceObj, messageQueue->getLooper());
+ });
+ LOG_ALWAYS_FATAL_IF(im == nullptr, "NativeInputManager was already initialized.");
return reinterpret_cast<jlong>(im);
}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 9fa23c2..e1de05c 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -482,6 +482,17 @@
agnssRilIface->setSetId(type, setid_string);
}
+static void android_location_gnss_hal_GnssNative_inject_ni_supl_message_data(JNIEnv* env, jclass,
+ jbyteArray data,
+ jint length,
+ jint slotIndex) {
+ if (agnssRilIface == nullptr) {
+ ALOGE("%s: IAGnssRil interface not available.", __func__);
+ return;
+ }
+ agnssRilIface->injectNiSuplMessageData(data, length, slotIndex);
+}
+
static jint android_location_gnss_hal_GnssNative_read_nmea(JNIEnv* env, jclass,
jbyteArray nmeaArray, jint buffer_size) {
return gnssHal->readNmea(nmeaArray, buffer_size);
@@ -974,6 +985,8 @@
android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid)},
{"native_set_agps_server", "(ILjava/lang/String;I)V",
reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_set_agps_server)},
+ {"native_inject_ni_supl_message_data", "([BII)V",
+ reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_ni_supl_message_data)},
{"native_send_ni_response", "(II)V",
reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_send_ni_response)},
{"native_get_internal_state", "()Ljava/lang/String;",
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index 34e4976..c7a1af7 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -84,8 +84,19 @@
networkAttributes.capabilities = static_cast<int32_t>(capabilities),
networkAttributes.apn = jniApn.c_str();
- auto result = mIAGnssRil->updateNetworkState(networkAttributes);
- return checkAidlStatus(result, "IAGnssRilAidl updateNetworkState() failed.");
+ auto status = mIAGnssRil->updateNetworkState(networkAttributes);
+ return checkAidlStatus(status, "IAGnssRilAidl updateNetworkState() failed.");
+}
+
+jboolean AGnssRil::injectNiSuplMessageData(const jbyteArray& msgData, jint length, jint slotIndex) {
+ JNIEnv* env = getJniEnv();
+ jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(msgData, 0));
+ auto status = mIAGnssRil->injectNiSuplMessageData(std::vector<uint8_t>((const uint8_t*)bytes,
+ (const uint8_t*)bytes +
+ length),
+ static_cast<int>(slotIndex));
+ env->ReleasePrimitiveArrayCritical(msgData, bytes, JNI_ABORT);
+ return checkAidlStatus(status, "IAGnssRil injectNiSuplMessageData() failed.");
}
// Implementation of AGnssRil_V1_0
@@ -151,6 +162,11 @@
return checkHidlReturn(result, "IAGnssRil_V1_0 updateNetworkState() failed.");
}
+jboolean AGnssRil_V1_0::injectNiSuplMessageData(const jbyteArray&, jint, jint) {
+ ALOGI("IAGnssRil_V1_0 interface does not support injectNiSuplMessageData.");
+ return JNI_FALSE;
+}
+
// Implementation of AGnssRil_V2_0
AGnssRil_V2_0::AGnssRil_V2_0(const sp<IAGnssRil_V2_0>& iAGnssRil)
diff --git a/services/core/jni/gnss/AGnssRil.h b/services/core/jni/gnss/AGnssRil.h
index ce14a77d..b7e0282 100644
--- a/services/core/jni/gnss/AGnssRil.h
+++ b/services/core/jni/gnss/AGnssRil.h
@@ -43,6 +43,8 @@
virtual jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming,
jboolean available, const jstring& apn, jlong networkHandle,
jshort capabilities) = 0;
+ virtual jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+ jint slotIndex) = 0;
};
class AGnssRil : public AGnssRilInterface {
@@ -55,6 +57,8 @@
jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
const jstring& apn, jlong networkHandle,
jshort capabilities) override;
+ jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+ jint slotIndex) override;
private:
const sp<android::hardware::gnss::IAGnssRil> mIAGnssRil;
@@ -70,6 +74,7 @@
jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
const jstring& apn, jlong networkHandle,
jshort capabilities) override;
+ jboolean injectNiSuplMessageData(const jbyteArray&, jint, jint) override;
private:
const sp<android::hardware::gnss::V1_0::IAGnssRil> mAGnssRil_V1_0;
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
new file mode 100644
index 0000000..e07bc77
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.credentials.CreateCredentialRequest;
+import android.credentials.CredentialManager;
+import android.credentials.ICreateCredentialCallback;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Central session for a single {@link CredentialManager#executeCreateCredential} request.
+ * This class listens to the responses from providers, and the UX app, and updates the
+ * provider(s) state maintained in {@link ProviderCreateSession}.
+ */
+public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
+ ICreateCredentialCallback> {
+ private static final String TAG = "CreateRequestSession";
+
+ CreateRequestSession(@NonNull Context context, int userId,
+ CreateCredentialRequest request,
+ ICreateCredentialCallback callback,
+ String callingPackage) {
+ super(context, userId, request, callback, RequestInfo.TYPE_CREATE, callingPackage);
+ }
+
+ /**
+ * Creates a new provider session, and adds it to list of providers that are contributing to
+ * this request session.
+ *
+ * @return the provider session that was started
+ */
+ @Override
+ @Nullable
+ public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+ RemoteCredentialService remoteCredentialService) {
+ ProviderCreateSession providerCreateSession = ProviderCreateSession
+ .createNewSession(mContext, mUserId, providerInfo,
+ this, remoteCredentialService);
+ if (providerCreateSession != null) {
+ Log.i(TAG, "In startProviderSession - provider session created and being added");
+ mProviders.put(providerCreateSession.getComponentName().flattenToString(),
+ providerCreateSession);
+ }
+ return providerCreateSession;
+ }
+
+ @Override
+ protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+ mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newCreateRequestInfo(
+ mRequestId, mClientRequest, mIsFirstUiTurn, mClientCallingPackage),
+ providerDataList));
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 321f022..374da1c 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCredentialOption;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialSessionCallback;
import android.credentials.ICreateCredentialCallback;
@@ -33,6 +34,7 @@
import android.os.ICancellationSignal;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.credentials.GetCredentialsRequest;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
@@ -43,6 +45,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* Entry point service for credential management.
@@ -91,17 +94,15 @@
return new ArrayList<>();
}
List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length);
- for (int i = 0; i < serviceNames.length; i++) {
- Log.i(TAG, "in newServiceListLocked, service: " + serviceNames[i]);
- if (TextUtils.isEmpty(serviceNames[i])) {
+ for (String serviceName : serviceNames) {
+ Log.i(TAG, "in newServiceListLocked, service: " + serviceName);
+ if (TextUtils.isEmpty(serviceName)) {
continue;
}
try {
serviceList.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
- serviceNames[i]));
- } catch (PackageManager.NameNotFoundException e) {
- Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
- } catch (SecurityException e) {
+ serviceName));
+ } catch (PackageManager.NameNotFoundException | SecurityException e) {
Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
}
}
@@ -115,15 +116,31 @@
synchronized (mLock) {
final List<CredentialManagerServiceImpl> services =
getServiceListForUserLocked(userId);
- services.forEach(s -> {
+ for (CredentialManagerServiceImpl s : services) {
c.accept(s);
- });
+ }
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
+ private List<ProviderSession> initiateProviderSessions(RequestSession session,
+ List<String> requestOptions) {
+ List<ProviderSession> providerSessions = new ArrayList<>();
+ // Invoke all services of a user to initiate a provider session
+ runForUser((service) -> {
+ if (service.isServiceCapable(requestOptions)) {
+ ProviderSession providerSession = service
+ .initiateProviderSessionForRequest(session);
+ if (providerSession != null) {
+ providerSessions.add(providerSession);
+ }
+ }
+ });
+ return providerSessions;
+ }
+
final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@Override
public ICancellationSignal executeGetCredential(
@@ -137,11 +154,22 @@
// New request session, scoped for this request only.
final GetRequestSession session = new GetRequestSession(getContext(),
UserHandle.getCallingUserId(),
- callback);
+ callback,
+ request,
+ callingPackage);
- // Invoke all services of a user
- runForUser((service) -> {
- service.getCredential(request, session, callingPackage);
+ // Initiate all provider sessions
+ List<ProviderSession> providerSessions =
+ initiateProviderSessions(session, request.getGetCredentialOptions()
+ .stream().map(GetCredentialOption::getType)
+ .collect(Collectors.toList()));
+ // TODO : Return error when no providers available
+
+ // Iterate over all provider sessions and invoke the request
+ providerSessions.forEach(providerGetSession -> {
+ providerGetSession.getRemoteCredentialService().onGetCredentials(
+ (GetCredentialsRequest) providerGetSession.getProviderRequest(),
+ /*callback=*/providerGetSession);
});
return cancelTransport;
}
@@ -151,9 +179,29 @@
CreateCredentialRequest request,
ICreateCredentialCallback callback,
String callingPackage) {
- // TODO: implement.
- Log.i(TAG, "executeCreateCredential");
+ Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
+ // TODO : Implement cancellation
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+ // New request session, scoped for this request only.
+ final CreateRequestSession session = new CreateRequestSession(getContext(),
+ UserHandle.getCallingUserId(),
+ request,
+ callback,
+ callingPackage);
+
+ // Initiate all provider sessions
+ List<ProviderSession> providerSessions =
+ initiateProviderSessions(session, List.of(request.getType()));
+ // TODO : Return error when no providers available
+
+ // Iterate over all provider sessions and invoke the request
+ providerSessions.forEach(providerCreateSession -> {
+ providerCreateSession.getRemoteCredentialService().onCreateCredential(
+ (android.service.credentials.CreateCredentialRequest)
+ providerCreateSession.getProviderRequest(),
+ /*callback=*/providerCreateSession);
+ });
return cancelTransport;
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index cc03f9b..0c32304 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -21,13 +21,13 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.credentials.GetCredentialRequest;
import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.GetCredentialsRequest;
import android.util.Slog;
import com.android.server.infra.AbstractPerUserSystemService;
+import java.util.List;
+
/**
* Per-user, per remote service implementation of {@link CredentialManagerService}
@@ -61,50 +61,38 @@
return mInfo.getServiceInfo();
}
- public void getCredential(GetCredentialRequest request, GetRequestSession requestSession,
- String callingPackage) {
- Slog.i(TAG, "in getCredential in CredManServiceImpl");
+ /**
+ * Starts a provider session and associates it with the given request session. */
+ @Nullable
+ public ProviderSession initiateProviderSessionForRequest(
+ RequestSession requestSession) {
+ Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl");
if (mInfo == null) {
- Slog.i(TAG, "in getCredential in CredManServiceImpl, but mInfo is null");
- return;
+ Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
+ + "but mInfo is null. This shouldn't happen");
+ return null;
}
-
- // TODO : Determine if remoteService instance can be reused across requests
final RemoteCredentialService remoteService = new RemoteCredentialService(
getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
- ProviderGetSession providerSession = new ProviderGetSession(mInfo,
- requestSession, mUserId, remoteService);
- // Set the provider info to the session when the request is initiated. This happens here
- // because there is one serviceImpl per remote provider, and so we can only retrieve
- // the provider information in the scope of this instance, whereas the session is for the
- // entire request.
- requestSession.addProviderSession(providerSession);
- GetCredentialsRequest filteredRequest = getRequestWithValidType(request, callingPackage);
- if (filteredRequest != null) {
- remoteService.onGetCredentials(getRequestWithValidType(request, callingPackage),
- providerSession);
- }
+ ProviderSession providerSession =
+ requestSession.initiateProviderSession(mInfo, remoteService);
+ return providerSession;
}
- @Nullable
- private GetCredentialsRequest getRequestWithValidType(GetCredentialRequest request,
- String callingPackage) {
- GetCredentialsRequest.Builder builder =
- new GetCredentialsRequest.Builder(callingPackage);
- request.getGetCredentialOptions().forEach( option -> {
- if (mInfo.hasCapability(option.getType())) {
- Slog.i(TAG, "Provider can handle: " + option.getType());
- builder.addGetCredentialOption(option);
- } else {
- Slog.i(TAG, "Skipping request as provider cannot handle it");
- }
- });
-
- try {
- return builder.build();
- } catch (IllegalArgumentException | NullPointerException e) {
- Slog.i(TAG, "issue with request build: " + e.getMessage());
+ /** Return true if at least one capability found. */
+ boolean isServiceCapable(List<String> requestedOptions) {
+ if (mInfo == null) {
+ Slog.i(TAG, "in isServiceCapable, mInfo is null");
+ return false;
}
- return null;
+ for (String capability : requestedOptions) {
+ if (mInfo.hasCapability(capability)) {
+ Slog.i(TAG, "Provider can handle: " + capability);
+ return true;
+ } else {
+ Slog.i(TAG, "Provider cannot handle: " + capability);
+ }
+ }
+ return false;
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index dcf094f..e889594 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -37,6 +37,7 @@
@NonNull
private final CredentialManagerUiCallback mCallbacks;
@NonNull private final Context mContext;
+ // TODO : Use for starting the activity for this user
private final int mUserId;
@NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
new Handler(Looper.getMainLooper())) {
@@ -56,7 +57,7 @@
Slog.i(TAG, "No selection found in UI result");
}
} else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_CANCELED) {
- mCallbacks.onUiCancelation();
+ mCallbacks.onUiCancellation();
}
}
@@ -67,7 +68,7 @@
/** Called when the user makes a selection. */
void onUiSelection(UserSelectionDialogResult selection);
/** Called when the user cancels the UI. */
- void onUiCancelation();
+ void onUiCancellation();
}
public CredentialManagerUi(Context context, int userId,
CredentialManagerUiCallback callbacks) {
@@ -83,9 +84,8 @@
*/
public void show(RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
Log.i(TAG, "In show");
- Intent intent = IntentFactory.newIntent(
- requestInfo, providerDataList,
- new ArrayList<>(), mResultReceiver);
+ Intent intent = IntentFactory.newIntent(requestInfo, providerDataList, new ArrayList<>(),
+ mResultReceiver);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 80f0fec..8238632 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -16,9 +16,10 @@
package com.android.server.credentials;
-import android.content.ComponentName;
+import android.annotation.Nullable;
import android.content.Context;
import android.credentials.Credential;
+import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
import android.credentials.IGetCredentialCallback;
import android.credentials.ui.ProviderData;
@@ -26,62 +27,52 @@
import android.credentials.ui.UserSelectionDialogResult;
import android.os.RemoteException;
import android.service.credentials.CredentialEntry;
+import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
-import android.util.Slog;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Central session for a single getCredentials request. This class listens to the
* responses from providers, and the UX app, and updates the provider(S) state.
*/
-public final class GetRequestSession extends RequestSession {
+public final class GetRequestSession extends RequestSession<GetCredentialRequest,
+ IGetCredentialCallback> {
private static final String TAG = "GetRequestSession";
- private final IGetCredentialCallback mClientCallback;
- private final Map<String, ProviderGetSession> mProviders;
-
public GetRequestSession(Context context, int userId,
- IGetCredentialCallback callback) {
- super(context, userId, RequestInfo.TYPE_GET);
- mClientCallback = callback;
- mProviders = new HashMap<>();
+ IGetCredentialCallback callback, GetCredentialRequest request,
+ String callingPackage) {
+ super(context, userId, request, callback, RequestInfo.TYPE_GET, callingPackage);
}
/**
- * Adds a new provider to the list of providers that are contributing to this session.
+ * Creates a new provider session, and adds it list of providers that are contributing to
+ * this session.
+ * @return the provider session created within this request session, for the given provider
+ * info.
*/
- public void addProviderSession(ProviderGetSession providerSession) {
- mProviders.put(providerSession.getComponentName().flattenToString(),
- providerSession);
- }
-
@Override
- public void onProviderStatusChanged(ProviderSession.Status status,
- ComponentName componentName) {
- Log.i(TAG, "in onStatusChanged");
- if (ProviderSession.isTerminatingStatus(status)) {
- Log.i(TAG, "in onStatusChanged terminating status");
-
- ProviderGetSession session = mProviders.remove(componentName.flattenToString());
- if (session != null) {
- Slog.i(TAG, "Provider session removed.");
- } else {
- Slog.i(TAG, "Provider session null, did not exist.");
- }
- } else if (ProviderSession.isCompletionStatus(status)) {
- Log.i(TAG, "in onStatusChanged isCompletionStatus status");
- onProviderResponseComplete();
+ @Nullable
+ public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+ RemoteCredentialService remoteCredentialService) {
+ ProviderGetSession providerGetSession = ProviderGetSession
+ .createNewSession(mContext, mUserId, providerInfo,
+ this, remoteCredentialService);
+ if (providerGetSession != null) {
+ Log.i(TAG, "In startProviderSession - provider session created and being added");
+ mProviders.put(providerGetSession.getComponentName().flattenToString(),
+ providerGetSession);
}
+ return providerGetSession;
}
+ // TODO: Override for this method not needed once get selection logic is
+ // moved to ProviderGetSession
@Override
public void onUiSelection(UserSelectionDialogResult selection) {
String providerId = selection.getProviderId();
- ProviderGetSession providerSession = mProviders.get(providerId);
+ ProviderGetSession providerSession = (ProviderGetSession) mProviders.get(providerId);
if (providerSession != null) {
CredentialEntry credentialEntry = providerSession.getCredentialEntry(
selection.getEntrySubkey());
@@ -89,57 +80,17 @@
respondToClientAndFinish(credentialEntry.getCredential());
}
// TODO : Handle action chips and authentication selection
- return;
}
// TODO : finish session and respond to client if provider not found
}
@Override
- public void onUiCancelation() {
- // User canceled the activity
- // TODO : Send error code to client
- finishSession();
- }
-
- private void onProviderResponseComplete() {
- Log.i(TAG, "in onProviderResponseComplete");
- if (isResponseCompleteAcrossProviders()) {
- Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
- getProviderDataAndInitiateUi();
- }
- }
-
- private void getProviderDataAndInitiateUi() {
- ArrayList<ProviderData> providerDataList = new ArrayList<>();
- for (ProviderGetSession session : mProviders.values()) {
- Log.i(TAG, "preparing data for : " + session.getComponentName());
- providerDataList.add(session.prepareUiData());
- }
- if (!providerDataList.isEmpty()) {
- Log.i(TAG, "provider list not empty about to initiate ui");
- initiateUi(providerDataList);
- }
- }
-
- private void initiateUi(ArrayList<ProviderData> providerDataList) {
+ protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo(
mRequestId, null, mIsFirstUiTurn, ""),
providerDataList));
}
- /**
- * Iterates over all provider sessions and returns true if all have responded.
- */
- private boolean isResponseCompleteAcrossProviders() {
- AtomicBoolean isRequestComplete = new AtomicBoolean(true);
- mProviders.forEach( (packageName, session) -> {
- if (session.getStatus() != ProviderSession.Status.COMPLETE) {
- isRequestComplete.set(false);
- }
- });
- return isRequestComplete.get();
- }
-
private void respondToClientAndFinish(Credential credential) {
try {
mClientCallback.onResponse(new GetCredentialResponse(credential));
@@ -148,17 +99,4 @@
}
finishSession();
}
-
- private void finishSession() {
- clearProviderSessions();
- }
-
- private void clearProviderSessions() {
- for (ProviderGetSession session : mProviders.values()) {
- // TODO : Evaluate if we should unbind remote services here or wait for them
- // to automatically unbind when idle. Re-binding frequently also has a cost.
- //session.destroy();
- }
- mProviders.clear();
- }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
new file mode 100644
index 0000000..49c416f
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 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.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.credentials.Credential;
+import android.credentials.ui.CreateCredentialProviderData;
+import android.credentials.ui.Entry;
+import android.os.Bundle;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.CreateCredentialResponse;
+import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.SaveEntry;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Central provider session that listens for provider callbacks, and maintains provider state.
+ * Will likely split this into remote response state and UI state.
+ */
+public final class ProviderCreateSession extends ProviderSession<
+ CreateCredentialRequest, CreateCredentialResponse> {
+ private static final String TAG = "ProviderCreateSession";
+
+ // Key to be used as an entry key for a save entry
+ private static final String SAVE_ENTRY_KEY = "save_entry_key";
+
+ @NonNull
+ private final Map<String, SaveEntry> mUiSaveEntries = new HashMap<>();
+ /** The complete request to be used in the second round. */
+ private final CreateCredentialRequest mCompleteRequest;
+
+ /** Creates a new provider session to be used by the request session. */
+ @Nullable public static ProviderCreateSession createNewSession(
+ Context context,
+ @UserIdInt int userId,
+ CredentialProviderInfo providerInfo,
+ CreateRequestSession createRequestSession,
+ RemoteCredentialService remoteCredentialService) {
+ CreateCredentialRequest providerRequest =
+ createProviderRequest(providerInfo.getCapabilities(),
+ createRequestSession.mClientRequest,
+ createRequestSession.mClientCallingPackage);
+ if (providerRequest != null) {
+ return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
+ remoteCredentialService, providerRequest);
+ }
+ Log.i(TAG, "Unable to create provider session");
+ return null;
+ }
+
+ @Nullable
+ private static CreateCredentialRequest createProviderRequest(List<String> providerCapabilities,
+ android.credentials.CreateCredentialRequest clientRequest,
+ String clientCallingPackage) {
+ String capability = clientRequest.getType();
+ if (providerCapabilities.contains(capability)) {
+ return new CreateCredentialRequest(clientCallingPackage, capability,
+ clientRequest.getData());
+ }
+ Log.i(TAG, "Unable to create provider request - capabilities do not match");
+ return null;
+ }
+
+ private static CreateCredentialRequest getFirstRoundRequest(CreateCredentialRequest request) {
+ // TODO: Replace with first round bundle from request when ready
+ return new CreateCredentialRequest(
+ request.getCallingPackage(),
+ request.getType(),
+ new Bundle());
+ }
+
+ private ProviderCreateSession(
+ @NonNull Context context,
+ @NonNull CredentialProviderInfo info,
+ @NonNull ProviderInternalCallback callbacks,
+ @UserIdInt int userId,
+ @NonNull RemoteCredentialService remoteCredentialService,
+ @NonNull CreateCredentialRequest request) {
+ super(context, info, getFirstRoundRequest(request), callbacks, userId,
+ remoteCredentialService);
+ // TODO : Replace with proper splitting of request
+ mCompleteRequest = request;
+ setStatus(Status.PENDING);
+ }
+
+ /** Returns the save entry maintained in state by this provider session. */
+ public SaveEntry getUiSaveEntry(String entryId) {
+ return mUiSaveEntries.get(entryId);
+ }
+
+ @Override
+ public void onProviderResponseSuccess(
+ @Nullable CreateCredentialResponse response) {
+ Log.i(TAG, "in onProviderResponseSuccess");
+ onUpdateResponse(response);
+ }
+
+ /** Called when the provider response resulted in a failure. */
+ @Override
+ public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
+ updateStatusAndInvokeCallback(toStatus(errorCode));
+ }
+
+ /** Called when provider service dies. */
+ @Override
+ public void onProviderServiceDied(RemoteCredentialService service) {
+ if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
+ updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+ } else {
+ Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ + "this should not happen");
+ }
+ }
+
+ private void onUpdateResponse(CreateCredentialResponse response) {
+ Log.i(TAG, "updateResponse with save entries");
+ mProviderResponse = response;
+ updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
+ }
+
+ @Override
+ @Nullable protected CreateCredentialProviderData prepareUiData()
+ throws IllegalArgumentException {
+ Log.i(TAG, "In prepareUiData");
+ if (!ProviderSession.isUiInvokingStatus(getStatus())) {
+ Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+ return null;
+ }
+ final CreateCredentialResponse response = getProviderResponse();
+ if (response == null) {
+ Log.i(TAG, "In prepareUiData response null");
+ throw new IllegalStateException("Response must be in completion mode");
+ }
+ if (response.getSaveEntries() != null) {
+ Log.i(TAG, "In prepareUiData save entries not null");
+ return prepareUiProviderData(
+ prepareUiSaveEntries(response.getSaveEntries()),
+ null,
+ /*isDefaultProvider=*/false);
+ }
+ return null;
+ }
+
+ @Override
+ public void onProviderIntentResult(Bundle resultData) {
+ Credential credential = resultData.getParcelable(
+ CredentialProviderService.EXTRA_SAVE_CREDENTIAL,
+ Credential.class);
+ if (credential == null) {
+ Log.i(TAG, "Credential returned from intent is null");
+ return;
+ }
+ updateFinalCredentialResponse(credential);
+ }
+
+ @Override
+ public void onUiEntrySelected(String entryType, String entryKey) {
+ if (entryType.equals(SAVE_ENTRY_KEY)) {
+ SaveEntry saveEntry = mUiSaveEntries.get(entryKey);
+ if (saveEntry == null) {
+ Log.i(TAG, "Save entry not found");
+ return;
+ }
+ // TODO: Uncomment when pending intent works
+ // onSaveEntrySelected(saveEntry);
+ }
+ }
+
+ @Override
+ public void onProviderIntentCancelled() {
+ //TODO (Implement)
+ }
+
+ private List<Entry> prepareUiSaveEntries(@NonNull List<SaveEntry> saveEntries) {
+ Log.i(TAG, "in populateUiSaveEntries");
+ List<Entry> uiSaveEntries = new ArrayList<>();
+
+ // Populate the save entries
+ for (SaveEntry saveEntry : saveEntries) {
+ String entryId = generateEntryId();
+ mUiSaveEntries.put(entryId, saveEntry);
+ Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
+ uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice()));
+ }
+ return uiSaveEntries;
+ }
+
+ private void updateFinalCredentialResponse(@NonNull Credential credential) {
+ mFinalCredentialResponse = credential;
+ updateStatusAndInvokeCallback(Status.CREDENTIAL_RECEIVED_FROM_INTENT);
+ }
+
+ private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
+ Entry remoteEntry, boolean isDefaultProvider) {
+ return new CreateCredentialProviderData.Builder(
+ mComponentName.flattenToString())
+ .setSaveEntries(saveEntries)
+ .setIsDefaultProvider(isDefaultProvider)
+ .build();
+ }
+
+ private void onSaveEntrySelected(SaveEntry saveEntry) {
+ mProviderIntentController.setupAndInvokePendingIntent(saveEntry.getPendingIntent(),
+ mProviderRequest);
+ setStatus(Status.PENDING_INTENT_INVOKED);
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index ff2107a..362d981 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -18,13 +18,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.slice.Slice;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.credentials.GetCredentialOption;
import android.credentials.ui.Entry;
import android.credentials.ui.GetCredentialProviderData;
+import android.os.Bundle;
import android.service.credentials.Action;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
import android.util.Log;
import android.util.Slog;
@@ -38,75 +41,95 @@
/**
* Central provider session that listens for provider callbacks, and maintains provider state.
* Will likely split this into remote response state and UI state.
+ *
+ * @hide
*/
-public final class ProviderGetSession extends ProviderSession<GetCredentialsResponse>
- implements RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
+public final class ProviderGetSession extends ProviderSession<GetCredentialsRequest,
+ GetCredentialsResponse>
+ implements
+ RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
private static final String TAG = "ProviderGetSession";
// Key to be used as an entry key for a credential entry
private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
- private GetCredentialsResponse mResponse;
-
@NonNull
- private final Map<String, CredentialEntry> mUiCredentials = new HashMap<>();
-
+ private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
@NonNull
- private final Map<String, Action> mUiActions = new HashMap<>();
+ private final Map<String, Action> mUiActionsEntries = new HashMap<>();
+ private Action mAuthenticationAction = null;
- public ProviderGetSession(CredentialProviderInfo info,
- ProviderInternalCallback callbacks,
- int userId, RemoteCredentialService remoteCredentialService) {
- super(info, callbacks, userId, remoteCredentialService);
- setStatus(Status.PENDING);
- }
-
- /** Updates the response being maintained in state by this provider session. */
- @Override
- public void updateResponse(GetCredentialsResponse response) {
- if (response.getAuthenticationAction() != null) {
- // TODO : Implement authentication logic
- } else if (response.getCredentialsDisplayContent() != null) {
- Log.i(TAG , "updateResponse with credentialEntries");
- mResponse = response;
- updateStatusAndInvokeCallback(Status.COMPLETE);
+ /** Creates a new provider session to be used by the request session. */
+ @Nullable public static ProviderGetSession createNewSession(
+ Context context,
+ @UserIdInt int userId,
+ CredentialProviderInfo providerInfo,
+ GetRequestSession getRequestSession,
+ RemoteCredentialService remoteCredentialService) {
+ GetCredentialsRequest providerRequest =
+ createProviderRequest(providerInfo.getCapabilities(),
+ getRequestSession.mClientRequest,
+ getRequestSession.mClientCallingPackage);
+ if (providerRequest != null) {
+ return new ProviderGetSession(context, providerInfo, getRequestSession, userId,
+ remoteCredentialService, providerRequest);
}
+ Log.i(TAG, "Unable to create provider session");
+ return null;
}
- /** Returns the response being maintained in this provider session. */
- @Override
@Nullable
- public GetCredentialsResponse getResponse() {
- return mResponse;
+ private static GetCredentialsRequest createProviderRequest(List<String> providerCapabilities,
+ android.credentials.GetCredentialRequest clientRequest,
+ String clientCallingPackage) {
+ List<GetCredentialOption> filteredOptions = new ArrayList<>();
+ for (GetCredentialOption option : clientRequest.getGetCredentialOptions()) {
+ if (providerCapabilities.contains(option.getType())) {
+ Log.i(TAG, "In createProviderRequest - capability found : " + option.getType());
+ filteredOptions.add(option);
+ } else {
+ Log.i(TAG, "In createProviderRequest - capability not "
+ + "found : " + option.getType());
+ }
+ }
+ if (!filteredOptions.isEmpty()) {
+ return new GetCredentialsRequest.Builder(clientCallingPackage).setGetCredentialOptions(
+ filteredOptions).build();
+ }
+ Log.i(TAG, "In createProviderRequest - returning null");
+ return null;
+ }
+
+ public ProviderGetSession(Context context,
+ CredentialProviderInfo info,
+ ProviderInternalCallback callbacks,
+ int userId, RemoteCredentialService remoteCredentialService,
+ GetCredentialsRequest request) {
+ super(context, info, request, callbacks, userId, remoteCredentialService);
+ setStatus(Status.PENDING);
}
/** Returns the credential entry maintained in state by this provider session. */
@Nullable
public CredentialEntry getCredentialEntry(@NonNull String entryId) {
- return mUiCredentials.get(entryId);
- }
-
- /** Returns the action entry maintained in state by this provider session. */
- @Nullable
- public Action getAction(@NonNull String entryId) {
- return mUiActions.get(entryId);
+ return mUiCredentialEntries.get(entryId);
}
/** Called when the provider response has been updated by an external source. */
- @Override
+ @Override // Callback from the remote provider
public void onProviderResponseSuccess(@Nullable GetCredentialsResponse response) {
Log.i(TAG, "in onProviderResponseSuccess");
- updateResponse(response);
+ onUpdateResponse(response);
}
/** Called when the provider response resulted in a failure. */
- @Override
+ @Override // Callback from the remote provider
public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
updateStatusAndInvokeCallback(toStatus(errorCode));
}
/** Called when provider service dies. */
- @Override
+ @Override // Callback from the remote provider
public void onProviderServiceDied(RemoteCredentialService service) {
if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
@@ -116,77 +139,106 @@
}
}
- @Override
- protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
- Log.i(TAG, "In prepareUiData");
- if (!ProviderSession.isCompletionStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData not complete");
+ @Override // Callback from the provider intent controller class
+ public void onProviderIntentResult(Bundle resultData) {
+ // TODO : Implement
+ }
- throw new IllegalStateException("Status must be in completion mode");
+ @Override
+ public void onProviderIntentCancelled() {
+ // TODO : Implement
+ }
+
+ @Override // Selection call from the request provider
+ protected void onUiEntrySelected(String entryType, String entryId) {
+ // TODO: Implement
+ }
+
+ @Override // Call from request session to data to be shown on the UI
+ @Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
+ Log.i(TAG, "In prepareUiData");
+ if (!ProviderSession.isUiInvokingStatus(getStatus())) {
+ Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
+ + mComponentName.flattenToString());
+ return null;
}
- GetCredentialsResponse response = getResponse();
+ GetCredentialsResponse response = getProviderResponse();
if (response == null) {
Log.i(TAG, "In prepareUiData response null");
-
throw new IllegalStateException("Response must be in completion mode");
}
if (response.getAuthenticationAction() != null) {
- Log.i(TAG, "In prepareUiData auth not null");
-
- return prepareUiProviderDataWithAuthentication(response.getAuthenticationAction());
+ Log.i(TAG, "In prepareUiData - top level authentication mode");
+ return prepareUiProviderData(null, null,
+ prepareUiAuthenticationActionEntry(response.getAuthenticationAction()),
+ /*remoteEntry=*/null);
}
if (response.getCredentialsDisplayContent() != null){
- Log.i(TAG, "In prepareUiData credentials not null");
-
- return prepareUiProviderDataWithCredentials(response.getCredentialsDisplayContent());
+ Log.i(TAG, "In prepareUiData displayContent not null");
+ return prepareUiProviderData(populateUiActionEntries(
+ response.getCredentialsDisplayContent().getActions()),
+ prepareUiCredentialEntries(response.getCredentialsDisplayContent()
+ .getCredentialEntries()),
+ /*authenticationActionEntry=*/null, /*remoteEntry=*/null);
}
return null;
}
- /**
- * To be called by {@link ProviderGetSession} when the UI is to be invoked.
- */
- @Nullable
- private GetCredentialProviderData prepareUiProviderDataWithCredentials(@NonNull
- CredentialsDisplayContent content) {
- Log.i(TAG, "in prepareUiProviderData");
- List<Entry> credentialEntries = new ArrayList<>();
- List<Entry> actionChips = new ArrayList<>();
- Entry authenticationEntry = null;
+ private Entry prepareUiAuthenticationActionEntry(@NonNull Action authenticationAction) {
+ String entryId = generateEntryId();
+ mUiActionsEntries.put(entryId, authenticationAction);
+ return new Entry(ACTION_ENTRY_KEY, entryId, authenticationAction.getSlice());
+ }
+
+ private List<Entry> prepareUiCredentialEntries(@NonNull
+ List<CredentialEntry> credentialEntries) {
+ Log.i(TAG, "in prepareUiProviderDataWithCredentials");
+ List<Entry> credentialUiEntries = new ArrayList<>();
// Populate the credential entries
- for (CredentialEntry credentialEntry : content.getCredentialEntries()) {
- String entryId = UUID.randomUUID().toString();
- mUiCredentials.put(entryId, credentialEntry);
+ for (CredentialEntry credentialEntry : credentialEntries) {
+ String entryId = generateEntryId();
+ mUiCredentialEntries.put(entryId, credentialEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
- Slice slice = credentialEntry.getSlice();
- // TODO : Remove conversion of string to int after change in Entry class
- credentialEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+ credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice()));
}
- // populate the action chip
- for (Action action : content.getActions()) {
- String entryId = UUID.randomUUID().toString();
- mUiActions.put(entryId, action);
- // TODO : Remove conversion of string to int after change in Entry class
- actionChips.add(new Entry(ACTION_ENTRY_KEY, entryId,
- action.getSlice()));
- }
+ return credentialUiEntries;
+ }
- return new GetCredentialProviderData.Builder(mComponentName.flattenToString())
+ private List<Entry> populateUiActionEntries(@Nullable List<Action> actions) {
+ List<Entry> actionEntries = new ArrayList<>();
+ for (Action action : actions) {
+ String entryId = UUID.randomUUID().toString();
+ mUiActionsEntries.put(entryId, action);
+ // TODO : Remove conversion of string to int after change in Entry class
+ actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice()));
+ }
+ return actionEntries;
+ }
+
+ private GetCredentialProviderData prepareUiProviderData(List<Entry> actionEntries,
+ List<Entry> credentialEntries, Entry authenticationActionEntry,
+ Entry remoteEntry) {
+ return new GetCredentialProviderData.Builder(
+ mComponentName.flattenToString()).setActionChips(actionEntries)
.setCredentialEntries(credentialEntries)
- .setActionChips(actionChips)
- .setAuthenticationEntry(authenticationEntry)
+ .setAuthenticationEntry(authenticationActionEntry)
.build();
}
- /**
- * To be called by {@link ProviderGetSession} when the UI is to be invoked.
- */
- @Nullable
- private GetCredentialProviderData prepareUiProviderDataWithAuthentication(@NonNull
- Action authenticationEntry) {
- // TODO : Implement authentication flow
- return null;
+ /** Updates the response being maintained in state by this provider session. */
+ private void onUpdateResponse(GetCredentialsResponse response) {
+ mProviderResponse = response;
+ if (response.getAuthenticationAction() != null) {
+ Log.i(TAG , "updateResponse with authentication entry");
+ // TODO validate authentication action
+ mAuthenticationAction = response.getAuthenticationAction();
+ updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
+ } else if (response.getCredentialsDisplayContent() != null) {
+ Log.i(TAG , "updateResponse with credentialEntries");
+ // TODO validate response
+ updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+ }
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderIntentController.java b/services/credentials/java/com/android/server/credentials/ProviderIntentController.java
new file mode 100644
index 0000000..0f2e8ec
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderIntentController.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 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.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.CredentialProviderService;
+import android.util.Log;
+
+/**
+ * Class that invokes providers' pending intents and listens to the responses.
+ */
+@SuppressLint("LongLogTag")
+public class ProviderIntentController {
+ private static final String TAG = "ProviderIntentController";
+ /**
+ * Interface to be implemented by any class that wishes to get callbacks from the UI.
+ */
+ public interface ProviderIntentControllerCallback {
+ /** Called when the user makes a selection. */
+ void onProviderIntentResult(Bundle resultData);
+ /** Called when the user cancels the UI. */
+ void onProviderIntentCancelled();
+ }
+
+ private final int mUserId;
+ private final Context mContext;
+ private final ProviderIntentControllerCallback mCallback;
+ private final ResultReceiver mResultReceiver = new ResultReceiver(
+ new Handler(Looper.getMainLooper())) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ Log.i(TAG, "onReceiveResult in providerIntentController");
+
+ if (resultCode == Activity.RESULT_OK) {
+ Log.i(TAG, "onReceiveResult - ACTIVITYOK");
+ mCallback.onProviderIntentResult(resultData);
+ } else if (resultCode == Activity.RESULT_CANCELED) {
+ Log.i(TAG, "onReceiveResult - RESULTCANCELED");
+ mCallback.onProviderIntentCancelled();
+ }
+ // Drop unknown result
+ }
+ };
+
+ public ProviderIntentController(@UserIdInt int userId,
+ Context context,
+ ProviderIntentControllerCallback callback) {
+ mUserId = userId;
+ mContext = context;
+ mCallback = callback;
+ }
+
+ /** Sets up the request data and invokes the given pending intent. */
+ public void setupAndInvokePendingIntent(@NonNull PendingIntent pendingIntent,
+ CreateCredentialRequest request) {
+ Log.i(TAG, "in invokePendingIntent");
+ setupIntent(pendingIntent, request);
+ Log.i(TAG, "in invokePendingIntent receiver set up");
+ Log.i(TAG, "creator package: " + pendingIntent.getIntentSender()
+ .getCreatorPackage());
+
+ try {
+ mContext.startIntentSender(pendingIntent.getIntentSender(),
+ null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ Log.i(TAG, "Error while invoking pending intent");
+ }
+
+ }
+
+ private void setupIntent(PendingIntent pendingIntent, CreateCredentialRequest request) {
+ pendingIntent.getIntent().putExtra(Intent.EXTRA_RESULT_RECEIVER,
+ toIpcFriendlyResultReceiver(mResultReceiver));
+ pendingIntent.getIntent().putExtra(
+ CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
+ request.getData());
+ }
+
+ private <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver(
+ T resultReceiver) {
+ final Parcel parcel = Parcel.obtain();
+ resultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ return ipcFriendly;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 3a9f964..14a9157 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -17,25 +17,70 @@
package com.android.server.credentials;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.Credential;
import android.credentials.ui.ProviderData;
+import android.os.Bundle;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderInfo;
+import java.util.UUID;
+
/**
* Provider session storing the state of provider response and ui entries.
- * @param <T> The request type expected from the remote provider, for a given request session.
+ * @param <T> The request to be sent to the provider
+ * @param <R> The response to be expected from the provider
*/
-public abstract class ProviderSession<T> implements RemoteCredentialService.ProviderCallbacks<T> {
+public abstract class ProviderSession<T, R> implements RemoteCredentialService.ProviderCallbacks<R>,
+ ProviderIntentController.ProviderIntentControllerCallback {
// Key to be used as the entry key for an action entry
protected static final String ACTION_ENTRY_KEY = "action_key";
+ @NonNull protected final Context mContext;
@NonNull protected final ComponentName mComponentName;
@NonNull protected final CredentialProviderInfo mProviderInfo;
@NonNull protected final RemoteCredentialService mRemoteCredentialService;
@NonNull protected final int mUserId;
@NonNull protected Status mStatus = Status.NOT_STARTED;
@NonNull protected final ProviderInternalCallback mCallbacks;
+ @NonNull protected final ProviderIntentController mProviderIntentController;
+ @Nullable protected Credential mFinalCredentialResponse;
+ @NonNull protected final T mProviderRequest;
+ @Nullable protected R mProviderResponse;
+
+ /**
+ * Returns true if the given status reflects that the provider state is ready to be shown
+ * on the credMan UI.
+ */
+ public static boolean isUiInvokingStatus(Status status) {
+ return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED;
+ }
+
+ /**
+ * Returns true if the given status reflects that the provider is waiting for a remote
+ * response.
+ */
+ public static boolean isStatusWaitingForRemoteResponse(Status status) {
+ return status == Status.PENDING;
+ }
+
+ /**
+ * Returns true if the given status means that the provider session must be terminated.
+ */
+ public static boolean isTerminatingStatus(Status status) {
+ return status == Status.CANCELED || status == Status.SERVICE_DEAD;
+ }
+
+ /**
+ * Returns true if the given status reflects that the provider is done getting the response,
+ * and is ready to return the final credential back to the user.
+ */
+ public static boolean isCompletionStatus(Status status) {
+ return status == Status.CREDENTIAL_RECEIVED_FROM_INTENT
+ || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION;
+ }
/**
* Interface to be implemented by any class that wishes to get a callback when a particular
@@ -49,35 +94,49 @@
void onProviderStatusChanged(Status status, ComponentName componentName);
}
- protected ProviderSession(@NonNull CredentialProviderInfo info,
+ protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
+ @NonNull T providerRequest,
@NonNull ProviderInternalCallback callbacks,
@NonNull int userId,
@NonNull RemoteCredentialService remoteCredentialService) {
+ mContext = context;
mProviderInfo = info;
+ mProviderRequest = providerRequest;
mCallbacks = callbacks;
mUserId = userId;
mComponentName = info.getServiceInfo().getComponentName();
mRemoteCredentialService = remoteCredentialService;
+ mProviderIntentController = new ProviderIntentController(userId, context, this);
}
- /** Update the response state stored with the provider session. */
- protected abstract void updateResponse (T response);
-
- /** Update the response state stored with the provider session. */
- protected abstract T getResponse ();
-
- /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
- * shown on the UI. */
- protected abstract ProviderData prepareUiData();
-
/** Provider status at various states of the request session. */
+ // TODO: Review status values, and adjust where needed
enum Status {
NOT_STARTED,
PENDING,
REQUIRES_AUTHENTICATION,
- COMPLETE,
+ CREDENTIALS_RECEIVED,
SERVICE_DEAD,
- CANCELED
+ CREDENTIAL_RECEIVED_FROM_INTENT,
+ PENDING_INTENT_INVOKED,
+ CREDENTIAL_RECEIVED_FROM_SELECTION,
+ SAVE_ENTRIES_RECEIVED, CANCELED
+ }
+
+ /** Converts exception to a provider session status. */
+ @NonNull
+ public static Status toStatus(
+ @CredentialProviderException.CredentialProviderError int errorCode) {
+ // TODO : Add more mappings as more flows are supported
+ return Status.CANCELED;
+ }
+
+ protected String generateEntryId() {
+ return UUID.randomUUID().toString();
+ }
+
+ public Credential getFinalCredentialResponse() {
+ return mFinalCredentialResponse;
}
protected void setStatus(@NonNull Status status) {
@@ -94,31 +153,38 @@
return mComponentName;
}
+ @NonNull
+ protected RemoteCredentialService getRemoteCredentialService() {
+ return mRemoteCredentialService;
+ }
+
/** Updates the status .*/
protected void updateStatusAndInvokeCallback(@NonNull Status status) {
setStatus(status);
mCallbacks.onProviderStatusChanged(status, mComponentName);
}
- @NonNull
- public static Status toStatus(
- @CredentialProviderException.CredentialProviderError int errorCode) {
- // TODO : Add more mappings as more flows are supported
- return Status.CANCELED;
+ /** Get the request to be sent to the provider. */
+ protected T getProviderRequest() {
+ return mProviderRequest;
}
- /**
- * Returns true if the given status means that the provider session must be terminated.
- */
- public static boolean isTerminatingStatus(Status status) {
- return status == Status.CANCELED || status == Status.SERVICE_DEAD;
+ /** Update the response state stored with the provider session. */
+ @Nullable protected R getProviderResponse() {
+ return mProviderResponse;
}
- /**
- * Returns true if the given status means that the provider is done getting the response,
- * and is ready for user interaction.
- */
- public static boolean isCompletionStatus(Status status) {
- return status == Status.COMPLETE || status == Status.REQUIRES_AUTHENTICATION;
- }
+ /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
+ * shown on the UI. */
+ @Nullable protected abstract ProviderData prepareUiData();
+
+ /** Should be overridden to handle the selected entry from the UI. */
+ protected abstract void onUiEntrySelected(String entryType, String entryId);
+
+ @Override
+ public abstract void onProviderIntentResult(Bundle resultData);
+
+ @Override
+ public abstract void onProviderIntentCancelled();
+
}
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index d0b6e7d..c2464b5 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -24,11 +24,14 @@
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.CreateCredentialResponse;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderException.CredentialProviderError;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
+import android.service.credentials.ICreateCredentialCallback;
import android.service.credentials.ICredentialProviderService;
import android.service.credentials.IGetCredentialsCallback;
import android.text.format.DateUtils;
@@ -76,7 +79,7 @@
public RemoteCredentialService(@NonNull Context context,
@NonNull ComponentName componentName, int userId) {
super(context, new Intent(CredentialProviderService.SERVICE_INTERFACE)
- .setComponent(componentName), Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
+ .setComponent(componentName), /*bindingFlags=*/0,
userId, ICredentialProviderService.Stub::asInterface);
mComponentName = componentName;
}
@@ -101,7 +104,7 @@
* provider service.
* @param request the request to be sent to the provider
* @param callback the callback to be used to send back the provider response to the
- * {@link ProviderSession} class that maintains provider state
+ * {@link ProviderGetSession} class that maintains provider state
*/
public void onGetCredentials(@NonNull GetCredentialsRequest request,
ProviderCallbacks<GetCredentialsResponse> callback) {
@@ -114,21 +117,21 @@
CompletableFuture<GetCredentialsResponse> getCredentials = new CompletableFuture<>();
ICancellationSignal cancellationSignal =
service.onGetCredentials(request, new IGetCredentialsCallback.Stub() {
- @Override
- public void onSuccess(GetCredentialsResponse response) {
- Log.i(TAG, "In onSuccess in RemoteCredentialService");
- getCredentials.complete(response);
- }
+ @Override
+ public void onSuccess(GetCredentialsResponse response) {
+ Log.i(TAG, "In onSuccess in RemoteCredentialService");
+ getCredentials.complete(response);
+ }
- @Override
- public void onFailure(@CredentialProviderError int errorCode,
- CharSequence message) {
- Log.i(TAG, "In onFailure in RemoteCredentialService");
- String errorMsg = message == null ? "" : String.valueOf(message);
- getCredentials.completeExceptionally(new CredentialProviderException(
- errorCode, errorMsg));
- }
- });
+ @Override
+ public void onFailure(@CredentialProviderError int errorCode,
+ CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" : String.valueOf(message);
+ getCredentials.completeExceptionally(new CredentialProviderException(
+ errorCode, errorMsg));
+ }
+ });
CompletableFuture<GetCredentialsResponse> future = futureRef.get();
if (future != null && future.isCancelled()) {
dispatchCancellationSignal(cancellationSignal);
@@ -137,38 +140,91 @@
}
return getCredentials;
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
- futureRef.set(connectThenExecute);
- connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> {
- if (error == null) {
- Log.i(TAG, "In RemoteCredentialService execute error is null");
- callback.onProviderResponseSuccess(result);
+ futureRef.set(connectThenExecute);
+ connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+ handleExecutionResponse(result, error, cancellationSink, callback)));
+ }
+
+ /** Main entry point to be called for executing a createCredential call on the remote
+ * provider service.
+ * @param request the request to be sent to the provider
+ * @param callback the callback to be used to send back the provider response to the
+ * {@link ProviderCreateSession} class that maintains provider state
+ */
+ public void onCreateCredential(@NonNull CreateCredentialRequest request,
+ ProviderCallbacks<CreateCredentialResponse> callback) {
+ Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
+ AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+ AtomicReference<CompletableFuture<CreateCredentialResponse>> futureRef =
+ new AtomicReference<>();
+
+ CompletableFuture<CreateCredentialResponse> connectThenExecute = postAsync(service -> {
+ CompletableFuture<CreateCredentialResponse> createCredentialFuture =
+ new CompletableFuture<>();
+ ICancellationSignal cancellationSignal = service.onCreateCredential(
+ request, new ICreateCredentialCallback.Stub() {
+ @Override
+ public void onSuccess(CreateCredentialResponse response) {
+ Log.i(TAG, "In onSuccess onCreateCredential "
+ + "in RemoteCredentialService");
+ createCredentialFuture.complete(response);
+ }
+
+ @Override
+ public void onFailure(@CredentialProviderError int errorCode,
+ CharSequence message) {
+ Log.i(TAG, "In onFailure in RemoteCredentialService");
+ String errorMsg = message == null ? "" : String.valueOf(message);
+ createCredentialFuture.completeExceptionally(
+ new CredentialProviderException(errorCode, errorMsg));
+ }});
+ CompletableFuture<CreateCredentialResponse> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellationSignal);
} else {
- if (error instanceof TimeoutException) {
- Log.i(TAG, "In RemoteCredentialService execute error is timeout");
- dispatchCancellationSignal(cancellationSink.get());
- callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_TIMEOUT,
- error.getMessage());
- } else if (error instanceof CancellationException) {
- Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
- dispatchCancellationSignal(cancellationSink.get());
- callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_TASK_CANCELED,
- error.getMessage());
- } else if (error instanceof CredentialProviderException) {
- Log.i(TAG, "In RemoteCredentialService execute error is provider error");
- callback.onProviderResponseFailure(((CredentialProviderException) error)
- .getErrorCode(),
- error.getMessage());
- } else {
- Log.i(TAG, "In RemoteCredentialService execute error is unknown");
- callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_UNKNOWN,
- error.getMessage());
- }
+ cancellationSink.set(cancellationSignal);
}
- }));
+ return createCredentialFuture;
+ }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+
+ futureRef.set(connectThenExecute);
+ connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+ handleExecutionResponse(result, error, cancellationSink, callback)));
+ }
+
+ private <T> void handleExecutionResponse(T result,
+ Throwable error,
+ AtomicReference<ICancellationSignal> cancellationSink,
+ ProviderCallbacks<T> callback) {
+ if (error == null) {
+ Log.i(TAG, "In RemoteCredentialService execute error is null");
+ callback.onProviderResponseSuccess(result);
+ } else {
+ if (error instanceof TimeoutException) {
+ Log.i(TAG, "In RemoteCredentialService execute error is timeout");
+ dispatchCancellationSignal(cancellationSink.get());
+ callback.onProviderResponseFailure(
+ CredentialProviderException.ERROR_TIMEOUT,
+ error.getMessage());
+ } else if (error instanceof CancellationException) {
+ Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
+ dispatchCancellationSignal(cancellationSink.get());
+ callback.onProviderResponseFailure(
+ CredentialProviderException.ERROR_TASK_CANCELED,
+ error.getMessage());
+ } else if (error instanceof CredentialProviderException) {
+ Log.i(TAG, "In RemoteCredentialService execute error is provider error");
+ callback.onProviderResponseFailure(((CredentialProviderException) error)
+ .getErrorCode(),
+ error.getMessage());
+ } else {
+ Log.i(TAG, "In RemoteCredentialService execute error is unknown");
+ callback.onProviderResponseFailure(
+ CredentialProviderException.ERROR_UNKNOWN,
+ error.getMessage());
+ }
+ }
}
private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 1bacbb3..056d0e8 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -20,18 +20,30 @@
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.ui.ProviderData;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
/**
* Base class of a request session, that listens to UI events. This class must be extended
* every time a new response type is expected from the providers.
*/
-abstract class RequestSession implements CredentialManagerUi.CredentialManagerUiCallback,
+abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback,
ProviderSession.ProviderInternalCallback {
+ private static final String TAG = "RequestSession";
+
+ // TODO: Revise access levels of attributes
+ @NonNull protected final T mClientRequest;
+ @NonNull protected final U mClientCallback;
@NonNull protected final IBinder mRequestId;
@NonNull protected final Context mContext;
@NonNull protected final CredentialManagerUi mCredentialManagerUi;
@@ -39,29 +51,120 @@
@NonNull protected final Handler mHandler;
@NonNull protected boolean mIsFirstUiTurn = true;
@UserIdInt protected final int mUserId;
+ @NonNull protected final String mClientCallingPackage;
+
+ protected final Map<String, ProviderSession> mProviders = new HashMap<>();
protected RequestSession(@NonNull Context context,
- @UserIdInt int userId, @NonNull String requestType) {
+ @UserIdInt int userId, @NonNull T clientRequest, U clientCallback,
+ @NonNull String requestType,
+ String clientCallingPackage) {
mContext = context;
mUserId = userId;
+ mClientRequest = clientRequest;
+ mClientCallback = clientCallback;
mRequestType = requestType;
+ mClientCallingPackage = clientCallingPackage;
mHandler = new Handler(Looper.getMainLooper(), null, true);
mRequestId = new Binder();
mCredentialManagerUi = new CredentialManagerUi(mContext,
mUserId, this);
}
- /** Returns the unique identifier of this request session. */
- public IBinder getRequestId() {
- return mRequestId;
+ public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+ RemoteCredentialService remoteCredentialService);
+
+ protected abstract void launchUiWithProviderData(ArrayList<ProviderData> providerDataList);
+
+ // UI callbacks
+
+ @Override // from CredentialManagerUiCallbacks
+ public void onUiSelection(UserSelectionDialogResult selection) {
+ String providerId = selection.getProviderId();
+ Log.i(TAG, "onUiSelection, providerId: " + providerId);
+ ProviderSession providerSession = mProviders.get(providerId);
+ if (providerSession == null) {
+ Log.i(TAG, "providerSession not found in onUiSelection");
+ return;
+ }
+ Log.i(TAG, "Provider session found");
+ providerSession.onUiEntrySelected(selection.getEntryKey(),
+ selection.getEntrySubkey());
}
- @Override // from CredentialManagerUiCallback
- public abstract void onUiSelection(UserSelectionDialogResult selection);
+ @Override // from CredentialManagerUiCallbacks
+ public void onUiCancellation() {
+ // User canceled the activity
+ finishSession();
+ }
- @Override // from CredentialManagerUiCallback
- public abstract void onUiCancelation();
+ @Override // from provider session
+ public void onProviderStatusChanged(ProviderSession.Status status,
+ ComponentName componentName) {
+ Log.i(TAG, "in onStatusChanged with status: " + status);
+ if (ProviderSession.isTerminatingStatus(status)) {
+ Log.i(TAG, "in onStatusChanged terminating status");
+ onProviderTerminated(componentName);
+ //TODO: Check if this was the provider we were waiting for and can invoke the UI now
+ } else if (ProviderSession.isCompletionStatus(status)) {
+ Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+ onProviderResponseComplete(componentName);
+ } else if (ProviderSession.isUiInvokingStatus(status)) {
+ Log.i(TAG, "in onStatusChanged isUiInvokingStatus status");
+ onProviderResponseRequiresUi();
+ }
+ }
- @Override // from ProviderInternalCallback
- public abstract void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName);
+ protected void onProviderTerminated(ComponentName componentName) {
+ //TODO: Implement
+ }
+
+ protected void onProviderResponseComplete(ComponentName componentName) {
+ //TODO: Implement
+ }
+
+ protected void onProviderResponseRequiresUi() {
+ Log.i(TAG, "in onProviderResponseComplete");
+ // TODO: Determine whether UI has already been invoked, and deal accordingly
+ if (!isAnyProviderPending()) {
+ Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
+ getProviderDataAndInitiateUi();
+ } else {
+ Log.i(TAG, "Can't invoke UI - waiting on some providers");
+ }
+ }
+
+ protected void finishSession() {
+ clearProviderSessions();
+ }
+
+ protected void clearProviderSessions() {
+ //TODO: Implement
+ mProviders.clear();
+ }
+
+ private boolean isAnyProviderPending() {
+ for (ProviderSession session : mProviders.values()) {
+ if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void getProviderDataAndInitiateUi() {
+ ArrayList<ProviderData> providerDataList = new ArrayList<>();
+ for (ProviderSession session : mProviders.values()) {
+ Log.i(TAG, "preparing data for : " + session.getComponentName());
+ ProviderData providerData = session.prepareUiData();
+ if (providerData != null) {
+ Log.i(TAG, "Provider data is not null");
+ providerDataList.add(providerData);
+ }
+ }
+ if (!providerDataList.isEmpty()) {
+ Log.i(TAG, "provider list not empty about to initiate ui");
+ launchUiWithProviderData(providerDataList);
+ }
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 316c736..89cbf53 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -646,6 +646,15 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long USE_SET_LOCATION_ENABLED = 117835097L;
+ /**
+ * Forces wipeDataNoLock to attempt removing the user or throw an error as
+ * opposed to trying to factory reset the device first and only then falling back to user
+ * removal.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long EXPLICIT_WIPE_BEHAVIOUR = 242193913L;
+
// Only add to the end of the list. Do not change or rearrange these values, that will break
// historical data. Do not use negative numbers or zero, logger only handles positive
// integers.
@@ -6699,8 +6708,8 @@
}
@Override
- public void wipeDataWithReason(int flags, String wipeReasonForUser,
- boolean calledOnParentInstance) {
+ public void wipeDataWithReason(int flags, @NonNull String wipeReasonForUser,
+ boolean calledOnParentInstance, boolean factoryReset) {
if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
return;
}
@@ -6782,7 +6791,8 @@
"DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
adminName, calledByProfileOwnerOnOrgOwnedDevice);
- wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId);
+ wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
+ calledOnParentInstance, factoryReset);
}
private String getGenericWipeReason(
@@ -6844,8 +6854,13 @@
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
}
+ /**
+ * @param factoryReset null: legacy behaviour, false: attempt to remove user, true: attempt to
+ * factory reset
+ */
private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
- String wipeReasonForUser, int userId) {
+ @NonNull String wipeReasonForUser, int userId, boolean calledOnParentInstance,
+ @Nullable Boolean factoryReset) {
wtfIfInLock();
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -6863,7 +6878,37 @@
+ " restriction is set for user " + userId);
}
- if (userId == UserHandle.USER_SYSTEM) {
+ boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+ boolean wipeDevice;
+ if (factoryReset == null || !CompatChanges.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR)) {
+ // Legacy mode
+ wipeDevice = isSystemUser;
+ } else {
+ // Explicit behaviour
+ if (factoryReset) {
+ // TODO(b/254031494) Replace with new factory reset permission checks
+ boolean hasPermission = isDeviceOwnerUserId(userId)
+ || (isOrganizationOwnedDeviceWithManagedProfile()
+ && calledOnParentInstance);
+ Preconditions.checkState(hasPermission,
+ "Admin %s does not have permission to factory reset the device.",
+ userId);
+ wipeDevice = true;
+ } else {
+ Preconditions.checkCallAuthorization(!isSystemUser,
+ "User %s is a system user and cannot be removed", userId);
+ boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
+ && mUserManager.getAliveUsers().stream()
+ .filter((it) -> it.getUserHandle().getIdentifier() != userId)
+ .noneMatch(UserInfo::isFull);
+ Preconditions.checkState(!isLastNonHeadlessUser,
+ "Removing user %s would leave the device without any active users. "
+ + "Consider factory resetting the device instead.",
+ userId);
+ wipeDevice = false;
+ }
+ }
+ if (wipeDevice) {
forceWipeDeviceNoLock(
(flags & WIPE_EXTERNAL_STORAGE) != 0,
internalReason,
@@ -7131,7 +7176,7 @@
}
@Override
- public void reportFailedPasswordAttempt(int userHandle) {
+ public void reportFailedPasswordAttempt(int userHandle, boolean parent) {
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
final CallerIdentity caller = getCallerIdentity();
@@ -7153,7 +7198,7 @@
saveSettingsLocked(userHandle);
if (mHasFeature) {
strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
- userHandle, /* parent */ false);
+ userHandle, /* parent= */ false);
int max = strictestAdmin != null
? strictestAdmin.maximumFailedPasswordsForWipe : 0;
if (max > 0 && policy.mFailedPasswordAttempts >= max) {
@@ -7183,10 +7228,13 @@
// IMPORTANT: Call without holding the lock to prevent deadlock.
try {
wipeDataNoLock(strictestAdmin.info.getComponent(),
- /*flags=*/ 0,
- /*reason=*/ "reportFailedPasswordAttempt()",
+ /* flags= */ 0,
+ /* reason= */ "reportFailedPasswordAttempt()",
getFailedPasswordAttemptWipeMessage(),
- userId);
+ userId,
+ /* calledOnParentInstance= */ parent,
+ // factoryReset=null to enable U- behaviour
+ /* factoryReset= */ null);
} catch (SecurityException e) {
Slogf.w(LOG_TAG, "Failed to wipe user " + userId
+ " after max failed password attempts reached.", e);
@@ -7195,7 +7243,7 @@
if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
- /*result*/ 0, /*method strength*/ 1);
+ /* result= */ 0, /* method strength= */ 1);
}
}
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index 46e59a9..c3329795 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -83,9 +83,10 @@
Slogf.d(TAG, "Creating initial user");
t.traceBegin("create-initial-user");
try {
+ int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
// TODO(b/204091126): proper name for user
UserInfo newUser = um.createUserEvenWhenDisallowed("Real User",
- UserManager.USER_TYPE_FULL_SECONDARY, UserInfo.FLAG_ADMIN,
+ UserManager.USER_TYPE_FULL_SECONDARY, flags,
/* disallowedPackages= */ null, /* token= */ null);
Slogf.i(TAG, "Created initial user: %s", newUser.toFullString());
initialUserId = newUser.id;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fe2d0be..d406e30 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -103,6 +103,7 @@
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockSettingsInternal;
import com.android.server.am.ActivityManagerService;
import com.android.server.ambientcontext.AmbientContextManagerService;
import com.android.server.appbinding.AppBindingService;
@@ -3019,6 +3020,14 @@
t.traceEnd();
}, t);
+ t.traceBegin("LockSettingsThirdPartyAppsStarted");
+ LockSettingsInternal lockSettingsInternal =
+ LocalServices.getService(LockSettingsInternal.class);
+ if (lockSettingsInternal != null) {
+ lockSettingsInternal.onThirdPartyAppsStarted();
+ }
+ t.traceEnd();
+
t.traceBegin("StartSystemUI");
try {
startSystemUi(context, windowManagerF);
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index eab3b77..292320e 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -53,6 +53,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -372,7 +373,8 @@
@Override
public boolean equals(Object o) {
ListenerKey key = (ListenerKey) o;
- return key.getPackageName().equals(mPackageName) && key.getUserId() == mUserId
+ return key.getPackageName().equals(mPackageName)
+ && Objects.equals(key.getUserId(), mUserId)
&& key.getShortcutId().equals(mShortcutId);
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 3fbc400..640bde3 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -244,7 +244,7 @@
.setCurrentMethodVisible();
}
verify(mMockInputMethod, times(showSoftInput ? 1 : 0))
- .showSoftInput(any(), anyInt(), any());
+ .showSoftInput(any(), any(), anyInt(), any());
}
protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)
@@ -254,6 +254,6 @@
.setCurrentMethodNotVisible();
}
verify(mMockInputMethod, times(hideSoftInput ? 1 : 0))
- .hideSoftInput(any(), anyInt(), any());
+ .hideSoftInput(any(), any(), anyInt(), any());
}
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 73b1907c..681bfcf 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -56,6 +56,7 @@
"service-jobscheduler",
"service-permission.impl",
"service-sdksandbox.impl",
+ "services.backup",
"services.companion",
"services.core",
"services.devicepolicy",
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index d78f6d83..24e5175 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -1507,6 +1507,39 @@
}
@Test
+ public void testSwitchUser() {
+ mockManageUsersGranted();
+ mockModifyGameModeGranted();
+
+ mockDeviceConfigBattery();
+ final Context context = InstrumentationRegistry.getContext();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper(), context.getFilesDir());
+ startUser(gameManagerService, USER_ID_1);
+ startUser(gameManagerService, USER_ID_2);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+ GameManager.GAME_MODE_BATTERY);
+ assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+ GameManager.GAME_MODE_BATTERY);
+
+ mockDeviceConfigAll();
+ switchUser(gameManagerService, USER_ID_1, USER_ID_2);
+ assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_2),
+ GameManager.GAME_MODE_STANDARD);
+ checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+ GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+
+ switchUser(gameManagerService, USER_ID_2, USER_ID_1);
+ checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+ GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ }
+
+ @Test
public void testUpdateResolutionScalingFactor() {
mockModifyGameModeGranted();
mockDeviceConfigBattery();
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index e1713b0..98e895a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -22,6 +22,7 @@
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_WIFI_SCAN;
import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
@@ -127,6 +128,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -137,6 +140,8 @@
.update();
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
@@ -151,6 +156,8 @@
.update();
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
@@ -169,6 +176,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -183,6 +192,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -197,6 +208,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -211,6 +224,8 @@
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -314,6 +329,8 @@
.update();
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -328,6 +345,8 @@
.update();
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -403,6 +422,8 @@
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -418,6 +439,8 @@
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
@@ -433,6 +456,8 @@
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
new file mode 100644
index 0000000..f535997
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 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.backup;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupAndRestoreFeatureFlagsTest {
+ @Rule
+ public TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ @Test
+ public void getBackupTransportFutureTimeoutMillis_notSet_returnsDefault() {
+ assertThat(
+ BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis()).isEqualTo(
+ 600000);
+ }
+
+ @Test
+ public void getBackupTransportFutureTimeoutMillis_set_returnsSetValue() {
+ DeviceConfig.setProperty("backup_and_restore", "backup_transport_future_timeout_millis",
+ "1234", false);
+
+ assertThat(
+ BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis()).isEqualTo(
+ 1234);
+ }
+
+ @Test
+ public void getBackupTransportCallbackTimeoutMillis_notSet_returnsDefault() {
+ assertThat(
+ BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis()).isEqualTo(
+ 300000);
+ }
+
+ @Test
+ public void getBackupTransportCallbackTimeoutMillis_set_returnsSetValue() {
+ DeviceConfig.setProperty("backup_and_restore", "backup_transport_callback_timeout_millis",
+ "5678", false);
+
+ assertThat(
+ BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis()).isEqualTo(
+ 5678);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index dc49a94..4c28c51 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -144,7 +144,7 @@
SensorManager sensorManager) {
return new DisplayPowerProximityStateController(wakelockController,
displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
- sensorManager);
+ sensorManager, /* injector= */ null);
}
};
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
new file mode 100644
index 0000000..6e91b24
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2022 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.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerProximityStateControllerTest {
+ @Mock
+ WakelockController mWakelockController;
+
+ @Mock
+ DisplayDeviceConfig mDisplayDeviceConfig;
+
+ @Mock
+ Runnable mNudgeUpdatePowerState;
+
+ @Mock
+ SensorManager mSensorManager;
+
+ private Sensor mProximitySensor;
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+ private SensorEventListener mSensorEventListener;
+ private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+ @Before
+ public void before() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ // This is kept null because currently there is no way to define a sensor
+ // name in TestUtils
+ name = null;
+ }
+ });
+ setUpProxSensor();
+ DisplayPowerProximityStateController.Injector injector =
+ new DisplayPowerProximityStateController.Injector() {
+ @Override
+ DisplayPowerProximityStateController.Clock createClock() {
+ return new DisplayPowerProximityStateController.Clock() {
+ @Override
+ public long uptimeMillis() {
+ return mClock.now();
+ }
+ };
+ }
+ };
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, 0,
+ mSensorManager, injector);
+ mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
+ // Set the system to pending wait for proximity
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Update the pending proximity wait request
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
+ // Will not wait or be in the pending wait state of not already pending
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
+ throws Exception {
+ // Set the system to the state where it will ignore proximity unless changed
+ enableProximitySensor();
+ emitAndValidatePositiveProximityEvent();
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+ advanceTime(1);
+ assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ verify(mNudgeUpdatePowerState, times(2)).run();
+
+ // Do not set the system to pending wait for proximity
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Set the system to pending wait for proximity. But because the proximity is being
+ // ignored, it will not wait or not set the pending wait
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void cleanupDisablesTheProximitySensor() {
+ enableProximitySensor();
+ mDisplayPowerProximityStateController.cleanup();
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsFalseWhenNotAvailable() {
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = null;
+ name = null;
+ }
+ });
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, 1,
+ mSensorManager, null);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
+ DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
+ when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ name = null;
+ }
+ });
+ Sensor newProxSensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(newProxSensor));
+ mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
+ updatedDisplayDeviceConfig);
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
+ // Doesn't do anything not asked to wait
+ assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ false));
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Sets pending wait negative proximity if not already waiting
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Will not set pending wait negative proximity if already waiting
+ assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ }
+
+ @Test
+ public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+ }
+
+ @Test
+ public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+
+ // Set the system to pending wait for proximity
+ mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
+ // Update the pending proximity wait request
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+
+ // Start ignoring proximity sensor
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ assertTrue(
+ mDisplayPowerProximityStateController
+ .shouldSkipRampBecauseOfProximityChangeToNegative());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+ }
+
+ @Test
+ public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ @Test
+ public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
+ throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+
+ private void setUpProxSensor() throws Exception {
+ mProximitySensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(mProximitySensor));
+ }
+
+ private void emitAndValidatePositiveProximityEvent() throws Exception {
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
+ verify(mSensorManager).registerListener(mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+ verify(mNudgeUpdatePowerState).run();
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ // Call evaluateProximityState with the request for using the proximity sensor. This will
+ // register the proximity sensor listener, which will be needed for mocking positive
+ // proximity scenarios.
+ private void enableProximitySensor() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.useProximitySensor = true;
+ mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ verify(mSensorManager).registerListener(
+ mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ verifyZeroInteractions(mWakelockController);
+ }
+
+ private void setScreenOffBecauseOfPositiveProximityState() {
+ // Prepare a request to indicate that the proximity sensor is to be used
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.useProximitySensor = true;
+
+ Runnable onProximityPositiveRunnable = mock(Runnable.class);
+ when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
+ onProximityPositiveRunnable);
+
+ mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ verify(mSensorManager).registerListener(
+ mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 923c3e3..9be370f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -153,14 +153,8 @@
assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
}
- @Test
- public void testUnassignUserFromDisplay() {
- testAssignUserToDisplay_displayAvailable();
-
- mMediator.unassignUserFromDisplay(USER_ID);
-
- assertNoUserAssignedToDisplay();
- }
+ // TODO(b/244644281): when start & assign are merged, rename tests above and also call
+ // stopUserAndAssertState() at the end of them
@Test
public void testIsUserVisible_bgUserOnSecondaryDisplay() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index 7af5f5d..7abdd9e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -15,8 +15,6 @@
*/
package com.android.server.pm;
-import static android.os.UserHandle.USER_SYSTEM;
-
import static org.junit.Assert.assertThrows;
import org.junit.Test;
@@ -34,6 +32,9 @@
super(/* usersOnSecondaryDisplaysEnabled= */ false);
}
+ // TODO(b/244644281): when start & assign are merged, rename tests below and also call
+ // stopUserAndAssertState() at the end of them
+
@Test
public void testAssignUserToDisplay_otherDisplay_currentUser() {
mockCurrentUser(USER_ID);
@@ -59,15 +60,4 @@
assertThrows(UnsupportedOperationException.class, () -> mMediator
.assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID));
}
-
- @Test
- public void testUnassignUserFromDisplay_ignored() {
- mockCurrentUser(USER_ID);
-
- mMediator.unassignUserFromDisplay(USER_SYSTEM);
- mMediator.unassignUserFromDisplay(USER_ID);
- mMediator.unassignUserFromDisplay(OTHER_USER_ID);
-
- assertNoUserAssignedToDisplay();
- }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 7b20092..e8be97d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -20,11 +20,11 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
-import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_FAILURE;
-import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_INVISIBLE;
-import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_VISIBLE;
-import static com.android.server.pm.UserVisibilityMediator.startUserResultToString;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -100,79 +100,92 @@
@Test
public final void testStartUser_currentUser() {
- int result = mMediator.startUser(USER_ID, USER_ID, FG, DEFAULT_DISPLAY);
- assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+ int result = mMediator.startOnly(USER_ID, USER_ID, FG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
assertCurrentUser(USER_ID);
assertIsCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
assertStartedProfileGroupIdOf(USER_ID, USER_ID);
+
+ stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_currentUserSecondaryDisplay() {
- int result = mMediator.startUser(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
- assertStartUserResult(result, START_USER_RESULT_FAILURE);
+ int result = mMediator.startOnly(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
assertIsNotCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
assertStartedProfileGroupIdOf(USER_ID, NO_PROFILE_GROUP_ID);
+
+ stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileBg_parentStarted() {
mockCurrentUser(PARENT_USER_ID);
- int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
- assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+ int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
assertCurrentUser(PARENT_USER_ID);
assertIsCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
- assertIsStartedProfile(PROFILE_USER_ID);
+ assertProfileIsStarted(PROFILE_USER_ID);
+
+ stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileBg_parentNotStarted() {
- int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
- assertStartUserResult(result, START_USER_RESULT_SUCCESS_INVISIBLE);
+ int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
- assertIsStartedProfile(PROFILE_USER_ID);
+ assertProfileIsStarted(PROFILE_USER_ID);
+
+ stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileBg_secondaryDisplay() {
- int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID);
- assertStartUserResult(result, START_USER_RESULT_FAILURE);
+ int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
+
+ stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileFg() {
- int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY);
- assertStartUserResult(result, START_USER_RESULT_FAILURE);
+ int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
- assertStartedProfileGroupIdOf(PROFILE_USER_ID, NO_PROFILE_GROUP_ID);
+
+ stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileFgSecondaryDisplay() {
- int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID);
+ int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID);
- assertStartUserResult(result, START_USER_RESULT_FAILURE);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
+
+ stopUserAndAssertState(USER_ID);
}
@Test
public final void testGetStartedProfileGroupId_whenStartedWithNoProfileGroupId() {
- int result = mMediator.startUser(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY);
- assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+ int result = mMediator.startOnly(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
assertWithMessage("shit").that(mMediator.getStartedProfileGroupId(USER_ID))
.isEqualTo(USER_ID);
@@ -386,59 +399,78 @@
.isEqualTo(USER_ID);
}
+ /**
+ * Stops the given user and assert the proper state is set.
+ *
+ * <p>This method should be called at the end of tests that starts a user, so it can test
+ * {@code stopUser()} as well (technically speaking, {@code stopUser()} should be tested on its
+ * own methods, but it depends on the user being started at first place, so pragmatically
+ * speaking, it's better to "reuse" such tests for both (start and stop)
+ */
+ private void stopUserAndAssertState(@UserIdInt int userId) {
+ mMediator.stopUser(userId);
+
+ assertUserIsStopped(userId);
+ assertNoUserAssignedToDisplay();
+ }
+
// TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
// it's not meant to be used to test startUser() itself.
protected void mockCurrentUser(@UserIdInt int userId) {
Log.d(TAG, "mockCurrentUser(" + userId + ")");
- int result = mMediator.startUser(userId, userId, FG, DEFAULT_DISPLAY);
- if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
+ int result = mMediator.startOnly(userId, userId, FG, DEFAULT_DISPLAY);
+ if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
throw new IllegalStateException("Failed to mock current user " + userId
- + ": mediator returned " + startUserResultToString(result));
+ + ": mediator returned " + userAssignmentResultToString(result));
}
}
- // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+ // TODO(b/244644281): remove when start & assign are merged; or add a note explaining
// it's not meant to be used to test startUser() itself.
protected void startDefaultProfile() {
mockCurrentUser(PARENT_USER_ID);
Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting"
+ " its parent (" + PARENT_USER_ID + ") on foreground");
- int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
- if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
+ int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+ if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
- + ": mediator returned " + startUserResultToString(result));
+ + ": mediator returned " + userAssignmentResultToString(result));
}
}
- // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+ // TODO(b/244644281): remove when start & assign are merged; or add a note explaining
// it's not meant to be used to test stopUser() itself.
protected void stopDefaultProfile() {
Log.d(TAG, "stopping default profile");
mMediator.stopUser(PROFILE_USER_ID);
}
- // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+ // TODO(b/244644281): remove when start & assign are merged; or add a note explaining
// it's not meant to be used to test assignUserToDisplay() itself.
protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
Log.d(TAG, "assignUserToDisplay(" + userId + ", " + displayId + ")");
- int result = mMediator.startUser(userId, userId, BG, displayId);
- if (result != START_USER_RESULT_SUCCESS_INVISIBLE) {
+ int result = mMediator.startOnly(userId, userId, BG, displayId);
+ if (result != USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE) {
throw new IllegalStateException("Failed to startuser " + userId
- + " on background: mediator returned " + startUserResultToString(result));
+ + " on background: mediator returned " + userAssignmentResultToString(result));
}
mMediator.assignUserToDisplay(userId, userId, displayId);
}
+ // TODO(b/244644281): remove when start & assign are merged; or rename to
+ // assertNoUserAssignedToSecondaryDisplays
protected final void assertNoUserAssignedToDisplay() {
- assertWithMessage("uses on secondary displays")
+ assertWithMessage("users on secondary displays")
.that(mMediator.getUsersOnSecondaryDisplays())
.isEmpty();
}
+ // TODO(b/244644281): remove when start & assign are merged; or rename to
+ // assertUserAssignedToSecondaryDisplay
protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
- assertWithMessage("uses on secondary displays")
+ assertWithMessage("users on secondary displays")
.that(mMediator.getUsersOnSecondaryDisplays())
.containsExactly(userId, displayId);
}
@@ -446,24 +478,44 @@
private void assertCurrentUser(@UserIdInt int userId) {
assertWithMessage("mediator.getCurrentUserId()").that(mMediator.getCurrentUserId())
.isEqualTo(userId);
+ if (userId != INITIAL_CURRENT_USER_ID) {
+ assertUserIsStarted(userId);
+ }
}
- private void assertIsStartedProfile(@UserIdInt int userId) {
+ private void assertUserIsStarted(@UserIdInt int userId) {
+ assertWithMessage("mediator.isStarted(%s)", userId).that(mMediator.isStartedUser(userId))
+ .isTrue();
+ }
+
+ private void assertUserIsStopped(@UserIdInt int userId) {
+ assertWithMessage("mediator.isStarted(%s)", userId).that(mMediator.isStartedUser(userId))
+ .isFalse();
+ }
+
+ private void assertProfileIsStarted(@UserIdInt int userId) {
assertWithMessage("mediator.isStartedProfile(%s)", userId)
.that(mMediator.isStartedProfile(userId))
.isTrue();
+ assertUserIsStarted(userId);
}
- private void assertStartedProfileGroupIdOf(@UserIdInt int profileId, @UserIdInt int parentId) {
- assertWithMessage("mediator.getStartedProfileGroupId(%s)", profileId)
- .that(mMediator.getStartedProfileGroupId(profileId))
- .isEqualTo(parentId);
+ private void assertStartedProfileGroupIdOf(@UserIdInt int userId,
+ @UserIdInt int profileGroupId) {
+ assertWithMessage("mediator.getStartedProfileGroupId(%s)", userId)
+ .that(mMediator.getStartedProfileGroupId(userId))
+ .isEqualTo(profileGroupId);
}
- private void assertIsCurrentUserOrRunningProfileOfCurrentUser(int userId) {
+ private void assertIsCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
assertWithMessage("mediator.isCurrentUserOrRunningProfileOfCurrentUser(%s)", userId)
.that(mMediator.isCurrentUserOrRunningProfileOfCurrentUser(userId))
.isTrue();
+ if (mMediator.getCurrentUserId() == userId) {
+ assertUserIsStarted(userId);
+ } else {
+ assertProfileIsStarted(userId);
+ }
}
private void assertIsNotCurrentUserOrRunningProfileOfCurrentUser(int userId) {
@@ -474,8 +526,8 @@
private void assertStartUserResult(int actualResult, int expectedResult) {
assertWithMessage("startUser() result (where %s=%s and %s=%s)",
- actualResult, startUserResultToString(actualResult),
- expectedResult, startUserResultToString(expectedResult))
+ actualResult, userAssignmentResultToString(actualResult),
+ expectedResult, userAssignmentResultToString(expectedResult))
.that(actualResult).isEqualTo(expectedResult);
}
}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 6551bde..6349b21 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -107,6 +107,9 @@
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
+ <intent>
+ <action android:name="android.media.browse.MediaBrowserService" />
+ </intent>
</queries>
<!-- Uses API introduced in O (26) -->
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 9052f58..9c7ce83 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -33,6 +33,7 @@
<option name="test-file-name" value="SimpleServiceTestApp1.apk" />
<option name="test-file-name" value="SimpleServiceTestApp2.apk" />
<option name="test-file-name" value="SimpleServiceTestApp3.apk" />
+ <option name="test-file-name" value="FakeMediaApp.apk" />
</target_preparer>
<!-- Create place to store apks -->
diff --git a/services/tests/servicestests/res/xml/usertypes_test_full.xml b/services/tests/servicestests/res/xml/usertypes_test_full.xml
index 099ccbe..9568143 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_full.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_full.xml
@@ -16,7 +16,8 @@
<user-types>
<full-type
name='android.test.1'
- max-allowed-per-parent='12' >
+ max-allowed-per-parent='12'
+ max-allowed='17' >
<default-restrictions no_remove_user='true' no_bluetooth='true' />
<badge-colors>
<item res='@*android:color/profile_badge_1' />
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index b27f49d..1a6dae37 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -33,6 +33,7 @@
<user-properties
showInLauncher='2020'
startWithParent='false'
+ useParentsContacts='false'
/>
</profile-type>
<profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 80cee50..a49214f 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -39,6 +39,8 @@
import static com.android.server.am.UserController.USER_START_MSG;
import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.google.android.collect.Lists.newArrayList;
import static com.google.android.collect.Sets.newHashSet;
@@ -100,6 +102,7 @@
import com.android.server.SystemService;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -162,10 +165,15 @@
USER_VISIBILITY_CHANGED_MSG,
USER_CURRENT_MSG);
- private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+ private static final Set<Integer> START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
USER_START_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
+ private static final Set<Integer> START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+ USER_START_MSG,
+ USER_VISIBILITY_CHANGED_MSG,
+ REPORT_LOCKED_BOOT_COMPLETE_MSG);
+
@Before
public void setUp() throws Exception {
runWithDexmakerShareClassLoader(() -> {
@@ -184,6 +192,12 @@
mockIsUsersOnSecondaryDisplaysEnabled(false);
// All UserController params are set to default.
+ // Starts with a generic assumption that the user starts visible, but on tests where
+ // that's not the case, the test should call mockAssignUserToMainDisplay()
+ doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE)
+ .when(mInjector.mUserManagerInternalMock)
+ .assignUserToDisplayOnStart(anyInt(), anyInt(), anyBoolean(), anyInt());
+
mUserController = new UserController(mInjector);
mUserController.setAllowUserUnlocking(true);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
@@ -211,16 +225,29 @@
@Test
public void testStartUser_background() {
+ mockAssignUserToMainDisplay(TEST_USER_ID, /* foreground= */ false,
+ USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
- startBackgroundUserAssertions();
+ startBackgroundUserAssertions(/*visible= */ false);
verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
}
@Test
+ public void testStartUser_displayAssignmentFailed() {
+ doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE)
+ .when(mInjector.mUserManagerInternalMock)
+ .assignUserToDisplayOnStart(eq(TEST_USER_ID), anyInt(), eq(true), anyInt());
+
+ boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
+
+ assertWithMessage("startUser(%s, foreground=true)", TEST_USER_ID).that(started).isFalse();
+ }
+
+ @Test
public void testStartUserOnSecondaryDisplay_defaultDisplay() {
assertThrows(IllegalArgumentException.class, () -> mUserController
.startUserOnSecondaryDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY));
@@ -240,7 +267,7 @@
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
- startBackgroundUserAssertions();
+ startBackgroundUserAssertions(/*visible= */ true);
}
@Test
@@ -266,6 +293,8 @@
@Test
public void testStartPreCreatedUser_background() throws Exception {
+ mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
+ USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
// Make sure no intents have been fired for pre-created users.
assertTrue(mInjector.mSentIntents.isEmpty());
@@ -284,8 +313,6 @@
// binder calls, but their side effects (in this case, that the user is stopped right away)
assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
.containsExactly(USER_START_MSG);
-
- verifyUserNeverAssignedToDisplay();
}
private void startUserAssertions(
@@ -295,8 +322,10 @@
assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
}
- private void startBackgroundUserAssertions() {
- startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
+ private void startBackgroundUserAssertions(boolean visible) {
+ startUserAssertions(START_BACKGROUND_USER_ACTIONS,
+ visible ? START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES
+ : START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES);
}
private void startForegroundUserAssertions() {
@@ -680,19 +709,24 @@
@Test
public void testStartProfile() throws Exception {
+ mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
+ USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
setUpAndStartProfileInBackground(TEST_USER_ID1);
- startBackgroundUserAssertions();
+ startBackgroundUserAssertions(/*visible= */ true);
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@Test
public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
+ mockAssignUserToMainDisplay(TEST_USER_ID1, /* foreground= */ false,
+ USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
mockIsUsersOnSecondaryDisplaysEnabled(true);
setUpAndStartProfileInBackground(TEST_USER_ID1);
- startBackgroundUserAssertions();
+ startBackgroundUserAssertions(/*visible= */ true);
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@@ -949,22 +983,29 @@
when(mInjector.isUsersOnSecondaryDisplaysEnabled()).thenReturn(value);
}
+ private void mockAssignUserToMainDisplay(@UserIdInt int userId, boolean foreground,
+ @UserAssignmentResult int result) {
+ when(mInjector.mUserManagerInternalMock.assignUserToDisplayOnStart(eq(userId),
+ /* profileGroupId= */ anyInt(), eq(foreground), eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(result);
+ }
+
private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
- verify(mInjector.getUserManagerInternal()).assignUserToDisplay(eq(userId), anyInt(),
+ verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
anyBoolean(), eq(displayId));
}
private void verifyUserNeverAssignedToDisplay() {
- verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt(),
- anyBoolean(), anyInt());
+ verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplayOnStart(anyInt(),
+ anyInt(), anyBoolean(), anyInt());
}
private void verifyUserUnassignedFromDisplay(@UserIdInt int userId) {
- verify(mInjector.getUserManagerInternal()).unassignUserFromDisplay(userId);
+ verify(mInjector.getUserManagerInternal()).unassignUserFromDisplayOnStop(userId);
}
private void verifyUserUnassignedFromDisplayNeverCalled(@UserIdInt int userId) {
- verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
+ verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplayOnStop(userId);
}
private void verifySystemUserVisibilityChangedNotified(boolean visible) {
diff --git a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
index ea746d1..faad961 100644
--- a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
+++ b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
@@ -30,7 +30,7 @@
import android.view.Display;
import android.view.Surface;
-import java.util.HashMap;
+import java.util.Map;
@RunWith(JUnit4.class)
public class CameraServiceProxyTest {
@@ -75,24 +75,22 @@
/*ignoreResizableAndSdkCheck*/true)).isEqualTo(
CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
// Check rotation and lens facing combinations
- HashMap<Integer, Integer> backFacingMap = new HashMap<Integer, Integer>() {{
- put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
- put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
- put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
- put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
- }};
+ Map<Integer, Integer> backFacingMap = Map.of(
+ Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE,
+ Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90,
+ Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270,
+ Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
taskInfo.isFixedOrientationPortrait = true;
backFacingMap.forEach((key, value) -> {
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
key, CameraCharacteristics.LENS_FACING_BACK,
/*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
});
- HashMap<Integer, Integer> frontFacingMap = new HashMap<Integer, Integer>() {{
- put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
- put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
- put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
- put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
- }};
+ Map<Integer, Integer> frontFacingMap = Map.of(
+ Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE,
+ Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270,
+ Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90,
+ Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
frontFacingMap.forEach((key, value) -> {
assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
key, CameraCharacteristics.LENS_FACING_FRONT,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 4c939f0..0262f56 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -82,6 +82,7 @@
/* blockedActivities= */ new ArraySet<>(),
VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
/* activityListener= */ null,
+ /* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,
/* secureWindowCallback= */ null,
/* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ddb3049..8e669f0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -140,7 +140,7 @@
import android.security.keystore.AttestationUtils;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
-import android.test.MoreAsserts; // TODO(b/171932723): replace by Truth
+import android.test.MoreAsserts;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -5087,7 +5087,7 @@
}
@Test
- public void testWipeDataDeviceOwner() throws Exception {
+ public void testWipeDevice_DeviceOwner() throws Exception {
setDeviceOwner();
when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_FACTORY_RESET,
@@ -5096,7 +5096,7 @@
when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
thenReturn("Just a test string.");
- dpm.wipeData(0);
+ dpm.wipeDevice(0);
verifyRebootWipeUserData(/* wipeEuicc= */ false);
}
@@ -5111,13 +5111,13 @@
when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
thenReturn("Just a test string.");
- dpm.wipeData(WIPE_EUICC);
+ dpm.wipeDevice(WIPE_EUICC);
verifyRebootWipeUserData(/* wipeEuicc= */ true);
}
@Test
- public void testWipeDataDeviceOwnerDisallowed() throws Exception {
+ public void testWipeDevice_DeviceOwnerDisallowed() throws Exception {
setDeviceOwner();
when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_FACTORY_RESET,
@@ -5128,7 +5128,7 @@
// The DO is not allowed to wipe the device if the user restriction was set
// by the system
assertExpectException(SecurityException.class, /* messageRegex= */ null,
- () -> dpm.wipeData(0));
+ () -> dpm.wipeDevice(0));
}
@Test
@@ -7986,7 +7986,7 @@
}
@Test
- public void testWipeData_financeDo_success() throws Exception {
+ public void testWipeDevice_financeDo_success() throws Exception {
setDeviceOwner();
dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
when(getServices().userManager.getUserRestrictionSource(
@@ -7997,7 +7997,7 @@
.getString(R.string.work_profile_deleted_description_dpm_wipe))
.thenReturn("Test string");
- dpm.wipeData(0);
+ dpm.wipeDevice(0);
verifyRebootWipeUserData(/* wipeEuicc= */ false);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
index df672c9..2c4fe53 100644
--- a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
@@ -424,7 +424,7 @@
@Override
public LocalDate getLocalDate() {
- return LocalDate.from(mLocalDate);
+ return mLocalDate;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index fa5b6b2..657bda6 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -22,8 +22,6 @@
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
-import static com.android.server.display.LogicalDisplay.DISPLAY_PHASE_DISABLED;
-import static com.android.server.display.LogicalDisplay.DISPLAY_PHASE_ENABLED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
@@ -54,8 +52,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.display.layout.Layout;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -87,7 +83,6 @@
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
- @Mock DeviceStateToLayoutMap mDeviceStateToLayoutMapMock;
@Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
@@ -133,13 +128,11 @@
when(mResourcesMock.getIntArray(
com.android.internal.R.array.config_deviceStatesOnWhichToSleep))
.thenReturn(new int[]{0});
- when(mDeviceStateToLayoutMapMock.get(-1)).thenReturn(new Layout());
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
- mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
- mDeviceStateToLayoutMapMock);
+ mListenerMock, new DisplayManagerService.SyncRoot(), mHandler);
}
@@ -510,58 +503,6 @@
/* isBootCompleted= */true));
}
- @Test
- public void testDeviceStateLocked() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
- Layout layout = new Layout();
- layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true);
- layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false);
- when(mDeviceStateToLayoutMapMock.get(0)).thenReturn(layout);
-
- layout = new Layout();
- layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false);
- layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true);
- when(mDeviceStateToLayoutMapMock.get(1)).thenReturn(layout);
- when(mDeviceStateToLayoutMapMock.get(2)).thenReturn(layout);
-
- LogicalDisplay display1 = add(device1);
- assertEquals(info(display1).address, info(device1).address);
- assertEquals(DEFAULT_DISPLAY, id(display1));
-
- LogicalDisplay display2 = add(device2);
- assertEquals(info(display2).address, info(device2).address);
- // We can only have one default display
- assertEquals(DEFAULT_DISPLAY, id(display1));
-
- mLogicalDisplayMapper.setDeviceStateLocked(0, false);
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
- assertEquals(DISPLAY_PHASE_ENABLED,
- mLogicalDisplayMapper.getDisplayLocked(device1).getPhase());
- assertEquals(DISPLAY_PHASE_DISABLED,
- mLogicalDisplayMapper.getDisplayLocked(device2).getPhase());
-
- mLogicalDisplayMapper.setDeviceStateLocked(1, false);
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
- assertEquals(DISPLAY_PHASE_DISABLED,
- mLogicalDisplayMapper.getDisplayLocked(device1).getPhase());
- assertEquals(DISPLAY_PHASE_ENABLED,
- mLogicalDisplayMapper.getDisplayLocked(device2).getPhase());
-
- mLogicalDisplayMapper.setDeviceStateLocked(2, false);
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
- assertEquals(DISPLAY_PHASE_DISABLED,
- mLogicalDisplayMapper.getDisplayLocked(device1).getPhase());
- assertEquals(DISPLAY_PHASE_ENABLED,
- mLogicalDisplayMapper.getDisplayLocked(device2).getPhase());
- }
-
/////////////////
// Helper Methods
/////////////////
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
index 0454587..a419b3f 100644
--- a/services/tests/servicestests/src/com/android/server/display/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -51,6 +51,12 @@
}
}
+ public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception {
+ Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE);
+ setter.setAccessible(true);
+ setter.invoke(sensor, maximumRange, 1);
+ }
+
public static Sensor createSensor(int type, String strType) throws Exception {
Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
constr.setAccessible(true);
@@ -59,6 +65,16 @@
return sensor;
}
+ public static Sensor createSensor(int type, String strType, float maximumRange)
+ throws Exception {
+ Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+ constr.setAccessible(true);
+ Sensor sensor = constr.newInstance();
+ setSensorType(sensor, type, strType);
+ setMaximumRange(sensor, maximumRange);
+ return sensor;
+ }
+
/**
* Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
* display-address implementation in our code. Intentionally uses default object (reference)
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 9092ec3..0884b78 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -367,6 +367,7 @@
assertFalse(item_en_us_allcaps.mIsSystemLocale);
}
+ @SuppressWarnings("SelfComparison")
@Test
public void testImeSubtypeListComparator() throws Exception {
final ComponentName imeX1 = new ComponentName("com.example.imeX", "Ime1");
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index 9c8e72c..f5029ec 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -71,7 +71,7 @@
private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
private static final long POLL_INTERVAL = 500;
- private static final long DEFAULT_WAIT_TIMEOUT = 5000;
+ private static final long DEFAULT_WAIT_TIMEOUT = 10_000;
private Context mContext;
private AppOpsManager mAppOpsManager;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
index 1e855a9..1eb4fa5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTestable.java
@@ -58,4 +58,4 @@
} catch (Exception e) {
}
}
-};
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java
new file mode 100644
index 0000000..1c4ee69
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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.media;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaButtonReceiverHolderTest {
+
+ @Test
+ public void createMediaButtonReceiverHolder_resolvesNullComponentName() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ PendingIntent pi = PendingIntent.getBroadcast(context, /* requestCode= */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ MediaButtonReceiverHolder a = MediaButtonReceiverHolder.create(/* userId= */ 0, pi,
+ context.getPackageName());
+ Truth.assertWithMessage("Component name must match PendingIntent creator package.").that(
+ a.getComponentName()).isNull();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/OWNERS b/services/tests/servicestests/src/com/android/server/media/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 5ece871..1f952c4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -61,11 +61,13 @@
.setStartWithParent(false)
.setShowInSettings(45)
.setInheritDevicePolicy(67)
+ .setUseParentsContacts(false)
.build();
final UserProperties actualProps = new UserProperties(defaultProps);
actualProps.setShowInLauncher(14);
actualProps.setShowInSettings(32);
actualProps.setInheritDevicePolicy(51);
+ actualProps.setUseParentsContacts(true);
// Write the properties to xml.
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -152,11 +154,14 @@
// Items requiring hasManagePermission - put them here using hasManagePermission.
assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
hasManagePermission);
+ assertEqualGetterOrThrows(orig::getUseParentsContacts,
+ copy::getUseParentsContacts, hasManagePermission);
// Items requiring hasQueryPermission - put them here using hasQueryPermission.
// Items with no permission requirements.
assertEqualGetterOrThrows(orig::getShowInLauncher, copy::getShowInLauncher, true);
+
}
/**
@@ -196,7 +201,7 @@
assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
- assertThat(expected.getInheritDevicePolicy()).isEqualTo(
- actual.getInheritDevicePolicy());
+ assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy());
+ assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 5f48004..d7c1e37 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -83,7 +83,8 @@
/* flags= */0,
/* letsPersonalDataIntoProfile= */false).build());
final UserProperties.Builder userProps = new UserProperties.Builder()
- .setShowInLauncher(17);
+ .setShowInLauncher(17)
+ .setUseParentsContacts(true);
final UserTypeDetails type = new UserTypeDetails.Builder()
.setName("a.name")
.setEnabled(1)
@@ -140,6 +141,7 @@
}
assertEquals(17, type.getDefaultUserPropertiesReference().getShowInLauncher());
+ assertTrue(type.getDefaultUserPropertiesReference().getUseParentsContacts());
assertEquals(23, type.getBadgeLabel(0));
assertEquals(24, type.getBadgeLabel(1));
@@ -182,6 +184,7 @@
final UserProperties props = type.getDefaultUserPropertiesReference();
assertNotNull(props);
assertFalse(props.getStartWithParent());
+ assertFalse(props.getUseParentsContacts());
assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
assertFalse(type.hasBadge());
@@ -263,7 +266,8 @@
final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
final UserProperties.Builder props = new UserProperties.Builder()
.setShowInLauncher(19)
- .setStartWithParent(true);
+ .setStartWithParent(true)
+ .setUseParentsContacts(true);
final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
builders.put(userTypeAosp1, new UserTypeDetails.Builder()
.setName(userTypeAosp1)
@@ -289,7 +293,9 @@
assertEquals(Resources.ID_NULL, aospType.getIconBadge());
assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
assertEquals(19, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
- assertEquals(true, aospType.getDefaultUserPropertiesReference().getStartWithParent());
+ assertTrue(aospType.getDefaultUserPropertiesReference().getStartWithParent());
+ assertTrue(aospType.getDefaultUserPropertiesReference()
+ .getUseParentsContacts());
// userTypeAosp2 should be modified.
aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -319,7 +325,9 @@
makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
aospType.getDefaultRestrictions()));
assertEquals(2020, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
- assertEquals(false, aospType.getDefaultUserPropertiesReference().getStartWithParent());
+ assertFalse(aospType.getDefaultUserPropertiesReference().getStartWithParent());
+ assertFalse(aospType.getDefaultUserPropertiesReference()
+ .getUseParentsContacts());
// userTypeOem1 should be created.
UserTypeDetails.Builder customType = builders.get(userTypeOem1);
@@ -347,6 +355,7 @@
UserTypeDetails details = builders.get(userTypeFull).createUserTypeDetails();
assertEquals(UNLIMITED_NUMBER_OF_USERS, details.getMaxAllowedPerParent());
assertFalse(details.isEnabled());
+ assertEquals(17, details.getMaxAllowed());
assertTrue(UserRestrictionsUtils.areEqual(
makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
details.getDefaultRestrictions()));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index a3c45b7..2e7e583 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
@@ -164,6 +165,14 @@
@Test
public void testCloneUser() throws Exception {
+
+ // Get the default properties for clone user type.
+ final UserTypeDetails userTypeDetails =
+ UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_CLONE);
+ assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_CLONE)
+ .that(userTypeDetails).isNotNull();
+ final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+
// Test that only one clone user can be created
final int primaryUserId = mUserManager.getPrimaryUser().id;
UserInfo userInfo = createProfileForUser("Clone user1",
@@ -187,6 +196,16 @@
.collect(Collectors.toList());
assertThat(cloneUsers.size()).isEqualTo(1);
+ // Check that the new clone user has the expected properties (relative to the defaults)
+ // provided that the test caller has the necessary permissions.
+ UserProperties cloneUserProperties =
+ mUserManager.getUserProperties(UserHandle.of(userInfo.id));
+ assertThat(typeProps.getUseParentsContacts())
+ .isEqualTo(cloneUserProperties.getUseParentsContacts());
+ assertThat(typeProps.getShowInLauncher())
+ .isEqualTo(cloneUserProperties.getShowInLauncher());
+ assertThrows(SecurityException.class, cloneUserProperties::getStartWithParent);
+
// Verify clone user parent
assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
@@ -600,6 +619,7 @@
// provided that the test caller has the necessary permissions.
assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
+ assertFalse(userProps.getUseParentsContacts());
assertThrows(SecurityException.class, userProps::getStartWithParent);
assertThrows(SecurityException.class, userProps::getInheritDevicePolicy);
}
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp b/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp
new file mode 100644
index 0000000..a4041b7
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "FakeMediaApp",
+
+ sdk_version: "current",
+
+ srcs: ["**/*.java"],
+
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml
new file mode 100644
index 0000000..c08ee7a
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.fakemediaapp">
+
+ <application>
+ <receiver
+ android:name=".FakeMediaButtonBroadcastReceiver"
+ android:enabled="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MEDIA_BUTTON" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS b/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS
new file mode 100644
index 0000000..55ffde2
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java b/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java
new file mode 100644
index 0000000..41f0cf5
--- /dev/null
+++ b/services/tests/servicestests/test-apps/FakeMediaApp/src/FakeMediaButtonBroadcastReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 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.servicestests.apps.fakemediaapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class FakeMediaButtonBroadcastReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "FakeMediaButtonBroadcastReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.v(TAG, "onReceive not expected");
+ }
+}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
index 3e79407..b8585f2 100644
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
+++ b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
@@ -34,7 +34,8 @@
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "Test job executing: " + params.getJobId());
Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
- reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+ reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
sendBroadcast(reportJobStartIntent);
return true;
}
@@ -43,7 +44,8 @@
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "Test job stopped executing: " + params.getJobId());
Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
- reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+ reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
sendBroadcast(reportJobStopIntent);
// Deadline constraint is dropped on reschedule, so it's more reliable to use a new job.
return false;
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 668345d..d54d1fe 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7605,6 +7605,65 @@
}
@Test
+ public void testAddAutomaticZenRule_systemCallTakesPackageFromOwner() throws Exception {
+ mService.isSystemUid = true;
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.setZenHelper(mockZenModeHelper);
+ ComponentName owner = new ComponentName("android", "ProviderName");
+ ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+ boolean isEnabled = true;
+ AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+ zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+ // verify that zen mode helper gets passed in a package name of "android"
+ verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+ }
+
+ @Test
+ public void testAddAutomaticZenRule_systemAppIdCallTakesPackageFromOwner() throws Exception {
+ // The multi-user case: where the calling uid doesn't match the system uid, but the calling
+ // *appid* is the system.
+ mService.isSystemUid = false;
+ mService.isSystemAppId = true;
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.setZenHelper(mockZenModeHelper);
+ ComponentName owner = new ComponentName("android", "ProviderName");
+ ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+ boolean isEnabled = true;
+ AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+ zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+ // verify that zen mode helper gets passed in a package name of "android"
+ verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+ }
+
+ @Test
+ public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception {
+ mService.isSystemUid = false;
+ mService.isSystemAppId = false;
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.setZenHelper(mockZenModeHelper);
+ ComponentName owner = new ComponentName("android", "ProviderName");
+ ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+ boolean isEnabled = true;
+ AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+ zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+ mBinderService.addAutomaticZenRule(rule, "another.package");
+
+ // verify that zen mode helper gets passed in the package name from the arg, not the owner
+ verify(mockZenModeHelper).addAutomaticZenRule(
+ eq("another.package"), eq(rule), anyString());
+ }
+
+ @Test
public void testAreNotificationsEnabledForPackage() throws Exception {
mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
mUid);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 0f93598..b64b281 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2727,7 +2727,7 @@
@Test
public void testCreateChannel_addToGroup() {
- NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "group");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false));
@@ -3177,8 +3177,8 @@
@Test
public void testGetNotificationChannelGroupWithChannels() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("group", "");
- NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "group");
+ NotificationChannelGroup other = new NotificationChannelGroup("something else", "name");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, other, true);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 8cf74fb..61a6985 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -32,6 +32,7 @@
public class TestableNotificationManagerService extends NotificationManagerService {
int countSystemChecks = 0;
boolean isSystemUid = true;
+ boolean isSystemAppId = true;
int countLogSmartSuggestionsVisible = 0;
Set<Integer> mChannelToastsSent = new HashSet<>();
@@ -58,6 +59,12 @@
}
@Override
+ protected boolean isCallingAppIdSystem() {
+ countSystemChecks++;
+ return isSystemUid || isSystemAppId;
+ }
+
+ @Override
protected boolean isCallerSystemOrPhone() {
countSystemChecks++;
return isSystemUid;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index ba61980..49edde5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1672,6 +1672,36 @@
}
@Test
+ public void testAddAutomaticZenRule_claimedSystemOwner() {
+ // Make sure anything that claims to have a "system" owner but not actually part of the
+ // system package still gets limited on number of rules
+ for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+ ScheduleInfo si = new ScheduleInfo();
+ si.startHour = i;
+ AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+ new ComponentName("android", "ScheduleConditionProvider" + i),
+ null, // configuration activity
+ ZenModeConfig.toScheduleConditionId(si),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ assertNotNull(id);
+ }
+ try {
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ new ComponentName("android", "ScheduleConditionProviderFinal"),
+ null, // configuration activity
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ fail("allowed too many rules to be created");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
+ }
+
+ @Test
public void testAddAutomaticZenRule_CA() {
AutomaticZenRule zenRule = new AutomaticZenRule("name",
null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 376399a..85c4975 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -324,7 +324,7 @@
// The activity reports fully drawn before windows drawn, then the fully drawn event will
// be pending (see {@link WindowingModeTransitionInfo#pendingFullyDrawn}).
- mActivityMetricsLogger.logAppTransitionReportedDrawn(mTopActivity, false);
+ mActivityMetricsLogger.notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
notifyTransitionStarting(mTopActivity);
// The pending fully drawn event should send when the actual windows drawn event occurs.
final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity);
@@ -337,7 +337,7 @@
verifyNoMoreInteractions(mLaunchObserver);
final ActivityMetricsLogger.TransitionInfoSnapshot fullyDrawnInfo = mActivityMetricsLogger
- .logAppTransitionReportedDrawn(mTopActivity, false /* restoredFromBundle */);
+ .notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
assertWithMessage("Invisible event must be dropped").that(fullyDrawnInfo).isNull();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 3a8e1cc..bd8da4e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2284,8 +2284,7 @@
doReturn(false).when(mAtm).shouldDisableNonVrUiLocked();
spyOn(mDisplayContent.mDwpcHelper);
- doReturn(false).when(mDisplayContent.mDwpcHelper).isWindowingModeSupported(
- WINDOWING_MODE_PINNED);
+ doReturn(false).when(mDisplayContent.mDwpcHelper).isEnteringPipAllowed(anyInt());
assertFalse(activity.checkEnterPictureInPictureState("TEST", false /* beforeStopping */));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 07dba00..fc1989e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1262,6 +1262,26 @@
}
@Test
+ public void testRecycleTaskWakeUpWhenDreaming() {
+ doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+ doReturn(true).when(mWm.mAtmService).isDreaming();
+ final ActivityStarter starter = prepareStarter(0 /* flags */);
+ final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ starter.mStartActivity = target;
+ target.mVisibleRequested = false;
+ target.setTurnScreenOn(true);
+ // Assume the flag was consumed by relayout.
+ target.setCurrentLaunchCanTurnScreenOn(false);
+ startActivityInner(starter, target, null /* source */, null /* options */,
+ null /* inTask */, null /* inTaskFragment */);
+ // The flag should be set again when resuming (from recycleTask) the target as top.
+ assertTrue(target.currentLaunchCanTurnScreenOn());
+ // In real case, dream activity has a higher priority (TaskDisplayArea#getPriority) that
+ // will be put at a higher z-order. So it relies on wakeUp() to be dismissed.
+ verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+ }
+
+ @Test
public void testTargetTaskInSplitScreen() {
final ActivityStarter starter =
prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 52af8ad..d99946f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -45,6 +45,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
@@ -375,10 +376,10 @@
displayPolicy.setCanSystemBarsBeShownByUser(false);
displayPolicy.requestTransientBars(windowState, true);
- verify(controlTarget, never()).showInsets(anyInt(), anyBoolean());
+ verify(controlTarget, never()).showInsets(anyInt(), anyBoolean(), any() /* statsToken */);
displayPolicy.setCanSystemBarsBeShownByUser(true);
displayPolicy.requestTransientBars(windowState, true);
- verify(controlTarget).showInsets(anyInt(), anyBoolean());
+ verify(controlTarget).showInsets(anyInt(), anyBoolean(), any() /* statsToken */);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index 21197ba..db1d15a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -246,5 +246,10 @@
public boolean canShowTasksInRecents() {
return true;
}
+
+ @Override
+ public boolean isEnteringPipAllowed(int uid) {
+ return true;
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index ac3d0f0..75c5b6e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -213,7 +213,7 @@
assertThat(newTaskBounds).isEqualTo(newDagBounds);
// Activity config bounds is unchanged, size compat bounds is (860x[860x860/1200=616])
- assertThat(mFirstActivity.getSizeCompatScale()).isLessThan(1f);
+ assertThat(mFirstActivity.getCompatScale()).isLessThan(1f);
assertThat(activityConfigBounds.width()).isEqualTo(activityBounds.width());
assertThat(activityConfigBounds.height()).isEqualTo(activityBounds.height());
assertThat(activitySizeCompatBounds.height()).isEqualTo(newTaskBounds.height());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index eb8b89d..a26cad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -55,7 +55,7 @@
mDisplayContent.setImeControlTarget(popup);
mDisplayContent.setImeLayeringTarget(appWin);
popup.mAttrs.format = PixelFormat.TRANSPARENT;
- mImeProvider.scheduleShowImePostLayout(appWin);
+ mImeProvider.scheduleShowImePostLayout(appWin, null /* statsToken */);
assertTrue(mImeProvider.isReadyToShowIme());
}
@@ -64,7 +64,7 @@
WindowState target = createWindow(null, TYPE_APPLICATION, "app");
mDisplayContent.setImeLayeringTarget(target);
mDisplayContent.updateImeInputAndControlTarget(target);
- mImeProvider.scheduleShowImePostLayout(target);
+ mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
assertTrue(mImeProvider.isReadyToShowIme());
}
@@ -78,7 +78,7 @@
mDisplayContent.setImeLayeringTarget(target);
mDisplayContent.setImeControlTarget(target);
- mImeProvider.scheduleShowImePostLayout(target);
+ mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
assertFalse(mImeProvider.isImeShowing());
mImeProvider.checkShowImePostLayout();
assertTrue(mImeProvider.isImeShowing());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e5842b4..e65610f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -90,7 +90,6 @@
import android.provider.DeviceConfig.Properties;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
-import android.view.InsetsVisibilities;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
@@ -3209,7 +3208,7 @@
/** Asserts that the size of activity is larger than its parent so it is scaling. */
private void assertScaled() {
assertTrue(mActivity.inSizeCompatMode());
- assertNotEquals(1f, mActivity.getSizeCompatScale(), 0.0001f /* delta */);
+ assertNotEquals(1f, mActivity.getCompatScale(), 0.0001f /* delta */);
}
/** Asserts that the activity is best fitted in the parent. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 83f1789..3ff2c0e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -118,10 +118,13 @@
doReturn(true).when(mTaskFragment).isVisibleRequested();
clearInvocations(mTransaction);
+ mTaskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
mTaskFragment.setBounds(endBounds);
+ assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds));
+ mTaskFragment.initializeChangeTransition(startBounds);
+ mTaskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
// Surface reset when prepare transition.
- verify(mTaskFragment).initializeChangeTransition(startBounds);
verify(mTransaction).setPosition(mLeash, 0, 0);
verify(mTransaction).setWindowCrop(mLeash, 0, 0);
@@ -166,7 +169,7 @@
mTaskFragment.setBounds(endBounds);
- verify(mTaskFragment, never()).initializeChangeTransition(any());
+ assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds));
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index bb5aceb..6e72bf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.Nullable;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -26,6 +27,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.ScrollCaptureResponse;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -117,10 +119,12 @@
}
@Override
- public void showInsets(int types, boolean fromIme) throws RemoteException {
+ public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
+ throws RemoteException {
}
@Override
- public void hideInsets(int types, boolean fromIme) throws RemoteException {
+ public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
+ throws RemoteException {
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 0139f6a..1b888f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -95,6 +95,8 @@
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
@@ -800,6 +802,39 @@
}
@Test
+ public void testEmbeddedActivityResizing_clearAllDrawn() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
+ final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
+ "App window");
+ doReturn(true).when(embeddedActivity).isVisible();
+ embeddedActivity.mVisibleRequested = true;
+ makeWindowVisible(win);
+ win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
+ // Set the bounds twice:
+ // 1. To make sure there is no orientation change after #reportResized, which can also cause
+ // #clearAllDrawn.
+ // 2. Make #isLastConfigReportedToClient to be false after #reportResized, so it can process
+ // to check if we need redraw.
+ embeddedTf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ embeddedTf.setBounds(0, 0, 1000, 2000);
+ win.reportResized();
+ embeddedTf.setBounds(500, 0, 1000, 2000);
+
+ // Clear all drawn when the embedded TaskFragment is in mDisplayContent.mChangingContainers.
+ win.updateResizingWindowIfNeeded();
+ verify(embeddedActivity, never()).clearAllDrawn();
+
+ mDisplayContent.mChangingContainers.add(embeddedTf);
+ win.updateResizingWindowIfNeeded();
+ verify(embeddedActivity).clearAllDrawn();
+ }
+
+ @Test
public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
win0.mActivityRecord.mVisibleRequested = false;
@@ -1000,7 +1035,7 @@
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
assertTrue(mDisplayContent.shouldImeAttachedToApp());
- controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+ controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
controller.getImeSourceProvider().getSource().setVisible(true);
controller.updateAboveInsetsState(false);
@@ -1037,7 +1072,7 @@
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
assertTrue(mDisplayContent.shouldImeAttachedToApp());
- controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+ controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
controller.getImeSourceProvider().getSource().setVisible(true);
controller.updateAboveInsetsState(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index eca7cbb..ab042d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -100,6 +100,7 @@
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
+import android.view.inputmethod.ImeTracker;
import android.window.ITransitionPlayer;
import android.window.ScreenCapture;
import android.window.StartingWindowInfo;
@@ -848,11 +849,13 @@
}
@Override
- public void showInsets(int i, boolean b) throws RemoteException {
+ public void showInsets(int i, boolean b, @Nullable ImeTracker.Token t)
+ throws RemoteException {
}
@Override
- public void hideInsets(int i, boolean b) throws RemoteException {
+ public void hideInsets(int i, boolean b, @Nullable ImeTracker.Token t)
+ throws RemoteException {
}
@Override
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 86f877f..72f6cc3 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -504,6 +504,15 @@
}
@Override
+ public boolean hasDevicePermissionWithIdentity(UsbDevice device, String packageName,
+ int pid, int uid) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+ final int userId = UserHandle.getUserId(uid);
+ return getPermissionsForUser(userId).hasPermission(device, packageName, pid, uid);
+ }
+
+ @Override
public boolean hasAccessoryPermission(UsbAccessory accessory) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -518,6 +527,14 @@
}
@Override
+ public boolean hasAccessoryPermissionWithIdentity(UsbAccessory accessory, int pid, int uid) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+ final int userId = UserHandle.getUserId(uid);
+ return getPermissionsForUser(userId).hasPermission(accessory, pid, uid);
+ }
+
+ @Override
public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
index df63795..7a41b50 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
@@ -46,4 +46,4 @@
// TODO Add reporting specific to this descriptor
super.report(canvas);
}
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
index 4aa8ca2..32275a6 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
@@ -46,4 +46,4 @@
super.report(canvas);
// TODO Add reporting specific to this descriptor
}
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
index 5ce842e..0692066 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
@@ -47,4 +47,4 @@
super.report(canvas);
// TODO Add reporting specific to this descriptor
}
-};
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
index 8e9b0d8..604dd66 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
@@ -47,4 +47,4 @@
super.report(canvas);
// TODO Add reporting specific to this descriptor
}
-};
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
index f921118..d5eea1f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
@@ -163,7 +163,7 @@
private static class SingleAudioStreamCopyTask implements Callable<Void> {
// TODO: Make this buffer size customizable from updateState()
- private static final int COPY_BUFFER_LENGTH = 1_024;
+ private static final int COPY_BUFFER_LENGTH = 2_560;
private final String mStreamTaskId;
private final ParcelFileDescriptor mAudioSource;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 1fc54d6..3e49aed 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -19,7 +19,6 @@
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
-import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -206,7 +205,7 @@
@Nullable AttentionManagerInternal mAttentionManagerInternal = null;
final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
- this::setProximityMeters;
+ this::setProximityValue;
volatile HotwordDetectionServiceIdentity mIdentity;
@@ -504,7 +503,7 @@
mSoftwareCallback.onError();
return;
}
- saveProximityMetersToBundle(result);
+ saveProximityValueToBundle(result);
HotwordDetectedResult newResult;
try {
newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
@@ -639,7 +638,7 @@
externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
return;
}
- saveProximityMetersToBundle(result);
+ saveProximityValueToBundle(result);
HotwordDetectedResult newResult;
try {
newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
@@ -1242,15 +1241,15 @@
});
}
- private void saveProximityMetersToBundle(HotwordDetectedResult result) {
+ private void saveProximityValueToBundle(HotwordDetectedResult result) {
synchronized (mLock) {
if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
- result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
+ result.setProximity(mProximityMeters);
}
}
}
- private void setProximityMeters(double proximityMeters) {
+ private void setProximityValue(double proximityMeters) {
synchronized (mLock) {
mProximityMeters = proximityMeters;
}
@@ -1322,4 +1321,4 @@
private static final String OP_MESSAGE =
"Providing hotword detection result to VoiceInteractionService";
-};
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 5d1901d..0a660b0 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -639,7 +639,7 @@
private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
int detectorType, boolean isCreated, int voiceInteractionServiceUid) {
if (callback != null) {
- HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, true,
+ HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, isCreated,
voiceInteractionServiceUid);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index f8bc499..763024f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -913,4 +913,4 @@
}
}
};
-};
+}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 432af3a..5cef2cb 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -168,6 +168,18 @@
public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
/**
+ * Extra key intended for {@link InCallService}s that notify the user of an incoming call. When
+ * EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB returns true, the {@link InCallService} should not
+ * interrupt the user of the incoming call because the call is being suppressed by Do Not
+ * Disturb settings.
+ *
+ * This extra will be removed from the {@link Call} object for {@link InCallService}s that do
+ * not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+ */
+ public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB =
+ "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
+
+ /**
* Key for extra used to pass along a list of {@link PhoneAccountSuggestion}s to the in-call
* UI when a call enters the {@link #STATE_SELECT_PHONE_ACCOUNT} state. The list included here
* will have the same length and be in the same order as the list passed with
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
index ff87ab0..a69dfb0b 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -111,6 +111,8 @@
public static final int FILTERING_INITIATED = 106;
public static final int FILTERING_COMPLETED = 107;
public static final int FILTERING_TIMED_OUT = 108;
+ public static final int DND_CHECK_INITIATED = 109;
+ public static final int DND_CHECK_COMPLETED = 110;
public static final int SKIP_RINGING = 200;
public static final int SILENCE = 201;
@@ -195,6 +197,7 @@
public static final int BLOCK_CHECK_FINISHED_TIMING = 9;
public static final int FILTERING_COMPLETED_TIMING = 10;
public static final int FILTERING_TIMED_OUT_TIMING = 11;
+ public static final int DND_PRE_CALL_PRE_CHECK_TIMING = 12;
/** {@hide} */
public static final int START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING = 12;
diff --git a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
index 8b01cb3..2787d83 100644
--- a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
+++ b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java
@@ -199,7 +199,6 @@
*/
@Override
public Object clone() throws CloneNotSupportedException {
- super.clone();
int len = mData.length;
byte[] dstBytes = new byte[len];
System.arraycopy(mData, 0, dstBytes, 0, len);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 936fad5..f38b902 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8698,6 +8698,15 @@
public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
/**
+ * Boolean indicating the default VoNR user preference setting.
+ * If true, the VoNR setting will be enabled. If false, it will be disabled initially.
+ *
+ * Enabled by default.
+ *
+ */
+ public static final String KEY_VONR_ON_BY_DEFAULT_BOOL = "vonr_on_by_default_bool";
+
+ /**
* Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
*
* @hide
@@ -9520,6 +9529,7 @@
sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
+ sDefaults.putBoolean(KEY_VONR_ON_BY_DEFAULT_BOOL, true);
sDefaults.putIntArray(KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY, new int[] {});
sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG,
TimeUnit.MINUTES.toMillis(30));
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 23835a7..b83b400 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1620,29 +1620,26 @@
// If we are not able to find the configuration from carrier config, use the default
// ones.
if (permanentFailureSet == null) {
- permanentFailureSet = new HashSet<Integer>() {
- {
- add(OPERATOR_BARRED);
- add(MISSING_UNKNOWN_APN);
- add(UNKNOWN_PDP_ADDRESS_TYPE);
- add(USER_AUTHENTICATION);
- add(ACTIVATION_REJECT_GGSN);
- add(SERVICE_OPTION_NOT_SUPPORTED);
- add(SERVICE_OPTION_NOT_SUBSCRIBED);
- add(NSAPI_IN_USE);
- add(ONLY_IPV4_ALLOWED);
- add(ONLY_IPV6_ALLOWED);
- add(PROTOCOL_ERRORS);
- add(RADIO_POWER_OFF);
- add(TETHERED_CALL_ACTIVE);
- add(RADIO_NOT_AVAILABLE);
- add(UNACCEPTABLE_NETWORK_PARAMETER);
- add(SIGNAL_LOST);
- add(DUPLICATE_CID);
- add(MATCH_ALL_RULE_NOT_ALLOWED);
- add(ALL_MATCHING_RULES_FAILED);
- }
- };
+ permanentFailureSet = new HashSet<Integer>();
+ permanentFailureSet.add(OPERATOR_BARRED);
+ permanentFailureSet.add(MISSING_UNKNOWN_APN);
+ permanentFailureSet.add(UNKNOWN_PDP_ADDRESS_TYPE);
+ permanentFailureSet.add(USER_AUTHENTICATION);
+ permanentFailureSet.add(ACTIVATION_REJECT_GGSN);
+ permanentFailureSet.add(SERVICE_OPTION_NOT_SUPPORTED);
+ permanentFailureSet.add(SERVICE_OPTION_NOT_SUBSCRIBED);
+ permanentFailureSet.add(NSAPI_IN_USE);
+ permanentFailureSet.add(ONLY_IPV4_ALLOWED);
+ permanentFailureSet.add(ONLY_IPV6_ALLOWED);
+ permanentFailureSet.add(PROTOCOL_ERRORS);
+ permanentFailureSet.add(RADIO_POWER_OFF);
+ permanentFailureSet.add(TETHERED_CALL_ACTIVE);
+ permanentFailureSet.add(RADIO_NOT_AVAILABLE);
+ permanentFailureSet.add(UNACCEPTABLE_NETWORK_PARAMETER);
+ permanentFailureSet.add(SIGNAL_LOST);
+ permanentFailureSet.add(DUPLICATE_CID);
+ permanentFailureSet.add(MATCH_ALL_RULE_NOT_ALLOWED);
+ permanentFailureSet.add(ALL_MATCHING_RULES_FAILED);
}
permanentFailureSet.add(NO_RETRY_FAILURE);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e099e69..541573c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14957,21 +14957,132 @@
* @return a Pair of (major version, minor version) or (-1,-1) if unknown.
*
* @hide
+ *
+ * @deprecated Use {@link #getHalVersion} instead.
*/
+ @Deprecated
@UnsupportedAppUsage
@TestApi
public Pair<Integer, Integer> getRadioHalVersion() {
+ return getHalVersion(HAL_SERVICE_RADIO);
+ }
+
+ /** @hide */
+ public static final int HAL_SERVICE_RADIO = 0;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioData
+ * {@link RadioDataProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_DATA = 1;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioMessaging
+ * {@link RadioMessagingProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_MESSAGING = 2;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioModem
+ * {@link RadioModemProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_MODEM = 3;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioNetwork
+ * {@link RadioNetworkProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_NETWORK = 4;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioSim
+ * {@link RadioSimProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_SIM = 5;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioVoice
+ * {@link RadioVoiceProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_VOICE = 6;
+
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioIms
+ * {@link RadioImsProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_IMS = 7;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"HAL_SERVICE_"},
+ value = {
+ HAL_SERVICE_RADIO,
+ HAL_SERVICE_DATA,
+ HAL_SERVICE_MESSAGING,
+ HAL_SERVICE_MODEM,
+ HAL_SERVICE_NETWORK,
+ HAL_SERVICE_SIM,
+ HAL_SERVICE_VOICE,
+ HAL_SERVICE_IMS,
+ })
+ public @interface HalService {}
+
+ /**
+ * The HAL Version indicating that the version is unknown or invalid.
+ * @hide
+ */
+ @TestApi
+ public static final Pair HAL_VERSION_UNKNOWN = new Pair(-1, -1);
+
+ /**
+ * The HAL Version indicating that the version is unsupported.
+ * @hide
+ */
+ @TestApi
+ public static final Pair HAL_VERSION_UNSUPPORTED = new Pair(-2, -2);
+
+ /**
+ * Retrieve the HAL Version of a specific service for this device.
+ *
+ * Get the HAL version for a specific HAL interface for test purposes.
+ *
+ * @param halService the service id to query.
+ * @return a Pair of (major version, minor version), HAL_VERSION_UNKNOWN if unknown
+ * or HAL_VERSION_UNSUPPORTED if unsupported.
+ *
+ * @hide
+ */
+ @TestApi
+ public @NonNull Pair<Integer, Integer> getHalVersion(@HalService int halService) {
try {
ITelephony service = getITelephony();
if (service != null) {
- int version = service.getRadioHalVersion();
- if (version == -1) return new Pair<Integer, Integer>(-1, -1);
- return new Pair<Integer, Integer>(version / 100, version % 100);
+ int version = service.getHalVersion(halService);
+ if (version != -1) {
+ return new Pair<Integer, Integer>(version / 100, version % 100);
+ }
+ } else {
+ throw new IllegalStateException("telephony service is null.");
}
} catch (RemoteException e) {
- Log.e(TAG, "getRadioHalVersion() RemoteException", e);
+ Log.e(TAG, "getHalVersion() RemoteException", e);
+ e.rethrowAsRuntimeException();
}
- return new Pair<Integer, Integer>(-1, -1);
+ return HAL_VERSION_UNKNOWN;
}
/**
@@ -17339,7 +17450,7 @@
* Subsequent attempts will return the same error until the request is made on the default
* data subscription.
*/
- public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14;
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION = 14;
/**
* Purchase premium capability was successful and is waiting for the network to setup the
@@ -17369,7 +17480,7 @@
PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
public @interface PurchasePremiumCapabilityResult {}
@@ -17409,8 +17520,8 @@
return "NETWORK_NOT_AVAILABLE";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED:
return "ENTITLEMENT_CHECK_FAILED";
- case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB:
- return "NOT_DEFAULT_DATA_SUB";
+ case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION:
+ return "NOT_DEFAULT_DATA_SUBSCRIPTION";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP:
return "PENDING_NETWORK_SETUP";
default:
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
index ba2b62d..8e27077 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl
@@ -27,4 +27,5 @@
oneway void createNetworkAvailabilityProvider(int slotId, IQualifiedNetworksServiceCallback callback);
oneway void removeNetworkAvailabilityProvider(int slotId);
oneway void reportThrottleStatusChanged(int slotId, in List<ThrottleStatus> statuses);
+ oneway void reportEmergencyDataNetworkPreferredTransportChanged (int slotId, int transportType);
}
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index 0ab7b61..a0d9c1bd 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -130,6 +130,10 @@
return precedence;
}
+ public int getProtocol() {
+ return protocol;
+ }
+
public static class PortRange implements Parcelable {
int start;
int end;
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index fb97336..56f0f9f 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -68,6 +68,7 @@
private static final int QNS_REMOVE_ALL_NETWORK_AVAILABILITY_PROVIDERS = 3;
private static final int QNS_UPDATE_QUALIFIED_NETWORKS = 4;
private static final int QNS_APN_THROTTLE_STATUS_CHANGED = 5;
+ private static final int QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED = 6;
private final HandlerThread mHandlerThread;
@@ -193,6 +194,20 @@
}
/**
+ * The framework calls this method when the preferred transport type used to set up
+ * emergency data network is changed.
+ *
+ * This method is meant to be overridden.
+ *
+ * @param transportType transport type changed to be preferred
+ */
+ public void reportEmergencyDataNetworkPreferredTransportChanged(
+ @AccessNetworkConstants.TransportType int transportType) {
+ Log.d(TAG, "reportEmergencyDataNetworkPreferredTransportChanged: "
+ + AccessNetworkConstants.transportTypeToString(transportType));
+ }
+
+ /**
* Called when the qualified networks provider is removed. The extended class should
* implement this method to perform cleanup works.
*/
@@ -237,6 +252,13 @@
}
break;
+ case QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED:
+ if (provider != null) {
+ int transportType = (int) message.arg2;
+ provider.reportEmergencyDataNetworkPreferredTransportChanged(transportType);
+ }
+ break;
+
case QNS_REMOVE_NETWORK_AVAILABILITY_PROVIDER:
if (provider != null) {
provider.close();
@@ -332,6 +354,14 @@
mHandler.obtainMessage(QNS_APN_THROTTLE_STATUS_CHANGED, slotIndex, 0, statuses)
.sendToTarget();
}
+
+ @Override
+ public void reportEmergencyDataNetworkPreferredTransportChanged(int slotIndex,
+ @AccessNetworkConstants.TransportType int transportType) {
+ mHandler.obtainMessage(
+ QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED,
+ slotIndex, transportType).sendToTarget();
+ }
}
private void log(String s) {
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index a3cbb4a..33c86d8 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -50,7 +50,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.CancellationException;
@@ -179,10 +178,9 @@
* Used for logging purposes, see {@link #getCapabilitiesString(long)}
* @hide
*/
- private static final Map<Long, String> CAPABILITIES_LOG_MAP = new HashMap<Long, String>() {{
- put(CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL");
- put(CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
- }};
+ private static final Map<Long, String> CAPABILITIES_LOG_MAP = Map.of(
+ CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL",
+ CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
/**
* The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 090d413..9996b86 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -78,24 +78,22 @@
/**@hide*/
// Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
// and WWAN are more accurate constants.
- Map<Integer, Integer> IMS_REG_TO_ACCESS_TYPE_MAP =
- new HashMap<Integer, Integer>() {{
- // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
- // case, since it is defined.
- put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
- AccessNetworkConstants.TRANSPORT_TYPE_INVALID);
- put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- put(ImsRegistrationImplBase.REGISTRATION_TECH_NR,
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
- /* As the cross sim will be using ePDG tunnel over internet, it behaves
- like IWLAN in most cases. Hence setting the access type as IWLAN
- */
- put(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
- }};
+ Map<Integer, Integer> IMS_REG_TO_ACCESS_TYPE_MAP = Map.of(
+ // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
+ // case, since it is defined.
+ ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
+ AccessNetworkConstants.TRANSPORT_TYPE_INVALID,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ ImsRegistrationImplBase.REGISTRATION_TECH_NR,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+ /* As the cross sim will be using ePDG tunnel over internet, it behaves
+ like IWLAN in most cases. Hence setting the access type as IWLAN
+ */
+ ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
/** @hide */
@NonNull
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index a42327b..174675f 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -34,7 +34,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
import java.util.Map;
/**
@@ -85,11 +84,10 @@
* Used for logging purposes.
* @hide
*/
- public static final Map<Integer, String> FEATURE_LOG_MAP = new HashMap<Integer, String>() {{
- put(FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL");
- put(FEATURE_MMTEL, "MMTEL");
- put(FEATURE_RCS, "RCS");
- }};
+ public static final Map<Integer, String> FEATURE_LOG_MAP = Map.of(
+ FEATURE_EMERGENCY_MMTEL, "EMERGENCY_MMTEL",
+ FEATURE_MMTEL, "MMTEL",
+ FEATURE_RCS, "RCS");
/**
* Integer values defining IMS features that are supported in ImsFeature.
@@ -145,11 +143,10 @@
* Used for logging purposes.
* @hide
*/
- public static final Map<Integer, String> STATE_LOG_MAP = new HashMap<Integer, String>() {{
- put(STATE_UNAVAILABLE, "UNAVAILABLE");
- put(STATE_INITIALIZING, "INITIALIZING");
- put(STATE_READY, "READY");
- }};
+ public static final Map<Integer, String> STATE_LOG_MAP = Map.of(
+ STATE_UNAVAILABLE, "UNAVAILABLE",
+ STATE_INITIALIZING, "INITIALIZING",
+ STATE_READY, "READY");
/**
* Integer values defining the result codes that should be returned from
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 648866b..abf4cde 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2159,6 +2159,12 @@
int getRadioHalVersion();
/**
+ * Get the HAL Version of a specific service
+ * encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown
+ */
+ int getHalVersion(int service);
+
+ /**
* Get the current calling package name.
*/
String getCurrentPackageName();
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
index 0f4e122..4bcf5a4 100644
--- a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
+++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java
@@ -16,14 +16,16 @@
package com.android.test.hwuicompare;
-import java.util.LinkedHashMap;
-import java.util.Map.Entry;
+import static java.util.Map.entry;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.Log;
+import java.util.Map;
+import java.util.Map.Entry;
+
public abstract class DisplayModifier {
// automated tests ignore any combination of operations that don't together return TOTAL_MASK
@@ -76,41 +78,36 @@
};
@SuppressWarnings("serial")
- private static final LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>> gMaps = new LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>>() {
- {
- put("aa", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("true", new DisplayModifier() {
+ private static final Map<String, Map<String, DisplayModifier>> gMaps = Map.of(
+ "aa", Map.of(
+ "true", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setAntiAlias(true);
}
- });
- put("false", new DisplayModifier() {
+ },
+ "false", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setAntiAlias(false);
}
- });
- }
- });
- put("style", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("fill", new DisplayModifier() {
+ }),
+ "style", Map.of(
+ "fill", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStyle(Paint.Style.FILL);
}
- });
- put("stroke", new DisplayModifier() {
+ },
+ "stroke", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStyle(Paint.Style.STROKE);
}
@Override
protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
- });
- put("fillAndStroke", new DisplayModifier() {
+ },
+ "fillAndStroke", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStyle(Paint.Style.FILL_AND_STROKE);
@@ -118,131 +115,118 @@
@Override
protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
- });
- }
- });
- put("strokeWidth", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("hair", new DisplayModifier() {
+ }),
+ "strokeWidth", Map.of(
+ "hair", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(0);
}
@Override
protected int mask() { return SWEEP_STROKE_WIDTH_BIT; }
- });
- put("0.3", new DisplayModifier() {
+ },
+ "0.3", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(0.3f);
}
- });
- put("1", new DisplayModifier() {
+ },
+ "1", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(1);
}
- });
- put("5", new DisplayModifier() {
+ },
+ "5", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(5);
}
- });
- put("30", new DisplayModifier() {
+ },
+ "30", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeWidth(30);
}
- });
- }
- });
- put("strokeCap", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("butt", new DisplayModifier() {
+ }),
+ "strokeCap", Map.of(
+ "butt", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeCap(Paint.Cap.BUTT);
}
@Override
protected int mask() { return SWEEP_STROKE_CAP_BIT; }
- });
- put("round", new DisplayModifier() {
+ },
+ "round", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeCap(Paint.Cap.ROUND);
}
- });
- put("square", new DisplayModifier() {
+ },
+ "square", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeCap(Paint.Cap.SQUARE);
}
- });
- }
- });
- put("strokeJoin", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("bevel", new DisplayModifier() {
+ }),
+ "strokeJoin", Map.of(
+ "bevel", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeJoin(Paint.Join.BEVEL);
}
@Override
protected int mask() { return SWEEP_STROKE_JOIN_BIT; }
- });
- put("round", new DisplayModifier() {
+ },
+ "round", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeJoin(Paint.Join.ROUND);
}
- });
- put("miter", new DisplayModifier() {
+ },
+ "miter", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setStrokeJoin(Paint.Join.MITER);
}
- });
+ }),
// TODO: add miter0, miter1 etc to test miter distances
- }
- });
-
- put("transform", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("noTransform", new DisplayModifier() {
+ "transform", Map.of(
+ "noTransform", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {}
@Override
protected int mask() { return SWEEP_TRANSFORM_BIT; };
- });
- put("rotate5", new DisplayModifier() {
+ },
+ "rotate5", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.rotate(5);
}
- });
- put("rotate45", new DisplayModifier() {
+ },
+ "rotate45", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.rotate(45);
}
- });
- put("rotate90", new DisplayModifier() {
+ },
+ "rotate90", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.rotate(90);
canvas.translate(0, -200);
}
- });
- put("scale2x2", new DisplayModifier() {
+ },
+ "scale2x2", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.scale(2, 2);
}
@Override
protected int mask() { return SWEEP_TRANSFORM_BIT; };
- });
- put("rot20scl1x4", new DisplayModifier() {
+ },
+ "rot20scl1x4", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.rotate(20);
@@ -250,180 +234,167 @@
}
@Override
protected int mask() { return SWEEP_TRANSFORM_BIT; };
- });
- }
- });
-
- put("shader", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("noShader", new DisplayModifier() {
+ }),
+ "shader", Map.ofEntries(
+ entry("noShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {}
@Override
protected int mask() { return SWEEP_SHADER_BIT; };
- });
- put("repeatShader", new DisplayModifier() {
+ }),
+ entry("repeatShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mRepeatShader);
}
@Override
protected int mask() { return SWEEP_SHADER_BIT; };
- });
- put("translatedShader", new DisplayModifier() {
+ }),
+ entry("translatedShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mTranslatedShader);
}
- });
- put("scaledShader", new DisplayModifier() {
+ }),
+ entry("scaledShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mScaledShader);
}
- });
- put("horGradient", new DisplayModifier() {
+ }),
+ entry("horGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mHorGradient);
}
- });
- put("diagGradient", new DisplayModifier() {
+ }),
+ entry("diagGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mDiagGradient);
}
@Override
protected int mask() { return SWEEP_SHADER_BIT; };
- });
- put("vertGradient", new DisplayModifier() {
+ }),
+ entry("vertGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mVertGradient);
}
- });
- put("radGradient", new DisplayModifier() {
+ }),
+ entry("radGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mRadGradient);
}
- });
- put("sweepGradient", new DisplayModifier() {
+ }),
+ entry("sweepGradient", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mSweepGradient);
}
- });
- put("composeShader", new DisplayModifier() {
+ }),
+ entry("composeShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mComposeShader);
}
- });
- put("bad composeShader", new DisplayModifier() {
+ }),
+ entry("bad composeShader", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mBadComposeShader);
}
- });
- put("bad composeShader 2", new DisplayModifier() {
+ }),
+ entry("bad composeShader 2", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setShader(ResourceModifiers.instance().mAnotherBadComposeShader);
}
- });
- }
- });
-
- // FINAL MAP: DOES ACTUAL DRAWING
- put("drawing", new LinkedHashMap<String, DisplayModifier>() {
- {
- put("roundRect", new DisplayModifier() {
+ })),
+ "drawing", Map.ofEntries(
+ entry("roundRect", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawRoundRect(gRect, 20, 20, paint);
}
- });
- put("rect", new DisplayModifier() {
+ }),
+ entry("rect", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawRect(gRect, paint);
}
@Override
protected int mask() { return SWEEP_SHADER_BIT | SWEEP_STROKE_CAP_BIT; };
- });
- put("circle", new DisplayModifier() {
+ }),
+ entry("circle", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawCircle(100, 100, 75, paint);
}
- });
- put("oval", new DisplayModifier() {
+ }),
+ entry("oval", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawOval(gRect, paint);
}
- });
- put("lines", new DisplayModifier() {
+ }),
+ entry("lines", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawLines(gLinePts, paint);
}
@Override
protected int mask() { return SWEEP_STROKE_CAP_BIT; };
- });
- put("plusPoints", new DisplayModifier() {
+ }),
+ entry("plusPoints", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawPoints(gPts, paint);
}
- });
- put("text", new DisplayModifier() {
+ }),
+ entry("text", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setTextSize(36);
canvas.drawText("TEXTTEST", 0, 50, paint);
}
- });
- put("shadowtext", new DisplayModifier() {
+ }),
+ entry("shadowtext", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
paint.setTextSize(36);
paint.setShadowLayer(3.0f, 0.0f, 3.0f, 0xffff00ff);
canvas.drawText("TEXTTEST", 0, 50, paint);
}
- });
- put("bitmapMesh", new DisplayModifier() {
+ }),
+ entry("bitmapMesh", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawBitmapMesh(ResourceModifiers.instance().mBitmap, 3, 3,
ResourceModifiers.instance().mBitmapVertices, 0, null, 0, null);
}
- });
- put("arc", new DisplayModifier() {
+ }),
+ entry("arc", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawArc(gRect, 260, 285, false, paint);
}
@Override
protected int mask() { return SWEEP_STROKE_CAP_BIT; };
- });
- put("arcFromCenter", new DisplayModifier() {
+ }),
+ entry("arcFromCenter", new DisplayModifier() {
@Override
public void modifyDrawing(Paint paint, Canvas canvas) {
canvas.drawArc(gRect, 260, 285, true, paint);
}
@Override
protected int mask() { return SWEEP_STROKE_JOIN_BIT; };
- });
- }
- });
+ })));
// WARNING: DON'T PUT MORE MAPS BELOW THIS
- }
- };
- private static LinkedHashMap<String, DisplayModifier> getMapAtIndex(int index) {
- for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+ private static Map<String, DisplayModifier> getMapAtIndex(int index) {
+ for (Map<String, DisplayModifier> map : gMaps.values()) {
if (index == 0) {
return map;
}
@@ -439,7 +410,7 @@
private static boolean stepInternal(boolean forward) {
int modifierMapIndex = gMaps.size() - 1;
while (modifierMapIndex >= 0) {
- LinkedHashMap<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex);
+ Map<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex);
mIndices[modifierMapIndex] += (forward ? 1 : -1);
if (mIndices[modifierMapIndex] >= 0 && mIndices[modifierMapIndex] < map.size()) {
@@ -471,7 +442,7 @@
private static boolean checkModificationStateMask() {
int operatorMask = 0x0;
int mapIndex = 0;
- for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+ for (Map<String, DisplayModifier> map : gMaps.values()) {
int displayModifierIndex = mIndices[mapIndex];
for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) {
if (displayModifierIndex == 0) {
@@ -488,7 +459,7 @@
public static void apply(Paint paint, Canvas canvas) {
int mapIndex = 0;
- for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+ for (Map<String, DisplayModifier> map : gMaps.values()) {
int displayModifierIndex = mIndices[mapIndex];
for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) {
if (displayModifierIndex == 0) {
@@ -510,7 +481,7 @@
String[][] keys = new String[gMaps.size()][];
int i = 0;
- for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) {
+ for (Map<String, DisplayModifier> map : gMaps.values()) {
keys[i] = new String[map.size()];
int j = 0;
for (String key : map.keySet()) {
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index d91aa1e..a7d6a01 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -15,6 +15,8 @@
<option name="run-command" value="cmd window tracing frame" />
<!-- ensure lock screen mode is swipe -->
<option name="run-command" value="locksettings set-disabled false" />
+ <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+ <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
<!-- restart launcher to activate TAPL -->
<option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
</target_preparer>
diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
index 2ad0da9..8b9c020 100644
--- a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
+++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java
@@ -58,7 +58,7 @@
Object hash = getProperty(props, "__hash__");
if (name instanceof String && hash instanceof Integer) {
- return String.format(Locale.US, "%s@%x", name, hash);
+ return String.format(Locale.US, "%s@%x", name, (Integer) hash);
} else {
return null;
}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
index 4de51fb..43dc9de 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java
@@ -140,9 +140,9 @@
handleNextBenchmark();
}
+ @SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-
}
private void handleNextBenchmark() {
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
index c16efbd..d015a56 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -367,6 +367,7 @@
}
}
+ @SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
diff --git a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
index 8afe841..17fa210 100644
--- a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
+++ b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
@@ -295,8 +295,8 @@
private void updateMirror(Rect displayFrame, float scale) {
if (displayFrame.isEmpty()) {
Rect bounds = mWindowBounds;
- int defaultCropW = Math.round(bounds.width() / 2);
- int defaultCropH = Math.round(bounds.height() / 2);
+ int defaultCropW = bounds.width() / 2;
+ int defaultCropH = bounds.height() / 2;
displayFrame.set(0, 0, defaultCropW, defaultCropH);
}
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
index 241206d..65b7549 100644
--- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -24,18 +24,14 @@
static final String KEY_NAME = "name";
static final String KEY_CLASS = "clazz";
- static Map<String,?> make(String name) {
- Map<String,Object> ret = new HashMap<String,Object>();
- ret.put(KEY_NAME, name);
- return ret;
- }
-
- @SuppressWarnings("serial")
- static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{
+ static final ArrayList<Map<String, ?>> SAMPLES = new ArrayList<>();
+ static {
for (int i = 1; i < 25; i++) {
- add(make("List Item: " + i));
+ Map<String, Object> sample = new HashMap<String, Object>();
+ sample.put(KEY_NAME, "List Item: " + i);
+ SAMPLES.add(sample);
}
- }};
+ }
Handler mHandler = new Handler();
diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
index c11b0f3..f85fb0f 100644
--- a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
+++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
@@ -30,6 +30,7 @@
setContentView(tv);
}
+ @SuppressWarnings("ReturnValueIgnored")
@Override
public void onResume() {
((String) null).length();
diff --git a/tests/TouchLatency/Android.bp b/tests/TouchLatency/Android.bp
index 3a9e240..4ef1ead 100644
--- a/tests/TouchLatency/Android.bp
+++ b/tests/TouchLatency/Android.bp
@@ -12,6 +12,7 @@
manifest: "app/src/main/AndroidManifest.xml",
// omit gradle 'build' dir
srcs: ["app/src/main/java/**/*.java"],
+ static_libs: ["com.google.android.material_material"],
resource_dirs: ["app/src/main/res"],
aaptflags: ["--auto-add-overlay"],
sdk_version: "current",
diff --git a/tests/TouchLatency/app/build.gradle b/tests/TouchLatency/app/build.gradle
index f5ae6f4..129baab 100644
--- a/tests/TouchLatency/app/build.gradle
+++ b/tests/TouchLatency/app/build.gradle
@@ -6,7 +6,7 @@
defaultConfig {
applicationId "com.prefabulated.touchlatency"
- minSdkVersion 28
+ minSdkVersion 30
targetSdkVersion 33
versionCode 1
versionName "1.0"
@@ -17,4 +17,9 @@
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+
+ dependencies {
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.6.0'
+ }
}
diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
index 6ab3b3e..2e93c87 100644
--- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
+++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
@@ -16,7 +16,6 @@
package com.prefabulated.touchlatency;
-import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Intent;
import android.hardware.display.DisplayManager;
@@ -30,25 +29,49 @@
import android.view.Window;
import android.view.WindowManager;
-public class TouchLatencyActivity extends Activity {
- private Mode mDisplayModes[];
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.android.material.slider.RangeSlider;
+import com.google.android.material.slider.RangeSlider.OnChangeListener;
+
+public class TouchLatencyActivity extends AppCompatActivity {
+ private static final int REFRESH_RATE_SLIDER_MIN = 20;
+ private static final int REFRESH_RATE_SLIDER_STEP = 5;
+
+ private Menu mMenu;
+ private Mode[] mDisplayModes;
private int mCurrentModeIndex;
+ private float mSliderPreferredRefreshRate;
private DisplayManager mDisplayManager;
+
private final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int i) {
- invalidateOptionsMenu();
+ updateOptionsMenu();
}
@Override
public void onDisplayRemoved(int i) {
- invalidateOptionsMenu();
+ updateOptionsMenu();
}
@Override
public void onDisplayChanged(int i) {
- invalidateOptionsMenu();
+ updateOptionsMenu();
+ }
+ };
+
+ private final RangeSlider.OnChangeListener mRefreshRateSliderListener = new OnChangeListener() {
+ @Override
+ public void onValueChange(@NonNull RangeSlider slider, float value, boolean fromUser) {
+ if (value == mSliderPreferredRefreshRate) return;
+
+ mSliderPreferredRefreshRate = value;
+ WindowManager.LayoutParams w = getWindow().getAttributes();
+ w.preferredRefreshRate = mSliderPreferredRefreshRate;
+ getWindow().setAttributes(w);
}
};
@@ -75,17 +98,23 @@
Trace.endSection();
}
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- Trace.beginSection("TouchLatencyActivity onCreateOptionsMenu");
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.menu_touch_latency, menu);
+ public void updateOptionsMenu() {
if (mDisplayModes.length > 1) {
- MenuItem menuItem = menu.findItem(R.id.display_mode);
+ MenuItem menuItem = mMenu.findItem(R.id.display_mode);
Mode currentMode = getWindowManager().getDefaultDisplay().getMode();
updateDisplayMode(menuItem, currentMode);
}
- updateMultiDisplayMenu(menu.findItem(R.id.multi_display));
+ updateRefreshRateMenu(mMenu.findItem(R.id.frame_rate));
+ updateMultiDisplayMenu(mMenu.findItem(R.id.multi_display));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ Trace.beginSection("TouchLatencyActivity onCreateOptionsMenu");
+ mMenu = menu;
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_touch_latency, mMenu);
+ updateOptionsMenu();
Trace.endSection();
return true;
}
@@ -96,6 +125,32 @@
menuItem.setVisible(true);
}
+ private float getHighestRefreshRate() {
+ float maxRefreshRate = 0;
+ for (Display.Mode mode : getDisplay().getSupportedModes()) {
+ if (sameSizeMode(mode) && mode.getRefreshRate() > maxRefreshRate) {
+ maxRefreshRate = mode.getRefreshRate();
+ }
+ }
+ return maxRefreshRate;
+ }
+
+ private void updateRefreshRateMenu(MenuItem item) {
+ item.setActionView(R.layout.refresh_rate_layout);
+ RangeSlider slider = item.getActionView().findViewById(R.id.slider_from_layout);
+ slider.addOnChangeListener(mRefreshRateSliderListener);
+
+ float highestRefreshRate = getHighestRefreshRate();
+ slider.setValueFrom(REFRESH_RATE_SLIDER_MIN);
+ slider.setValueTo(highestRefreshRate);
+ slider.setStepSize(REFRESH_RATE_SLIDER_STEP);
+ if (mSliderPreferredRefreshRate < REFRESH_RATE_SLIDER_MIN
+ || mSliderPreferredRefreshRate > highestRefreshRate) {
+ mSliderPreferredRefreshRate = highestRefreshRate;
+ }
+ slider.setValues(mSliderPreferredRefreshRate);
+ }
+
private void updateMultiDisplayMenu(MenuItem item) {
item.setVisible(mDisplayManager.getDisplays().length > 1);
}
@@ -105,6 +160,12 @@
mDisplayManager.registerDisplayListener(mDisplayListener, new Handler());
}
+ private boolean sameSizeMode(Display.Mode mode) {
+ Mode currentMode = mDisplayModes[mCurrentModeIndex];
+ return currentMode.getPhysicalHeight() == mode.getPhysicalHeight()
+ && currentMode.getPhysicalWidth() == mode.getPhysicalWidth();
+ }
+
public void changeDisplayMode(MenuItem item) {
Window w = getWindow();
WindowManager.LayoutParams params = w.getAttributes();
@@ -112,10 +173,7 @@
int modeIndex = (mCurrentModeIndex + 1) % mDisplayModes.length;
while (modeIndex != mCurrentModeIndex) {
// skip modes with different resolutions
- Mode currentMode = mDisplayModes[mCurrentModeIndex];
- Mode nextMode = mDisplayModes[modeIndex];
- if (currentMode.getPhysicalHeight() == nextMode.getPhysicalHeight()
- && currentMode.getPhysicalWidth() == nextMode.getPhysicalWidth()) {
+ if (sameSizeMode(mDisplayModes[modeIndex])) {
break;
}
modeIndex = (modeIndex + 1) % mDisplayModes.length;
diff --git a/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml b/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml
new file mode 100644
index 0000000..bb9ce60
--- /dev/null
+++ b/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <com.google.android.material.slider.RangeSlider
+ android:id="@+id/slider_from_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:tickColor="@color/cardview_light_background"
+ app:trackColor="@color/cardview_light_background"
+ app:thumbColor="@color/cardview_dark_background"
+ android:visibility="visible"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml
index abc7fd5..7169021 100644
--- a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml
+++ b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml
@@ -14,21 +14,25 @@
limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" tools:context=".TouchLatencyActivity">
<item
android:id="@+id/action_settings"
android:orderInCategory="101"
- android:showAsAction="always"
- android:title="@string/mode"/>
+ android:title="@string/mode"
+ app:showAsAction="always" />
+ <item
+ android:id="@+id/frame_rate"
+ android:title="@string/frame_rate"
+ app:showAsAction="collapseActionView" />
<item
android:id="@+id/display_mode"
- android:showAsAction="ifRoom"
android:title="@string/display_mode"
- android:visible="false"/>
-
+ android:visible="false"
+ app:showAsAction="always" />
<item
android:id="@+id/multi_display"
- android:showAsAction="ifRoom"
android:title="@string/multi_display"
- android:visible="false"/>
+ android:visible="false"
+ app:showAsAction="ifRoom" />
</menu>
diff --git a/tests/TouchLatency/app/src/main/res/values/strings.xml b/tests/TouchLatency/app/src/main/res/values/strings.xml
index 5ee86d8..cad2df7 100644
--- a/tests/TouchLatency/app/src/main/res/values/strings.xml
+++ b/tests/TouchLatency/app/src/main/res/values/strings.xml
@@ -18,5 +18,6 @@
<string name="mode">Touch</string>
<string name="display_mode">Mode</string>
+ <string name="frame_rate">Frame Rate</string>
<string name="multi_display">multi-display</string>
</resources>
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index 22da7c1..b23a87e 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -16,7 +16,7 @@
<resources>
<!-- Base application theme. -->
- <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
+ <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
diff --git a/tests/TouchLatency/gradle.properties b/tests/TouchLatency/gradle.properties
index 1d3591c..ccd5dda 100644
--- a/tests/TouchLatency/gradle.properties
+++ b/tests/TouchLatency/gradle.properties
@@ -15,4 +15,5 @@
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
+# org.gradle.parallel=true
+android.useAndroidX=true
\ No newline at end of file
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index f924b2e..ad06830 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -637,8 +637,7 @@
final BroadcastReceiver receiver = getPackageChangeReceiver();
verify(mMockContext).registerReceiver(any(), argThat(filter -> {
- return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED)
- && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
+ return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED);
}), any(), any());
receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_REMOVED));
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 2a450ba..1d7fd1d 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -46,6 +46,13 @@
string version = 2;
}
+// References to non local resources
+message DynamicRefTable {
+ PackageId package_id = 1;
+ string package_name = 2;
+}
+
+
// Top level message representing a resource table.
message ResourceTable {
// The string pool containing source paths referenced throughout the resource table. This does
@@ -60,6 +67,8 @@
// The version fingerprints of the tools that built the resource table.
repeated ToolFingerprint tool_fingerprint = 4;
+
+ repeated DynamicRefTable dynamic_ref_table = 5;
}
// A package ID in the range [0x00, 0xff].
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 6a1e8c1..e39f327 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -562,6 +562,11 @@
}
}
+ for (const pb::DynamicRefTable& dynamic_ref : pb_table.dynamic_ref_table()) {
+ out_table->included_packages_.insert(
+ {dynamic_ref.package_id().id(), dynamic_ref.package_name()});
+ }
+
// Deserialize the overlayable groups of the table
std::vector<std::shared_ptr<Overlayable>> overlayables;
for (const pb::Overlayable& pb_overlayable : pb_table.overlayable()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 163a60a..a6d58fd 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -345,7 +345,11 @@
pb::ToolFingerprint* pb_fingerprint = out_table->add_tool_fingerprint();
pb_fingerprint->set_tool(util::GetToolName());
pb_fingerprint->set_version(util::GetToolFingerprint());
-
+ for (auto it = table.included_packages_.begin(); it != table.included_packages_.end(); ++it) {
+ pb::DynamicRefTable* pb_dynamic_ref = out_table->add_dynamic_ref_table();
+ pb_dynamic_ref->mutable_package_id()->set_id(it->first);
+ pb_dynamic_ref->set_package_name(it->second);
+ }
std::vector<Overlayable*> overlayables;
auto table_view = table.GetPartitionedView();
for (const auto& package : table_view.packages) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 692fa42..5adc5e6 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -1024,4 +1024,28 @@
EXPECT_THAT(*(custom_layout->path), Eq("res/layout/bar.xml"));
}
+TEST(ProtoSerializeTest, SerializeDynamicRef) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build();
+ table->included_packages_.insert({20, "foobar"});
+ table->included_packages_.insert({30, "barfoo"});
+
+ ResourceTable new_table;
+ pb::ResourceTable pb_table;
+ MockFileCollection files;
+ std::string error;
+ SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
+ EXPECT_THAT(error, IsEmpty());
+
+ int result = new_table.included_packages_.size();
+ EXPECT_THAT(result, Eq(2));
+ auto it = new_table.included_packages_.begin();
+ EXPECT_THAT(it->first, Eq(20));
+ EXPECT_THAT(it->second, Eq("foobar"));
+ it++;
+ EXPECT_THAT(it->first, Eq(30));
+ EXPECT_THAT(it->second, Eq("barfoo"));
+}
} // namespace aapt
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index a750696..5012622 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -117,13 +117,12 @@
private static final byte[] TEST_PSK =
new byte[]{'T', 'e', 's', 't'};
- private static final Set<Integer> SCAN_FREQ_SET =
- new HashSet<Integer>() {{
- add(2410);
- add(2450);
- add(5050);
- add(5200);
- }};
+ private static final Set<Integer> SCAN_FREQ_SET = Set.of(
+ 2410,
+ 2450,
+ 5050,
+ 5200);
+
private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
private static final int[] TEST_FREQUENCIES_1 = {};
@@ -131,13 +130,11 @@
private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes(
new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05});
- private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
- new ArrayList<byte[]>() {{
- add(LocalNativeUtil.byteArrayFromArrayList(
- LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
- add(LocalNativeUtil.byteArrayFromArrayList(
- LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
- }};
+ private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST = List.of(
+ LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)),
+ LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
private static final PnoSettings TEST_PNO_SETTINGS = new PnoSettings();
static {