diff options
566 files changed, 12133 insertions, 8957 deletions
diff --git a/.clang-format b/.clang-format index 03af56d64062..d60d33c08f3f 100644 --- a/.clang-format +++ b/.clang-format @@ -9,5 +9,17 @@ CommentPragmas: NOLINT:.* 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 98e4f4509b52..9366ff2d81a9 100644 --- a/apct-tests/perftests/core/Android.bp +++ b/apct-tests/perftests/core/Android.bp @@ -62,4 +62,10 @@ android_test { 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 2ef68ca7bdb2..05a3e1201a00 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 @@ public class ReferencePerfTest { 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/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java index 0802072ae144..0cce6ad73eda 100644 --- a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java +++ b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java @@ -18,11 +18,11 @@ package android.os; import android.content.Context; import android.hardware.display.DisplayManager; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; import android.provider.Settings; import android.view.Display; -import androidx.benchmark.BenchmarkState; -import androidx.benchmark.junit4.BenchmarkRule; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -38,7 +38,7 @@ public final class DisplayPerfTest { private static final float DELTA = 0.001f; @Rule - public final BenchmarkRule mBenchmarkRule = new BenchmarkRule(); + public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); private DisplayManager mDisplayManager; private Context mContext; @@ -51,7 +51,7 @@ public final class DisplayPerfTest { @Test public void testBrightnessChanges() throws Exception { - final BenchmarkState state = mBenchmarkRule.getState(); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); 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 d9fe30da2cb7..e0d1a30e7ced 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 class JobSchedulerService extends com.android.server.SystemService 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 4f8faca59e4c..7a08cbdcddd4 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -222,6 +222,7 @@ cc_test { }, 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 44311648da80..10947dc90a76 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 @@ Status Idmap2Service::createFabricatedOverlay( 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 @@ Status Idmap2Service::createFabricatedOverlay( 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 c773e112997d..3ad6d58e8253 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 @@ parcelable FabricatedOverlayInternalEntry { 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 05b0618131c9..9f57710edb0b 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 @@ struct FabricatedOverlay { 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 @@ struct FabricatedOverlay { 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 @@ struct FabricatedOverlay { std::string name_; std::string target_package_name_; std::string target_overlayable_; + std::string frro_path_; std::vector<Entry> entries_; }; @@ -79,10 +91,14 @@ struct FabricatedOverlay { 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 af4dd8960cc3..2214a83bd2da 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 @@ struct TargetValue { 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 bde9b0be4361..d517e29f3369 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 @@ void Write32(std::ostream& stream, uint32_t value) { 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::SetOverlayable(const std 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 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { } 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 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { 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 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { 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 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { } } } - 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 @@ Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stre 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 @@ Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stre 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 @@ Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stre 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 @@ Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const { 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 e804c879ee82..e13a0eb5d488 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, OverlayInfo) { } 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 @@ TEST(FabricatedOverlayTests, SetResourceValue) { 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 @@ TEST(FabricatedOverlayTests, SetResourceValue) { 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 @@ TEST(FabricatedOverlayTests, SetResourceValue) { 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 @@ TEST(FabricatedOverlayTests, SetResourceValue) { 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 7b7dc17deea4..b473f26b2230 100644 --- a/cmds/idmap2/tests/IdmapTests.cpp +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -260,11 +260,17 @@ TEST(IdmapTests, FabricatedOverlay) { 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 @@ TEST(IdmapTests, FabricatedOverlay) { 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 ad998b9a7a4c..80c062d92640 100644 --- a/cmds/idmap2/tests/R.h +++ b/cmds/idmap2/tests/R.h @@ -26,24 +26,27 @@ namespace android::idmap2 { // 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 7112eeb9ea0c..68164e26f352 100644 --- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp +++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp @@ -79,22 +79,22 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { 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 016d427e7452..380e462a3aba 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 @@ Result<Unit> MappingExists(const ResourceMapping& mapping, ResourceId target_res 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 @@ Result<Unit> MappingExists(const ResourceMapping& mapping, const ResourceId& tar 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, InlineResources) { } 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 @@ TEST(ResourceMappingTests, FabricatedOverlay) { 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 d5799adf0ec3..794d6221c2c0 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 Binary files differnew file mode 100644 index 000000000000..b7317b0933fb --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/res/drawable/android.png diff --git a/cmds/idmap2/tests/data/target/build b/cmds/idmap2/tests/data/target/build index e6df742cc9da..cd13a7ec1bd8 100755 --- a/cmds/idmap2/tests/data/target/build +++ b/cmds/idmap2/tests/data/target/build @@ -17,5 +17,7 @@ aapt2 link --manifest AndroidManifest.xml -A assets -o target.apk compiled.flata 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 Binary files differnew file mode 100644 index 000000000000..1a56e68fd6d2 --- /dev/null +++ b/cmds/idmap2/tests/data/target/res/drawable/dr1.png diff --git a/cmds/idmap2/tests/data/target/res/values/overlayable.xml b/cmds/idmap2/tests/data/target/res/values/overlayable.xml index 57e6c439c23c..aac9081b9513 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 Binary files differindex cc3491de894d..680eeb609f8e 100644 --- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk +++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk Binary files differindex 4a58c5e28f49..145e737ca138 100644 --- a/cmds/idmap2/tests/data/target/target.apk +++ b/cmds/idmap2/tests/data/target/target.apk diff --git a/core/api/current.txt b/core/api/current.txt index b64afea17e0b..6a875b83674c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -91,6 +91,18 @@ package android { 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 @@ package android.accessibilityservice { 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 @@ package android.app { 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 @@ package android.app { 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(); @@ -12123,6 +12145,7 @@ package android.content.pm { 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"; @@ -12184,6 +12207,7 @@ package android.content.pm { 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 @@ -12397,16 +12421,20 @@ package android.content.pm { 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; } @@ -39928,7 +39956,7 @@ package android.service.wallpaper { 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"; } @@ -39943,20 +39971,20 @@ package android.service.wallpaper { 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); } @@ -41885,6 +41913,7 @@ package android.telephony { 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"; diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index ce18745046ff..e6ddf9f6daf0 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -349,6 +349,7 @@ package android.os { } 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 @@ package android.os { 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 } @@ -411,6 +413,7 @@ package android.provider { public final class DeviceConfig { field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager"; + field public static final String NAMESPACE_APP_CLONING = "app_cloning"; field public static final String NAMESPACE_APP_STANDBY = "app_standby"; field public static final String NAMESPACE_DEVICE_IDLE = "device_idle"; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index db1e4e094257..001ebf5c0840 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -588,6 +588,7 @@ package android.app { 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"; @@ -2908,6 +2909,7 @@ package android.companion.virtual { method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations(); method public int getDefaultActivityPolicy(); method public int getDefaultNavigationPolicy(); + method public int getDevicePolicy(int); method public int getLockState(); method @Nullable public String getName(); method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); @@ -2915,14 +2917,18 @@ package android.companion.virtual { field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0 field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1 field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR; + field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1 + field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0 field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1 field public static final int LOCK_STATE_DEFAULT = 0; // 0x0 field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0 field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1 + field public static final int POLICY_TYPE_SENSORS = 0; // 0x0 } public static final class VirtualDeviceParams.Builder { ctor public VirtualDeviceParams.Builder(); + method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int); method @NonNull public android.companion.virtual.VirtualDeviceParams build(); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); @@ -7155,6 +7161,7 @@ package android.media.tv.tuner { 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 +7192,6 @@ package android.media.tv.tuner { 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 +9974,10 @@ package android.os { 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 +11931,39 @@ package android.service.timezone { 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(); @@ -12225,7 +12260,7 @@ package android.service.wallpaper { public class WallpaperService.Engine { method public boolean isInAmbientMode(); - method public void onAmbientModeChanged(boolean, long); + method @MainThread public void onAmbientModeChanged(boolean, long); } } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 535f6cee041c..3fee610943ba 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -905,6 +905,7 @@ package android.content.pm { 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(); @@ -923,6 +924,7 @@ package android.content.pm { 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 @@ -1901,6 +1903,7 @@ package android.os { 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; } @@ -2627,13 +2630,23 @@ package android.telephony { 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 f9b8a3006880..8e21d8c2cb56 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -567,6 +567,10 @@ MissingNullability: android.telephony.SmsManager#checkSmsShortCodeDestination(St 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 e86d2f35ef16..2fe5d5140371 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -696,7 +696,8 @@ public abstract class AccessibilityService extends Service { 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 abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { * @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 @@ public abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { } /** + * 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 @@ public abstract class AccessibilityService extends Service { 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 9abce3a39944..da14b50e6481 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -30,6 +30,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 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 @@ interface IAccessibilityServiceConnection { 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 860bfc5d2f4c..1b3282e752f4 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -42,6 +42,7 @@ import android.content.pm.ApplicationInfo; 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 class AppOpsManager { 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 @@ public class AppOpsManager { "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 class AppOpsManager { 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 @@ public class AppOpsManager { 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,7 +2435,12 @@ public class AppOpsManager { "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 @@ -2560,7 +2600,14 @@ public class AppOpsManager { @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; } /** @@ -7224,10 +7271,14 @@ public class AppOpsManager { */ 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; } /** @@ -8456,8 +8507,9 @@ public class AppOpsManager { */ 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); } /** @@ -8469,7 +8521,8 @@ public class AppOpsManager { * * @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) { @@ -8487,7 +8540,7 @@ public class AppOpsManager { } } - SyncNotedAppOp syncOp = mService.startProxyOperation(op, + SyncNotedAppOp syncOp = mService.startProxyOperation(clientId, op, attributionSource, false, collectionMode == COLLECT_ASYNC, message, shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); @@ -8585,9 +8638,10 @@ public class AppOpsManager { */ 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); } /** @@ -8602,10 +8656,11 @@ public class AppOpsManager { * * @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(); } diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java index 4d6e4aedba66..43023fe9c2ab 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -26,13 +26,11 @@ import android.util.SparseArray; 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 @@ public abstract class AppOpsManagerInternal { /** * 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 @@ public abstract class AppOpsManagerInternal { * @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 @@ public abstract class AppOpsManagerInternal { * * @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 10cdf5315b55..042bdd7e2ed5 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1814,12 +1814,6 @@ class ContextImpl extends Context { } } 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 000000000000..c258242627c9 --- /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 000000000000..eccc5631d86a --- /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 5388282a1b46..bd5d1057f5bf 100644 --- a/core/java/android/app/SearchableInfo.java +++ b/core/java/android/app/SearchableInfo.java @@ -396,6 +396,17 @@ public final class SearchableInfo implements Parcelable { 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 7635138f6cdd..01e4b150fd7c 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -21,6 +21,7 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST; 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 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * 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 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac } } - /** - * 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/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java index 760c6f0fc333..6f62c8a03078 100644 --- a/core/java/android/app/backup/BackupRestoreEventLogger.java +++ b/core/java/android/app/backup/BackupRestoreEventLogger.java @@ -53,6 +53,8 @@ public class BackupRestoreEventLogger { /** * Operation types for which this logger can be used. + * + * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -87,6 +89,8 @@ public class BackupRestoreEventLogger { * {@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 @@ public class BackupRestoreEventLogger { * * @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 @@ public class BackupRestoreEventLogger { * @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 @@ public class BackupRestoreEventLogger { * * @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 @@ public class BackupRestoreEventLogger { * * @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 @@ public class BackupRestoreEventLogger { * @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 @@ public class BackupRestoreEventLogger { * * @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 @@ public class BackupRestoreEventLogger { * * @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/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index 82d7534c84d9..7d6336a225bd 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -52,6 +52,11 @@ interface IVirtualDeviceManager { List<VirtualDevice> getVirtualDevices(); /** + * Returns the device policy for the given virtual device and policy type. + */ + int getDevicePolicy(int deviceId, int policyType); + + /** * Creates a virtual display owned by a particular virtual device. * * @param virtualDisplayConfig The configuration used in creating the display diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 0bb86fbf00f8..c14bb1beb025 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -182,6 +182,28 @@ public final class VirtualDeviceManager { } /** + * Returns the device policy for the given virtual device and policy type. + * + * <p>In case the virtual device identifier is not valid, or there's no explicitly specified + * policy for that device and policy type, then + * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned. + * + * @hide + */ + public @VirtualDeviceParams.DevicePolicy int getDevicePolicy( + int deviceId, @VirtualDeviceParams.PolicyType int policyType) { + if (mService == null) { + Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service."); + return VirtualDeviceParams.DEVICE_POLICY_DEFAULT; + } + try { + return mService.getDevicePolicy(deviceId, policyType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * A virtual device has its own virtual display, audio output, microphone, and camera etc. The * creator of a virtual device can take the output from the virtual display and stream it over * to another device, and inject input events that are received from the remote device. diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index d40c9d63039d..c6e6f8324cff 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -28,6 +28,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.util.ArraySet; +import android.util.SparseIntArray; import com.android.internal.util.Preconditions; @@ -103,6 +104,47 @@ public final class VirtualDeviceParams implements Parcelable { */ public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; + /** @hide */ + @IntDef(prefix = "DEVICE_POLICY_", value = {DEVICE_POLICY_DEFAULT, DEVICE_POLICY_CUSTOM}) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface DevicePolicy {} + + /** + * Indicates that there is no special logic for this virtual device and it should be treated + * the same way as the default device, keeping the default behavior unchanged. + */ + public static final int DEVICE_POLICY_DEFAULT = 0; + + /** + * Indicates that there is custom logic, specific to this virtual device, which should be + * triggered instead of the default behavior. + */ + public static final int DEVICE_POLICY_CUSTOM = 1; + + /** + * Any relevant component must be able to interpret the correct meaning of a custom policy for + * a given policy type. + * @hide + */ + @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS}) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface PolicyType {} + + /** + * Tells the sensor framework how to handle sensor requests from contexts associated with this + * virtual device, namely the sensors returned by + * {@link android.hardware.SensorManager#getSensorList}: + * + * <ul> + * <li>{@link #DEVICE_POLICY_DEFAULT}: Return the sensors of the default device. + * <li>{@link #DEVICE_POLICY_CUSTOM}: Return the sensors of the virtual device. Note that if + * the virtual device did not create any virtual sensors, then an empty list is returned. + * </ul> + */ + public static final int POLICY_TYPE_SENSORS = 0; + private final int mLockState; @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts; @NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations; @@ -114,6 +156,8 @@ public final class VirtualDeviceParams implements Parcelable { @ActivityPolicy private final int mDefaultActivityPolicy; @Nullable private final String mName; + // Mapping of @PolicyType to @DevicePolicy + @NonNull private final SparseIntArray mDevicePolicies; private VirtualDeviceParams( @LockState int lockState, @@ -124,12 +168,14 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull Set<ComponentName> allowedActivities, @NonNull Set<ComponentName> blockedActivities, @ActivityPolicy int defaultActivityPolicy, - @Nullable String name) { + @Nullable String name, + @NonNull SparseIntArray devicePolicies) { Preconditions.checkNotNull(usersWithMatchingAccounts); Preconditions.checkNotNull(allowedCrossTaskNavigations); Preconditions.checkNotNull(blockedCrossTaskNavigations); Preconditions.checkNotNull(allowedActivities); Preconditions.checkNotNull(blockedActivities); + Preconditions.checkNotNull(devicePolicies); mLockState = lockState; mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); @@ -140,6 +186,7 @@ public final class VirtualDeviceParams implements Parcelable { mBlockedActivities = new ArraySet<>(blockedActivities); mDefaultActivityPolicy = defaultActivityPolicy; mName = name; + mDevicePolicies = devicePolicies; } @SuppressWarnings("unchecked") @@ -153,6 +200,7 @@ public final class VirtualDeviceParams implements Parcelable { mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null); mDefaultActivityPolicy = parcel.readInt(); mName = parcel.readString8(); + mDevicePolicies = parcel.readSparseIntArray(); } /** @@ -258,6 +306,16 @@ public final class VirtualDeviceParams implements Parcelable { return mName; } + /** + * Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no + * policy for this type has been explicitly specified. + * + * @see Builder#addDevicePolicy + */ + public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) { + return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT); + } + @Override public int describeContents() { return 0; @@ -274,6 +332,7 @@ public final class VirtualDeviceParams implements Parcelable { dest.writeArraySet(mBlockedActivities); dest.writeInt(mDefaultActivityPolicy); dest.writeString8(mName); + dest.writeSparseIntArray(mDevicePolicies); } @Override @@ -285,6 +344,18 @@ public final class VirtualDeviceParams implements Parcelable { return false; } VirtualDeviceParams that = (VirtualDeviceParams) o; + final int devicePoliciesCount = mDevicePolicies.size(); + if (devicePoliciesCount != that.mDevicePolicies.size()) { + return false; + } + for (int i = 0; i < devicePoliciesCount; i++) { + if (mDevicePolicies.keyAt(i) != that.mDevicePolicies.keyAt(i)) { + return false; + } + if (mDevicePolicies.valueAt(i) != that.mDevicePolicies.valueAt(i)) { + return false; + } + } return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts) && Objects.equals(mAllowedCrossTaskNavigations, that.mAllowedCrossTaskNavigations) @@ -298,10 +369,15 @@ public final class VirtualDeviceParams implements Parcelable { @Override public int hashCode() { - return Objects.hash( + int hashCode = Objects.hash( mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations, mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities, - mBlockedActivities, mDefaultActivityPolicy, mName); + mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies); + for (int i = 0; i < mDevicePolicies.size(); i++) { + hashCode = 31 * hashCode + mDevicePolicies.keyAt(i); + hashCode = 31 * hashCode + mDevicePolicies.valueAt(i); + } + return hashCode; } @Override @@ -317,6 +393,7 @@ public final class VirtualDeviceParams implements Parcelable { + " mBlockedActivities=" + mBlockedActivities + " mDefaultActivityPolicy=" + mDefaultActivityPolicy + " mName=" + mName + + " mDevicePolicies=" + mDevicePolicies + ")"; } @@ -350,6 +427,7 @@ public final class VirtualDeviceParams implements Parcelable { private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED; private boolean mDefaultActivityPolicyConfigured = false; @Nullable private String mName; + @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray(); /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} @@ -528,6 +606,18 @@ public final class VirtualDeviceParams implements Parcelable { } /** + * Specifies a policy for this virtual device. + * + * @param policyType the type of policy, i.e. which behavior to specify a policy for. + * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior. + */ + @NonNull + public Builder addDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) { + mDevicePolicies.put(policyType, devicePolicy); + return this; + } + + /** * Builds the {@link VirtualDeviceParams} instance. */ @NonNull @@ -541,7 +631,8 @@ public final class VirtualDeviceParams implements Parcelable { mAllowedActivities, mBlockedActivities, mDefaultActivityPolicy, - mName); + mName, + mDevicePolicies); } } } diff --git a/core/java/android/content/ActivityNotFoundException.java b/core/java/android/content/ActivityNotFoundException.java index 16149bbc7012..5b50189015af 100644 --- a/core/java/android/content/ActivityNotFoundException.java +++ b/core/java/android/content/ActivityNotFoundException.java @@ -31,5 +31,4 @@ public class ActivityNotFoundException extends RuntimeException { super(name); } -}; - +} diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index b3435b1180c2..8b6c4dd8497b 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -28,6 +28,7 @@ import android.os.Parcelable; 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 @@ public class IntentFilter implements Parcelable { @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 class IntentFilter implements Parcelable { */ public IntentFilter() { mPriority = 0; - mActions = new ArrayList<String>(); + mActions = new ArraySet<>(); } /** @@ -445,7 +446,7 @@ public class IntentFilter implements Parcelable { */ public IntentFilter(String action) { mPriority = 0; - mActions = new ArrayList<String>(); + mActions = new ArraySet<>(); addAction(action); } @@ -468,7 +469,7 @@ public class IntentFilter implements Parcelable { 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 class IntentFilter implements Parcelable { 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 @@ public class IntentFilter implements Parcelable { * @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 @@ public class IntentFilter implements Parcelable { * Return an action in the filter. */ public final String getAction(int index) { - return mActions.get(index); + return mActions.valueAt(index); } /** @@ -797,8 +796,11 @@ public class IntentFilter implements Parcelable { 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 @@ public class IntentFilter implements Parcelable { 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 class IntentFilter implements Parcelable { } 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 @@ public class IntentFilter implements Parcelable { /** @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 f888813135be..1b5f64c456a1 100644 --- a/core/java/android/content/integrity/AtomicFormula.java +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -261,8 +261,8 @@ public abstract class AtomicFormula extends IntegrityFormula { } 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 @@ public abstract class AtomicFormula extends IntegrityFormula { 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 3ca056097c1f..cc7977a267a5 100644 --- a/core/java/android/content/om/FabricatedOverlay.java +++ b/core/java/android/content/om/FabricatedOverlay.java @@ -20,11 +20,13 @@ import android.annotation.NonNull; 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 @@ public class FabricatedOverlay { } /** + * 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 @@ public class FabricatedOverlay { * @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 class FabricatedOverlay { */ 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 @@ public class FabricatedOverlay { * @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 class FabricatedOverlay { */ 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 @@ public class FabricatedOverlay { 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 aa86af92df87..6c1d84be6c9f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -165,6 +165,21 @@ public abstract class PackageManager { "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 @@ public abstract class PackageManager { @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 abstract class PackageManager { */ 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 abstract class PackageManager { */ 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 @@ public abstract class PackageManager { * 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 88d700432e8a..ab20b6f7f9cc 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 @@ public class ServiceInfo extends ComponentInfo /** * 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 @@ public class ServiceInfo extends ComponentInfo * 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 @@ public class ServiceInfo extends ComponentInfo * 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 @@ public class ServiceInfo extends ComponentInfo * 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 @@ public class ServiceInfo extends ComponentInfo * 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 @@ public class ServiceInfo extends ComponentInfo 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 @@ public class ServiceInfo extends ComponentInfo 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 1f83d7532f04..295df5cc42d0 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -50,6 +50,7 @@ import android.view.contentcapture.ContentCaptureContext; 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 @@ public final class ShortcutInfo implements Parcelable { @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 @@ public final class ShortcutInfo implements Parcelable { @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 9baa6ba2fb49..2be0323a1e8b 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -159,6 +159,18 @@ public class UserInfo implements Parcelable { 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 @@ public class UserInfo implements Parcelable { 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 @@ public class UserInfo implements Parcelable { } /** + * @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/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index ff072916292b..09d24d47cc7a 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.ColorStateListDrawable; 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 libcore.util.NativeAllocationRegistry; 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 @@ public class ResourcesImpl { 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 @@ public class ResourcesImpl { } 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 072be50ad2fb..41ef6aa54ae3 100644 --- a/core/java/android/hardware/CameraInfo.java +++ b/core/java/android/hardware/CameraInfo.java @@ -60,4 +60,4 @@ public class CameraInfo implements Parcelable { return new CameraInfo[size]; } }; -}; +} diff --git a/core/java/android/hardware/CameraStatus.java b/core/java/android/hardware/CameraStatus.java index 874af297683e..fa35efbcee91 100644 --- a/core/java/android/hardware/CameraStatus.java +++ b/core/java/android/hardware/CameraStatus.java @@ -68,4 +68,4 @@ public class CameraStatus implements Parcelable { return new CameraStatus[size]; } }; -}; +} diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java index d41570682fe1..267ef3637ce7 100644 --- a/core/java/android/hardware/biometrics/CryptoObject.java +++ b/core/java/android/hardware/biometrics/CryptoObject.java @@ -118,4 +118,4 @@ public class CryptoObject { } 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 7b6a457411f3..8304796f636a 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.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.TreeMap; /** @@ -63,11 +64,11 @@ public class FrameNumberTracker { } 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 621a418f43c9..92a2fb6f16b1 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 @@ public class MarshalQueryableEnum<T extends Enum<T>> implements MarshalQueryable 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 a0d8f8db6853..f4b87b9f5ae9 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -1344,7 +1344,8 @@ public final class OutputConfiguration implements Parcelable { 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 9ce834ca5981..c01c94c43997 100644 --- a/core/java/android/hardware/fingerprint/Fingerprint.java +++ b/core/java/android/hardware/fingerprint/Fingerprint.java @@ -69,4 +69,4 @@ public final class Fingerprint extends BiometricAuthenticator.Identifier { 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 5a442445d832..51236fe3b2c0 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -79,9 +79,20 @@ interface IUsbManager /* 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 2c38f7031eff..50dd0064a5cb 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -838,6 +838,28 @@ public class UsbManager { } /** + * 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 @@ public class UsbManager { } /** + * 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/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java index f8e25584c419..d4b76c888eac 100644 --- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java +++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java @@ -53,8 +53,8 @@ import java.lang.annotation.RetentionPolicy; 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 @@ public class NetworkStatsDataMigrationUtils { @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 4bc5b49aa207..0427742f9c0a 100644 --- a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java +++ b/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java @@ -53,7 +53,7 @@ public final class TunnelConnectionParamsUtils { 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/ServiceManager.java b/core/java/android/os/ServiceManager.java index e321a660e8e0..b6ff102b00b1 100644 --- a/core/java/android/os/ServiceManager.java +++ b/core/java/android/os/ServiceManager.java @@ -258,12 +258,14 @@ public final class ServiceManager { * 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/Trace.java b/core/java/android/os/Trace.java index 8bfa0e99e3b4..fb197f5be2a8 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -100,6 +100,7 @@ public final class Trace { /** @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 8aed2ac122e6..1f21bfe6cd72 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2341,12 +2341,18 @@ public class UserManager { } /** - * 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, @@ -2371,6 +2377,29 @@ public class UserManager { } /** + * 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. @@ -4392,6 +4421,7 @@ public class UserManager { * @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/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 7095d1b20d84..6eeb5e7f4b0f 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -131,6 +131,13 @@ public final class DeviceConfig { public static final String NAMESPACE_APP_STANDBY = "app_standby"; /** + * Namespace for all App Cloning related features. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final String NAMESPACE_APP_CLONING = "app_cloning"; + + /** * Namespace for AttentionManagerService related features. * * @hide diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java index 2c382efab1be..c78fb1a1f6b5 100644 --- a/core/java/android/security/keymaster/ExportResult.java +++ b/core/java/android/security/keymaster/ExportResult.java @@ -61,4 +61,4 @@ public class ExportResult implements Parcelable { 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 e330d1e94134..f69dca81950d 100644 --- a/core/java/android/service/credentials/CreateCredentialResponse.java +++ b/core/java/android/service/credentials/CreateCredentialResponse.java @@ -33,13 +33,11 @@ import java.util.Objects; * @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 @@ public final class CreateCredentialResponse implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeCharSequence(mHeader); dest.writeTypedList(mSaveEntries); dest.writeTypedObject(mRemoteSaveEntry, flags); } @@ -72,21 +69,14 @@ public final class CreateCredentialResponse implements Parcelable { }; /* 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 @@ public final class CreateCredentialResponse implements Parcelable { */ @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 @@ public final class CreateCredentialResponse implements Parcelable { Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must " + "not be empty"); return new CreateCredentialResponse( - mHeader, mSaveEntries, mRemoteSaveEntry); } diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java index ab5b5240dbfa..4b23800891a8 100644 --- a/core/java/android/service/credentials/CredentialsDisplayContent.java +++ b/core/java/android/service/credentials/CredentialsDisplayContent.java @@ -34,9 +34,6 @@ import java.util.Objects; * @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 @@ public final class CredentialsDisplayContent implements Parcelable { /** 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 @@ public final class CredentialsDisplayContent implements Parcelable { @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 @@ public final class CredentialsDisplayContent implements Parcelable { * 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 @@ public final class CredentialsDisplayContent implements Parcelable { 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/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index aa45c20a8e13..6e8198ba0cd1 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -49,6 +49,17 @@ public abstract class DreamOverlayService extends Service { 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 class DreamOverlayService extends Service { 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 32bdf7962273..8b9852a8f1b7 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -312,7 +312,14 @@ public class DreamService extends Service implements Window.Callback { @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 @@ public class DreamService extends Service implements Window.Callback { * </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 @@ public class DreamService extends Service implements Window.Callback { 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 05ebbfe98c9f..7aeceb2ce538 100644 --- a/core/java/android/service/dreams/IDreamOverlay.aidl +++ b/core/java/android/service/dreams/IDreamOverlay.aidl @@ -38,4 +38,7 @@ interface IDreamOverlay { */ 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 ec76a334d5b2..4ad63f1317d1 100644 --- a/core/java/android/service/dreams/IDreamOverlayCallback.aidl +++ b/core/java/android/service/dreams/IDreamOverlayCallback.aidl @@ -28,4 +28,7 @@ interface IDreamOverlayCallback { * 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 2cea95a6c89f..41ca94b2eef2 100644 --- a/core/java/android/service/timezone/TimeZoneProviderService.java +++ b/core/java/android/service/timezone/TimeZoneProviderService.java @@ -44,8 +44,8 @@ import java.util.Objects; * * <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 @@ public abstract class TimeZoneProviderService extends Service { * * @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 @@ public abstract class TimeZoneProviderService extends Service { /** * 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 @@ public abstract class TimeZoneProviderService extends Service { * * @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 @@ public abstract class TimeZoneProviderService extends Service { * <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 @@ public abstract class TimeZoneProviderService extends Service { * 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 513068f8a4b2..e0b78e9ae008 100644 --- a/core/java/android/service/timezone/TimeZoneProviderStatus.java +++ b/core/java/android/service/timezone/TimeZoneProviderStatus.java @@ -19,6 +19,7 @@ package android.service.timezone; 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 @@ import java.util.regex.Pattern; * * @hide */ +@SystemApi public final class TimeZoneProviderStatus implements Parcelable { /** diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 104570d03b42..007478a25016 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -31,6 +31,7 @@ import android.animation.Animator; 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 @@ public abstract class WallpaperService extends Service { * 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 @@ public abstract class WallpaperService extends Service { * surface will be destroyed and this Engine object is no longer * valid. */ + @MainThread public void onDestroy() { } @@ -673,6 +676,7 @@ public abstract class WallpaperService extends Service { * 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 @@ public abstract class WallpaperService extends Service { * * @param insets Insets to apply. */ + @MainThread public void onApplyWindowInsets(WindowInsets insets) { } @@ -693,6 +698,7 @@ public abstract class WallpaperService extends Service { * 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 @@ public abstract class WallpaperService extends Service { * 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 @@ public abstract class WallpaperService extends Service { * @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 @@ public abstract class WallpaperService extends Service { * @hide */ @SystemApi + @MainThread public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) { } @@ -749,6 +758,7 @@ public abstract class WallpaperService extends Service { * 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 @@ public abstract class WallpaperService extends Service { * 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 @@ public abstract class WallpaperService extends Service { * Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded * SurfaceHolder.Callback.surfaceRedrawNeeded()}. */ + @MainThread public void onSurfaceRedrawNeeded(SurfaceHolder holder) { } @@ -770,6 +782,7 @@ public abstract class WallpaperService extends Service { * Convenience for {@link SurfaceHolder.Callback#surfaceCreated * SurfaceHolder.Callback.surfaceCreated()}. */ + @MainThread public void onSurfaceCreated(SurfaceHolder holder) { } @@ -777,6 +790,7 @@ public abstract class WallpaperService extends Service { * Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed * SurfaceHolder.Callback.surfaceDestroyed()}. */ + @MainThread public void onSurfaceDestroyed(SurfaceHolder holder) { } @@ -787,6 +801,7 @@ public abstract class WallpaperService extends Service { * @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 @@ public abstract class WallpaperService extends Service { * * @return Wallpaper colors. */ + @MainThread public @Nullable WallpaperColors onComputeColors() { return null; } @@ -2510,6 +2526,7 @@ public abstract class WallpaperService extends Service { * 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 bd816234a652..e280bdf8b339 100644 --- a/core/java/android/text/style/AccessibilityURLSpan.java +++ b/core/java/android/text/style/AccessibilityURLSpan.java @@ -26,6 +26,7 @@ import android.view.accessibility.AccessibilityNodeInfo; * 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/util/AndroidException.java b/core/java/android/util/AndroidException.java index 1345ddf189e1..d1b9d9f3c53a 100644 --- a/core/java/android/util/AndroidException.java +++ b/core/java/android/util/AndroidException.java @@ -40,5 +40,5 @@ public class AndroidException extends Exception { boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } -}; +} diff --git a/core/java/android/util/AndroidRuntimeException.java b/core/java/android/util/AndroidRuntimeException.java index 2b824bf9cb2a..72c34d8b75ac 100644 --- a/core/java/android/util/AndroidRuntimeException.java +++ b/core/java/android/util/AndroidRuntimeException.java @@ -34,5 +34,4 @@ public class AndroidRuntimeException extends RuntimeException { public AndroidRuntimeException(Exception cause) { super(cause); } -}; - +} diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 7b6a6d29baf8..4afd26879218 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -132,6 +132,12 @@ public class FeatureFlagUtils { */ 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 @@ public class FeatureFlagUtils { 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 d4cf6e6e90c2..377200675ddf 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 9fd0ab99f01b..41c171a0bbd7 100644 --- a/core/java/android/util/Range.java +++ b/core/java/android/util/Range.java @@ -356,4 +356,4 @@ public final class Range<T extends Comparable<? super T>> { 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 19de396c4a4a..44318bbc5468 100644 --- a/core/java/android/util/TypedValue.java +++ b/core/java/android/util/TypedValue.java @@ -696,5 +696,5 @@ public class TypedValue { sb.append("}"); return sb.toString(); } -}; +} diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 510bde1deb5d..44168ca096cf 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.ACTION_ARGUMENT_A 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.AccessibilityNodeInfo.AccessibilityAction; 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 @@ public final class AccessibilityInteractionController { } } + /** + * 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 f8aa934af595..3fc3b6a3ccb3 100644 --- a/core/java/android/view/CutoutSpecification.java +++ b/core/java/android/view/CutoutSpecification.java @@ -394,7 +394,6 @@ public class CutoutSpecification { 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/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java index ea9799584e20..ff282ba0da39 100644 --- a/core/java/android/view/RemoteAnimationDefinition.java +++ b/core/java/android/view/RemoteAnimationDefinition.java @@ -19,6 +19,7 @@ package android.view; 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 class RemoteAnimationDefinition implements Parcelable { } } - 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 @@ public class RemoteAnimationDefinition implements Parcelable { return 0; } - private static final @android.annotation.NonNull Creator<RemoteAnimationAdapterEntry> CREATOR - = new Creator<RemoteAnimationAdapterEntry>() { - - @Override - public RemoteAnimationAdapterEntry createFromParcel(Parcel in) { - return new RemoteAnimationAdapterEntry(in); - } - - @Override - public RemoteAnimationAdapterEntry[] newArray(int size) { - return new RemoteAnimationAdapterEntry[size]; - } - }; + 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[] newArray(int size) { + return new RemoteAnimationAdapterEntry[size]; + } + }; } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index b24303b41abd..720813ad81ef 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1073,7 +1073,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks, SyncBufferTransactionCallback syncBufferTransactionCallback) { - getViewRootImpl().addToSync(syncBufferCallback -> + getViewRootImpl().addToSync((parentSyncGroup, syncBufferCallback) -> redrawNeededAsync(callbacks, () -> { Transaction t = null; if (mBlastBufferQueue != null) { @@ -1081,7 +1081,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall t = syncBufferTransactionCallback.waitForTransaction(); } - syncBufferCallback.onBufferReady(t); + syncBufferCallback.onTransactionReady(t); onDrawFinished(); })); } @@ -1092,9 +1092,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSyncGroups.add(syncGroup); } - syncGroup.addToSync(syncBufferCallback -> redrawNeededAsync(callbacks, - () -> { - syncBufferCallback.onBufferReady(null); + syncGroup.addToSync((parentSyncGroup, syncBufferCallback) -> + redrawNeededAsync(callbacks, () -> { + syncBufferCallback.onTransactionReady(null); onDrawFinished(); synchronized (mSyncGroups) { mSyncGroups.remove(syncGroup); diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index 6d25523ac5bc..00170cbe53d5 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -28,7 +28,7 @@ import java.lang.annotation.RetentionPolicy; 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 @@ public final class VelocityTracker { 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 @@ public final class VelocityTracker { } /** - * 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 @@ public final class VelocityTracker { * @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 @@ public final class VelocityTracker { * 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 @@ public final class VelocityTracker { * 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 @@ public final class VelocityTracker { * @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 636023938b9d..5e1dc340a7a7 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.InputMethodCl 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; @@ -199,6 +200,7 @@ 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; @@ -229,6 +231,7 @@ import java.util.List; import java.util.Objects; import java.util.Queue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; /** * The top of a view hierarchy, implementing the needed protocol between View @@ -849,7 +852,7 @@ public final class ViewRootImpl implements ViewParent, } private SurfaceSyncGroup mSyncGroup; - private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback; + private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback; private int mNumSyncsInProgress = 0; private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks; @@ -3608,8 +3611,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } - if (mSyncBufferCallback != null) { - mSyncBufferCallback.onBufferReady(null); + if (mTransactionReadyCallback != null) { + mTransactionReadyCallback.onTransactionReady(null); } } else if (cancelAndRedraw) { mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener @@ -3624,8 +3627,8 @@ public final class ViewRootImpl implements ViewParent, } mPendingTransitions.clear(); } - if (!performDraw() && mSyncBufferCallback != null) { - mSyncBufferCallback.onBufferReady(null); + if (!performDraw() && mTransactionReadyCallback != null) { + mTransactionReadyCallback.onTransactionReady(null); } } @@ -3639,7 +3642,7 @@ public final class ViewRootImpl implements ViewParent, if (!cancelAndRedraw) { mReportNextDraw = false; mLastReportNextDrawReason = null; - mSyncBufferCallback = null; + mTransactionReadyCallback = null; mSyncBuffer = false; if (isInLocalSync()) { mSyncGroup.markSyncReady(); @@ -4386,7 +4389,7 @@ public final class ViewRootImpl implements ViewParent, return false; } - final boolean fullRedrawNeeded = mFullRedrawNeeded || mSyncBufferCallback != null; + final boolean fullRedrawNeeded = mFullRedrawNeeded || mTransactionReadyCallback != null; mFullRedrawNeeded = false; mIsDrawing = true; @@ -4394,9 +4397,9 @@ public final class ViewRootImpl implements ViewParent, addFrameCommitCallbackIfNeeded(); - boolean usingAsyncReport = isHardwareEnabled() && mSyncBufferCallback != null; + boolean usingAsyncReport = isHardwareEnabled() && mTransactionReadyCallback != null; if (usingAsyncReport) { - registerCallbacksForSync(mSyncBuffer, mSyncBufferCallback); + registerCallbacksForSync(mSyncBuffer, mTransactionReadyCallback); } else if (mHasPendingTransactions) { // These callbacks are only needed if there's no sync involved and there were calls to // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and @@ -4447,10 +4450,11 @@ public final class ViewRootImpl implements ViewParent, } if (mSurfaceHolder != null && mSurface.isValid()) { - final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback = mSyncBufferCallback; + final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback = + mTransactionReadyCallback; SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> - mHandler.post(() -> syncBufferCallback.onBufferReady(null))); - mSyncBufferCallback = null; + mHandler.post(() -> transactionReadyCallback.onTransactionReady(null))); + mTransactionReadyCallback = null; SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); @@ -4461,8 +4465,8 @@ public final class ViewRootImpl implements ViewParent, } } } - if (mSyncBufferCallback != null && !usingAsyncReport) { - mSyncBufferCallback.onBufferReady(null); + if (mTransactionReadyCallback != null && !usingAsyncReport) { + mTransactionReadyCallback.onTransactionReady(null); } if (mPerformContentCapture) { performContentCaptureInitialReport(); @@ -10707,6 +10711,25 @@ public final class ViewRootImpl implements ViewParent, .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 */ + } + } + } } /** @@ -11113,7 +11136,7 @@ public final class ViewRootImpl implements ViewParent, } private void registerCallbacksForSync(boolean syncBuffer, - final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) { + final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) { if (!isHardwareEnabled()) { return; } @@ -11140,7 +11163,7 @@ public final class ViewRootImpl implements ViewParent, // pendingDrawFinished. if ((syncResult & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) { - syncBufferCallback.onBufferReady( + transactionReadyCallback.onTransactionReady( mBlastBufferQueue.gatherPendingTransactions(frame)); return null; } @@ -11150,7 +11173,8 @@ public final class ViewRootImpl implements ViewParent, } if (syncBuffer) { - mBlastBufferQueue.syncNextTransaction(syncBufferCallback::onBufferReady); + mBlastBufferQueue.syncNextTransaction( + transactionReadyCallback::onTransactionReady); } return didProduceBuffer -> { @@ -11170,7 +11194,7 @@ public final class ViewRootImpl implements ViewParent, // since the frame didn't draw on this vsync. It's possible the frame will // draw later, but it's better to not be sync than to block on a frame that // may never come. - syncBufferCallback.onBufferReady( + transactionReadyCallback.onTransactionReady( mBlastBufferQueue.gatherPendingTransactions(frame)); return; } @@ -11179,22 +11203,49 @@ public final class ViewRootImpl implements ViewParent, // syncNextTransaction callback. Instead, just report back to the Syncer so it // knows that this sync request is complete. if (!syncBuffer) { - syncBufferCallback.onBufferReady(null); + transactionReadyCallback.onTransactionReady(null); } }; } }); } - public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() { + private final Executor mPostAtFrontExecutor = new Executor() { @Override - public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) { - readyToSync(syncBufferCallback); + public void execute(Runnable command) { + mHandler.postAtFrontOfQueue(command); } + }; + public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() { @Override - public void onSyncComplete() { - mHandler.postAtFrontOfQueue(() -> { + public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup, + SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) { + updateSyncInProgressCount(parentSyncGroup); + if (!isInLocalSync()) { + // Always sync the buffer if the sync request did not come from VRI. + mSyncBuffer = true; + } + if (mAttachInfo.mThreadedRenderer != null) { + HardwareRenderer.setRtAnimationsEnabled(false); + } + + if (mTransactionReadyCallback != null) { + Log.d(mTag, "Already set sync for the next draw."); + mTransactionReadyCallback.onTransactionReady(null); + } + if (DEBUG_BLAST) { + Log.d(mTag, "Setting syncFrameCallback"); + } + mTransactionReadyCallback = transactionReadyCallback; + if (!mIsInTraversal && !mTraversalScheduled) { + scheduleTraversals(); + } + } + + private void updateSyncInProgressCount(SurfaceSyncGroup parentSyncGroup) { + mNumSyncsInProgress++; + parentSyncGroup.addSyncCompleteCallback(mPostAtFrontExecutor, () -> { if (--mNumSyncsInProgress == 0 && mAttachInfo.mThreadedRenderer != null) { HardwareRenderer.setRtAnimationsEnabled(true); } @@ -11207,29 +11258,6 @@ public final class ViewRootImpl implements ViewParent, return mSyncTarget; } - private void readyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) { - mNumSyncsInProgress++; - if (!isInLocalSync()) { - // Always sync the buffer if the sync request did not come from VRI. - mSyncBuffer = true; - } - if (mAttachInfo.mThreadedRenderer != null) { - HardwareRenderer.setRtAnimationsEnabled(false); - } - - if (mSyncBufferCallback != null) { - Log.d(mTag, "Already set sync for the next draw."); - mSyncBufferCallback.onBufferReady(null); - } - if (DEBUG_BLAST) { - Log.d(mTag, "Setting syncFrameCallback"); - } - mSyncBufferCallback = syncBufferCallback; - if (!mIsInTraversal && !mTraversalScheduled) { - scheduleTraversals(); - } - } - void mergeSync(SurfaceSyncGroup otherSyncGroup) { if (!isInLocalSync()) { return; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cc85181f52f2..16f6cea2135c 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3408,6 +3408,11 @@ public interface WindowManager extends ViewManager { * 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 @@ public interface WindowManager extends ViewManager { * 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 e3ffc9dcbbde..7030ab54b790 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -22,7 +22,9 @@ import static android.os.Build.VERSION_CODES.S; 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.content.Context; 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.Collections; 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 @@ public final class AccessibilityInteractionClient 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 @@ public final class AccessibilityInteractionClient } /** + * 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 @@ public final class AccessibilityInteractionClient } /** + * 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 472a36361585..fb01921dbaa1 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -22,6 +22,7 @@ import android.os.Bundle; 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 @@ oneway interface IAccessibilityInteractionConnection { 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 231e75a19a06..456bf5893ea0 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -63,4 +63,9 @@ oneway interface IAccessibilityInteractionConnectionCallback { */ @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/webkit/ConsoleMessage.java b/core/java/android/webkit/ConsoleMessage.java index 5474557c9998..89cb6b2761be 100644 --- a/core/java/android/webkit/ConsoleMessage.java +++ b/core/java/android/webkit/ConsoleMessage.java @@ -68,4 +68,4 @@ public class ConsoleMessage { public int lineNumber() { return mLineNumber; } -}; +} diff --git a/core/java/android/webkit/ValueCallback.java b/core/java/android/webkit/ValueCallback.java index 5c7d97fc5d8c..3d5bb4922a77 100644 --- a/core/java/android/webkit/ValueCallback.java +++ b/core/java/android/webkit/ValueCallback.java @@ -25,4 +25,4 @@ public interface ValueCallback<T> { * @param value The value. */ public void onReceiveValue(T value); -}; +} diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index 4248096f307d..395073941930 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -57,12 +57,13 @@ import java.util.function.Supplier; * option is provided. * * The following is what happens within the {@link SurfaceSyncGroup} - * 1. Each SyncTarget will get a {@link SyncTarget#onReadyToSync} callback that contains a - * {@link SyncBufferCallback}. - * 2. Each {@link SyncTarget} needs to invoke {@link SyncBufferCallback#onBufferReady(Transaction)}. - * This makes sure the SurfaceSyncGroup knows when the SyncTarget is complete, allowing the - * SurfaceSyncGroup to get the Transaction that contains the buffer. - * 3. When the final SyncBufferCallback finishes for the SurfaceSyncGroup, in most cases the + * 1. Each SyncTarget will get a {@link SyncTarget#onAddedToSyncGroup} callback that contains a + * {@link TransactionReadyCallback}. + * 2. Each {@link SyncTarget} needs to invoke + * {@link TransactionReadyCallback#onTransactionReady(Transaction)}. This makes sure the + * SurfaceSyncGroup knows when the SyncTarget is complete, allowing the SurfaceSyncGroup to get the + * Transaction that contains the buffer. + * 3. When the final TransactionReadyCallback finishes for the SurfaceSyncGroup, in most cases the * transaction is applied and then the sync complete callbacks are invoked, letting the callers know * the sync is now complete. * @@ -86,8 +87,6 @@ public final class SurfaceSyncGroup { private final Transaction mTransaction = sTransactionFactory.get(); @GuardedBy("mLock") private boolean mSyncReady; - @GuardedBy("mLock") - private final Set<SyncTarget> mSyncTargets = new ArraySet<>(); @GuardedBy("mLock") private Consumer<Transaction> mSyncRequestCompleteCallback; @@ -197,14 +196,13 @@ public final class SurfaceSyncGroup { * Add a {@link SyncTarget} to a sync set. The sync set will wait for all * SyncableSurfaces to complete before notifying. * - * @param syncTarget A SyncableSurface that implements how to handle syncing - * buffers. + * @param syncTarget A SyncTarget that implements how to handle syncing transactions. * @return true if the SyncTarget was successfully added to the SyncGroup, false otherwise. */ public boolean addToSync(SyncTarget syncTarget) { - SyncBufferCallback syncBufferCallback = new SyncBufferCallback() { + TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() { @Override - public void onBufferReady(Transaction t) { + public void onTransactionReady(Transaction t) { synchronized (mLock) { if (t != null) { mTransaction.merge(t); @@ -221,10 +219,9 @@ public final class SurfaceSyncGroup { + "SyncTargets can be added."); return false; } - mPendingSyncs.add(syncBufferCallback.hashCode()); - mSyncTargets.add(syncTarget); + mPendingSyncs.add(transactionReadyCallback.hashCode()); } - syncTarget.onReadyToSync(syncBufferCallback); + syncTarget.onAddedToSyncGroup(this, transactionReadyCallback); return true; } @@ -256,17 +253,13 @@ public final class SurfaceSyncGroup { Log.d(TAG, "Successfully finished sync id=" + this); } - for (SyncTarget syncTarget : mSyncTargets) { - syncTarget.onSyncComplete(); - } - mSyncTargets.clear(); mSyncRequestCompleteCallback.accept(mTransaction); mFinished = true; } /** * Add a Transaction to this sync set. This allows the caller to provide other info that - * should be synced with the buffers. + * should be synced with the transactions. */ public void addTransactionToSync(Transaction t) { synchronized (mLock) { @@ -334,9 +327,10 @@ public final class SurfaceSyncGroup { } @Override - public void onReadyToSync(SyncBufferCallback syncBufferCallback) { + public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup, + TransactionReadyCallback transactionReadyCallback) { mFrameCallbackConsumer.accept( - () -> mSurfaceView.syncNextFrame(syncBufferCallback::onBufferReady)); + () -> mSurfaceView.syncNextFrame(transactionReadyCallback::onTransactionReady)); } } @@ -345,22 +339,19 @@ public final class SurfaceSyncGroup { */ public interface SyncTarget { /** - * Called when the Syncable is ready to begin handing a sync request. When invoked, the - * implementor is required to call {@link SyncBufferCallback#onBufferReady(Transaction)} - * and {@link SyncBufferCallback#onBufferReady(Transaction)} in order for this Syncable - * to be marked as complete. + * Called when the SyncTarget has been added to a SyncGroup as is ready to begin handing a + * sync request. When invoked, the implementor is required to call + * {@link TransactionReadyCallback#onTransactionReady(Transaction)} in order for this + * SurfaceSyncGroup to fully complete. * * Always invoked on the thread that initiated the call to {@link #addToSync(SyncTarget)} * - * @param syncBufferCallback A SyncBufferCallback that the caller must invoke onBufferReady - */ - void onReadyToSync(SyncBufferCallback syncBufferCallback); - - /** - * There's no guarantee about the thread this callback is invoked on. + * @param parentSyncGroup The sync group this target has been added to. + * @param transactionReadyCallback A TransactionReadyCallback that the caller must invoke + * onTransactionReady */ - default void onSyncComplete() { - } + void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup, + TransactionReadyCallback transactionReadyCallback); } /** @@ -368,14 +359,14 @@ public final class SurfaceSyncGroup { * completed. The caller should invoke the calls when the rendering has started and finished a * frame. */ - public interface SyncBufferCallback { + public interface TransactionReadyCallback { /** - * Invoked when the transaction contains the buffer and is ready to sync. + * Invoked when the transaction is ready to sync. * - * @param t The transaction that contains the buffer to be synced. This can be null if - * there's nothing to sync + * @param t The transaction that contains the anything to be included in the synced. This + * can be null if there's nothing to sync */ - void onBufferReady(@Nullable Transaction t); + void onTransactionReady(@Nullable Transaction t); } /** diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java index db15145b0a1e..e62d5c95a1f8 100644 --- a/core/java/android/window/TransitionFilter.java +++ b/core/java/android/window/TransitionFilter.java @@ -296,7 +296,7 @@ public final class TransitionFilter implements Parcelable { 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 2d29c5946ede..c7a2d24a8c94 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -454,6 +454,23 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * 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 final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { 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 @@ public final class WindowContainerTransaction implements Parcelable { 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 30da4b470ab6..88447daf7338 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -58,11 +58,12 @@ interface IAppOpsService { 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 97f4b0fc8733..a21a84261ae0 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 @@ public final class AssociationState { /** * 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/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 696f0ffba518..556e1467c7df 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -403,7 +403,7 @@ public class BatteryStatsHistory { * Returns true if this instance only supports reading history. */ public boolean isReadOnly() { - return mActiveFile == null; + return mActiveFile == null || mHistoryDir == null; } /** @@ -1292,7 +1292,9 @@ public class BatteryStatsHistory { && mHistoryLastWritten.batteryHealth == cur.batteryHealth && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature - && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) { + && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage + && mHistoryLastWritten.measuredEnergyDetails == null + && mHistoryLastWritten.cpuUsageDetails == null) { // We can merge this new change in with the last one. Merging is // allowed as long as only the states have changed, and within those states // as long as no bit has changed both between now and the last entry, as @@ -1761,8 +1763,8 @@ public class BatteryStatsHistory { * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} . */ public void writeHistory() { - if (mActiveFile == null) { - Slog.w(TAG, "writeHistory: no history file associated with this instance"); + if (isReadOnly()) { + Slog.w(TAG, "writeHistory: this instance instance is read-only"); return; } diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index 0a29fc5285a5..eb62cb07ee68 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -735,7 +735,7 @@ public class BinderCallsStats implements BinderInternal.Observer { } 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 e9d55db3a5b4..1276fb980799 100644 --- a/core/java/com/android/internal/os/BinderLatencyObserver.java +++ b/core/java/com/android/internal/os/BinderLatencyObserver.java @@ -236,7 +236,7 @@ public class BinderLatencyObserver { } 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 2805dccffe50..0645eb7f0835 100644 --- a/core/java/com/android/internal/os/LooperStats.java +++ b/core/java/com/android/internal/os/LooperStats.java @@ -290,7 +290,7 @@ public class LooperStats implements Looper.Observer { } 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/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java index 2143bc1b4532..9ddb8c75f5c8 100644 --- a/core/java/com/android/internal/os/ProcLocksReader.java +++ b/core/java/com/android/internal/os/ProcLocksReader.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import android.util.IntArray; + import com.android.internal.util.ProcFileReader; import java.io.FileInputStream; @@ -35,6 +37,7 @@ import java.io.IOException; public class ProcLocksReader { private final String mPath; private ProcFileReader mReader = null; + private IntArray mPids = new IntArray(); public ProcLocksReader() { mPath = "/proc/locks"; @@ -51,9 +54,13 @@ public class ProcLocksReader { public interface ProcLocksReaderCallback { /** * Call the callback function of handleBlockingFileLocks(). - * @param pid Each process that hold file locks blocking other processes. + * @param pids Each process that hold file locks blocking other processes. + * pids[0] is the process blocking others + * pids[1..n-1] are the processes being blocked + * NOTE: pids are cleared immediately after onBlockingFileLock() returns. If the caller + * needs to cache it, please make a copy, e.g. by calling pids.toArray(). */ - void onBlockingFileLock(int pid); + void onBlockingFileLock(IntArray pids); } /** @@ -64,8 +71,7 @@ public class ProcLocksReader { public void handleBlockingFileLocks(ProcLocksReaderCallback callback) throws IOException { long last = -1; long id; // ordinal position of the lock in the list - int owner = -1; // the PID of the process that owns the lock - int pid = -1; // the PID of the process blocking others + int pid = -1; // the PID of the process being blocked if (mReader == null) { mReader = new ProcFileReader(new FileInputStream(mPath)); @@ -73,26 +79,49 @@ public class ProcLocksReader { mReader.rewind(); } + mPids.clear(); while (mReader.hasMoreData()) { id = mReader.nextLong(true); // lock id if (id == last) { - mReader.finishLine(); // blocked lock - if (pid < 0) { - pid = owner; // get pid from the previous line - callback.onBlockingFileLock(pid); + // blocked lock found + mReader.nextIgnored(); // -> + mReader.nextIgnored(); // lock type: POSIX? + mReader.nextIgnored(); // lock type: MANDATORY? + mReader.nextIgnored(); // lock type: RW? + + pid = mReader.nextInt(); // pid + if (pid > 0) { + mPids.add(pid); } - continue; + + mReader.finishLine(); } else { - pid = -1; // a new lock - } + // process blocking lock and move on to a new lock + if (mPids.size() > 1) { + callback.onBlockingFileLock(mPids); + mPids.clear(); + } - mReader.nextIgnored(); // lock type: POSIX? - mReader.nextIgnored(); // lock type: MANDATORY? - mReader.nextIgnored(); // lock type: RW? + // new lock found + mReader.nextIgnored(); // lock type: POSIX? + mReader.nextIgnored(); // lock type: MANDATORY? + mReader.nextIgnored(); // lock type: RW? - owner = mReader.nextInt(); // pid - mReader.finishLine(); - last = id; + pid = mReader.nextInt(); // pid + if (pid > 0) { + if (mPids.size() == 0) { + mPids.add(pid); + } else { + mPids.set(0, pid); + } + } + mReader.finishLine(); + last = id; + } + } + // The last unprocessed blocking lock immediately before EOF + if (mPids.size() > 1) { + callback.onBlockingFileLock(mPids); } } } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 3e988e61d978..145aeafb46a1 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -2402,7 +2402,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 8fcb6d5514d4..4b7b91c74f94 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -468,7 +468,7 @@ public class LatencyTracker { 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/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java index 32ce0fe1282b..1ae1307633bb 100644 --- a/core/java/com/android/internal/view/BaseSurfaceHolder.java +++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java @@ -241,4 +241,4 @@ public abstract class BaseSurfaceHolder implements SurfaceHolder { mSurfaceFrame.right = width; mSurfaceFrame.bottom = height; } -}; +} diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java index 5b08bb1f42d3..6063c90d6ab9 100644 --- a/core/java/com/android/internal/widget/LockSettingsInternal.java +++ b/core/java/com/android/internal/widget/LockSettingsInternal.java @@ -54,6 +54,12 @@ public abstract class LockSettingsInternal { // 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 7ada5483b92b..196ea59fe0f4 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/anim/dock_bottom_enter.xml b/core/res/res/anim/dock_bottom_enter.xml deleted file mode 100644 index bfb97b6d9b80..000000000000 --- a/core/res/res/anim/dock_bottom_enter.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2012, 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. -*/ ---> - -<!-- Animation for when a dock window at the bottom of the screen is entering. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/decelerate_cubic"> - <translate android:fromYDelta="100%" android:toYDelta="0" - android:duration="@integer/dock_enter_exit_duration"/> -</set> diff --git a/core/res/res/anim/dock_bottom_exit.xml b/core/res/res/anim/dock_bottom_exit.xml deleted file mode 100644 index 4e15448aba3e..000000000000 --- a/core/res/res/anim/dock_bottom_exit.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2012, 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. -*/ ---> - -<!-- Animation for when a dock window at the bottom of the screen is exiting. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/accelerate_cubic"> - <translate android:fromYDelta="0" android:toYDelta="100%" - android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/> -</set> diff --git a/core/res/res/anim/dock_bottom_exit_keyguard.xml b/core/res/res/anim/dock_bottom_exit_keyguard.xml deleted file mode 100644 index 4de3ce5b8932..000000000000 --- a/core/res/res/anim/dock_bottom_exit_keyguard.xml +++ /dev/null @@ -1,22 +0,0 @@ -<!-- - ~ Copyright (C) 2016 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<!-- Animation for when a dock window at the bottom of the screen is exiting while on Keyguard --> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/fast_out_linear_in"> - <translate android:fromYDelta="0" android:toYDelta="100%" - android:duration="200"/> -</set>
\ No newline at end of file diff --git a/core/res/res/anim/dock_left_enter.xml b/core/res/res/anim/dock_left_enter.xml deleted file mode 100644 index 7f5dfd50afc7..000000000000 --- a/core/res/res/anim/dock_left_enter.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2012, 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. -*/ ---> - -<!-- Animation for when a dock window at the left of the screen is entering. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/decelerate_cubic"> - <translate android:fromXDelta="-100%" android:toXDelta="0" - android:duration="250"/> -</set> diff --git a/core/res/res/anim/dock_left_exit.xml b/core/res/res/anim/dock_left_exit.xml deleted file mode 100644 index 11cbc0b36405..000000000000 --- a/core/res/res/anim/dock_left_exit.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2012, 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. -*/ ---> - -<!-- Animation for when a dock window at the right of the screen is exiting. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/accelerate_cubic"> - <translate android:fromXDelta="0" android:toXDelta="-100%" - android:startOffset="100" android:duration="250"/> -</set> diff --git a/core/res/res/anim/dock_right_enter.xml b/core/res/res/anim/dock_right_enter.xml deleted file mode 100644 index a92c7d234e58..000000000000 --- a/core/res/res/anim/dock_right_enter.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2012, 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. -*/ ---> - -<!-- Animation for when a dock window at the right of the screen is entering. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/decelerate_cubic"> - <translate android:fromXDelta="100%" android:toXDelta="0" - android:duration="250"/> -</set> diff --git a/core/res/res/anim/dock_right_exit.xml b/core/res/res/anim/dock_right_exit.xml deleted file mode 100644 index 80e4dc318192..000000000000 --- a/core/res/res/anim/dock_right_exit.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2012, 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. -*/ ---> - -<!-- Animation for when a dock window at the right of the screen is exiting. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/accelerate_cubic"> - <translate android:fromXDelta="0" android:toXDelta="100%" - android:startOffset="100" android:duration="250"/> -</set> diff --git a/core/res/res/anim/dock_top_enter.xml b/core/res/res/anim/dock_top_enter.xml deleted file mode 100644 index f763fb5c1cc9..000000000000 --- a/core/res/res/anim/dock_top_enter.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2007, 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. -*/ ---> - -<!-- Animation for when a dock window at the top of the screen is entering. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/decelerate_cubic"> - <translate android:fromYDelta="-100%" android:toYDelta="0" - android:duration="@integer/dock_enter_exit_duration"/> -</set> diff --git a/core/res/res/anim/dock_top_exit.xml b/core/res/res/anim/dock_top_exit.xml deleted file mode 100644 index 995b7d0ffc8c..000000000000 --- a/core/res/res/anim/dock_top_exit.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2007, 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. -*/ ---> - -<!-- Animation for when a dock window at the top of the screen is exiting. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@android:interpolator/accelerate_cubic"> - <translate android:fromYDelta="0" android:toYDelta="-100%" - android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/> -</set> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index eac2b9443631..607467abe24f 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 ac383e69e02e..932b24ec08c9 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2434,7 +2434,7 @@ <!-- 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) --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 216975d2d2e6..7714082dcfc4 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> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 89ec5ba48142..437dedcfe5e3 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1691,15 +1691,6 @@ <!-- From android.policy --> <java-symbol type="anim" name="app_starting_exit" /> - <java-symbol type="anim" name="dock_top_enter" /> - <java-symbol type="anim" name="dock_top_exit" /> - <java-symbol type="anim" name="dock_bottom_enter" /> - <java-symbol type="anim" name="dock_bottom_exit" /> - <java-symbol type="anim" name="dock_bottom_exit_keyguard" /> - <java-symbol type="anim" name="dock_left_enter" /> - <java-symbol type="anim" name="dock_left_exit" /> - <java-symbol type="anim" name="dock_right_enter" /> - <java-symbol type="anim" name="dock_right_exit" /> <java-symbol type="anim" name="fade_in" /> <java-symbol type="anim" name="fade_out" /> <java-symbol type="anim" name="voice_activity_close_exit" /> diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java index 1cf430205627..372bca4664a2 100644 --- a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java +++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java @@ -88,6 +88,7 @@ public class ParcelableBenchmark { } } + @SuppressWarnings("ParcelableCreator") @SuppressLint("ParcelCreator") private static class PointArray implements Parcelable { Rect mBounds = new Rect(); diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java index 67b24ec17a27..bbd2ef38d786 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 class BackupRestoreEventLoggerTest { 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 @@ public class BackupRestoreEventLoggerTest { 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 @@ public class BackupRestoreEventLoggerTest { 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 index 0073d86873da..13e5e14ce01c 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 final class ParcelableTestSupport { } 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 c9b96c6071ee..1a276ad0b3c3 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_ALLOWED; 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 @@ public class TimeCapabilitiesTest { { TimeCapabilities one = builder1.build(); TimeCapabilities two = builder2.build(); - assertEquals(one, two); + assertEqualsAndHashCode(one, two); } builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED); @@ -69,7 +70,7 @@ public class TimeCapabilitiesTest { { TimeCapabilities one = builder1.build(); TimeCapabilities two = builder2.build(); - assertEquals(one, two); + assertEqualsAndHashCode(one, two); } builder2.setSetManualTimeCapability(CAPABILITY_NOT_ALLOWED); @@ -83,7 +84,7 @@ public class TimeCapabilitiesTest { { 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 bce09099e4d3..25e6e2b66219 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 @@ public class TimeStateTest { 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 3f7da8a6fbd0..8bed31f4821a 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 @@ package android.app.time; 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 35a9dbc200e4..595b7006c0e5 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 @@ public class TimeZoneStateTest { 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 0c7c8c1803eb..28da164952ec 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 26cb90231931..e9ca069d372d 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 17838bb19a8f..b5bdea71c3b6 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 28009d4168ad..d5dcac2b2aee 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 d505492f3b80..86e958320a04 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.assertEquals; 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 @@ public class PackageManagerPropertyTests { @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/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java index 86ebdf3c0759..7f772dd3b12d 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 b7a595c8c748..9006cd91d616 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 @@ public class TimeZoneProviderStatusTest { 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/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 35d5948bc2af..7a5ab0458406 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.Bundle; 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 class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon 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 00b3693c902b..bbf9f3c99402 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -128,6 +128,7 @@ public class RemoteViewsTest { 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 @@ public class RemoteViewsTest { } } + @SuppressWarnings("ReturnValueIgnored") @Test public void nestedAddViews() { RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); @@ -509,6 +511,7 @@ public class RemoteViewsTest { 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 82b2bf4185e6..8207c9ee5ff3 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 @@ public class BinderCallsStatsTest { 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 5af7376dc132..7bd53b9d4fcc 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 @@ public class BinderLatencyObserverTest { 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 @@ public class BinderLatencyObserverTest { 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/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java index b34554cd113d..c3d40eb09237 100644 --- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.os.FileUtils; +import android.util.IntArray; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -33,13 +34,15 @@ import org.junit.runner.RunWith; import java.io.File; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Arrays; @SmallTest @RunWith(AndroidJUnit4.class) public class ProcLocksReaderTest implements ProcLocksReader.ProcLocksReaderCallback { private File mProcDirectory; - private ArrayList<Integer> mPids = new ArrayList<>(); + + private ArrayList<int[]> mPids = new ArrayList<>(); @Before public void setUp() { @@ -54,41 +57,51 @@ public class ProcLocksReaderTest implements @Test public void testRunSimpleLocks() throws Exception { - String simpleLocks = - "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" + - "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"; + String simpleLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" + + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"; runHandleBlockingFileLocks(simpleLocks); assertTrue(mPids.isEmpty()); } @Test public void testRunBlockingLocks() throws Exception { - String blockedLocks = - "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" + - "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" + - "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" + - "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" + - "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" + - "4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n"; + String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" + + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" + + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" + + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" + + "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" + + "4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n"; + runHandleBlockingFileLocks(blockedLocks); + assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293})); + assertTrue(mPids.isEmpty()); + } + + @Test + public void testRunLastBlockingLocks() throws Exception { + String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" + + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" + + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" + + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n"; runHandleBlockingFileLocks(blockedLocks); - assertTrue(mPids.remove(0).equals(18292)); + assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293})); assertTrue(mPids.isEmpty()); } @Test public void testRunMultipleBlockingLocks() throws Exception { - String blockedLocks = - "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" + - "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" + - "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" + - "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" + - "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" + - "4: FLOCK ADVISORY WRITE 3840 fe:01:5111809 0 EOF\n" + - "4: -> FLOCK ADVISORY WRITE 3841 fe:01:5111809 0 EOF\n" + - "5: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n"; + String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" + + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" + + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" + + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" + + "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" + + "4: FLOCK ADVISORY WRITE 3840 fe:01:5111809 0 EOF\n" + + "4: -> FLOCK ADVISORY WRITE 3841 fe:01:5111809 0 EOF\n" + + "5: FLOCK ADVISORY READ 3888 fd:09:14230 0 EOF\n" + + "5: -> FLOCK ADVISORY READ 3887 fd:09:14230 0 EOF\n"; runHandleBlockingFileLocks(blockedLocks); - assertTrue(mPids.remove(0).equals(18292)); - assertTrue(mPids.remove(0).equals(3840)); + assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293})); + assertTrue(Arrays.equals(mPids.remove(0), new int[]{3840, 3841})); + assertTrue(Arrays.equals(mPids.remove(0), new int[]{3888, 3887})); assertTrue(mPids.isEmpty()); } @@ -102,11 +115,12 @@ public class ProcLocksReaderTest implements /** * Call the callback function of handleBlockingFileLocks(). - * - * @param pid Each process that hold file locks blocking other processes. + * @param pids Each process that hold file locks blocking other processes. + * pids[0] is the process blocking others + * pids[1..n-1] are the processes being blocked */ @Override - public void onBlockingFileLock(int pid) { - mPids.add(pid); + public void onBlockingFileLock(IntArray pids) { + mPids.add(pids.toArray()); } } 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 6c50bce86638..8b30828a8936 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 @@ public class TokenBucketTest extends TestCase { } 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 41b8956f55d0..a2263256508b 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 @@ public class Test extends ActivityInstrumentationTestCase2<MainActivity> { 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 34659897e588..2da9a2ebbdb6 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 @@ package com.android.overlaytest; 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.runner.RunWith; 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 @@ public class FabricatedOverlaysTest { } @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 c53f4cc7ee52..1581abb5a9c6 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 junit.framework.TestCase; 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 @@ public class CallbackRegistryTest extends TestCase { 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 class CallbackRegistryTest extends TestCase { 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 class CallbackRegistryTest extends TestCase { 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 decfb9fc59df..3e2b71f18b75 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -495,6 +495,10 @@ applications that come with the platform <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 8706a68226ef..42e304699cd4 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 @@ public final class EfficientXmlChecker extends BugChecker */ 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 @@ public final class EfficientXmlChecker extends BugChecker */ 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 abf7e9911086..42c892a240b6 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1667,6 +1667,9 @@ public class Canvas extends BaseCanvas { * 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 @@ public class Canvas extends BaseCanvas { * 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 @@ public class Canvas extends BaseCanvas { /** * 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 @@ public class Canvas extends BaseCanvas { /** * 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 @@ public class Canvas extends BaseCanvas { * 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 @@ public class Canvas extends BaseCanvas { * @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 1a80ab308bb5..f0e496f3a178 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -137,6 +137,13 @@ public class Paint { * <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 54c9f6222f7e..1a878dfa2cf2 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -766,7 +766,7 @@ public class RippleDrawable extends LayerDrawable { 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 4065bd110c7e..e25ee906b410 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -60,7 +60,7 @@ import java.io.IOException; 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 @@ public class VectorDrawable extends Drawable { 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 @@ public class VectorDrawable extends Drawable { } }; - 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 @@ public class VectorDrawable extends Drawable { 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 @@ public class VectorDrawable extends Drawable { } }; - 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/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index a7fa2d936940..16760e26b3f1 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen 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/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index d9eaeeeaf45f..f811940fd304 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 @@ -116,7 +116,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private final TouchTracker mTouchTracker = new TouchTracker(); private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>(); - + @Nullable private IOnBackInvokedCallback mActiveCallback; @VisibleForTesting @@ -180,6 +180,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void initBackAnimationRunners() { + if (!IS_U_ANIMATION_ENABLED) { + return; + } + final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext); mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK, new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner)); @@ -207,7 +211,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void updateEnableAnimationFromSetting() { int settingValue = Global.getInt(mContext.getContentResolver(), Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF); - boolean isEnabled = settingValue == SETTING_VALUE_ON && IS_U_ANIMATION_ENABLED; + boolean isEnabled = settingValue == SETTING_VALUE_ON; mEnableAnimations.set(isEnabled); ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled); } @@ -350,10 +354,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return; } final int backType = backNavigationInfo.getType(); - final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType); + final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(); if (shouldDispatchToAnimator) { - mActiveCallback = mAnimationDefinition.get(backType).getCallback(); - mAnimationDefinition.get(backType).startGesture(); + if (mAnimationDefinition.contains(backType)) { + mActiveCallback = mAnimationDefinition.get(backType).getCallback(); + mAnimationDefinition.get(backType).startGesture(); + } else { + mActiveCallback = null; + } } else { mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback(); dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null)); @@ -361,9 +369,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void onMove() { - if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) { + if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get() + || mActiveCallback == null) { return; } + final BackEvent backEvent = mTouchTracker.createProgressEvent(); dispatchOnBackProgressed(mActiveCallback, backEvent); } @@ -387,11 +397,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } - private boolean shouldDispatchToAnimator(int backType) { + private boolean shouldDispatchToAnimator() { return mEnableAnimations.get() && mBackNavigationInfo != null - && mBackNavigationInfo.isPrepareRemoteAnimation() - && mAnimationDefinition.contains(backType); + && mBackNavigationInfo.isPrepareRemoteAnimation(); } private void dispatchOnBackStarted(IOnBackInvokedCallback callback, @@ -461,6 +470,30 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTouchTracker.setProgressThreshold(progressThreshold); } + private void invokeOrCancelBack() { + // Make a synchronized call to core before dispatch back event to client side. + // If the close transition happens before the core receives onAnimationFinished, there will + // play a second close animation for that transition. + if (mBackAnimationFinishedCallback != null) { + try { + mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack); + } catch (RemoteException e) { + Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e); + } + mBackAnimationFinishedCallback = null; + } + + if (mBackNavigationInfo != null) { + final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); + if (mTriggerBack) { + dispatchOnBackInvoked(callback); + } else { + dispatchOnBackCancelled(callback); + } + } + finishBackNavigation(); + } + /** * Called when the gesture is released, then it could start the post commit animation. */ @@ -493,15 +526,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } 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(); + // Simply trigger and finish back navigation when no animator defined. + if (!shouldDispatchToAnimator() || mActiveCallback == null) { + invokeOrCancelBack(); return; } @@ -549,16 +576,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont 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(); + invokeOrCancelBack(); } /** @@ -567,25 +585,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @VisibleForTesting void finishBackNavigation() { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()"); - BackNavigationInfo backNavigationInfo = mBackNavigationInfo; - boolean triggerBack = mTriggerBack; - mBackNavigationInfo = null; - mTriggerBack = false; mShouldStartOnNextMoveEvent = false; mTouchTracker.reset(); mActiveCallback = null; - if (backNavigationInfo == null) { - return; - } - if (mBackAnimationFinishedCallback != null) { - try { - mBackAnimationFinishedCallback.onAnimationFinished(triggerBack); - } catch (RemoteException e) { - Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e); - } - mBackAnimationFinishedCallback = null; + if (mBackNavigationInfo != null) { + mBackNavigationInfo.onBackNavigationFinished(mTriggerBack); + mBackNavigationInfo = null; } - backNavigationInfo.onBackNavigationFinished(triggerBack); + mTriggerBack = false; } private void createAdapter() { @@ -611,8 +618,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont 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) { dispatchOnBackStarted( 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 4459f57a1356..f1670cd792cf 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.jank.InteractionJankMonitor; 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 @@ public abstract class WMShellModule { 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/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java index b5ed509b6935..b310ee2095bf 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.graphics.Rect; 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.NonNull; 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 @@ public final class BackgroundWindowManager extends WindowlessWindowManager { 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 @@ public final class BackgroundWindowManager extends WindowlessWindowManager { .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 ad135d1baa14..679d4ca2ac48 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 androidx.annotation.VisibleForTesting; 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 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, 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 7365b9525919..1f7a7fc9ed4c 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.AccessibilityNodeInfo; 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 @@ public class PipAccessibilityInteractionConnection { } @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/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index c6f31c23ff25..56d51bda762f 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 @@ public class Transitions implements RemoteCallable<Transitions> { /** 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 ca15f0002fac..ebe5c5e716d7 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 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { @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 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } 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 03cad043ed67..affde3009456 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 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL private boolean mDesktopActive; + private DragDetector mDragDetector; + private AdditionalWindow mHandleMenu; CaptionWindowDecoration( @@ -79,6 +81,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mChoreographer = choreographer; mSyncQueue = syncQueue; mDesktopActive = DesktopModeStatus.isActive(mContext); + mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop()); } void setCaptionListeners( @@ -92,6 +95,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mDragResizeCallback = dragResizeCallback; } + DragDetector getDragDetector() { + return mDragDetector; + } + @Override void relayout(ActivityManager.RunningTaskInfo taskInfo) { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); @@ -182,6 +189,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL } 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 000000000000..0abe8ab2e30b --- /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 b9f16b63de48..48c0cea150cc 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.PRIVATE_FLAG_TRUSTED_OVERL 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.InputEventReceiver; 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 @@ class DragResizeInputListener implements AutoCloseable { private Rect mRightBottomCornerBounds; private int mDragPointerId = -1; - private int mTouchSlop; + private DragDetector mDragDetector; DragResizeInputListener( Context context, @@ -115,6 +115,7 @@ class DragResizeInputListener implements AutoCloseable { mInputEventReceiver = new TaskResizeInputEventReceiver( mInputChannel, mHandler, mChoreographer); mCallback = callback; + mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop()); } /** @@ -146,7 +147,7 @@ class DragResizeInputListener implements AutoCloseable { 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 @@ class DragResizeInputListener implements AutoCloseable { 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 @@ class DragResizeInputListener implements AutoCloseable { // 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 @@ class DragResizeInputListener implements AutoCloseable { 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 @@ class DragResizeInputListener implements AutoCloseable { 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 @@ class DragResizeInputListener implements AutoCloseable { } 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 f0f2db7ded80..a49a300995e6 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 @@ class TaskPositioner implements DragResizeCallback { 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 @@ class TaskPositioner implements DragResizeCallback { @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 @@ class TaskPositioner implements DragResizeCallback { @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 @@ class TaskPositioner implements DragResizeCallback { } 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/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index b603e0355e98..d75c36c99a4c 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 @@ -38,6 +38,7 @@ import android.app.WindowConfiguration; import android.content.pm.ApplicationInfo; import android.graphics.Point; import android.graphics.Rect; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteCallback; @@ -56,6 +57,7 @@ import android.window.BackNavigationInfo; import android.window.IBackAnimationFinishedCallback; import android.window.IOnBackInvokedCallback; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -189,31 +191,23 @@ public class BackAnimationControllerTest extends ShellTestCase { } for (int type: testTypes) { - boolean[] backNavigationDone = new boolean[]{false}; - boolean[] triggerBack = new boolean[]{false}; - + final ResultListener result = new ResultListener(); createNavigationInfo(new BackNavigationInfo.Builder() .setType(type) .setOnBackInvokedCallback(mAppCallback) .setPrepareRemoteAnimation(true) - .setOnBackNavigationDone( - new RemoteCallback(result -> { - backNavigationDone[0] = true; - triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK); - }))); + .setOnBackNavigationDone(new RemoteCallback(result))); 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]); + + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); + assertTrue("TriggerBack should have been true", result.mTriggerBack); } } - - @Test public void backToHome_dispatchesEvents() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); @@ -351,6 +345,65 @@ public class BackAnimationControllerTest extends ShellTestCase { verify(mAnimatorCallback, never()).onBackInvoked(); } + @Test + public void animationNotDefined() throws RemoteException { + final int[] testTypes = new int[] { + BackNavigationInfo.TYPE_RETURN_TO_HOME, + BackNavigationInfo.TYPE_CROSS_TASK, + BackNavigationInfo.TYPE_CROSS_ACTIVITY, + BackNavigationInfo.TYPE_DIALOG_CLOSE}; + + for (int type: testTypes) { + final ResultListener result = new ResultListener(); + createNavigationInfo(new BackNavigationInfo.Builder() + .setType(type) + .setOnBackInvokedCallback(mAppCallback) + .setPrepareRemoteAnimation(true) + .setOnBackNavigationDone(new RemoteCallback(result))); + triggerBackGesture(); + simulateRemoteAnimationStart(type); + mShellExecutor.flushAll(); + + assertTrue("Navigation Done callback not called for " + + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); + assertTrue("TriggerBack should have been true", result.mTriggerBack); + } + + verify(mAppCallback, never()).onBackStarted(any()); + verify(mAppCallback, never()).onBackProgressed(any()); + verify(mAppCallback, times(testTypes.length)).onBackInvoked(); + + verify(mAnimatorCallback, never()).onBackStarted(any()); + verify(mAnimatorCallback, never()).onBackProgressed(any()); + verify(mAnimatorCallback, never()).onBackInvoked(); + } + + @Test + public void callbackShouldDeliverProgress() throws RemoteException { + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); + + final int type = BackNavigationInfo.TYPE_CALLBACK; + final ResultListener result = new ResultListener(); + createNavigationInfo(new BackNavigationInfo.Builder() + .setType(type) + .setOnBackInvokedCallback(mAppCallback) + .setOnBackNavigationDone(new RemoteCallback(result))); + triggerBackGesture(); + mShellExecutor.flushAll(); + + assertTrue("Navigation Done callback not called for " + + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); + assertTrue("TriggerBack should have been true", result.mTriggerBack); + + verify(mAppCallback, times(1)).onBackStarted(any()); + verify(mAppCallback, times(1)).onBackProgressed(any()); + verify(mAppCallback, times(1)).onBackInvoked(); + + verify(mAnimatorCallback, never()).onBackStarted(any()); + verify(mAnimatorCallback, never()).onBackProgressed(any()); + verify(mAnimatorCallback, never()).onBackInvoked(); + } + private void doMotionEvent(int actionDown, int coordinate) { mController.onMotionEvent( coordinate, coordinate, @@ -377,4 +430,14 @@ public class BackAnimationControllerTest extends ShellTestCase { mController.registerAnimation(type, new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner)); } + + private static class ResultListener implements RemoteCallback.OnResultListener { + boolean mBackNavigationDone = false; + boolean mTriggerBack = false; + @Override + public void onResult(@Nullable Bundle result) { + mBackNavigationDone = true; + mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK); + } + }; } 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 11948dbf9659..f3f70673b332 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.annotation.UiThreadTest; 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 @@ public class BackgroundWindowManagerTest extends ShellTestCase { 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/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 1a1bebd28aef..5ee8bf3006a3 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 @@ import org.mockito.MockitoAnnotations; @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 000000000000..ac10ddb0116a --- /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/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp index 58fc5bbbab5e..a1385f2cf7b1 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 @@ class FileReader : public zip_archive::Reader { 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 @@ class FdReader : public zip_archive::Reader { 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 @@ class BufferReader : public zip_archive::Reader { 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 9309091f4124..a625889eaf3c 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -53,7 +53,7 @@ constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endi // 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/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java index 4f45602276b5..a6da0a301309 100644 --- a/location/java/android/location/GnssCapabilities.java +++ b/location/java/android/location/GnssCapabilities.java @@ -25,6 +25,7 @@ import android.os.Parcelable; 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 @@ public final class GnssCapabilities implements Parcelable { * @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 @@ public final class GnssCapabilities implements Parcelable { mTopFlags = topFlags; mMeasurementCorrectionsFlags = measurementCorrectionsFlags; mPowerFlags = powerFlags; - mGnssSignalTypes = gnssSignalTypes; + mGnssSignalTypes = Collections.unmodifiableList(gnssSignalTypes); } /** @@ -155,7 +156,7 @@ public final class GnssCapabilities implements Parcelable { return this; } else { return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags, - new ArrayList<>(mGnssSignalTypes)); + mGnssSignalTypes); } } @@ -171,7 +172,7 @@ public final class GnssCapabilities implements Parcelable { return this; } else { return new GnssCapabilities(mTopFlags, flags, mPowerFlags, - new ArrayList<>(mGnssSignalTypes)); + mGnssSignalTypes); } } @@ -186,7 +187,7 @@ public final class GnssCapabilities implements Parcelable { return this; } else { return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags, - new ArrayList<>(mGnssSignalTypes)); + mGnssSignalTypes); } } @@ -606,7 +607,7 @@ public final class GnssCapabilities implements Parcelable { 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 d975e96f193b..17d7045eacaf 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2328,10 +2328,9 @@ public class AudioManager { 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 @@ public class AudioManager { // 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 @@ public class AudioManager { 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 ca175b4853a6..0f962f9e9d4b 100644 --- a/media/java/android/media/AudioMetadata.java +++ b/media/java/android/media/AudioMetadata.java @@ -30,6 +30,7 @@ import java.nio.charset.Charset; 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 @@ public final class AudioMetadata { // 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 @@ public final class AudioMetadata { * 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 @@ public final class AudioMetadata { 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 @@ public final class AudioMetadata { 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 @@ public final class AudioMetadata { 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 @@ public final class AudioMetadata { 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 @@ public final class AudioMetadata { 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 85cd342b5e11..d51f1e1327bd 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -48,8 +48,8 @@ import java.lang.ref.WeakReference; 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 @@ public class AudioTrack extends PlayerBase } // 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 @@ public class AudioTrack extends PlayerBase 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 0c8cacd894cf..524bde4e7d15 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -70,6 +70,7 @@ import java.util.HashMap; 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 @@ public class ExifInterface { 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 3008067daefb..2342a426e77c 100644 --- a/media/java/android/media/MediaHTTPService.java +++ b/media/java/android/media/MediaHTTPService.java @@ -21,6 +21,8 @@ import android.compat.annotation.UnsupportedAppUsage; 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 @@ import java.util.List; 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 class MediaHTTPService extends IMediaHTTPService.Stub { 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 @@ public class MediaHTTPService extends IMediaHTTPService.Stub { 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 77b574695ede..79a59023a1b3 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -2507,6 +2507,8 @@ public class MediaPlayer extends PlayerBase * * @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/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl index 6ae7dfb2d929..9b8ec5e53ae9 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl @@ -45,6 +45,7 @@ oneway interface ITvInteractiveAppClient { 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 84b9c9ebfc18..38fc71711f7d 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl @@ -64,6 +64,7 @@ interface ITvInteractiveAppManager { 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 95b4ffa3ef5e..9e3353672af2 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl @@ -54,6 +54,7 @@ oneway interface ITvInteractiveAppSession { 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 64780570e6ff..4ce58711ceac 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl @@ -44,6 +44,7 @@ oneway interface ITvInteractiveAppSessionCallback { 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 042cb153226d..a2fdfe01ccec 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java @@ -82,6 +82,7 @@ public class ITvInteractiveAppSessionWrapper 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 @@ public class ITvInteractiveAppSessionWrapper 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 @@ public class ITvInteractiveAppSessionWrapper } @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 7d84fb2c431f..287df40d0d31 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -499,6 +499,18 @@ public final class TvInteractiveAppManager { } @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 @@ public final class TvInteractiveAppManager { } } + 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 @@ public final class TvInteractiveAppManager { }); } + 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 @@ public final class TvInteractiveAppManager { * 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 2956a0a93e41..90eed9ec83ca 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -463,6 +463,16 @@ public abstract class TvInteractiveAppService extends Service { } /** + * 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 @@ public abstract class TvInteractiveAppService extends Service { } /** + * 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 @@ public abstract class TvInteractiveAppService extends Service { 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 1f270d0ba768..fcd781b85aca 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -581,9 +581,10 @@ public class TvInteractiveAppView extends ViewGroup { } /** - * 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 @@ public class TvInteractiveAppView extends ViewGroup { } /** + * 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 @@ public class TvInteractiveAppView extends ViewGroup { } /** + * 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 @@ public class TvInteractiveAppView extends ViewGroup { } @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 51b976bf9f69..fab63aa18097 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -2376,19 +2376,20 @@ public class Tuner implements AutoCloseable { } /** - * 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 @@ public class Tuner implements AutoCloseable { 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 1a658320a1a9..4bcc3c6beffa 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.ParcelFileDescriptor; 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 @@ public class DvrRecorder implements AutoCloseable { 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 @@ public class DvrRecorder implements AutoCloseable { .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 @@ public class DvrRecorder implements AutoCloseable { .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 @@ public class DvrRecorder implements AutoCloseable { */ @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 aff2e1b4cf31..89e5e0d783ff 100644 --- a/media/java/android/mtp/MtpPropertyGroup.java +++ b/media/java/android/mtp/MtpPropertyGroup.java @@ -230,7 +230,7 @@ class MtpPropertyGroup { 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 f6fcba71e339..cbb273608ad7 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 @@ public class EffectFactory { 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 a608ef5be3f4..e82c046b7390 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 @@ public abstract class Filter { 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 779df990a9a5..736e51131ca1 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 class FilterFactory { 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 8cf9a13438e5..6ff1885fa8eb 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 class KeyValueMap extends HashMap<String, Object> { 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 c52816571ffb..8c05725a9ef1 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 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med 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 39add7ea8a42..c814eba7d187 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 @@ public class CameraMetadataTest extends junit.framework.TestCase { 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 fd1c2d3e8e94..37dd4b5330c5 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 @@ package com.android.mediaframeworktest.unit; 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/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml index 8a19709589fd..3dcdf00ca8f5 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/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java index dd7b218959bf..b322b8bd95bb 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java @@ -240,11 +240,15 @@ public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{ 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) diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index a7e1a5954aea..ae4046064a20 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -203,6 +203,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements 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 f1f453da4f38..61e11feff3c0 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.R 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 @@ fun ModalBottomSheetLayout( 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 @@ object ModalBottomSheetDefaults { */ 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 67b704f5d787..8a1f83d2cb77 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.SuggestionChip 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.ModalBottomSheetValue 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 @@ fun CreatePasskeyScreen( } }, scrimColor = MaterialTheme.colorScheme.scrim, - sheetShape = MaterialTheme.shapes.medium, + sheetShape = EntryShape.TopRoundedCorner, ) {} LaunchedEffect(state.currentValue) { if (state.currentValue == ModalBottomSheetValue.Hidden) { @@ -100,6 +104,7 @@ fun CreatePasskeyScreen( } } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ConfirmationCard( onConfirm: () -> Unit, @@ -179,7 +184,7 @@ fun ProviderSelectionCard( 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 @@ fun MoreOptionsSelectionCard( 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 @@ fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit // TODO: add description. contentDescription = "") }, - shape = MaterialTheme.shapes.large, + shape = EntryShape.FullRoundedCorner, label = { Text( text = providerInfo.displayName, @@ -391,7 +398,8 @@ fun CreationSelectionCard( 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 @@ fun CreationSelectionCard( ) } Card( - shape = MaterialTheme.shapes.large, + shape = EntryShape.FullRoundedCorner, modifier = Modifier .padding(horizontal = 24.dp) .align(alignment = Alignment.CenterHorizontally), @@ -499,7 +507,7 @@ fun PrimaryCreateOptionRow( bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(), contentDescription = null) }, - shape = MaterialTheme.shapes.large, + shape = EntryShape.FullRoundedCorner, label = { Column() { Text( @@ -532,7 +540,7 @@ fun MoreOptionsInfoRow( bitmap = providerInfo.icon.toBitmap().asImageBitmap(), contentDescription = null) }, - shape = MaterialTheme.shapes.large, + shape = EntryShape.FullRoundedCorner, label = { Column() { Text( @@ -603,7 +611,7 @@ fun MoreOptionsDisabledProvidersRow( modifier = Modifier.padding(start = 16.dp) ) }, - shape = MaterialTheme.shapes.large, + shape = EntryShape.FullRoundedCorner, label = { Column() { Text( @@ -637,7 +645,7 @@ fun RemoteEntryRow( 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 000000000000..e0954ad13348 --- /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 5ea69930e334..32def89be06a 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 @@ val Shapes = Shapes( 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 248df92bac59..3ca0e4494ab6 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 @@ package com.android.credentialmanager.ui.theme 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 93e6271319f6..3029d10d4cf3 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 @@ public class CloudPrintPlugin implements PrintServicePlugin { 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 34e7e3d1cd6b..0c5de2741d1b 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 @@ import java.util.Set; 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 d03bb1d76003..b9983c306289 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 android.util.Log; 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 @@ import java.util.Set; 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 b9b90988c37b..680dd84a25df 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 androidx.annotation.NonNull; 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 @@ import java.util.Set; 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 @@ class PrinterFilterSamsung implements MDNSFilteredDiscovery.PrinterFilter { 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 ae1bdcedaabb..cbd5833925b8 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.HashSet; 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 00b3736f8d6b..b0aa8f19f24b 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 @@ public final class PrintContentView extends ViewGroup implements View.OnClickLis @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 8eafbdfdf6bc..a53782a0bc6f 100644 --- a/packages/SettingsLib/OWNERS +++ b/packages/SettingsLib/OWNERS @@ -3,10 +3,11 @@ dsandler@android.com 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/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index 50cab84e0d9d..1e52aaf72076 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/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 016b27f0c82c..941e7705441b 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 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { } 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 26e59ff699a4..ff89f2b25e7d 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.runtime.remember 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.compose.toState 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.Compan 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 @@ object PreferencePageProvider : SettingsPageProvider { 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 @@ object PreferencePageProvider : SettingsPageProvider { } } ) + }.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 @@ object PreferencePageProvider : SettingsPageProvider { } ) } - .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 @@ object PreferencePageProvider : SettingsPageProvider { 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 d874417c1a56..fc6f10f79ceb 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 @@ class PreferencePageModel : PageModel() { 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/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt index 8c038c811bb0..58131e69d329 100644 --- 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 @@ -23,7 +23,7 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory class SpaSliceBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - val sliceRepository = SpaEnvironmentFactory.instance.sliceDataRepository + 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 index d800e507b35a..faa04fda1904 100644 --- 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 @@ -32,7 +32,8 @@ private const val TAG = "SpaSliceProvider" class SpaSliceProvider : SliceProvider(), Observer<Slice?> { private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? { if (!SpaEnvironmentFactory.isReady()) return null - return SpaEnvironmentFactory.instance.sliceDataRepository.getOrBuildSliceData(sliceUri) + val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository + return sliceRepository.getOrBuildSliceData(sliceUri) } override fun onBindSlice(sliceUri: Uri): Slice? { 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 9bb06f02c1b6..945add47b965 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 @@ -65,7 +65,7 @@ abstract class SpaEnvironment(context: Context) { val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) } - val sliceDataRepository = SettingsSliceDataRepository() + val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) } // In Robolectric test, applicationContext is not available. Use context as fallback. val appContext: Context = context.applicationContext ?: context 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 index d8d23788c204..14855a8aed59 100644 --- 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 @@ -19,11 +19,11 @@ 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.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.common.SettingsEntryRepository private const val TAG = "SliceDataRepository" -class SettingsSliceDataRepository { +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() @@ -46,13 +46,10 @@ class SettingsSliceDataRepository { private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? { Log.d(TAG, "buildLiveData: $sliceUri") - if (!SpaEnvironmentFactory.isReady()) return null - val entryRepository by SpaEnvironmentFactory.instance.entryRepository 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/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt index 373b57f73ef0..a7122d0eb03a 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 @@ interface AppListModel<T : AppRecord> { ) /** + * 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 215d22cd0c5e..cb0bfc648090 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 @@ import com.android.settingslib.spa.framework.util.asyncFilter private const val TAG = "PackageManagers" -object PackageManagers { +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 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() } - fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? = + override fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? = getPackageInfoAsUser(packageName, 0, userId) - fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? = + override fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? = PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId) - /** Checks whether a package is installed for a given user. */ - fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean = + 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 7f5fe9f75d51..681eb1c3508e 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.LogCompositions 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 @@ private fun <T : AppRecord> AppListWidget( } 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 000000000000..6c31f4b7e2f1 --- /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 80c4eac9b98f..9f20c78485a8 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 @@ class AppListTest { @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 @@ class AppListTest { 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 @@ private class TestAppListModel : AppListModel<TestAppRecord> { 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/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java index 6ce72bbc6909..3af64e2889e7 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 @@ public class BluetoothDiscoverableTimeoutReceiver extends BroadcastReceiver { 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 3e33da5a2ba7..ece898618f8c 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 @@ public class EventLogWriter implements LogWriter { @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 cceca13ec3e5..dcd6ccedf9bc 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 @@ public interface LogWriter { 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 915421a8f7a0..09abc394634a 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 @@ public class MetricsFeatureProvider { } /** + * 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 869de0debd37..067afa4ff726 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 @@ public class SharedPreferencesLogger implements SharedPreferences { 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 @@ public class SharedPreferencesLogger implements SharedPreferences { 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 34da30555fb3..3e710e4c7e35 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.PowerManager; 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 @@ public class BatterySaverUtils { } /** - * 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/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java index 89de81fde889..a2b208aced64 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 @@ public class SharedPreferenceLoggerTest { 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 @@ public class SharedPreferenceLoggerTest { 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 @@ public class SharedPreferenceLoggerTest { 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 @@ public class SharedPreferenceLoggerTest { 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 @@ public class SharedPreferenceLoggerTest { 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 @@ public class SharedPreferenceLoggerTest { 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 @@ public class SharedPreferenceLoggerTest { 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 @@ public class SharedPreferenceLoggerTest { 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 @@ public class SharedPreferenceLoggerTest { 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 @@ public class SharedPreferenceLoggerTest { 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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 90fab08ed43e..2e4a245df6a6 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 4d94bab6c26c..000000000000 --- 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 06ea381d0c1d..7fc9a8392d12 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 89f5c2c80e29..66e44b9005de 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 @@ interface ClockController { } /** 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 @@ interface ClockFaceController { /** 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 @@ interface ClockAnimations { /** 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 c29714957318..b49afeef09f3 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/ic_watch.xml b/packages/SystemUI/res/drawable/ic_watch.xml new file mode 100644 index 000000000000..8ff880cef029 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_watch.xml @@ -0,0 +1,29 @@ +<?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 + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M16,0L8,0l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24 + h8l0.96,-5.73C18.81,16.81 20,14.54 20,12s-1.19,-4.81 -3.04,-6.27L16,0z + M12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/> +</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 000000000000..8f6b4c246ba4 --- /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 ae2537fe29f6..f14be410bf75 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 9b8b611558fe..530db0d0304a 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/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml index 887e3e715369..f1bc88370071 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/config.xml b/packages/SystemUI/res/values/config.xml index 55d637916d0c..f4d802bf745e 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -743,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. --> @@ -780,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 6577b07f7139..fbdccff38731 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1552,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 80dff856cfb2..eb291fd5f12a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4e4bfe2ee8f3..ae80070dfa97 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 148e5ec1606f..1eb621e0368b 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 ac484d7dde8e..64c2ef1fc915 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 e07a6c1dbc46..5d3650ccc8e6 100644 --- a/packages/SystemUI/res/xml/qqs_header.xml +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -59,6 +59,7 @@ <Layout 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" diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt index 36ac1ff9ad30..0b0595f4405f 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt @@ -34,7 +34,6 @@ import org.junit.runners.model.Statement import platform.test.screenshot.DeviceEmulationRule import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.MaterialYouColorsRule -import platform.test.screenshot.PathConfig import platform.test.screenshot.ScreenshotTestRule import platform.test.screenshot.getEmulatedDevicePathConfig import platform.test.screenshot.matchers.BitmapMatcher @@ -42,19 +41,13 @@ import platform.test.screenshot.matchers.BitmapMatcher /** A rule for View screenshot diff unit tests. */ class ViewScreenshotTestRule( emulationSpec: DeviceEmulationSpec, - private val matcher: BitmapMatcher = UnitTestBitmapMatcher, - pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec), - assetsPathRelativeToRepo: String = "" + private val matcher: BitmapMatcher = UnitTestBitmapMatcher ) : TestRule { private val colorsRule = MaterialYouColorsRule() private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = ScreenshotTestRule( - if (assetsPathRelativeToRepo.isBlank()) { - SystemUIGoldenImagePathManager(pathConfig) - } else { - SystemUIGoldenImagePathManager(pathConfig, assetsPathRelativeToRepo) - } + SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) ) private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java) private val delegateRule = 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 ca780c8dd3c9..599cd23f6616 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.graphics.Rect 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 @@ class DefaultClockController( } 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 @@ class DefaultClockController( // 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 @@ class DefaultClockController( 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 @@ class DefaultClockController( 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 @@ class DefaultClockController( 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 8612b3a2c587..000000000000 --- 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 c9b8712bdde9..87e9d5630b74 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -26,6 +26,7 @@ import android.view.View 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.shared.regionsampling.RegionSampler 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.collect 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 @@ open class ClockEventController @Inject constructor( value.initialize(resources, dozeAmount, 0f) updateRegionSamplers(value) + updateFontSizes() } } @@ -150,7 +152,7 @@ open class ClockEventController @Inject constructor( mainExecutor, bgExecutor, regionSamplingEnabled, - updateFun = { updateColors() } ) + updateColors) } var smallRegionSampler: RegionSampler? = null @@ -166,7 +168,7 @@ open class ClockEventController @Inject constructor( } override fun onDensityOrFontScaleChanged() { - clock?.events?.onFontSettingChanged() + updateFontSizes() } } @@ -251,6 +253,13 @@ open class ClockEventController @Inject constructor( 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 8ebad6c0fdbf..40423cd9ac2c 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.AnimatorListenerAdapter; 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 com.android.systemui.plugins.ClockController; 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 @@ public class KeyguardClockSwitch extends RelativeLayout { */ private FrameLayout mSmallClockFrame; private FrameLayout mLargeClockFrame; + private ClockController mClock; private View mStatusArea; private int mSmartspaceTopOffset; @@ -95,6 +98,8 @@ public class KeyguardClockSwitch extends RelativeLayout { } void setClock(ClockController clock, int statusBarState) { + mClock = clock; + // Disconnect from existing plugin. mSmallClockFrame.removeAllViews(); mLargeClockFrame.removeAllViews(); @@ -108,6 +113,35 @@ public class KeyguardClockSwitch extends RelativeLayout { 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 @@ public class KeyguardClockSwitch extends RelativeLayout { 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 ace942de1221..e6aae9bc461a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -77,7 +77,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS @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 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS 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 class KeyguardClockSwitchController extends ViewController<KeyguardClockS */ 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 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS 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/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index 8fa7b11e2664..2b660dee4f16 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -21,7 +21,6 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.media.AudioManager; import android.os.SystemClock; -import android.service.trust.TrustAgentService; import android.telephony.TelephonyManager; import android.util.Log; import android.util.MathUtils; @@ -68,30 +67,24 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> private final KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override - public void onTrustGrantedWithFlags(int flags, int userId, String message) { - if (userId != KeyguardUpdateMonitor.getCurrentUser()) return; - boolean bouncerVisible = mView.isVisibleToUser(); - boolean temporaryAndRenewable = - (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) - != 0; - boolean initiatedByUser = - (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0; - boolean dismissKeyguard = - (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0; - - if (initiatedByUser || dismissKeyguard) { - if ((mViewMediatorCallback.isScreenOn() || temporaryAndRenewable) - && (bouncerVisible || dismissKeyguard)) { - if (!bouncerVisible) { - // The trust agent dismissed the keyguard without the user proving - // that they are present (by swiping up to show the bouncer). That's - // fine if the user proved presence via some other way to the trust - //agent. - Log.i(TAG, "TrustAgent dismissed Keyguard."); - } - mSecurityCallback.dismiss(false /* authenticated */, userId, - /* bypassSecondaryLockScreen */ false, SecurityMode.Invalid); - } else { + public void onTrustGrantedForCurrentUser(boolean dismissKeyguard, + TrustGrantFlags flags, String message) { + if (dismissKeyguard) { + if (!mView.isVisibleToUser()) { + // The trust agent dismissed the keyguard without the user proving + // that they are present (by swiping up to show the bouncer). That's + // fine if the user proved presence via some other way to the trust + // agent. + Log.i(TAG, "TrustAgent dismissed Keyguard."); + } + mSecurityCallback.dismiss( + false /* authenticated */, + KeyguardUpdateMonitor.getCurrentUser(), + /* bypassSecondaryLockScreen */ false, + SecurityMode.Invalid + ); + } else { + if (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) { mViewMediatorCallback.playTrustedSound(); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 2ac93b51b147..2d3dda9b44a5 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.dagger.qualifiers.Main; 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab "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; @@ -470,10 +472,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab FACE_AUTH_TRIGGERED_TRUST_DISABLED); } - String message = null; - if (KeyguardUpdateMonitor.getCurrentUser() == userId) { - final boolean userHasTrust = getUserHasTrust(userId); - if (userHasTrust && trustGrantedMessages != null) { + if (enabled) { + String message = null; + if (KeyguardUpdateMonitor.getCurrentUser() == userId + && trustGrantedMessages != null) { + // Show the first non-empty string provided by a trust agent OR intentionally pass + // an empty string through (to prevent the default trust agent string from showing) for (String msg : trustGrantedMessages) { message = msg; if (!TextUtils.isEmpty(message)) { @@ -481,21 +485,39 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } } + + mLogger.logTrustGrantedWithFlags(flags, userId, message); + if (userId == getCurrentUser()) { + final TrustGrantFlags trustGrantFlags = new TrustGrantFlags(flags); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onTrustGrantedForCurrentUser( + shouldDismissKeyguardOnTrustGrantedWithCurrentUser(trustGrantFlags), + trustGrantFlags, message); + } + } + } } + mLogger.logTrustChanged(wasTrusted, enabled, userId); - if (message != null) { - mLogger.logShowTrustGrantedMessage(message.toString()); - } for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { cb.onTrustChanged(userId); - if (enabled) { - cb.onTrustGrantedWithFlags(flags, userId, message); - } } } + } + /** + * Whether the trust granted call with its passed flags should dismiss keyguard. + * It's assumed that the trust was granted for the current user. + */ + private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) { + final boolean isBouncerShowing = mPrimaryBouncerIsOrWillBeShowing || mUdfpsBouncerShowing; + return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) + && (mDeviceInteractive || flags.temporaryAndRenewable()) + && (isBouncerShowing || flags.dismissKeyguardRequested()); } @Override @@ -848,13 +870,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 +1088,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 +1938,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Inject protected KeyguardUpdateMonitor( Context context, + UserTracker userTracker, @Main Looper mainLooper, BroadcastDispatcher broadcastDispatcher, SecureSettings secureSettings, @@ -1960,6 +1971,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) { mContext = context; mSubscriptionManager = subscriptionManager; + mUserTracker = userTracker; mTelephonyListenerManager = telephonyListenerManager; mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged, @@ -2192,7 +2204,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 +3830,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 +3870,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } 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/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index c5142f309a46..1d58fc9cf94b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -19,6 +19,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.telephony.TelephonyManager; import android.view.WindowManagerPolicyConstants; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settingslib.fuelgauge.BatteryStatus; @@ -175,11 +176,13 @@ public class KeyguardUpdateMonitorCallback { /** * Called after trust was granted. - * @param userId of the user that has been granted trust + * @param dismissKeyguard whether the keyguard should be dismissed as a result of the + * trustGranted * @param message optional message the trust agent has provided to show that should indicate * why trust was granted. */ - public void onTrustGrantedWithFlags(int flags, int userId, @Nullable String message) { } + public void onTrustGrantedForCurrentUser(boolean dismissKeyguard, + @NonNull TrustGrantFlags flags, @Nullable String message) { } /** * Called when a biometric has been acquired. diff --git a/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java new file mode 100644 index 000000000000..d33732cd5536 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java @@ -0,0 +1,98 @@ +/* + * 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.keyguard; + +import android.service.trust.TrustAgentService; + +import java.util.Objects; + +/** + * Translating {@link android.service.trust.TrustAgentService.GrantTrustFlags} to a more + * parsable object. These flags are requested by a TrustAgent. + */ +public class TrustGrantFlags { + final int mFlags; + + public TrustGrantFlags(int flags) { + this.mFlags = flags; + } + + /** {@link TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER} */ + public boolean isInitiatedByUser() { + return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0; + } + + /** + * Trust agent is requesting to dismiss the keyguard. + * See {@link TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD}. + * + * This does not guarantee that the keyguard is dismissed. + * KeyguardUpdateMonitor makes the final determination whether the keyguard should be dismissed. + * {@link KeyguardUpdateMonitorCallback#onTrustGrantedForCurrentUser( + * boolean, TrustGrantFlags, String). + */ + public boolean dismissKeyguardRequested() { + return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0; + } + + /** {@link TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE} */ + public boolean temporaryAndRenewable() { + return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0; + } + + /** {@link TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE} */ + public boolean displayMessage() { + return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TrustGrantFlags)) { + return false; + } + + return ((TrustGrantFlags) o).mFlags == this.mFlags; + } + + @Override + public int hashCode() { + return Objects.hash(mFlags); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(mFlags); + sb.append("]="); + + if (isInitiatedByUser()) { + sb.append("initiatedByUser|"); + } + if (dismissKeyguardRequested()) { + sb.append("dismissKeyguard|"); + } + if (temporaryAndRenewable()) { + sb.append("temporaryAndRenewable|"); + } + if (displayMessage()) { + sb.append("displayMessage|"); + } + + return sb.toString(); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java index 9a0bfc19f848..ad9609f9ec42 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.ArrayMap; 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.Collection; 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 @@ public final class ClockManager { 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 final class ClockManager { 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 @@ public final class ClockManager { /** * 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 @@ public final class ClockManager { @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 @@ public final class ClockManager { 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 @@ public final class ClockManager { 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 @@ public final class ClockManager { 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 @@ public final class ClockManager { } } 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/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 81b8dfed36a8..676370093aee 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -25,6 +25,7 @@ import com.android.keyguard.ActiveUnlockConfig import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.KeyguardListenModel import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.keyguard.TrustGrantFlags import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.log.LogLevel.DEBUG @@ -368,12 +369,16 @@ class KeyguardUpdateMonitorLogger @Inject constructor( }, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" }) } - fun logShowTrustGrantedMessage( + fun logTrustGrantedWithFlags( + flags: Int, + userId: Int, message: String? ) { logBuffer.log(TAG, DEBUG, { + int1 = flags + int2 = userId str1 = message - }, { "showTrustGrantedMessage message$str1" }) + }, { "trustGrantedWithFlags[user=$int2] flags=${TrustGrantFlags(int1)} message=$str1" }) } fun logTrustChanged( diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index a5fdc68226e8..51bcd6b18936 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.ReduceBrightColorsController; 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 @@ public class Dependency { @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy; @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy; @Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy; + @Inject Lazy<UserTracker> mUserTrackerLazy; @Inject public Dependency() { @@ -564,6 +566,7 @@ public class Dependency { 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 45f9385a2620..7e3b1389792c 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -26,7 +26,6 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M 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 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { 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 0a2dc5b182d8..d60cc7579ea8 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 @@ public class SystemActions implements CoreStartable { 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/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 33e155df80e3..b8f14aef648a 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 @@ class MenuViewLayer extends FrameLayout { 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/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java index 77cb9d1bf594..f4ec33ad24b5 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java @@ -17,21 +17,23 @@ package com.android.systemui.battery; 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.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.settings.CurrentUserTracker; +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; @@ -44,12 +46,13 @@ import javax.inject.Inject; 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() { @@ -93,6 +96,16 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> } }; + 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 private boolean mIgnoreTunerUpdates; private boolean mIsSubscribedForTunerUpdates; @@ -100,16 +113,18 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> @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; @@ -117,15 +132,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> 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 @@ -134,9 +141,9 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> subscribeForTunerUpdates(); mBatteryController.addCallback(mBatteryStateChangeCallback); - registerShowBatteryPercentObserver(ActivityManager.getCurrentUser()); + registerShowBatteryPercentObserver(mUserTracker.getUserId()); registerGlobalBatteryUpdateObserver(); - mCurrentUserTracker.startTracking(); + mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler)); mView.updateShowPercent(); } @@ -147,7 +154,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView> unsubscribeFromTunerUpdates(); mBatteryController.removeCallback(mBatteryStateChangeCallback); - mCurrentUserTracker.stopTracking(); + mUserTracker.removeCallback(mUserChangedCallback); mContentResolver.unregisterContentObserver(mSettingObserver); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index c93fe6ac9f34..4b57d455a137 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.View 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/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING deleted file mode 100644 index 794eba4d8de9..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING +++ /dev/null @@ -1,16 +0,0 @@ -{ - "presubmit": [ - { - // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+) - "name": "SystemUIGoogleBiometricsScreenshotTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] - } - ] -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java index 49e378e4a76f..af7e0b6244df 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -99,12 +99,11 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { mProgressColor = context.getColor(R.color.udfps_enroll_progress); final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); mIsAccessibilityEnabled = am.isTouchExplorationEnabled(); + mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error); if (!mIsAccessibilityEnabled) { mHelpColor = context.getColor(R.color.udfps_enroll_progress_help); - mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error); } else { mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback); - mOnFirstBucketFailedColor = mHelpColor; } mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark); mCheckmarkDrawable.mutate(); @@ -197,6 +196,7 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { } } + mShowingHelp = showingHelp; mRemainingSteps = remainingSteps; mTotalSteps = totalSteps; 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 7dde94701e24..5110a9cfb33b 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.EditorInfo 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,6 +15,7 @@ import com.android.systemui.biometrics.ui.CredentialPasswordView import com.android.systemui.biometrics.ui.CredentialView import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.launch /** Sub-binder for the [CredentialPasswordView]. */ @@ -23,17 +26,16 @@ object CredentialPasswordViewBinder { view: CredentialPasswordView, host: CredentialView.Host, viewModel: CredentialViewModel, - requestFocusForInput: Boolean, ) { val imeManager = view.context.getSystemService(InputMethodManager::class.java)!! val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword) + val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() } + view.repeatWhenAttached { - if (requestFocusForInput) { - passwordField.requestFocus() - passwordField.scheduleShowSoftInput() - } + passwordField.requestFocus() + passwordField.scheduleShowSoftInput() repeatOnLifecycle(Lifecycle.State.STARTED) { // observe credential validation attempts and submit/cancel buttons @@ -45,9 +47,7 @@ object CredentialPasswordViewBinder { launch { viewModel.checkCredential(text, header) } } ) - passwordField.setOnKeyListener( - OnBackButtonListener { host.onCredentialAborted() } - ) + passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback)) } } @@ -68,18 +68,35 @@ object CredentialPasswordViewBinder { } } } + + 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/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt index b692ad35caee..4765551df3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt @@ -9,6 +9,7 @@ import com.android.systemui.biometrics.ui.CredentialPatternView 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.launch /** Sub-binder for the [CredentialPatternView]. */ @@ -29,7 +30,7 @@ object CredentialPatternViewBinder { viewModel.header.collect { header -> lockPatternView.setOnPatternListener( OnPatternDetectedListener { pattern -> - if (pattern.isPatternTooShort()) { + if (pattern.isPatternLongEnough()) { // Pattern size is less than the minimum // do not count it as a failed attempt viewModel.showPatternTooShortError() @@ -70,5 +71,5 @@ private class OnPatternDetectedListener( } } -private fun List<LockPatternView.Cell>.isPatternTooShort(): Boolean = +private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean = size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt index e2d36dc6abe1..fcc948756972 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt @@ -17,6 +17,7 @@ import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.lifecycle.repeatWhenAttached import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -39,7 +40,6 @@ object CredentialViewBinder { panelViewController: AuthPanelController, animatePanel: Boolean, maxErrorDuration: Long = 3_000L, - requestFocusForInput: Boolean = true, ) { val titleView: TextView = view.requireViewById(R.id.title) val subtitleView: TextView = view.requireViewById(R.id.subtitle) @@ -110,8 +110,7 @@ object CredentialViewBinder { // bind the auth widget when (view) { - is CredentialPasswordView -> - CredentialPasswordViewBinder.bind(view, host, viewModel, requestFocusForInput) + is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel) is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel) else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}") } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt index 616e49c0b709..1454210ad798 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.R 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 e82d0ea85490..3808ab742419 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 android.view.WindowManager; 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 145569919e8e..36103f8db8d8 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 android.widget.TextView; 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 beaccbaf9a70..e8e1f2e95f5d 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 @@ public class BrightLineFalsingManager implements FalsingManager { // 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 b11103a4d27b..7df08651d5ab 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 @@ package com.android.systemui.controls.management 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.GridLayoutManager 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 @@ open class ControlsEditingActivity @Inject constructor( 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 @@ open class ControlsEditingActivity @Inject constructor( super.onStart() setUpList() - currentUserTracker.startTracking() + userTracker.addCallback(userTrackerCallback, mainExecutor) if (DEBUG) { Log.d(TAG, "Registered onBackInvokedCallback") @@ -115,7 +118,7 @@ open class ControlsEditingActivity @Inject constructor( override fun onStop() { super.onStop() - currentUserTracker.stopTracking() + userTracker.removeCallback(userTrackerCallback) if (DEBUG) { Log.d(TAG, "Unregistered onBackInvokedCallback") @@ -248,7 +251,7 @@ open class ControlsEditingActivity @Inject constructor( } 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 9b2a72823d86..3e97d3132bc7 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.Animator 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.activity.ComponentActivity 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.controller.StructureInfo 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 @@ open class ControlsFavoritingActivity @Inject constructor( @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 @@ open class ControlsFavoritingActivity @Inject constructor( 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 @@ open class ControlsFavoritingActivity @Inject constructor( super.onStart() listingController.addCallback(listingCallback) - currentUserTracker.startTracking() + userTracker.addCallback(userTrackerCallback, executor) if (DEBUG) { Log.d(TAG, "Registered onBackInvokedCallback") @@ -388,7 +388,7 @@ open class ControlsFavoritingActivity @Inject constructor( 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 47690a7fa487..90bc5d0f8daa 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 @@ package com.android.systemui.controls.management 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.activity.ComponentActivity 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 @@ open class ControlsProviderSelectorActivity @Inject constructor( @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 @@ open class ControlsProviderSelectorActivity @Inject constructor( } 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 @@ open class ControlsProviderSelectorActivity @Inject constructor( override fun onStart() { super.onStart() - currentUserTracker.startTracking() + userTracker.addCallback(userTrackerCallback, executor) recyclerView.alpha = 0.0f recyclerView.adapter = AppAdapter( @@ -161,7 +161,7 @@ open class ControlsProviderSelectorActivity @Inject constructor( override fun onStop() { super.onStop() - currentUserTracker.stopTracking() + userTracker.removeCallback(userTrackerCallback) if (DEBUG) { Log.d(TAG, "Unregistered onBackInvokedCallback") @@ -190,7 +190,7 @@ open class ControlsProviderSelectorActivity @Inject constructor( } 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 b376455ee815..86bde5c3cf4d 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 @@ package com.android.systemui.controls.management 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.ImageView 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 @@ open class ControlsRequestDialog @Inject constructor( 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 @@ open class ControlsRequestDialog @Inject constructor( 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 @@ open class ControlsRequestDialog @Inject constructor( 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 25418c3f2105..0664e9f6ac39 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.PowerManager; 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 @@ import dagger.Provides; /** * Provides Non-SystemUI, Framework-Owned instances to the dependency graph. */ +@SuppressLint("NonInjectedService") @Module public class FrameworkServicesModule { @Provides @@ -469,7 +472,13 @@ public class FrameworkServicesModule { @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/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index d0258d37cc96..f64d91869fcb 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_ 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.internal.logging.UiEventLoggerImpl; 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 @@ public class DozeSensors { 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 @@ public class DozeSensors { ProximitySensor proximitySensor, SecureSettings secureSettings, AuthController authController, - DevicePostureController devicePostureController + DevicePostureController devicePostureController, + UserTracker userTracker ) { mSensorManager = sensorManager; mConfig = config; @@ -170,6 +172,7 @@ public class DozeSensors { mDevicePostureController = devicePostureController; mDevicePosture = mDevicePostureController.getDevicePosture(); mAuthController = authController; + mUserTracker = userTracker; mUdfpsEnrolled = mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()); @@ -441,7 +444,7 @@ public class DozeSensors { 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 32cb1c01b776..0b69b80689e0 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.dock.DockManager; 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 @@ public class DozeTriggers implements DozeMachine.Part { UiEventLogger uiEventLogger, SessionTracker sessionTracker, KeyguardStateController keyguardStateController, - DevicePostureController devicePostureController) { + DevicePostureController devicePostureController, + UserTracker userTracker) { mContext = context; mDozeHost = dozeHost; mConfig = config; @@ -200,7 +202,7 @@ public class DozeTriggers implements DozeMachine.Part { 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 d8dd6a21d4c1..0087c8439370 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 @@ constructor( 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() - - mEntryAnimations = AnimatorSet() - mEntryAnimations?.apply { - playTogether( - buildDreamInBlurAnimator(view), - buildDreamInTopComplicationsAnimator(), - buildDreamInBottomComplicationsAnimator() - ) - doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) } - start() - } + @JvmOverloads + fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) { + cancelAnimations() + + 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) }) + /** 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) + } } - private fun setAlphaAndEnsureVisible(view: View, alpha: Float) { - if (alpha > 0 && view.visibility != View.VISIBLE) { - view.visibility = View.VISIBLE + /** 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) } - - view.alpha = alpha } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 5c6d24813570..9d7ad305c7e5 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.util.MathUtils; 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.statusbar.phone.StatusBarKeyguardViewManager; 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 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve } mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback); - mDreamOverlayAnimationsController.cancelRunningEntryAnimations(); + mDreamOverlayAnimationsController.cancelAnimations(); } View getContainerView() { @@ -251,4 +254,17 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve : 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 8542412f82f8..e76d5b30d3e6 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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 e80d0beabf2b..5f942b6fb834 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 class DreamOverlayStateController implements 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 @@ public class DreamOverlayStateController implements 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 @@ public class DreamOverlayStateController implements } /** + * 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 d17fbe31c8d2..f1bb156199ef 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.R; 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 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve } /** - * 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 41f557850f88..b07efdfff5f2 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 @@ public interface Complication { */ 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 440dcbc18a12..48159aed524e 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.ComplicationHostVi 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 androidx.constraintlayout.widget.Constraints; 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 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll private final TouchInsetManager.TouchInsetSession mSession; private final int mFadeInDuration; private final int mFadeOutDuration; - private ViewPropertyAnimator mViewPropertyAnimator; /** */ @Inject @@ -498,26 +495,16 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll } @Override - public void setVisibility(int visibility, boolean animate) { - final boolean appearing = visibility == View.VISIBLE; - - if (mViewPropertyAnimator != null) { - mViewPropertyAnimator.cancel(); - } - - if (appearing) { - mLayout.setVisibility(View.VISIBLE); + public void setVisibility(int visibility) { + if (visibility == View.VISIBLE) { + CrossFadeHelper.fadeIn(mLayout, mFadeInDuration, /* delay= */ 0); + } else { + CrossFadeHelper.fadeOut( + mLayout, + mFadeOutDuration, + /* delay= */ 0, + /* endRunnable= */ null); } - - 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 2b32d349dd67..4fae68d57ee8 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 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { 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/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java index c9fecc96c1b5..09cc7c51ccf4 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 abstract class ComplicationHostViewModule { 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 @@ public abstract class ComplicationHostViewModule { } /** + * 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/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index cb012fa42e94..ed0e1d97e40a 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 @@ public abstract class DreamOverlayModule { "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 @@ public abstract class DreamOverlayModule { */ @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 @@ public abstract class DreamOverlayModule { */ @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 @@ public abstract class DreamOverlayModule { */ @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 @@ public abstract class DreamOverlayModule { */ @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 @@ public abstract class DreamOverlayModule { */ @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 3087cdfd0cc0..e276e0c65b1e 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 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { 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 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { 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 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { 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 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { || 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 076e7058264b..784e92d37e28 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -79,6 +79,11 @@ object Flags { 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") @@ -115,28 +120,6 @@ object Flags { @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. */ @@ -210,9 +193,7 @@ object Flags { @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 @@ -297,6 +278,8 @@ object Flags { // 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") @@ -307,7 +290,7 @@ object Flags { @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 diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java index 18fb423b87a5..d9bcb508c8e2 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 @@ public class ExtensionFragmentListener<T extends FragmentBase> implements Consum @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 3ef5499237f1..db2cd91374e5 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.dagger.qualifiers.Main; 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene 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 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @NonNull VibratorHelper vibrator, @Main Resources resources, ConfigurationController configurationController, + UserTracker userTracker, KeyguardStateController keyguardStateController, UserManager userManager, TrustManager trustManager, @@ -370,6 +373,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mSecureSettings = secureSettings; mResources = resources; mConfigurationController = configurationController; + mUserTracker = userTracker; mUserManager = userManager; mTrustManager = trustManager; mIActivityManager = iActivityManager; @@ -1198,11 +1202,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } 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 5d564f74772b..bafd2e70a676 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.keyguard.KeyguardUpdateMonitorCallback; 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 class KeyguardSliceProvider extends SliceProvider implements public KeyguardBypassController mKeyguardBypassController; @Inject public KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Inject + UserTracker mUserTracker; private CharSequence mMediaTitle; private CharSequence mMediaArtist; protected boolean mDozing; @@ -355,7 +357,7 @@ public class KeyguardSliceProvider extends SliceProvider implements 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 d52efab746a7..3d976d43759c 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 static com.android.systemui.DejankUtils.whitelistIpcs; 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.dump.DumpManager; 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @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 class KeyguardViewMediator implements CoreStartable, Dumpable, 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } 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 class KeyguardViewMediator implements CoreStartable, Dumpable, */ public KeyguardViewMediator( Context context, + UserTracker userTracker, FalsingCollector falsingCollector, LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, @@ -1156,6 +1164,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, Lazy<ActivityLaunchAnimator> activityLaunchAnimator) { mContext = context; + mUserTracker = userTracker; mFalsingCollector = falsingCollector; mLockPatternUtils = lockPatternUtils; mBroadcastDispatcher = broadcastDispatcher; @@ -1205,6 +1214,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 78a7c9e35eb7..ef3c44340e57 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.data.repository.KeyguardRepositoryModule; 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 @@ public class KeyguardModule { @SysUISingleton public static KeyguardViewMediator newKeyguardViewMediator( Context context, + UserTracker userTracker, FalsingCollector falsingCollector, LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, @@ -114,6 +116,7 @@ public class KeyguardModule { Lazy<ActivityLaunchAnimator> activityLaunchAnimator) { return new KeyguardViewMediator( context, + userTracker, falsingCollector, lockPatternUtils, broadcastDispatcher, 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 a7f1b95555ba..a8f39fa9a456 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 android.widget.TextView 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 @@ class MediaViewHolder constructor(itemView: View) { // 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 45b319b274b2..cf71d675865b 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.content.Context 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 @@ class MediaDataFilter @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 @@ constructor( 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 918417fcd9a9..93be6a78ccd5 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.internal.annotations.VisibleForTesting 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 @@ internal constructor( 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 @@ internal constructor( 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 215fa03c8c59..21e64e28ff19 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.Intent; 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.annotation.UiThread; 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.ColorScheme; 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { } 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 @@ public class MediaControlPanel { /* 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 @@ public class MediaControlPanel { ); } + 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 e354a03f1725..1ea202582f83 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 @@ package com.android.systemui.media.taptotransfer.receiver 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 e8b49cd8ec1c..7a77c476aa11 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 @@ class ShellRecentTaskListProvider 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 @@ constructor( 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 59bb2278edfe..2a7704f13c3b 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.recents.OverviewProxyService; 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 @@ public class NavigationBarInflaterView extends FrameLayout 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 @@ public class NavigationBarInflaterView extends FrameLayout 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 @@ public class NavigationBarInflaterView extends FrameLayout 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 7964d164dc3a..4e3831cce374 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 android.window.BackEvent; 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.FalsingManager; 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 @@ import javax.inject.Provider; /** * 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 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private final Context mContext; + private final UserTracker mUserTracker; private final OverviewProxyService mOverviewProxyService; private final SysUiState mSysUiState; private Runnable mStateChangeCallback; @@ -321,6 +321,15 @@ public class EdgeBackGestureHandler extends CurrentUserTracker 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 @@ public class EdgeBackGestureHandler extends CurrentUserTracker PluginManager pluginManager, @Main Executor executor, @Background Executor backgroundExecutor, - BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker, ProtoTracer protoTracer, NavigationModeController navigationModeController, BackPanelController.Factory backPanelControllerFactory, @@ -340,11 +349,11 @@ public class EdgeBackGestureHandler extends CurrentUserTracker 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 @@ public class EdgeBackGestureHandler extends CurrentUserTracker } } - @Override - public void onUserSwitched(int newUserId) { - updateIsEnabled(); - updateCurrentUserResources(); - } - /** * @see NavigationBarView#onAttachedToWindow() */ @@ -478,7 +481,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mOverviewProxyService.addCallback(mQuickSwitchListener); mSysUiState.addCallback(mSysUiStateCallback); updateIsEnabled(); - startTracking(); + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); } /** @@ -490,7 +493,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mOverviewProxyService.removeCallback(mQuickSwitchListener); mSysUiState.removeCallback(mSysUiStateCallback); updateIsEnabled(); - stopTracking(); + mUserTracker.removeCallback(mUserChangedCallback); } /** @@ -1093,7 +1096,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker 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 @@ public class EdgeBackGestureHandler extends CurrentUserTracker PluginManager pluginManager, @Main Executor executor, @Background Executor backgroundExecutor, - BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker, ProtoTracer protoTracer, NavigationModeController navigationModeController, BackPanelController.Factory backPanelControllerFactory, @@ -1131,7 +1134,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mPluginManager = pluginManager; mExecutor = executor; mBackgroundExecutor = backgroundExecutor; - mBroadcastDispatcher = broadcastDispatcher; + mUserTracker = userTracker; mProtoTracer = protoTracer; mNavigationModeController = navigationModeController; mBackPanelControllerFactory = backPanelControllerFactory; @@ -1154,7 +1157,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker 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 be82b1faac8e..67e96645f9c5 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 @@ public class PeopleTileViewHelper { 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/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java index 6b0abd41dfff..7794fa071f45 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 @@ public abstract class SettingObserver extends ContentObserver implements Listena 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/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 86d4fa3002fc..033dbe0f82ee 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.QSHost; 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 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { 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 4abe3097dfe2..5bc209a840cb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java @@ -108,14 +108,14 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { 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(); 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 a895d72a7492..9743c3e64950 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 @@ public class InternetDialog extends SystemUIDialog implements 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 ba97297421b3..547b496beaff 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.logging.UiEventLogger; 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.NavigationBarView; 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.io.PrintWriter; 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 @@ public class OverviewProxyService extends CurrentUserTracker implements 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 @@ public class OverviewProxyService extends CurrentUserTracker implements 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 @@ public class OverviewProxyService extends CurrentUserTracker implements return; } - mCurrentBoundedUserId = getCurrentUserId(); + mCurrentBoundedUserId = mUserTracker.getUserId(); mOverviewProxy = IOverviewProxy.Stub.asInterface(service); Bundle params = new Bundle(); @@ -498,34 +498,44 @@ public class OverviewProxyService extends CurrentUserTracker implements } }; + 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 @@ public class OverviewProxyService extends CurrentUserTracker implements mCommandQueue = commandQueue; // Listen for user setup - startTracking(); + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); screenLifecycle.addObserver(mLifecycleObserver); @@ -579,12 +589,6 @@ public class OverviewProxyService extends CurrentUserTracker implements 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 @@ public class OverviewProxyService extends CurrentUserTracker implements 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 @@ public class OverviewProxyService extends CurrentUserTracker implements } 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 6de46483892b..000000000000 --- 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 dea8c32dc88d..000000000000 --- 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 9599d77bf65a..000000000000 --- 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 7801c68586f1..5880003cdb1a 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.convertGammaToLine 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.display.BrightnessSynchronizer; 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 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig 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 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } 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 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } mBrightnessObserver.stopObserving(); - mUserTracker.stopTracking(); + mUserTracker.removeCallback(mUserChangedCallback); mHandler.sendEmptyMessage(MSG_DETACH_LISTENER); } @@ -275,22 +279,27 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig } }; + 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 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mControl.setEnforcedAdmin( RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_CONFIG_BRIGHTNESS, - mUserTracker.getCurrentUserId())); + mUserTracker.getUserId())); } }); } @@ -440,16 +449,19 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig /** 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 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig 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 d5a395436271..e208be957510 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 android.widget.FrameLayout; 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 @@ public class BrightnessDialog extends Activity { 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 @@ public class BrightnessDialog extends Activity { 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 954534d42fdd..5011227ad2cc 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 @@ object CombinedShadeHeadersConstraintManagerImpl : CombinedShadeHeadersConstrain 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,6 +94,8 @@ object CombinedShadeHeadersConstraintManagerImpl : CombinedShadeHeadersConstrain centerEnd, ConstraintSet.END ) + constrainedWidth(R.id.date, true) + constrainedWidth(R.id.statusIcons, true) }, qsConstraintsChanges = { setGuidelineBegin(centerStart, offsetFromEdge) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 2101efb61443..8355c6426f1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -67,6 +67,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -74,6 +75,7 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.logging.KeyguardLogger; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; @@ -154,7 +156,8 @@ public class KeyguardIndicationController { private final AccessibilityManager mAccessibilityManager; private final Handler mHandler; - protected KeyguardIndicationRotateTextViewController mRotateTextViewController; + @VisibleForTesting + public KeyguardIndicationRotateTextViewController mRotateTextViewController; private BroadcastReceiver mBroadcastReceiver; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -1188,9 +1191,9 @@ public class KeyguardIndicationController { } @Override - public void onTrustGrantedWithFlags(int flags, int userId, @Nullable String message) { - if (!isCurrentUser(userId)) return; - showTrustGrantedMessage(flags, message); + public void onTrustGrantedForCurrentUser(boolean dismissKeyguard, + @NonNull TrustGrantFlags flags, @Nullable String message) { + showTrustGrantedMessage(dismissKeyguard, message); } @Override @@ -1254,7 +1257,7 @@ public class KeyguardIndicationController { return getCurrentUser() == userId; } - void showTrustGrantedMessage(int flags, @Nullable CharSequence message) { + protected void showTrustGrantedMessage(boolean dismissKeyguard, @Nullable String message) { mTrustGrantedIndication = message; updateDeviceEntryIndication(false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 184dc253bfc6..cdefae6b87f9 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 android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER 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.dump.DumpManager; 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 @@ public class NotificationLockscreenUserManagerImpl implements 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 @@ public class NotificationLockscreenUserManagerImpl implements BroadcastDispatcher broadcastDispatcher, DevicePolicyManager devicePolicyManager, UserManager userManager, + UserTracker userTracker, Lazy<NotificationVisibilityProvider> visibilityProviderLazy, Lazy<CommonNotifCollection> commonNotifCollectionLazy, NotificationClickNotifier clickNotifier, @@ -210,7 +212,8 @@ public class NotificationLockscreenUserManagerImpl implements mMainHandler = mainHandler; mDevicePolicyManager = devicePolicyManager; mUserManager = userManager; - mCurrentUserId = ActivityManager.getCurrentUser(); + mUserTracker = userTracker; + mCurrentUserId = mUserTracker.getUserId(); mVisibilityProviderLazy = visibilityProviderLazy; mCommonNotifCollectionLazy = commonNotifCollectionLazy; mClickNotifier = clickNotifier; @@ -295,7 +298,7 @@ public class NotificationLockscreenUserManagerImpl implements 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 815b86e0adb8..cd130854ff3f 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 @@ public class NotificationShelf extends ActivatableNotificationView implements 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 73d6483e65fb..99ff06a247bc 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.net.wifi.WifiManager; 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.log.dagger.StatusBarNetworkControllerLog; 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 @@ public class NetworkControllerImpl extends BroadcastReceiver 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 @@ public class NetworkControllerImpl extends BroadcastReceiver } }; + 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 @@ public class NetworkControllerImpl extends BroadcastReceiver CallbackHandler callbackHandler, DeviceProvisionedController deviceProvisionedController, BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker, ConnectivityManager connectivityManager, TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, @@ -251,6 +261,7 @@ public class NetworkControllerImpl extends BroadcastReceiver new SubscriptionDefaults(), deviceProvisionedController, broadcastDispatcher, + userTracker, demoModeController, carrierConfigTracker, trackerFactory, @@ -277,6 +288,7 @@ public class NetworkControllerImpl extends BroadcastReceiver SubscriptionDefaults defaultsHandler, DeviceProvisionedController deviceProvisionedController, BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker, DemoModeController demoModeController, CarrierConfigTracker carrierConfigTracker, WifiStatusTrackerFactory trackerFactory, @@ -333,13 +345,9 @@ public class NetworkControllerImpl extends BroadcastReceiver // 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 58738377a3db..6bd9502263ff 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 @@ class LockscreenSmartspaceController @Inject constructor( 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 58f59be1db6f..5f6a5cb00543 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.Collection; 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 @@ public class NotificationLogger implements StateListener { 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/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 334f1aff0d7d..18a08f73e596 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.plugins.statusbar.StatusBarStateController; 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.UserInfoControllerImpl; 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } // 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/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index 16fddb420fc4..6bf54430ab38 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.CommandQueue.Callbacks; 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 @@ import dagger.assisted.AssistedInject; /** * 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 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, 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 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, /** 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 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, 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 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, 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 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, 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 8793a57e4ef8..1d7dfe199871 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.Dumpable; 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 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen 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 94d1bf4be806..26e6db664e07 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 androidx.annotation.NonNull; 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 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { 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 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { /** */ @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 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { 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 d54a8638f2e9..c527f30c424c 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.SysUISingleton; 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump @Main Executor mainExecutor, ScreenOffAnimationController screenOffAnimationController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, - KeyguardViewMediator keyguardViewMediator) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager) { mScrimStateListener = lightBarController::setScrimState; mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; @@ -315,8 +312,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } }); mColors = new GradientColors(); - - mKeyguardViewMediator = keyguardViewMediator; } /** @@ -812,13 +807,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump 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/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 2d580abbf7bd..344d23341dc1 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.R; 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,9 +220,9 @@ public abstract class StatusBarViewModule { @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, @@ -230,9 +230,9 @@ public abstract class StatusBarViewModule { ) { return new BatteryMeterViewController( batteryMeterView, + userTracker, configurationController, tunerService, - broadcastDispatcher, mainHandler, contentResolver, featureFlags, 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 cf4106c508cb..68d30d3f3d1e 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.ColorFilter 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 @@ protected constructor( } fun refresh() { - controller.refreshUsers(UserHandle.USER_NULL) + controller.refreshUsers() } companion object { 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 aae0f93a0e19..38b3769c1071 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.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.UserTracker; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -83,6 +83,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Inject public BluetoothControllerImpl( Context context, + UserTracker userTracker, DumpManager dumpManager, BluetoothLogger logger, @Background Looper bgLooper, @@ -100,7 +101,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa 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 576962dee747..d84cbcc60853 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.broadcast.BroadcastDispatcher; 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 @@ public class Clock extends TextView implements 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 @@ public class Clock extends TextView implements 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 @@ public class Clock extends TextView implements 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 @@ public class Clock extends TextView implements 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 @@ public class Clock extends TextView implements 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 69b55c81f48b..a4821e0e9299 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 @@ package com.android.systemui.statusbar.policy; 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.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.UserTracker; import java.io.PrintWriter; import java.util.ArrayList; @@ -59,6 +59,7 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof 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 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof @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 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof @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 cc241d924d45..ba947149d287 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.R; 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 @@ import javax.inject.Inject; /** */ @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 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi 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 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi // 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 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi 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 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi // 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 29285f886f4e..a593d518c207 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.database.Cursor; 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.internal.util.UserIcons; 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 @@ public class UserInfoControllerImpl implements UserInfoController { 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 @@ public class UserInfoControllerImpl implements UserInfoController { /** */ @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 @@ public class UserInfoControllerImpl implements UserInfoController { 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 @@ public class UserInfoControllerImpl implements UserInfoController { 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 @@ public class UserInfoControllerImpl implements UserInfoController { 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 146b222c94ce..bdb656b9d2d5 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 + +/** 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 a class that provides user switching functionality and state. */ -interface UserSwitcherController : Dumpable { + /** 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 @@ interface UserSwitcherController : Dumpable { * @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 @@ interface UserSwitcherController : Dumpable { * @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 @@ interface UserSwitcherController : Dumpable { * @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 935fc7f10198..000000000000 --- 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 c294c370a601..000000000000 --- 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 9866389a39d7..b135d0d8c9dc 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.content.IntentFilter; 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.SysUISingleton; 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 @@ import javax.inject.Inject; /** 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 @@ public class ZenModeControllerImpl extends CurrentUserTracker 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 @@ public class ZenModeControllerImpl extends CurrentUserTracker 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 @@ public class ZenModeControllerImpl extends CurrentUserTracker @Override public boolean isVolumeRestricted() { return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, - new UserHandle(mUserId)); + UserHandle.of(mUserId)); } @Override @@ -183,19 +206,6 @@ public class ZenModeControllerImpl extends CurrentUserTracker } @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 @@ public class ZenModeControllerImpl extends CurrentUserTracker @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 b1b45b51d8e4..1b7353923ada 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.SecurityController; 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 @@ public interface StatusBarPolicyModule { 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 index 48df15c78ea5..93e78acc63fd 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 index c7f0b7e0056e..f558fee776e6 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 @@ import android.view.View 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 @@ class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, a 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 index aca9e254e4c3..b2f8994ebf51 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 index 88122544c7cd..ae73df201f8d 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 index d2f3a6a7bee1..a950d34513ee 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 @@ import android.util.MathUtils * 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 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C 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_CIRCLE_MAIN = """vec4 main(vec2 p) { + 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) { 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 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C } """ - 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 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C } """ - 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 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C } """ - 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 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C } } - /** - * 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 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C 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 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C 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 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C } /** - * 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 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C 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 index a6d79303962f..299469494295 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.graphics.Paint 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 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a 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 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a 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 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a // 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 index 5e256c653992..8b2f46648fef 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 @@ class SdfShaderLibrary { } """ - 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 @@ class SdfShaderLibrary { // 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 @@ class SdfShaderLibrary { } """ - 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 000000000000..d78e0c153db8 --- /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 000000000000..5ac3aad749fc --- /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 000000000000..4c7e5f4c7093 --- /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 000000000000..19c114d2693c --- /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 000000000000..8649d5924587 --- /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 3507cb7c40a4..095718bddf97 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.R; 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 @@ public class StorageNotification implements CoreStartable { 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 @@ public class StorageNotification implements CoreStartable { @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 ffaf524bb0d1..ed53de7dbee7 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 @@ package com.android.systemui.user.data.repository 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.channels.awaitClose 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 @@ import kotlinx.coroutines.withContext * 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 @@ interface UserRepository { /** 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 @@ class UserRepositoryImpl 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 @@ constructor( 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 @@ constructor( override var isRefreshUsersPaused: Boolean = false init { - if (isNewImpl) { - observeSelectedUser() - observeUserSettings() - } + observeSelectedUser() + observeUserSettings() } override fun refreshUsers() { @@ -327,64 +247,6 @@ constructor( } } - 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 dda78aad54c6..6b81bf2cfb08 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.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.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.StateFlow 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 @@ class UserInteractor 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 @@ constructor( 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 @@ constructor( /** 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) - } + val canCreateUsers = + UserActionsUtil.canCreateUser( + manager, + repository, + settings.isUserSwitcherEnabled, + settings.isAddUsersFromLockscreen, + ) + + if (canCreateUsers) { + add(UserActionModel.ADD_USER) } if ( - UserActionsUtil.canManageUsers( + UserActionsUtil.canCreateSupervisedUser( + manager, repository, settings.isUserSwitcherEnabled, settings.isAddUsersFromLockscreen, + supervisedUserPackageName, ) ) { - add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) + add(UserActionModel.ADD_SUPERVISED_USER) } } - } - } 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()) - } + + if ( + UserActionsUtil.canManageUsers( + repository, + settings.isUserSwitcherEnabled, + settings.isAddUsersFromLockscreen, + ) + ) { + add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) } + } } 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 @@ constructor( 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) - - 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) - } + 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) } fun addCallback(callback: UserCallback) { @@ -414,48 +362,43 @@ constructor( 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 - } + ) + 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, - ) + 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 - } + ) + return + } - dialogShower?.dismiss() + dialogShower?.dismiss() - switchUser(newlySelectedUserId) - } else { - controller.onUserSelected(newlySelectedUserId, dialogShower) - } + switchUser(newlySelectedUserId) } /** Executes the given action. */ @@ -463,51 +406,38 @@ constructor( 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.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_USER -> { + val currentUser = repository.getSelectedUserInfo() + showDialog( + ShowDialogRequestModel.ShowAddUserDialog( + userHandle = currentUser.userHandle, + isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), + showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral, + dialogShower = 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.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 ad09ee3c10d9..e13710786fbb 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 @@ object UserSwitcherViewBinder { 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 e9217209530b..58a4473186b3 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.animation.DialogLaunchAnimator 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 @@ constructor( 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 d857e85bac53..0910ea36b7ff 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 @@ package com.android.systemui.user.ui.viewmodel 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 constructor( 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 constructor( 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 @@ private constructor( 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 d7c4e93bb26e..3c570810b318 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 @@ import javax.inject.Inject; */ 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 52b6b38ca8ef..e8f8e25364b3 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 @@ class ClockEventControllerTest : SysuiTestCase() { 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 27094c0b9853..1ec23e9b542d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -29,6 +29,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; +import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser; import static com.google.common.truth.Truth.assertThat; @@ -36,6 +37,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; @@ -89,6 +91,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.service.dreams.IDreamManager; +import android.service.trust.TrustAgentService; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -113,6 +116,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; 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 +161,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private static final int FINGERPRINT_SENSOR_ID = 1; @Mock + private UserTracker mUserTracker; + @Mock private DumpManager mDumpManager; @Mock private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker; @@ -306,8 +312,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { 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( @@ -351,7 +356,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @After public void tearDown() { - mMockitoSession.finishMocking(); + if (mMockitoSession != null) { + mMockitoSession.finishMocking(); + } cleanupKeyguardUpdateMonitor(); } @@ -1032,6 +1039,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @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"; @@ -1313,9 +1321,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { Arrays.asList("Unlocked by wearable")); // THEN the showTrustGrantedMessage should be called with the first message - verify(mTestCallback).onTrustGrantedWithFlags( - eq(0), - eq(KeyguardUpdateMonitor.getCurrentUser()), + verify(mTestCallback).onTrustGrantedForCurrentUser( + anyBoolean(), + eq(new TrustGrantFlags(0)), eq("Unlocked by wearable")); } @@ -1726,6 +1734,155 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); } + + @Test + public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_deviceInteractive() { + // GIVEN device is interactive + deviceIsInteractive(); + + // GIVEN callback is registered + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag + mKeyguardUpdateMonitor.onTrustChanged( + true /* enabled */, + getCurrentUser() /* userId */, + TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */, + null /* trustGrantedMessages */); + + // THEN onTrustGrantedForCurrentUser callback called + verify(callback).onTrustGrantedForCurrentUser( + eq(true) /* dismissKeyguard */, + eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)), + eq(null) /* message */ + ); + } + + @Test + public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_doesNotDismiss() { + // GIVEN device is NOT interactive + + // GIVEN callback is registered + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag + mKeyguardUpdateMonitor.onTrustChanged( + true /* enabled */, + getCurrentUser() /* userId */, + TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */, + null /* trustGrantedMessages */); + + // THEN onTrustGrantedForCurrentUser callback called + verify(callback).onTrustGrantedForCurrentUser( + eq(false) /* dismissKeyguard */, + eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)), + eq(null) /* message */ + ); + } + + @Test + public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_temporaryAndRenewable() { + // GIVEN device is interactive + deviceIsInteractive(); + + // GIVEN callback is registered + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN onTrustChanged for a different user + mKeyguardUpdateMonitor.onTrustChanged( + true /* enabled */, + 546 /* userId, not the current userId */, + 0 /* flags */, + null /* trustGrantedMessages */); + + // THEN onTrustGrantedForCurrentUser callback called + verify(callback, never()).onTrustGrantedForCurrentUser( + anyBoolean() /* dismissKeyguard */, + anyObject() /* flags */, + anyString() /* message */ + ); + } + + @Test + public void testOnTrustGranted_differentUser_noCallback() { + // GIVEN device is interactive + + // GIVEN callback is registered + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD AND TRUST_TEMPORARY_AND_RENEWABLE + // flags (temporary & rewable is active unlock) + mKeyguardUpdateMonitor.onTrustChanged( + true /* enabled */, + getCurrentUser() /* userId */, + TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD + | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */, + null /* trustGrantedMessages */); + + // THEN onTrustGrantedForCurrentUser callback called + verify(callback).onTrustGrantedForCurrentUser( + eq(true) /* dismissKeyguard */, + eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD + | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)), + eq(null) /* message */ + ); + } + + @Test + public void testOnTrustGrantedForCurrentUser_bouncerShowing_initiatedByUser() { + // GIVEN device is interactive & bouncer is showing + deviceIsInteractive(); + bouncerFullyVisible(); + + // GIVEN callback is registered + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN onTrustChanged with INITIATED_BY_USER flag + mKeyguardUpdateMonitor.onTrustChanged( + true /* enabled */, + getCurrentUser() /* userId, not the current userId */, + TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */, + null /* trustGrantedMessages */); + + // THEN onTrustGrantedForCurrentUser callback called + verify(callback, never()).onTrustGrantedForCurrentUser( + eq(true) /* dismissKeyguard */, + eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER)), + anyString() /* message */ + ); + } + + @Test + public void testOnTrustGrantedForCurrentUser_bouncerShowing_temporaryRenewable() { + // GIVEN device is NOT interactive & bouncer is showing + bouncerFullyVisible(); + + // GIVEN callback is registered + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(callback); + + // WHEN onTrustChanged with INITIATED_BY_USER flag + mKeyguardUpdateMonitor.onTrustChanged( + true /* enabled */, + getCurrentUser() /* userId, not the current userId */, + TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER + | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */, + null /* trustGrantedMessages */); + + // THEN onTrustGrantedForCurrentUser callback called + verify(callback, never()).onTrustGrantedForCurrentUser( + eq(true) /* dismissKeyguard */, + eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER + | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)), + anyString() /* message */ + ); + } + private void cleanupKeyguardUpdateMonitor() { if (mKeyguardUpdateMonitor != null) { mKeyguardUpdateMonitor.removeCallback(mTestCallback); @@ -1886,7 +2043,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { 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 ff4412e97c04..27701be66761 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 @@ package com.android.keyguard.clock; 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.AndroidTestingRunner; 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 @@ import java.util.Arrays; @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 @@ public final class ClockManagerTest extends SysuiTestCase { 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 @@ public final class ClockManagerTest extends SysuiTestCase { 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 @@ public final class ClockManagerTest extends SysuiTestCase { @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 @@ public final class ClockManagerTest extends SysuiTestCase { 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 @@ public final class ClockManagerTest extends SysuiTestCase { // 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/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index d20eeafde09c..2d5188fcb95a 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 @@ package com.android.systemui.accessibility.floatingmenu; 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 androidx.test.filters.SmallTest; 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.Mock; 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 @@ public class MenuViewLayerTest extends SysuiTestCase { @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 @@ public class MenuViewLayerTest extends SysuiTestCase { 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/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java index bc8f96198b27..1482f291b57a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java @@ -34,9 +34,9 @@ import android.provider.Settings; 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; @@ -52,12 +52,12 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase { 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; @@ -153,9 +153,9 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase { private void initController() { mController = new BatteryMeterViewController( mBatteryMeterView, + mUserTracker, mConfigurationController, mTunerService, - mBroadcastDispatcher, mHandler, mContentResolver, mFeatureFlags, 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 12c2bbfd464b..898f37048eec 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 @@ class AuthContainerViewTest : SysuiTestCase() { } @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 @@ class AuthContainerViewTest : SysuiTestCase() { @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 @@ class AuthContainerViewTest : SysuiTestCase() { 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 2af055783c22..d1597149ce0b 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.internal.logging.UiEventLogger 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 f8579fff488b..0fadc138637a 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 @@ public class BrightLineClassifierTest extends SysuiTestCase { 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 0b72a68005a6..3b6f7d19e93e 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.filters.SmallTest 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 @@ import org.mockito.MockitoAnnotations @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 @@ class ControlsEditingActivityTest : SysuiTestCase() { ) { override fun create(intent: Intent?): TestableControlsEditingActivity { return TestableControlsEditingActivity( + uiExecutor, controller, - broadcastDispatcher, + userTracker, customIconCache, uiController, mockDispatcher, @@ -92,13 +97,14 @@ class ControlsEditingActivityTest : SysuiTestCase() { } 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 4b0f7e6cd736..0f06de2a0684 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.filters.SmallTest 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 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @Mock lateinit var listingController: ControlsListingController - @Mock lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock lateinit var userTracker: UserTracker @Mock lateinit var uiController: ControlsUiController @@ -60,7 +60,7 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { executor, controller, listingController, - broadcastDispatcher, + userTracker, uiController, mockDispatcher, latch @@ -97,7 +97,7 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { executor: Executor, controller: ControlsControllerImpl, listingController: ControlsListingController, - broadcastDispatcher: BroadcastDispatcher, + userTracker: UserTracker, uiController: ControlsUiController, private val mockDispatcher: OnBackInvokedDispatcher, private val latch: CountDownLatch @@ -106,7 +106,7 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { 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 acc62227cde6..56c3efe1b8e6 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.filters.SmallTest 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 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { @Mock lateinit var controlsController: ControlsController - @Mock lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock lateinit var userTracker: UserTracker @Mock lateinit var uiController: ControlsUiController @@ -80,7 +80,7 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { backExecutor, listingController, controlsController, - broadcastDispatcher, + userTracker, uiController, mockDispatcher, latch @@ -118,7 +118,7 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { backExecutor: Executor, listingController: ControlsListingController, controlsController: ControlsController, - broadcastDispatcher: BroadcastDispatcher, + userTracker: UserTracker, uiController: ControlsUiController, private val mockDispatcher: OnBackInvokedDispatcher, private val latch: CountDownLatch @@ -128,7 +128,7 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { 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 efb3db700804..314b17625f00 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 androidx.test.runner.intercepting.SingleActivityFactory 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.junit.runner.RunWith 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 @@ class ControlsRequestDialogTest : SysuiTestCase() { 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 @@ class ControlsRequestDialogTest : SysuiTestCase() { ) { 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 3f6308b18d03..ec239f64e254 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 2f206adc5acf..07d7e79ec256 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.SysuiTestCase; 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 @@ public class DozeSensorsTest extends SysuiTestCase { @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 @@ public class DozeSensorsTest extends SysuiTestCase { 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 @@ public class DozeSensorsTest extends SysuiTestCase { 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 6091d3a93f14..82432ce31fa2 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.broadcast.BroadcastDispatcher; 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 @@ public class DozeTriggersTest extends SysuiTestCase { @Mock private DevicePostureController mDevicePostureController; @Mock + private UserTracker mUserTracker; + @Mock private SessionTracker mSessionTracker; private DozeTriggers mTriggers; @@ -131,7 +134,7 @@ public class DozeTriggersTest extends SysuiTestCase { 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 000000000000..99406ed44606 --- /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 517804db2a70..73c226d11bc4 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 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mController.onViewAttached(); verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView); - verify(mAnimationsController, never()).cancelRunningEntryAnimations(); + verify(mAnimationsController, never()).cancelAnimations(); } @Test @@ -221,6 +221,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { 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 f04a37f4c3fa..ffb8342a56a5 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 com.google.common.truth.Truth.assertThat; 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 @@ public class DreamOverlayServiceTest extends SysuiTestCase { 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/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java index 14a5702c8e5b..4e3aca710884 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.concurrent.futures.CallbackToFutureAdapter; 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 @@ import org.mockito.MockitoAnnotations; @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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { @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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { 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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { // 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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { 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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { // 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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { 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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { // 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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { 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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { 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 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { 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 8b1554c1f66f..d52616bfefcc 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.broadcast.BroadcastDispatcher; 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 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @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 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { 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 4d66a168303c..000000000000 --- 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 23516c94d851..729a1ccd30f9 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.keyguard.KeyguardUpdateMonitor; 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 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { private NextAlarmController mNextAlarmController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private UserTracker mUserTracker; private TestableKeyguardSliceProvider mProvider; private boolean mIsZenMode; @@ -105,6 +108,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { mProvider.attachInfo(getContext(), null); reset(mContentResolver); SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST))); + when(mUserTracker.getUserId()).thenReturn(100); } @After @@ -267,6 +271,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { 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 b6780a12e6a9..45aaaa2418a1 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.classifier.FalsingCollectorFake; 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 @@ import dagger.Lazy; 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 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private void createAndStartViewMediator() { mViewMediator = new KeyguardViewMediator( mContext, + mUserTracker, mFalsingCollector, mLockPatternUtils, mBroadcastDispatcher, 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 7cd8e749a6e9..56c91bc4525d 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.junit.runner.RunWith 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 @@ public class SeekBarViewModelTest : SysuiTestCase() { 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 575b1c6b126e..9d33e6f84972 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 android.testing.TestableLooper 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 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! 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 @@ class MediaDataFilterTest : SysuiTestCase() { 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 a8f413848009..a94374680b91 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.SysuiTestCase 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 @@ class ColorSchemeTransitionTest : SysuiTestCase() { @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 @@ class ColorSchemeTransitionTest : SysuiTestCase() { 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 1ad2ca9b9db3..761773b1a345 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.controls.util.MediaUiEventLogger 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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { } multiRippleView = MultiRippleView(context, null) + turbulenceNoiseView = TurbulenceNoiseView(context, null) whenever(viewHolder.player).thenReturn(view) whenever(viewHolder.appIcon).thenReturn(appIcon) @@ -425,6 +431,7 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 939af16d81cd..d35a21236ae8 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.app.ActivityManager.RecentTaskInfo 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.recents.RecentTasks 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 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { 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/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt index 73a0cbc5054d..030c59faa696 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.plugins.statusbar.StatusBarStateController 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 @@ class AirplaneModeTileTest : SysuiTestCase() { 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 @@ class AirplaneModeTileTest : SysuiTestCase() { 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 3131f60893c7..08a90b79089e 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.any 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 @@ class UserDetailViewAdapterTest : SysuiTestCase() { `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 2ef731236851..48a53bc659b3 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.R; 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mInternetDialogController.mActivityStarter = mActivityStarter; mInternetDialogController.mWifiIconInjector = mWifiIconInjector; + mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, false); } @After @@ -410,7 +411,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @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 @@ public class InternetDialogControllerTest extends SysuiTestCase { @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 @@ public class InternetDialogControllerTest extends SysuiTestCase { @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 @@ public class InternetDialogControllerTest extends SysuiTestCase { @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 @@ public class InternetDialogControllerTest extends SysuiTestCase { @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 @@ public class InternetDialogControllerTest extends SysuiTestCase { @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 @@ public class InternetDialogControllerTest extends SysuiTestCase { @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 f4bc232702e8..df3a62f6cd71 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 @@ public class ImageExporterTest extends SysuiTestCase { 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 1b515c6bbdbd..000000000000 --- 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 1130bda9b881..9d1802a686fa 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.rule.ActivityTestRule 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 @@ import org.mockito.MockitoAnnotations @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 @@ class BrightnessDialogTest : SysuiTestCase() { object : SingleActivityFactory<TestDialog>(TestDialog::class.java) { override fun create(intent: Intent?): TestDialog { return TestDialog( - fakeBroadcastDispatcher, + userTracker, brightnessSliderControllerFactory, + mainExecutor, backgroundHandler ) } @@ -100,8 +104,15 @@ class BrightnessDialogTest : SysuiTestCase() { } 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 d7eb337efd3b..bc17c19df8f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt @@ -363,6 +363,22 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { } } + @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 539a54b731ec..f5bed79b5e6f 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 @@ class DefaultClockProviderTest : SysuiTestCase() { } @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 e8a7ec82aab1..ab7c52e8ad80 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.app.IBatteryStats; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.logging.KeyguardLogger; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; @@ -1068,7 +1069,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // GIVEN a trust granted message but trust isn't granted final String trustGrantedMsg = "testing trust granted message"; - mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, trustGrantedMsg); + mController.getKeyguardCallback().onTrustGrantedForCurrentUser( + false, new TrustGrantFlags(0), trustGrantedMsg); verifyHideIndication(INDICATION_TYPE_TRUST); @@ -1092,7 +1094,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN the showTrustGranted method is called final String trustGrantedMsg = "testing trust granted message"; - mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, trustGrantedMsg); + mController.getKeyguardCallback().onTrustGrantedForCurrentUser( + false, new TrustGrantFlags(0), trustGrantedMsg); // THEN verify the trust granted message shows verifyIndicationMessage( @@ -1109,7 +1112,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true); // WHEN the showTrustGranted method is called with a null message - mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, null); + mController.getKeyguardCallback().onTrustGrantedForCurrentUser( + false, new TrustGrantFlags(0), null); // THEN verify the default trust granted message shows verifyIndicationMessage( @@ -1126,7 +1130,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true); // WHEN the showTrustGranted method is called with an EMPTY string - mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, ""); + mController.getKeyguardCallback().onTrustGrantedForCurrentUser( + false, new TrustGrantFlags(0), ""); // THEN verify NO trust message is shown verifyNoMessage(INDICATION_TYPE_TRUST); 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 bdafa4893c9e..15a687d2adc7 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.broadcast.BroadcastDispatcher; 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 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { private NotificationPresenter mPresenter; @Mock private UserManager mUserManager; + @Mock + private UserTracker mUserTracker; // Dependency mocks: @Mock @@ -115,6 +118,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { 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 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { 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 9c870b5aa363..faf4592d26e3 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.broadcast.BroadcastDispatcher; 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 @@ public class NetworkControllerBaseTest extends SysuiTestCase { 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 @@ public class NetworkControllerBaseTest extends SysuiTestCase { 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 @@ public class NetworkControllerBaseTest extends SysuiTestCase { 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 1d112262765e..ca75a40300cb 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 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { 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 d5f5105036d3..84c242cda459 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 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockSubDefaults, mMockProvisionController, mMockBd, + mUserTracker, mDemoModeController, mCarrierConfigTracker, mWifiStatusTrackerFactory, @@ -118,6 +119,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockSubDefaults, mMockProvisionController, mMockBd, + mUserTracker, mDemoModeController, mCarrierConfigTracker, mWifiStatusTrackerFactory, @@ -152,6 +154,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, + mUserTracker, mDemoModeController, mock(CarrierConfigTracker.class), mWifiStatusTrackerFactory, @@ -189,6 +192,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, + mUserTracker, mDemoModeController, mock(CarrierConfigTracker.class), mWifiStatusTrackerFactory, @@ -274,6 +278,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { 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 808abc8e9de5..de71e2c250c4 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.SysuiTestCase; 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 @@ public class ScrimControllerTest extends SysuiTestCase { // 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 @@ public class ScrimControllerTest extends SysuiTestCase { 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 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.setWallpaperSupportsAmbientMode(false); mScrimController.transitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); - - mScrimController.setLaunchingAffordanceWithPreview(false); } @After @@ -858,8 +852,7 @@ public class ScrimControllerTest extends SysuiTestCase { 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 @@ public class ScrimControllerTest extends SysuiTestCase { 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/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt index f3046477f4d1..0a3da0b5b029 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 @@ class BaseUserSwitcherAdapterTest : SysuiTestCase() { 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/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index d0391ac0795c..833cabbaecf4 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.settingslib.bluetooth.LocalBluetoothProfileManager; 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 @@ import java.util.List; @SmallTest public class BluetoothControllerImplTest extends SysuiTestCase { + private UserTracker mUserTracker; private LocalBluetoothManager mMockBluetoothManager; private CachedBluetoothDeviceManager mMockDeviceManager; private LocalBluetoothAdapter mMockAdapter; @@ -70,6 +72,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { 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 @@ public class BluetoothControllerImplTest extends SysuiTestCase { 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 26df03f31b9a..dc08aba7d5e5 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 androidx.test.filters.SmallTest; 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 @@ import java.util.concurrent.Executor; public class HotspotControllerImplTest extends SysuiTestCase { @Mock + private UserTracker mUserTracker; + @Mock private DumpManager mDumpManager; @Mock private TetheringManager mTetheringManager; @@ -104,7 +107,8 @@ public class HotspotControllerImplTest extends SysuiTestCase { 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 @@ public class HotspotControllerImplTest extends SysuiTestCase { 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 d44cdb24421a..15235b68c881 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 androidx.test.runner.AndroidJUnit4; 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 @@ public class SecurityControllerTest extends SysuiTestCase { 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 @@ public class SecurityControllerTest extends SysuiTestCase { 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 169f4fb2715b..000000000000 --- 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 3fe1a9f6ed97..c06dbdcf3a1b 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 androidx.test.filters.SmallTest; 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 @@ public class ZenModeControllerImplTest extends SysuiTestCase { BroadcastDispatcher mBroadcastDispatcher; @Mock DumpManager mDumpManager; + @Mock + UserTracker mUserTracker; private ZenModeControllerImpl mController; @@ -72,7 +75,8 @@ public class ZenModeControllerImplTest extends SysuiTestCase { 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 index 05512e5bf1ce..0d19ab1db390 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 000000000000..2024d53b0212 --- /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 index 7662282a04f4..756397a30e43 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 index 2d2f4cc9edd2..1e5ab7e25599 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 com.android.systemui.SysuiTestCase 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 000000000000..d25c8c1a5899 --- /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 000000000000..633aac076502 --- /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 7c7f0e1e0e12..000000000000 --- 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 dcea83a55a74..2e527be1af89 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 a363a037c499..000000000000 --- 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 f682e31c0547..000000000000 --- 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 58f55314c1b6..8fb98c12d6ff 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 @@ package com.android.systemui.user.domain.interactor 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 @@ abstract class UserInteractorTest : SysuiTestCase() { 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 @@ abstract class UserInteractorTest : SysuiTestCase() { ) } + @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 6a17c8ddc63d..000000000000 --- 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 116023aca655..db136800a3cc 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 @@ package com.android.systemui.user.ui.viewmodel 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.GuestResetOrExitSessionReceiver 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.junit.runners.JUnit4 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 @@ class UserSwitcherViewModelTest : SysuiTestCase() { 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 @@ class UserSwitcherViewModelTest : SysuiTestCase() { 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 @@ class UserSwitcherViewModelTest : SysuiTestCase() { PowerInteractor( repository = powerRepository, ), - featureFlags = featureFlags, guestUserInteractor = guestUserInteractor, ) .create(UserSwitcherViewModel::class.java) } @Test - fun users() = - runBlocking(IMMEDIATE) { - userRepository.setUsers( - 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, - ), - ) + 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, + ), ) - - 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() - } + 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`() = - runBlocking(IMMEDIATE) { - setUsers(count = 2) - var value: Int? = null - val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this) - - assertThat(value).isEqualTo(4) - job.cancel() - } + 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`() = - runBlocking(IMMEDIATE) { - setUsers(count = 5) - var value: Int? = null - val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this) - - assertThat(value).isEqualTo(3) - job.cancel() - } + 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`() = - runBlocking(IMMEDIATE) { - userRepository.setActions(UserActionModel.values().toList()) + fun `isOpenMenuButtonVisible - has actions - true`() = selfCancelingTest { + setUsers(2) - var isVisible: Boolean? = null - val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this) + val isVisible = mutableListOf<Boolean>() + val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } - assertThat(isVisible).isTrue() - job.cancel() - } + assertThat(isVisible.last()).isTrue() + job.cancel() + } @Test - fun `isOpenMenuButtonVisible - no actions - false`() = - runBlocking(IMMEDIATE) { - userRepository.setActions(emptyList()) + fun `isOpenMenuButtonVisible - no actions - false`() = selfCancelingTest { + val userInfos = setUsers(2) + userRepository.setSelectedUserInfo(userInfos[1]) + keyguardRepository.setKeyguardShowing(true) + whenever(manager.canAddMoreUsers(any())).thenReturn(false) - var isVisible: Boolean? = null - val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this) + val isVisible = mutableListOf<Boolean>() + val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } - assertThat(isVisible).isFalse() - job.cancel() - } + assertThat(isVisible.last()).isFalse() + 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() + fun menu() = selfCancelingTest { + val isMenuVisible = mutableListOf<Boolean>() + val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) } + assertThat(isMenuVisible.last()).isFalse() - underTest.onOpenMenuButtonClicked() - assertThat(isMenuVisible).isTrue() + underTest.onOpenMenuButtonClicked() + assertThat(isMenuVisible.last()).isTrue() - underTest.onMenuClosed() - assertThat(isMenuVisible).isFalse() + underTest.onMenuClosed() + assertThat(isMenuVisible.last()).isFalse() - job.cancel() - } + 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(), - ) + 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( + 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() - } + 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() - } + 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() + + userRepository.setSelectedUserInfo(userInfos[1]) + + assertThat(isFinishRequested.last()).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() - } + 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() + + powerRepository.setInteractive(false) + + assertThat(isFinishRequested.last()).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() - } + 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() + + underTest.onCancelButtonClicked() + + assertThat(isFinishRequested.last()).isTrue() + + underTest.onFinished() + + assertThat(isFinishRequested.last()).isFalse() - private suspend fun setUsers(count: Int) { - userRepository.setUsers( + job.cancel() + } + + 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 @@ class UserSwitcherViewModelTest : SysuiTestCase() { 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/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 4df8aa42ea2f..b7c8cbf40bea 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 @@ package com.android.systemui.user.data.repository 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 @@ class FakeUserRepository : UserRepository { 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 @@ class FakeUserRepository : UserRepository { 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/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index f35de17088d1..c77b59717678 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.AccessibilityNodeInfo; 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ /** 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @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 6958b667da37..c08b6ab56f55 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 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection 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 8525e3634e3a..592045c23372 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 @@ final class RemoteAugmentedAutofillService }); } + @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 000000000000..042bcbd0a0bb --- /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 466f647fd034..8f73436d193a 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 @@ public interface OperationStorage { * @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 6908c60034b6..a94167eb9fa7 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 @@ public class LifecycleOperationStorage implements OperationStorage { 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 40d7cad9405a..21005bbf8af9 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 android.util.Slog; 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 @@ public class BackupTransportClient { 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 fb98825f1343..deaa86c09b36 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 android.util.Slog; 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 @@ public class TransportStatusCallback extends ITransportStatusCallback.Stub { private boolean mHasCompletedOperation = false; public TransportStatusCallback() { - mOperationTimeout = TIMEOUT_MILLIS; + mOperationTimeout = BackupAndRestoreFeatureFlags.getBackupTransportCallbackTimeoutMillis(); } @VisibleForTesting 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 be2107529f8b..fbde9e0ea5d1 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -227,6 +227,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mParams.getName(); } + /** Returns the policy specified for this policy type */ + public @VirtualDeviceParams.DevicePolicy int getDevicePolicy( + @VirtualDeviceParams.PolicyType int policyType) { + return mParams.getDevicePolicy(policyType); + } + /** Returns the unique device ID of this device. */ @Override // Binder call public int getDeviceId() { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index c400a74da4ce..a8797a05ed24 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -233,6 +233,13 @@ public class VirtualDeviceManagerService extends SystemService { mLocalService.onAppsOnVirtualDeviceChanged(); } + @VisibleForTesting + void addVirtualDevice(VirtualDeviceImpl virtualDevice) { + synchronized (mVirtualDeviceManagerLock) { + mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice); + } + } + class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements VirtualDeviceImpl.PendingTrampolineCallback { @@ -358,6 +365,12 @@ public class VirtualDeviceManagerService extends SystemService { return virtualDevices; } + @Override // BinderCall + @VirtualDeviceParams.DevicePolicy + public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) { + return mLocalService.getDevicePolicy(deviceId, policyType); + } + @Nullable private AssociationInfo getAssociationInfo(String packageName, int associationId) { final int callingUserId = getCallingUserHandle().getIdentifier(); @@ -439,6 +452,20 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + @VirtualDeviceParams.DevicePolicy + public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) { + synchronized (mVirtualDeviceManagerLock) { + for (int i = 0; i < mVirtualDevices.size(); i++) { + final VirtualDeviceImpl device = mVirtualDevices.valueAt(i); + if (device.getDeviceId() == deviceId) { + return device.getDevicePolicy(policyType); + } + } + } + return VirtualDeviceParams.DEVICE_POLICY_DEFAULT; + } + + @Override public void onVirtualDisplayCreated(int displayId) { final VirtualDisplayListener[] listeners; synchronized (mVirtualDeviceManagerLock) { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 0f101b08ba70..08ee6d76f284 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 @@ public final class ContentCaptureManagerService extends 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 @@ public final class ContentCaptureManagerService extends @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/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index c3cd1359cd5f..a05b84baf667 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -351,12 +351,24 @@ public final class SystemServiceManager implements Dumpable { * Starts the given user. */ public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) { - EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId); - final TargetUser targetUser = newTargetUser(userId); synchronized (mTargetUsers) { + // On Automotive / Headless System User Mode, the system user will be started twice: + // - Once by some external or local service that switches the system user to + // the background. + // - Once by the ActivityManagerService, when the system is marked ready. + // These two events are not synchronized and the order of execution is + // non-deterministic. To avoid starting the system user twice, verify whether + // the system user has already been started by checking the mTargetUsers. + // TODO(b/242195409): this workaround shouldn't be necessary once we move + // the headless-user start logic to UserManager-land. + if (userId == UserHandle.USER_SYSTEM && mTargetUsers.contains(userId)) { + Slog.e(TAG, "Skipping starting system user twice"); + return; + } mTargetUsers.put(userId, targetUser); } + EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId); onUser(t, USER_STARTING, /* prevUser= */ null, targetUser); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d0f245f91d5c..86bb699f07d2 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.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGRO 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; 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.SYSTEM_UID; 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.ActivityThread; 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.PackageManagerInternal; 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 @@ public final class ActiveServices { getAppStateTracker().addBackgroundRestrictedAppListener(new BackgroundRestrictedListener()); mAppWidgetManagerInternal = LocalServices.getService(AppWidgetManagerInternal.class); setAllowListWhileInUsePermissionInFgs(); + initSystemExemptedFgsTypePermission(); } private AppStateTracker getAppStateTracker() { @@ -724,7 +747,7 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { ignoreForeground = true; } + int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN; if (!ignoreForeground) { if (r.mStartForegroundCount == 0) { /* @@ -1931,7 +1955,9 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { // 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { } } 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { */ 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { } 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 @@ public final class ActiveServices { */ 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { */ 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 @@ public final class ActiveServices { + "; callerTargetSdkVersion:" + callerTargetSdkVersion + "; startForegroundCount:" + r.mStartForegroundCount + "; bindFromPackage:" + bindFromPackage + + ": isBindService:" + isBindService + "]"; if (!debugInfo.equals(r.mInfoAllowStartForeground)) { r.mLoggedInfoAllowStartForeground = false; @@ -6799,7 +6980,7 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { } 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 @@ public final class ActiveServices { } 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 @@ public final class ActiveServices { * @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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 16fe121fecb7..003f7f0d88fb 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { */ 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 7d640772a4be..28f5bf3f718d 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.FastPrintWriter; 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; @@ -8402,13 +8400,10 @@ public class ActivityManagerService extends IActivityManager.Stub // On Automotive / Headless System User Mode, at this point the system user has already been // started and unlocked, and some of the tasks we do here have already been done. So skip - // those in that case. + // those in that case. The duplicate system user start is guarded in SystemServiceManager. // TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user - // start logic to UserManager-land - final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM; - if (bootingSystemUser) { - mUserController.onSystemUserStarting(); - } + // start logic to UserManager-land. + mUserController.onSystemUserStarting(); synchronized (this) { // Only start up encryption-aware persistent apps; once user is @@ -8438,7 +8433,15 @@ public class ActivityManagerService extends IActivityManager.Stub t.traceEnd(); } - if (bootingSystemUser) { + // Some systems - like automotive - will explicitly unlock system user then switch + // to a secondary user. Hence, we don't want to send duplicate broadcasts for + // the system user here. + // TODO(b/242195409): this workaround shouldn't be necessary once we move + // the headless-user start logic to UserManager-land. + final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM) + && !UserManager.isHeadlessSystemUserMode(); + + if (isBootingSystemUser) { t.traceBegin("startHomeOnAllDisplays"); mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady"); t.traceEnd(); @@ -8449,7 +8452,7 @@ public class ActivityManagerService extends IActivityManager.Stub t.traceEnd(); - if (bootingSystemUser) { + if (isBootingSystemUser) { t.traceBegin("sendUserStartBroadcast"); final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -8490,7 +8493,7 @@ public class ActivityManagerService extends IActivityManager.Stub mAtmInternal.resumeTopActivities(false /* scheduleIdle */); t.traceEnd(); - if (bootingSystemUser) { + if (isBootingSystemUser) { t.traceBegin("sendUserSwitchBroadcasts"); mUserController.sendUserSwitchBroadcasts(-1, currentUserId); t.traceEnd(); @@ -13506,9 +13509,19 @@ public class ActivityManagerService extends IActivityManager.Stub // Don't enforce the flag check if we're EITHER registering for only protected // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should // not be used generally, so we will be marking them as exported by default - final boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled( + boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled( DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid) && mConstants.mEnforceReceiverExportedFlagRequirement; + // STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of + // updating their receivers to be exempt from this requirement until their receivers + // are flagged. + if (requireExplicitFlagForDynamicReceivers) { + if ("com.google.android.apps.messaging".equals(callerPackage)) { + // Note, a versionCode check for this package is not performed because it could + // cause breakage with a subsequent update outside the system image. + requireExplicitFlagForDynamicReceivers = false; + } + } if (!onlyProtectedBroadcasts) { if (receiver == null && !explicitExportStateDefined) { // sticky broadcast, no flag specified (flag isn't required) @@ -18904,19 +18917,20 @@ public class ActivityManagerService extends IActivityManager.Stub } @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 +18940,22 @@ public class ActivityManagerService extends IActivityManager.Stub 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 +18963,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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 50515cd923d1..1f98aba5bbd7 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 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac 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 ba1c3b34d7a2..6abf6d8e9eea 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 @@ public final class AppRestrictionController { */ @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 @@ public final class AppRestrictionController { } 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 @@ public final class AppRestrictionController { 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 56909e35bbe3..dfac82cbe91b 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 class BroadcastConstants { */ 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 class BroadcastConstants { */ 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 47ca427be9ff..739d2777fe17 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 @@ class BroadcastProcessQueue { private boolean mProcessCached; private boolean mProcessInstrumented; + private boolean mProcessPersistent; private String mCachedToString; private String mCachedToShortString; @@ -323,8 +324,10 @@ class BroadcastProcessQueue { this.app = app; if (app != null) { setProcessInstrumented(app.getActiveInstrumentation() != null); + setProcessPersistent(app.isPersistent()); } else { setProcessInstrumented(false); + setProcessPersistent(false); } } @@ -352,6 +355,17 @@ class BroadcastProcessQueue { } /** + * 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 @@ class BroadcastProcessQueue { 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 @@ class BroadcastProcessQueue { REASON_MAX_PENDING, REASON_BLOCKED, REASON_INSTRUMENTED, + REASON_PERSISTENT, REASON_CONTAINS_FOREGROUND, REASON_CONTAINS_ORDERED, REASON_CONTAINS_ALARM, @@ -672,6 +688,7 @@ class BroadcastProcessQueue { 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 @@ class BroadcastProcessQueue { } 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 6793876942f6..008d95a51cd9 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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } case MSG_DELIVERY_TIMEOUT_SOFT: { synchronized (mService) { - deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj); + deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1); } return true; } @@ -746,9 +746,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } 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 2d7b0dc7b536..b98639e31d00 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -37,6 +37,7 @@ import android.provider.DeviceConfig.Properties; import android.provider.Settings; import android.text.TextUtils; import android.util.EventLog; +import android.util.IntArray; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -845,7 +846,7 @@ public final class CachedAppOptimizer { /** * Retrieves the free swap percentage. */ - static private native double getFreeSwapPercent(); + static native double getFreeSwapPercent(); /** * Retrieves the total used physical ZRAM @@ -2110,15 +2111,28 @@ public final class CachedAppOptimizer { @GuardedBy({"mAm"}) @Override - public void onBlockingFileLock(int pid) { + public void onBlockingFileLock(IntArray pids) { if (DEBUG_FREEZER) { - Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock"); + Slog.d(TAG_AM, "Blocking file lock found: " + pids); } synchronized (mProcLock) { + int pid = pids.get(0); ProcessRecord app = mFrozenProcesses.get(pid); + ProcessRecord pr; if (app != null) { - Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock"); - unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE); + for (int i = 1; i < pids.size(); i++) { + int blocked = pids.get(i); + synchronized (mAm.mPidsSelfLocked) { + pr = mAm.mPidsSelfLocked.get(blocked); + } + if (pr != null && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) { + Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks " + + pr.processName + " (" + blocked + ")"); + // Found at least one blocked non-cached process + unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE); + break; + } + } } } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 68e5a5df9562..eb2b7d493251 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 @@ public class OomAdjuster { 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 @@ public class OomAdjuster { 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 @@ public class OomAdjuster { ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED, true); + } else if (proactiveKillsEnabled) { + lruCachedApp = app; } break; case PROCESS_STATE_CACHED_EMPTY: @@ -1198,6 +1211,8 @@ public class OomAdjuster { ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, true); + } else if (proactiveKillsEnabled) { + lruCachedApp = app; } } break; @@ -1229,6 +1244,20 @@ public class OomAdjuster { } } + 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 d2ef479ed524..2ad2077d01bc 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.annotation.ElapsedRealtimeLong; 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 @@ import java.io.PrintWriter; * 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 @@ final class ProcessStateRecord { @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 @@ final class ProcessStateRecord { @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 92133274c0cc..4d86140816ea 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 @@ class UserController implements Handler.Callback { // 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 @@ class UserController implements Handler.Callback { 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 @@ class UserController implements Handler.Callback { } 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 @@ class UserController implements Handler.Callback { // 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 31d707da014f..64f2aa38e5b1 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 a58583c53321..7e00c3212c95 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } @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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } @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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } if (!skipProxyOperation) { - finishOperationUnchecked(proxyToken, code, proxyUid, resolvedProxyPackageName, + finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, proxyAttributionTag); } @@ -3195,7 +3191,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return null; } - finishOperationUnchecked(proxiedToken, code, proxiedUid, resolvedProxiedPackageName, + finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag); return null; @@ -6262,7 +6258,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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/broadcastradio/hal2/Mutable.java b/services/core/java/com/android/server/broadcastradio/hal2/Mutable.java index a9d80549f963..a6cf72c548c4 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 @@ final class Mutable<E> { 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 f6e90ef4197c..188c25d37fc3 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 @@ enum FrequencyBand { AM_LW, AM_MW, AM_SW, -}; +} class Utils { diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 81b56a310738..d2e572f2f979 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -18,6 +18,7 @@ package com.android.server.companion.virtual; import android.annotation.NonNull; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.VirtualDeviceParams; import java.util.Set; @@ -109,4 +110,14 @@ public abstract class VirtualDeviceManagerInternal { * Returns true if the {@code displayId} is owned by any virtual device */ public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId); + + /** + * Returns the device policy for the given virtual device and policy type. + * + * <p>In case the virtual device identifier is not valid, or there's no explicitly specified + * policy for that device and policy type, then + * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned. + */ + public abstract @VirtualDeviceParams.DevicePolicy int getDevicePolicy( + int deviceId, @VirtualDeviceParams.PolicyType int policyType); } 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 000000000000..2f42363ee7e4 --- /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/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 838bb53f0b86..d6f0fd070f94 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1703,6 +1703,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mTempBrightnessEvent.setRbcStrength(mCdsi != null ? mCdsi.getReduceBrightColorsStrength() : -1); mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor); + mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint); // Temporary is what we use during slider interactions. We avoid logging those so that // we don't spam logcat when the slider is being used. boolean tempToTempTransition = @@ -1713,12 +1714,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call || brightnessAdjustmentFlags != 0) { float lastBrightness = mLastBrightnessEvent.getBrightness(); mTempBrightnessEvent.setInitialBrightness(lastBrightness); - mTempBrightnessEvent.setFastAmbientLux( - mAutomaticBrightnessController == null - ? -1f : mAutomaticBrightnessController.getFastAmbientLux()); - mTempBrightnessEvent.setSlowAmbientLux( - mAutomaticBrightnessController == null - ? -1f : mAutomaticBrightnessController.getSlowAmbientLux()); mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness); mLastBrightnessEvent.copyFrom(mTempBrightnessEvent); BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent); @@ -2846,9 +2841,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED, convertToNits(event.getInitialBrightness()), convertToNits(event.getBrightness()), - event.getSlowAmbientLux(), + event.getLux(), event.getPhysicalDisplayId(), - event.isShortTermModelActive(), + event.wasShortTermModelActive(), appliedLowPowerMode, appliedRbcStrength, appliedHbmMaxNits, diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index bf0b388fdb56..300b5895ca98 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -1568,6 +1568,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mTempBrightnessEvent.setRbcStrength(mCdsi != null ? mCdsi.getReduceBrightColorsStrength() : -1); mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor); + mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint); // Temporary is what we use during slider interactions. We avoid logging those so that // we don't spam logcat when the slider is being used. boolean tempToTempTransition = @@ -1578,12 +1579,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal || brightnessAdjustmentFlags != 0) { float lastBrightness = mLastBrightnessEvent.getBrightness(); mTempBrightnessEvent.setInitialBrightness(lastBrightness); - mTempBrightnessEvent.setFastAmbientLux( - mAutomaticBrightnessController == null - ? -1f : mAutomaticBrightnessController.getFastAmbientLux()); - mTempBrightnessEvent.setSlowAmbientLux( - mAutomaticBrightnessController == null - ? -1f : mAutomaticBrightnessController.getSlowAmbientLux()); mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness); mLastBrightnessEvent.copyFrom(mTempBrightnessEvent); BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent); @@ -2517,9 +2512,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED, convertToNits(event.getInitialBrightness()), convertToNits(event.getBrightness()), - event.getSlowAmbientLux(), + event.getLux(), event.getPhysicalDisplayId(), - event.isShortTermModelActive(), + event.wasShortTermModelActive(), appliedLowPowerMode, appliedRbcStrength, appliedHbmMaxNits, @@ -2694,7 +2689,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal 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 5b64dd5ff319..a3433d9570a4 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.util.TimeUtils; 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 @@ import java.io.PrintWriter; * 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 @@ public final class DisplayPowerProximityStateController { 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 final class DisplayPowerProximityStateController { 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 @@ public final class DisplayPowerProximityStateController { 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 @@ public final class DisplayPowerProximityStateController { + mSkipRampBecauseOfProximityChangeToNegative); } - private void ignoreProximitySensorUntilChangedInternal() { + void ignoreProximitySensorUntilChangedInternal() { if (!mIgnoreProximityUntilChanged && mProximity == PROXIMITY_POSITIVE) { // Only ignore if it is still reporting positive (near) @@ -414,7 +426,7 @@ public final class DisplayPowerProximityStateController { 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 @@ public final class DisplayPowerProximityStateController { } } + @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/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java index e3fa6220edf4..f19852b3eff5 100644 --- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java +++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java @@ -39,8 +39,6 @@ public final class BrightnessEvent { private String mPhysicalDisplayId; private long mTime; private float mLux; - private float mFastAmbientLux; - private float mSlowAmbientLux; private float mPreThresholdLux; private float mInitialBrightness; private float mBrightness; @@ -51,6 +49,7 @@ public final class BrightnessEvent { private int mRbcStrength; private float mThermalMax; private float mPowerFactor; + private boolean mWasShortTermModelActive; private int mFlags; private int mAdjustmentFlags; private boolean mAutomaticBrightnessEnabled; @@ -76,8 +75,6 @@ public final class BrightnessEvent { mTime = that.getTime(); // Lux values mLux = that.getLux(); - mFastAmbientLux = that.getFastAmbientLux(); - mSlowAmbientLux = that.getSlowAmbientLux(); mPreThresholdLux = that.getPreThresholdLux(); // Brightness values mInitialBrightness = that.getInitialBrightness(); @@ -90,6 +87,7 @@ public final class BrightnessEvent { mRbcStrength = that.getRbcStrength(); mThermalMax = that.getThermalMax(); mPowerFactor = that.getPowerFactor(); + mWasShortTermModelActive = that.wasShortTermModelActive(); mFlags = that.getFlags(); mAdjustmentFlags = that.getAdjustmentFlags(); // Auto-brightness setting @@ -105,8 +103,6 @@ public final class BrightnessEvent { mPhysicalDisplayId = ""; // Lux values mLux = 0; - mFastAmbientLux = 0; - mSlowAmbientLux = 0; mPreThresholdLux = 0; // Brightness values mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; @@ -119,6 +115,7 @@ public final class BrightnessEvent { mRbcStrength = 0; mThermalMax = PowerManager.BRIGHTNESS_MAX; mPowerFactor = 1f; + mWasShortTermModelActive = false; mFlags = 0; mAdjustmentFlags = 0; // Auto-brightness setting @@ -140,10 +137,6 @@ public final class BrightnessEvent { && mDisplayId == that.mDisplayId && mPhysicalDisplayId.equals(that.mPhysicalDisplayId) && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux) - && Float.floatToRawIntBits(mFastAmbientLux) - == Float.floatToRawIntBits(that.mFastAmbientLux) - && Float.floatToRawIntBits(mSlowAmbientLux) - == Float.floatToRawIntBits(that.mSlowAmbientLux) && Float.floatToRawIntBits(mPreThresholdLux) == Float.floatToRawIntBits(that.mPreThresholdLux) && Float.floatToRawIntBits(mInitialBrightness) @@ -161,6 +154,7 @@ public final class BrightnessEvent { == Float.floatToRawIntBits(that.mThermalMax) && Float.floatToRawIntBits(mPowerFactor) == Float.floatToRawIntBits(that.mPowerFactor) + && mWasShortTermModelActive == that.mWasShortTermModelActive && mFlags == that.mFlags && mAdjustmentFlags == that.mAdjustmentFlags && mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled; @@ -182,14 +176,13 @@ public final class BrightnessEvent { + ", rcmdBrt=" + mRecommendedBrightness + ", preBrt=" + mPreThresholdBrightness + ", lux=" + mLux - + ", fastLux=" + mFastAmbientLux - + ", slowLux=" + mSlowAmbientLux + ", preLux=" + mPreThresholdLux + ", hbmMax=" + mHbmMax + ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode) + ", rbcStrength=" + mRbcStrength + ", thrmMax=" + mThermalMax + ", powerFactor=" + mPowerFactor + + ", wasShortTermModelActive=" + mWasShortTermModelActive + ", flags=" + flagsToString() + ", reason=" + mReason.toString(mAdjustmentFlags) + ", autoBrightness=" + mAutomaticBrightnessEnabled; @@ -240,22 +233,6 @@ public final class BrightnessEvent { this.mLux = lux; } - public float getFastAmbientLux() { - return mFastAmbientLux; - } - - public void setFastAmbientLux(float mFastAmbientLux) { - this.mFastAmbientLux = mFastAmbientLux; - } - - public float getSlowAmbientLux() { - return mSlowAmbientLux; - } - - public void setSlowAmbientLux(float mSlowAmbientLux) { - this.mSlowAmbientLux = mSlowAmbientLux; - } - public float getPreThresholdLux() { return mPreThresholdLux; } @@ -344,6 +321,20 @@ public final class BrightnessEvent { return (mFlags & FLAG_LOW_POWER_MODE) != 0; } + /** + * Set whether the short term model was active before the brightness event. + */ + public boolean setWasShortTermModelActive(boolean wasShortTermModelActive) { + return this.mWasShortTermModelActive = wasShortTermModelActive; + } + + /** + * Returns whether the short term model was active before the brightness event. + */ + public boolean wasShortTermModelActive() { + return this.mWasShortTermModelActive; + } + public int getFlags() { return mFlags; } @@ -352,10 +343,6 @@ public final class BrightnessEvent { this.mFlags = flags; } - public boolean isShortTermModelActive() { - return (mFlags & FLAG_USER_SET) != 0; - } - public int getAdjustmentFlags() { return mAdjustmentFlags; } diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java index 573bf19023fa..5646e1b9a9ef 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_1; 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.io.InputStream; 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 @@ final class HdmiUtils { 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/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index cb615a9729f6..8497dfb58ba6 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1814,8 +1814,8 @@ public class InputManagerService extends IInputManager.Stub */ 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 */); } 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 4f6d0d472d26..e46b8c0c06b3 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 @@ import java.util.concurrent.atomic.AtomicInteger; @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 b8abd98456e4..77cd67304729 100644 --- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java +++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java @@ -296,26 +296,24 @@ public class GnssConfiguration { 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/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 92b685c78d98..d02faad1956e 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 @@ public class LockSettingsService extends ILockSettings.Stub { // 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 @@ public class LockSettingsService extends ILockSettings.Stub { @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 @@ public class LockSettingsService extends ILockSettings.Stub { * <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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { setString("migrated_all_users_to_sp_and_bound_ce", "true", 0); } - mBootComplete = true; + mThirdPartyAppsStarted = true; } } @@ -2304,14 +2299,14 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { * 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 @@ public class LockSettingsService extends ILockSettings.Stub { pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size()); synchronized (mUserCreationAndRemovalLock) { - pw.println("BootComplete: " + mBootComplete); + pw.println("ThirdPartyAppsStarted: " + mThirdPartyAppsStarted); } } @@ -3317,6 +3311,11 @@ public class LockSettingsService extends ILockSettings.Stub { 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/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index dcdb881df12b..72ce38b72340 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 @@ final class MediaButtonReceiverHolder { 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 c3a555862d01..90135ad7684b 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 @@ public class NotificationManagerService extends SystemService { } 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 @@ public class NotificationManagerService extends SystemService { 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 bbbf4520b791..1bbcc839ccd1 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 @@ public class PreferencesHelper implements RankingConfig { 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 f2c78ad1e82c..4b2c88c2bf06 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 class ZenModeHelper { 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 @@ public class ZenModeHelper { } } - 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 5e98cc06d84b..978e43633b92 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.AssetManager; 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 @@ final class OverlayManagerShellCommand extends ShellCommand { 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 @@ final class OverlayManagerShellCommand extends ShellCommand { 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 @@ final class OverlayManagerShellCommand extends ShellCommand { filename = getNextArgRequired(); break; case "--config": - configuration = getNextArgRequired(); + config = getNextArgRequired(); break; default: err.println("Error: Unknown option: " + opt); @@ -312,7 +314,9 @@ final class OverlayManagerShellCommand extends ShellCommand { 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 @@ final class OverlayManagerShellCommand extends ShellCommand { 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 @@ final class OverlayManagerShellCommand extends ShellCommand { 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 @@ final class OverlayManagerShellCommand extends ShellCommand { } 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 @@ final class OverlayManagerShellCommand extends ShellCommand { } 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 3b676c65db75..b4792c65bce5 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.pkg.PackageStateInternal; 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 @@ public abstract class AppsFilterBase implements AppsFilterSnapshot { @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 2e67bf2fcdf7..c97711b3aa80 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.Snappable; 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 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, 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 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, 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 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, 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 7daa0b94000c..483fa8ade150 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.ParsedComponent; 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 @@ final class AppsFilterUtils { /** 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 @@ final class AppsFilterUtils { } 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 @@ final class AppsFilterUtils { 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 @@ final class AppsFilterUtils { } 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 @@ final class AppsFilterUtils { } 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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 2a0b44d43bd7..70bd24ce20a2 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2619,11 +2619,29 @@ final class InstallPackageHelper { } } + 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(); @@ -2631,13 +2649,9 @@ final class InstallPackageHelper { 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) { @@ -2650,8 +2664,8 @@ final class InstallPackageHelper { 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*/, diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java index 3c863d080d79..4cac1151136d 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 @@ final class PackageRemovedInfo { } 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 @@ final class PackageRemovedInfo { 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 3b306a850b64..b310c62aafb4 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/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 91558308e305..9dafcceefdd0 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.content.pm.UserProperties; 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 abstract class UserManagerInternal { 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 class UserManagerInternal { 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 class UserManagerInternal { */ 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 1c50f387a610..4a4a231f4fba 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -265,7 +265,7 @@ public class UserManagerService extends IUserManager.Stub { @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 @@ -3355,6 +3355,7 @@ public class UserManagerService extends IUserManager.Stub { 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; @@ -3647,6 +3648,22 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ -3776,6 +3793,21 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ -6802,15 +6834,14 @@ public class UserManagerService extends IUserManager.Stub { } @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/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 871420a92f44..8fb5773706c5 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -262,7 +262,8 @@ public final class UserTypeFactory { 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); } /** diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java index bd81062b0ff1..cbf7dfe77ca6 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_NULL; 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 android.view.Display; 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 @@ public final class UserVisibilityMediator implements Dumpable { 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 @@ public final class UserVisibilityMediator implements Dumpable { } /** - * 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 @@ public final class UserVisibilityMediator implements Dumpable { 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 @@ public final class UserVisibilityMediator implements Dumpable { 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 @@ public final class UserVisibilityMediator implements Dumpable { 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 @@ public final class UserVisibilityMediator implements Dumpable { } /** - * TODO(b/244644281): merge with unassignUserFromDisplay() or add javadoc (and unit tests) - */ - 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)}. + * @deprecated - see comment inside {@link #startUser(int, int, boolean, 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,22 +253,25 @@ public final class UserVisibilityMediator implements Dumpable { } /** - * 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. + synchronized (mLock) { if (DBG) { - Slogf.d(TAG, "ignoring when device doesn't support MUMD"); + Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId, + mStartedProfileGroupIds); } - return; - } + mStartedProfileGroupIds.delete(userId); - synchronized (mLock) { + 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 @@ public final class UserVisibilityMediator implements Dumpable { 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 @@ public final class UserVisibilityMediator implements Dumpable { 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 @@ public final class UserVisibilityMediator implements Dumpable { } @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 5ba209d14933..9bca15519206 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 @@ public final class DexoptUtils { * 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 28f86edaa724..83e17a5a9075 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.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -165,6 +166,11 @@ final class DefaultPermissionGrantPolicy { 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); @@ -787,6 +793,8 @@ final class DefaultPermissionGrantPolicy { 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); } } @@ -1932,7 +1940,7 @@ final class DefaultPermissionGrantPolicy { 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 5f9ab959f80a..9ec63fcf7125 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.ParceledListSlice; 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (resolvedPackageName == null) { return; } - appOpsManager.finishOp(accessorSource.getToken(), op, + appOpsManager.finishOp(attributionSourceState.token, op, accessorSource.getUid(), resolvedPackageName, accessorSource.getAttributionTag()); } else { @@ -1109,8 +1110,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { && 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @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 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (selfAccess) { try { startedOpResult = appOpsManager.startOpNoThrow( - resolvedAttributionSource.getToken(), startedOp, + chainStartToken, startedOp, resolvedAttributionSource.getUid(), resolvedAttributionSource.getPackageName(), /*startIfModeDefault*/ false, @@ -1575,14 +1580,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { + " 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 a6d148c824c9..383249f41f39 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.Log; 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 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat } @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 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat } @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 98b5c1ba639b..3aa333aacdb6 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { @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/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 916df89d6521..0c5e451a93cc 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -11507,6 +11507,9 @@ public class BatteryStatsImpl extends BatteryStats { mHistory.reset(); + // Store the empty state to disk to ensure consistency + writeSyncLocked(); + // Flush external data, gathering snapshots, but don't process it since it is pre-reset data mIgnoreNextExternalStats = true; mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ON_RESET); diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java index d9f504e5f270..ac970389b8b1 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 @@ class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements 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 8d106f7d7fa4..5801920864b4 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 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { @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 f52f0b7e308f..2c8fd967ae8a 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 @@ public class TvInteractiveAppManagerService extends SystemService { } 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 @@ public class TvInteractiveAppManagerService extends SystemService { } @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 b8cd8d9f3d71..c875f4af9c8b 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - 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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index e6d94920c00e..a857d900d771 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 @@ class ActivityStarter { ? 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 @@ class ActivityStarter { // 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/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 9398bbec4a63..f5da4c80ba97 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 @@ package com.android.server.wm; 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 @@ class BackNavigationController { } 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/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index e0644b61772a..b735b3083cbf 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 @@ class DisplayWindowSettings { 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 @@ class DisplayWindowSettings { 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 @@ class DisplayWindowSettings { 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 @@ class DisplayWindowSettings { 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/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java index 4c18d0b8a0dc..56edde09f747 100644 --- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java +++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java @@ -57,7 +57,6 @@ import android.view.WindowInsets; import android.view.WindowInsets.Type; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.Button; @@ -109,18 +108,13 @@ public class ImmersiveModeConfirmation { mContext = display.getDisplayId() == DEFAULT_DISPLAY ? uiContext : uiContext.createDisplayContext(display); mHandler = new H(looper); - mShowDelayMs = getNavBarExitDuration() * 3; + mShowDelayMs = context.getResources().getInteger(R.integer.dock_enter_exit_duration) * 3L; mPanicThresholdMs = context.getResources() .getInteger(R.integer.config_immersive_mode_confirmation_panic); mVrModeEnabled = vrModeEnabled; mCanSystemBarsBeShownByUser = canSystemBarsBeShownByUser; } - private long getNavBarExitDuration() { - Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit); - return exit != null ? exit.getDuration() : 0; - } - static boolean loadSetting(int currentUserId, Context context) { final boolean wasConfirmed = sConfirmed; sConfirmed = false; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index cdb332123fe2..dfb61a8cf989 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4291,13 +4291,14 @@ class Task extends TaskFragment { } /** - * @return true if the task is currently focused. + * @return {@code true} if the task is currently focused or one of its children is focused. */ boolean isFocused() { if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) { return false; } - return mDisplayContent.mFocusedApp.getTask() == this; + final Task focusedTask = mDisplayContent.mFocusedApp.getTask(); + return focusedTask == this || (focusedTask != null && focusedTask.getParent() == this); } /** @@ -4317,6 +4318,8 @@ class Task extends TaskFragment { */ void onAppFocusChanged(boolean hasFocus) { dispatchTaskInfoChangedIfNeeded(false /* force */); + final Task parentTask = getParent().asTask(); + if (parentTask != null) parentTask.dispatchTaskInfoChangedIfNeeded(false /* force */); } void onPictureInPictureParamsChanged() { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 872542af0075..dfcad48046b0 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { @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 @@ class TaskFragment extends WindowContainer<WindowContainer> { } /** 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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 37bef3a833ee..25df5112e395 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 @@ class TransitionController { 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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 3419207eb14f..df343db8b3bf 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -426,7 +426,7 @@ public class WindowManagerService extends IWindowManager.Stub * @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 diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java index 6f2930c46b12..1b70d1d4a8b6 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.THREAD_PRIORITY_TOP_APP_BOOST; 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 741537629dcb..de12a4ef7739 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.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub @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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub 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; - } - - 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); + 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); } - - 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 5c5c70334e65..86dd0b5452b5 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1540,10 +1540,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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, " @@ -4160,6 +4161,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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(); } diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java index 46e59a9f10dd..c3329795d4e2 100644 --- a/services/java/com/android/server/BootUserInitializer.java +++ b/services/java/com/android/server/BootUserInitializer.java @@ -83,9 +83,10 @@ final class BootUserInitializer { 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 fe2d0be0fe36..d406e300a0eb 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.ConcurrentUtils; 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 @@ public final class SystemServer implements Dumpable { 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 eab3b770a94a..292320e498a3 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 com.android.server.people.data.DataManager; 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 @@ public class PeopleService extends SystemService { @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/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 73b1907c9f42..681bfcf68cc3 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -56,6 +56,7 @@ android_test { "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 d78f6d83d0ab..24e5175ecbdc 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 @@ public class GameManagerServiceTests { } @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/backup/BackupAndRestoreFeatureFlagsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java new file mode 100644 index 000000000000..f53599731e10 --- /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/backup/OWNERS b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS new file mode 100644 index 000000000000..d99779e3d9da --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS @@ -0,0 +1 @@ +include /services/backup/OWNERS 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 dc49a94eb5c5..4c28c51f7e62 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 @@ public final class DisplayPowerController2Test { 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 000000000000..6e91b249b490 --- /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 923c3e385b5e..9be370fe3045 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 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator 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 7af5f5d6b2fe..7abdd9e7bbaf 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 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator 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 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator 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 7b20092b503a..e8be97db717d 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.os.UserHandle.USER_NULL; 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 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @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 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { .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 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { private void assertCurrentUser(@UserIdInt int userId) { assertWithMessage("mediator.getCurrentUserId()").that(mMediator.getCurrentUserId()) .isEqualTo(userId); + if (userId != INITIAL_CURRENT_USER_ID) { + assertUserIsStarted(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 assertIsStartedProfile(@UserIdInt int userId) { + 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 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { 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 6551bdebe06e..6349b21183fe 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 9052f58b2a8b..9c7ce835632a 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/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 80cee50cef79..a49214f9b4f5 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_CURRENT_MSG; 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.FgThread; 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { @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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { @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 @@ public class UserControllerTest { // 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { @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 @@ public class UserControllerTest { 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 ea746d1f4fd3..faad961510d6 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.hardware.camera2.CameraMetadata; 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 @@ public class CameraServiceProxyTest { /*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/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 5fda3d6b36ab..c715a217f221 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -16,6 +16,9 @@ package com.android.server.companion.virtual; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS; import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES; import static com.google.common.truth.Truth.assertThat; @@ -44,6 +47,7 @@ import android.app.WindowConfiguration; import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDeviceActivityListener; +import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; @@ -240,6 +244,55 @@ public class VirtualDeviceManagerServiceTest { mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1, mInputController, (int associationId) -> {}, mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params); + mVdms.addVirtualDevice(mDeviceImpl); + } + + @Test + public void getDevicePolicy_invalidDeviceId_returnsDefault() { + assertThat( + mLocalService.getDevicePolicy( + VirtualDeviceManager.INVALID_DEVICE_ID, POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); + } + + @Test + public void getDevicePolicy_defaultDeviceId_returnsDefault() { + assertThat( + mLocalService.getDevicePolicy( + VirtualDeviceManager.DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); + } + + @Test + public void getDevicePolicy_nonExistentDeviceId_returnsDefault() { + assertThat( + mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); + } + + @Test + public void getDevicePolicy_unspecifiedPolicy_returnsDefault() { + assertThat( + mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); + } + + @Test + public void getDevicePolicy_returnsCustom() { + VirtualDeviceParams params = new VirtualDeviceParams + .Builder() + .setBlockedActivities(getBlockedActivities()) + .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM) + .build(); + mDeviceImpl = new VirtualDeviceImpl(mContext, + mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1, + mInputController, (int associationId) -> {}, mPendingTrampolineCallback, + mActivityListener, mRunningAppsChangedCallback, params); + mVdms.addVirtualDevice(mDeviceImpl); + + assertThat( + mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_CUSTOM); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java index 77f1e24ee771..036b6df92ef9 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java @@ -37,6 +37,8 @@ public class VirtualDeviceParamsTest { VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder() .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456))) + .addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS, + VirtualDeviceParams.DEVICE_POLICY_CUSTOM) .build(); Parcel parcel = Parcel.obtain(); originalParams.writeToParcel(parcel, 0); @@ -47,5 +49,7 @@ public class VirtualDeviceParamsTest { assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED); assertThat(params.getUsersWithMatchingAccounts()) .containsExactly(UserHandle.of(123), UserHandle.of(456)); + assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS)) + .isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM); } } 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 df672c9f248d..2c4fe536b75c 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 @@ public class AmbientBrightnessStatsTrackerTest { @Override public LocalDate getLocalDate() { - return LocalDate.from(mLocalDate); + return mLocalDate; } } 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 0454587bfefe..a419b3f80aac 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 final class TestUtils { } } + 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 @@ public final class TestUtils { 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/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java index fabf535b729a..d332b3081fdf 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java +++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java @@ -39,8 +39,6 @@ public final class BrightnessEventTest { getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER)); mBrightnessEvent.setPhysicalDisplayId("test"); mBrightnessEvent.setLux(100.0f); - mBrightnessEvent.setFastAmbientLux(90.0f); - mBrightnessEvent.setSlowAmbientLux(85.0f); mBrightnessEvent.setPreThresholdLux(150.0f); mBrightnessEvent.setTime(System.currentTimeMillis()); mBrightnessEvent.setInitialBrightness(25.0f); @@ -50,6 +48,7 @@ public final class BrightnessEventTest { mBrightnessEvent.setRbcStrength(-1); mBrightnessEvent.setThermalMax(0.65f); mBrightnessEvent.setPowerFactor(0.2f); + mBrightnessEvent.setWasShortTermModelActive(true); mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); mBrightnessEvent.setFlags(0); mBrightnessEvent.setAdjustmentFlags(0); @@ -69,9 +68,9 @@ public final class BrightnessEventTest { String actualString = mBrightnessEvent.toString(false); String expectedString = "BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6," - + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62," - + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze" - + " [ low_pwr ], autoBrightness=true"; + + " preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off, rbcStrength=-1," + + " thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true, flags=," + + " reason=doze [ low_pwr ], autoBrightness=true"; assertEquals(expectedString, actualString); } 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 9092ec325946..0884b784ac73 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 @@ public class InputMethodSubtypeSwitchingControllerTest { 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/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index 164161e34b6f..dc47b5eaea0e 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -8,7 +8,6 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -20,8 +19,6 @@ import android.content.Context; import android.content.pm.PackageManagerInternal; import android.net.NetworkRequest; import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; import android.os.PersistableBundle; import android.os.SystemClock; import android.test.RenamingDelegatingContext; @@ -32,7 +29,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.HexDump; import com.android.server.LocalServices; import com.android.server.job.JobStore.JobSet; import com.android.server.job.controllers.JobStatus; @@ -44,7 +40,6 @@ import org.junit.runner.RunWith; import java.time.Clock; import java.time.ZoneOffset; -import java.util.Arrays; import java.util.Iterator; /** @@ -143,15 +138,8 @@ public class JobStoreTest { assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size()); final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0); - assertTasksEqual(task, loadedTaskStatus.getJob()); + assertJobsEqual(ts, loadedTaskStatus); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts)); - assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid()); - assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION, - loadedTaskStatus.getInternalFlags()); - compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", - ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime()); - compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", - ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed()); } @Test @@ -202,19 +190,10 @@ public class JobStoreTest { loaded2 = tmp; } - assertTasksEqual(task1, loaded1.getJob()); - assertTasksEqual(task2, loaded2.getJob()); + assertJobsEqual(taskStatus1, loaded1); + assertJobsEqual(taskStatus2, loaded2); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1)); assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2)); - // Check that the loaded task has the correct runtimes. - compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", - taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime()); - compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", - taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed()); - compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", - taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime()); - compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", - taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed()); } @Test @@ -240,7 +219,7 @@ public class JobStoreTest { mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); - assertTasksEqual(task, loaded.getJob()); + assertJobsEqual(taskStatus, loaded); } @Test @@ -544,71 +523,30 @@ public class JobStoreTest { final JobSet jobStatusSet = new JobSet(); mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); final JobStatus second = jobStatusSet.getAllJobs().iterator().next(); - assertTasksEqual(first.getJob(), second.getJob()); + assertJobsEqual(first, second); } /** - * Helper function to throw an error if the provided task and TaskStatus objects are not equal. + * Helper function to throw an error if the provided JobStatus objects are not equal. */ - private void assertTasksEqual(JobInfo first, JobInfo second) { - assertEquals("Different task ids.", first.getId(), second.getId()); - assertEquals("Different components.", first.getService(), second.getService()); - assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic()); - assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis()); - assertEquals("Different inital backoff.", first.getInitialBackoffMillis(), - second.getInitialBackoffMillis()); - assertEquals("Different backoff policy.", first.getBackoffPolicy(), - second.getBackoffPolicy()); - - assertEquals("Invalid charging constraint.", first.isRequireCharging(), - second.isRequireCharging()); - assertEquals("Invalid battery not low constraint.", first.isRequireBatteryNotLow(), - second.isRequireBatteryNotLow()); - assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(), - second.isRequireDeviceIdle()); - assertEquals("Invalid network type.", - first.getNetworkType(), second.getNetworkType()); - assertEquals("Invalid network.", - first.getRequiredNetwork(), second.getRequiredNetwork()); - assertEquals("Download bytes don't match", - first.getEstimatedNetworkDownloadBytes(), - second.getEstimatedNetworkDownloadBytes()); - assertEquals("Upload bytes don't match", - first.getEstimatedNetworkUploadBytes(), - second.getEstimatedNetworkUploadBytes()); - assertEquals("Minimum chunk bytes don't match", - first.getMinimumNetworkChunkBytes(), - second.getMinimumNetworkChunkBytes()); - assertEquals("Invalid deadline constraint.", - first.hasLateConstraint(), - second.hasLateConstraint()); - assertEquals("Invalid delay constraint.", - first.hasEarlyConstraint(), - second.hasEarlyConstraint()); - assertEquals("Extras don't match", - first.getExtras().toString(), second.getExtras().toString()); - assertEquals("Transient xtras don't match", - first.getTransientExtras().toString(), second.getTransientExtras().toString()); - - // Since people can forget to add tests here for new fields, do one last - // validity check based on bits-on-wire equality. - final byte[] firstBytes = marshall(first); - final byte[] secondBytes = marshall(second); - if (!Arrays.equals(firstBytes, secondBytes)) { - Log.w(TAG, "First: " + HexDump.dumpHexString(firstBytes)); - Log.w(TAG, "Second: " + HexDump.dumpHexString(secondBytes)); - fail("Raw JobInfo aren't equal; see logs for details"); - } - } + private void assertJobsEqual(JobStatus expected, JobStatus actual) { + assertEquals(expected.getJob(), actual.getJob()); - private static byte[] marshall(Parcelable p) { - final Parcel parcel = Parcel.obtain(); - try { - p.writeToParcel(parcel, 0); - return parcel.marshall(); - } finally { - parcel.recycle(); - } + // Source UID isn't persisted, but the rest of the app info is. + assertEquals("Source package not equal", + expected.getSourcePackageName(), actual.getSourcePackageName()); + assertEquals("Source user not equal", expected.getSourceUserId(), actual.getSourceUserId()); + assertEquals("Calling UID not equal", expected.getUid(), actual.getUid()); + assertEquals("Calling user not equal", expected.getUserId(), actual.getUserId()); + + assertEquals("Internal flags not equal", + expected.getInternalFlags(), actual.getInternalFlags()); + + // Check that the loaded task has the correct runtimes. + compareTimestampsSubjectToIoLatency("Early run-times not the same after read.", + expected.getEarliestRunTime(), actual.getEarliestRunTime()); + compareTimestampsSubjectToIoLatency("Late run-times not the same after read.", + expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed()); } /** @@ -623,5 +561,4 @@ public class JobStoreTest { } private static class StubClass {} - } 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 1e855a9819ac..1eb4fa5439e6 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 @@ public class PasswordSlotManagerTestable extends PasswordSlotManager { } 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 000000000000..1c4ee691fc77 --- /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 000000000000..55ffde223374 --- /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/test-apps/FakeMediaApp/Android.bp b/services/tests/servicestests/test-apps/FakeMediaApp/Android.bp new file mode 100644 index 000000000000..a4041b79e0ff --- /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 000000000000..c08ee7a43976 --- /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 000000000000..55ffde223374 --- /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 000000000000..41f0cf52301d --- /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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 668345d51c1b..d54d1fed1016 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 0f93598d0f72..b64b28137f80 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { @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 @@ public class PreferencesHelperTest extends UiServiceTestCase { @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 8cf74fbf88b7..61a6985d473e 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 @@ import java.util.Set; 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 @@ public class TestableNotificationManagerService extends NotificationManagerServi } @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 ba61980928bc..49edde585430 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 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @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/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 07dba003f504..fc1989e84ac5 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 @@ public class ActivityStarterTests extends WindowTestsBase { } @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/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java index 846a5066e036..5e1fae095db8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java @@ -192,16 +192,17 @@ public class SurfaceSyncGroupTest { } private static class SyncTarget implements SurfaceSyncGroup.SyncTarget { - private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback; + private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback; @Override - public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) { - mSyncBufferCallback = syncBufferCallback; + public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup, + SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) { + mTransactionReadyCallback = transactionReadyCallback; } void onBufferReady() { SurfaceControl.Transaction t = new StubTransaction(); - mSyncBufferCallback.onBufferReady(t); + mTransactionReadyCallback.onTransactionReady(t); } } } 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 83f17897eb62..3ff2c0e0d024 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 @@ public class TaskFragmentTest extends WindowTestsBase { 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 @@ public class TaskFragmentTest extends WindowTestsBase { mTaskFragment.setBounds(endBounds); - verify(mTaskFragment, never()).initializeChangeTransition(any()); + assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds)); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 66bf78b6a13b..0b6cea28e86b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -433,6 +433,24 @@ public class TaskTests extends WindowTestsBase { } @Test + public void testPropagateFocusedStateToRootTask() { + final Task rootTask = createTask(mDefaultDisplay); + final Task leafTask = createTaskInRootTask(rootTask, 0 /* userId */); + + final ActivityRecord activity = createActivityRecord(leafTask); + + leafTask.getDisplayContent().setFocusedApp(activity); + + assertTrue(leafTask.getTaskInfo().isFocused); + assertTrue(rootTask.getTaskInfo().isFocused); + + leafTask.getDisplayContent().setFocusedApp(null); + + assertFalse(leafTask.getTaskInfo().isFocused); + assertFalse(rootTask.getTaskInfo().isFocused); + } + + @Test public void testReturnsToHomeRootTask() throws Exception { final Task task = createTask(1); spyOn(task); 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 6bd341210cad..1b888f629251 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.InsetsSource; 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 @@ public class WindowStateTests extends WindowTestsBase { } @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; diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 86f877fcd531..72f6cc3649a7 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 @@ public class UsbService extends IUsbManager.Stub { } @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 @@ public class UsbService extends IUsbManager.Stub { } @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 df637950899b..7a41b50e26bd 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 @@ public final class UsbVCInputTerminal extends UsbVCInterface { // 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 4aa8ca22cc4e..32275a60644c 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 @@ public final class UsbVCOutputTerminal extends UsbVCInterface { 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 5ce842e82598..0692066e1dee 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 @@ public final class UsbVCProcessingUnit extends UsbVCInterface { 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 8e9b0d886389..604dd66905da 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 @@ public final class UsbVCSelectorUnit extends UsbVCInterface { 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 f9211181c924..d5eea1f3ff35 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 @@ final class HotwordAudioStreamManager { 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 0721c281f15c..3e49aedbb41e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -1321,4 +1321,4 @@ final class HotwordDetectionConnection { 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 5d1901d96bcd..0a660b0dd44c 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 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne 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 f8bc499e87aa..763024fac838 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 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } } }; -}; +} diff --git a/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/common/com/google/android/mms/pdu/EncodedStringValue.java index 8b01cb3c4405..2787d83a99a3 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 @@ public class EncodedStringValue implements Cloneable { */ @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 936fad51f095..f38b902f7531 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8698,6 +8698,15 @@ public class CarrierConfigManager { 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 @@ public class CarrierConfigManager { 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 23835a7732cf..b83b400cb811 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -1620,29 +1620,26 @@ public final class DataFailCause { // 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/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java index 383561ad01a0..c352f2bc3f83 100644 --- a/telephony/java/android/telephony/DomainSelectionService.java +++ b/telephony/java/android/telephony/DomainSelectionService.java @@ -28,7 +28,6 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; -import android.telephony.AccessNetworkConstants.RadioAccessNetworkType; import android.telephony.Annotation.DisconnectCauses; import android.telephony.Annotation.PreciseDisconnectCauses; import android.telephony.ims.ImsReasonInfo; @@ -703,9 +702,9 @@ public class DomainSelectionService extends Service { } @Override - public void onDomainSelected(@RadioAccessNetworkType int accessNetworkType) { + public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) { try { - mCallback.onDomainSelected(accessNetworkType); + mCallback.onDomainSelected(domain); } catch (Exception e) { Rlog.e(TAG, "onDomainSelected e=" + e); } @@ -835,7 +834,14 @@ public class DomainSelectionService extends Service { return Runnable::run; } - private @NonNull Executor getCachedExecutor() { + /** + * Gets the {@link Executor} which executes methods of this service. + * This method should be private when this service is implemented in a separated process + * other than telephony framework. + * @return {@link Executor} instance. + * @hide + */ + public @NonNull Executor getCachedExecutor() { synchronized (mExecutorLock) { if (mExecutor == null) { Executor e = getExecutor(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index d473c6ab794a..541573cad3ec 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -14957,21 +14957,132 @@ public class TelephonyManager { * @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; } /** diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java index 489a589d2b62..b3682caa4eb3 100644 --- a/telephony/java/android/telephony/WwanSelectorCallback.java +++ b/telephony/java/android/telephony/WwanSelectorCallback.java @@ -18,7 +18,6 @@ package android.telephony; import android.annotation.NonNull; import android.os.CancellationSignal; -import android.telephony.AccessNetworkConstants.RadioAccessNetworkType; import android.telephony.DomainSelectionService.EmergencyScanType; import java.util.List; @@ -46,7 +45,7 @@ public interface WwanSelectorCallback { * Notifies the FW that the domain has been selected. After this method is called, * this interface can be discarded. * - * @param accessNetworkType the selected network type. + * @param domain The selected domain. */ - void onDomainSelected(@RadioAccessNetworkType int accessNetworkType); + void onDomainSelected(@NetworkRegistrationInfo.Domain int domain); } diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java index 0ab7b61bd73d..a0d9c1bdf1ed 100644 --- a/telephony/java/android/telephony/data/QosBearerFilter.java +++ b/telephony/java/android/telephony/data/QosBearerFilter.java @@ -130,6 +130,10 @@ public final class QosBearerFilter implements Parcelable { return precedence; } + public int getProtocol() { + return protocol; + } + public static class PortRange implements Parcelable { int start; int end; diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index a3cbb4a436bc..33c86d8299a0 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -50,7 +50,6 @@ import com.android.internal.telephony.util.TelephonyUtils; 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 @@ public class ImsService extends Service { * 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 090d4136872e..9996b868afc7 100644 --- a/telephony/java/android/telephony/ims/RegistrationManager.java +++ b/telephony/java/android/telephony/ims/RegistrationManager.java @@ -78,24 +78,22 @@ public interface RegistrationManager { /**@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 a42327b8a1a9..174675fcde4c 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -34,7 +34,6 @@ import com.android.internal.telephony.util.RemoteCallbackListExt; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.HashMap; import java.util.Map; /** @@ -85,11 +84,10 @@ public abstract class ImsFeature { * 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 @@ public abstract class ImsFeature { * 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 648866b6662d..abf4cde62323 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2159,6 +2159,12 @@ interface ITelephony { 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/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl index 65d994b9066b..339fbee91812 100644 --- a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl +++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl @@ -21,6 +21,6 @@ import com.android.internal.telephony.IWwanSelectorResultCallback; oneway interface IWwanSelectorCallback { void onRequestEmergencyNetworkScan(in int[] preferredNetworks, int scanType, in IWwanSelectorResultCallback cb); - void onDomainSelected(int accessNetworkType); + void onDomainSelected(int domain); void onCancel(); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 9f612e6d7dd9..340ee7202757 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -542,6 +542,7 @@ public interface RILConstants { int RIL_REQUEST_STOP_IMS_TRAFFIC = 236; int RIL_REQUEST_SEND_ANBR_QUERY = 237; int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238; + int RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED = 239; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java index 0f4e122d147a..4bcf5a4e30d5 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 @@ public abstract class DisplayModifier { }; @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 @@ public abstract class DisplayModifier { @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 @@ public abstract class DisplayModifier { } @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 @@ public abstract class DisplayModifier { 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 @@ public abstract class DisplayModifier { 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 abstract class DisplayModifier { 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 @@ public abstract class DisplayModifier { 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/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java index 2ad0da98c409..8b9c02049351 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 @@ public class ViewDumpParser { 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 4de51fb57308..43dc9de6c90a 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 @@ public class HomeActivity extends AppCompatActivity implements Button.OnClickLis 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 c16efbda1830..d015a5695ec0 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 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { } } + @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 8afe8411a790..17fa210a1db6 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 @@ public class MirrorSurfaceActivity extends Activity implements View.OnClickListe 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 241206d8919b..65b7549f22d1 100644 --- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java +++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java @@ -24,18 +24,14 @@ public class MainActivity extends Activity implements OnItemClickListener { 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 c11b0f3acf79..f85fb0f267d5 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 @@ public class CrashyApp extends Activity { setContentView(tv); } + @SuppressWarnings("ReturnValueIgnored") @Override public void onResume() { ((String) null).length(); diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index f924b2e9b932..ad068308d481 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -637,8 +637,7 @@ public class VcnManagementServiceTest { 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/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java index a750696628f9..50126226eb94 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 @@ public class WifiNl80211ManagerTest { 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 @@ public class WifiNl80211ManagerTest { 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 { |