Merge "Set caption bar height to status bar height when in fullscreen" into main
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index e86f814..b0ba019 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -21,18 +21,19 @@
* header := magic version target_crc overlay_crc fulfilled_policies
* enforce_overlayable target_path overlay_path overlay_name
* debug_info
- * data := data_header target_entry* target_inline_entry*
- target_inline_entry_value* config* overlay_entry* string_pool
+ * data := data_header target_entries target_inline_entries
+ target_inline_entry_value* config* overlay_entries string_pool
* data_header := target_entry_count target_inline_entry_count
target_inline_entry_value_count config_count overlay_entry_count
* string_pool_index
- * target_entry := target_id overlay_id
- * target_inline_entry := target_id start_value_index value_count
+ * target_entries := target_id* overlay_id*
+ * target_inline_entries := target_id* target_inline_value_header*
+ * target_inline_value_header := start_value_index value_count
* target_inline_entry_value := config_index Res_value::size padding(1) Res_value::type
* Res_value::value
* config := target_id Res_value::size padding(1) Res_value::type
* Res_value::value
- * overlay_entry := overlay_id target_id
+ * overlay_entries := overlay_id* target_id*
*
* debug_info := string
* enforce_overlayable := <uint32_t>
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index 8976924..00ef0c7 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -66,43 +66,57 @@
void BinaryStreamVisitor::visit(const IdmapData& data) {
for (const auto& target_entry : data.GetTargetEntries()) {
Write32(target_entry.target_id);
+ }
+ for (const auto& target_entry : data.GetTargetEntries()) {
Write32(target_entry.overlay_id);
}
- static constexpr uint16_t kValueSize = 8U;
- std::vector<std::pair<ConfigDescription, TargetValue>> target_values;
- target_values.reserve(data.GetHeader()->GetTargetInlineEntryValueCount());
- for (const auto& target_entry : data.GetTargetInlineEntries()) {
- Write32(target_entry.target_id);
- Write32(target_values.size());
- Write32(target_entry.values.size());
- target_values.insert(
- target_values.end(), target_entry.values.begin(), target_entry.values.end());
+ uint32_t current_inline_entry_values_count = 0;
+ for (const auto& target_inline_entry : data.GetTargetInlineEntries()) {
+ Write32(target_inline_entry.target_id);
+ }
+ for (const auto& target_inline_entry : data.GetTargetInlineEntries()) {
+ Write32(current_inline_entry_values_count);
+ Write32(target_inline_entry.values.size());
+ current_inline_entry_values_count += target_inline_entry.values.size();
}
std::vector<ConfigDescription> configs;
configs.reserve(data.GetHeader()->GetConfigCount());
- for (const auto& target_entry_value : target_values) {
- auto config_it = find(configs.begin(), configs.end(), target_entry_value.first);
- if (config_it != configs.end()) {
- Write32(config_it - configs.begin());
- } else {
- Write32(configs.size());
- configs.push_back(target_entry_value.first);
+ for (const auto& target_entry : data.GetTargetInlineEntries()) {
+ for (const auto& target_entry_value : target_entry.values) {
+ auto config_it = std::find(configs.begin(), configs.end(), target_entry_value.first);
+ if (config_it != configs.end()) {
+ Write32(config_it - configs.begin());
+ } else {
+ Write32(configs.size());
+ configs.push_back(target_entry_value.first);
+ }
+ // We're writing a Res_value entry here, and the first 3 bytes of that are
+ // sizeof() + a padding 0 byte
+ static constexpr decltype(android::Res_value::size) kSize = sizeof(android::Res_value);
+ Write16(kSize);
+ Write8(0U);
+ Write8(target_entry_value.second.data_type);
+ Write32(target_entry_value.second.data_value);
}
- Write16(kValueSize);
- Write8(0U); // padding
- Write8(target_entry_value.second.data_type);
- Write32(target_entry_value.second.data_value);
}
- for( auto& cd : configs) {
- cd.swapHtoD();
- stream_.write(reinterpret_cast<char*>(&cd), sizeof(cd));
+ if (!configs.empty()) {
+ stream_.write(reinterpret_cast<const char*>(&configs.front()),
+ sizeof(configs.front()) * configs.size());
+ if (configs.size() >= 100) {
+ // Let's write a message to future us so that they know when to replace the linear search
+ // in `configs` vector with something more efficient.
+ LOG(WARNING) << "Idmap got " << configs.size()
+ << " configurations, time to fix the bruteforce search";
+ }
}
for (const auto& overlay_entry : data.GetOverlayEntries()) {
Write32(overlay_entry.overlay_id);
+ }
+ for (const auto& overlay_entry : data.GetOverlayEntries()) {
Write32(overlay_entry.target_id);
}
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 12d9dd9..7680109 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -204,73 +204,91 @@
}
// Read the mapping of target resource id to overlay resource value.
+ data->target_entries_.resize(data->header_->GetTargetEntryCount());
for (size_t i = 0; i < data->header_->GetTargetEntryCount(); i++) {
- TargetEntry target_entry{};
- if (!Read32(stream, &target_entry.target_id) || !Read32(stream, &target_entry.overlay_id)) {
+ if (!Read32(stream, &data->target_entries_[i].target_id)) {
return nullptr;
}
- data->target_entries_.emplace_back(target_entry);
+ }
+ for (size_t i = 0; i < data->header_->GetTargetEntryCount(); i++) {
+ if (!Read32(stream, &data->target_entries_[i].overlay_id)) {
+ return nullptr;
+ }
}
// Read the mapping of target resource id to inline overlay values.
- std::vector<std::tuple<TargetInlineEntry, uint32_t, uint32_t>> target_inline_entries;
+ struct TargetInlineEntryHeader {
+ ResourceId target_id;
+ uint32_t values_offset;
+ uint32_t values_count;
+ };
+ std::vector<TargetInlineEntryHeader> target_inline_entries(
+ data->header_->GetTargetInlineEntryCount());
for (size_t i = 0; i < data->header_->GetTargetInlineEntryCount(); i++) {
- TargetInlineEntry target_entry{};
- uint32_t entry_offset;
- uint32_t entry_count;
- if (!Read32(stream, &target_entry.target_id) || !Read32(stream, &entry_offset)
- || !Read32(stream, &entry_count)) {
+ if (!Read32(stream, &target_inline_entries[i].target_id)) {
return nullptr;
}
- target_inline_entries.emplace_back(target_entry, entry_offset, entry_count);
+ }
+ for (size_t i = 0; i < data->header_->GetTargetInlineEntryCount(); i++) {
+ if (!Read32(stream, &target_inline_entries[i].values_offset) ||
+ !Read32(stream, &target_inline_entries[i].values_count)) {
+ return nullptr;
+ }
}
// Read the inline overlay resource values
- std::vector<std::pair<uint32_t, TargetValue>> target_values;
- uint8_t unused1;
- uint16_t unused2;
- for (size_t i = 0; i < data->header_->GetTargetInlineEntryValueCount(); i++) {
+ struct TargetValueHeader {
uint32_t config_index;
- if (!Read32(stream, &config_index)) {
+ DataType data_type;
+ DataValue data_value;
+ };
+ std::vector<TargetValueHeader> target_values(data->header_->GetTargetInlineEntryValueCount());
+ for (size_t i = 0; i < data->header_->GetTargetInlineEntryValueCount(); i++) {
+ auto& value = target_values[i];
+ if (!Read32(stream, &value.config_index)) {
return nullptr;
}
- TargetValue value;
- if (!Read16(stream, &unused2)
- || !Read8(stream, &unused1)
- || !Read8(stream, &value.data_type)
- || !Read32(stream, &value.data_value)) {
+ // skip the padding
+ stream.seekg(3, std::ios::cur);
+ if (!Read8(stream, &value.data_type) || !Read32(stream, &value.data_value)) {
return nullptr;
}
- target_values.emplace_back(config_index, value);
}
// Read the configurations
- std::vector<ConfigDescription> configurations;
- for (size_t i = 0; i < data->header_->GetConfigCount(); i++) {
- ConfigDescription cd;
- if (!stream.read(reinterpret_cast<char*>(&cd), sizeof(ConfigDescription))) {
+ std::vector<ConfigDescription> configurations(data->header_->GetConfigCount());
+ if (!configurations.empty()) {
+ if (!stream.read(reinterpret_cast<char*>(&configurations.front()),
+ sizeof(configurations.front()) * configurations.size())) {
return nullptr;
}
- configurations.emplace_back(cd);
}
// Construct complete target inline entries
- for (auto [target_entry, entry_offset, entry_count] : target_inline_entries) {
- for(size_t i = 0; i < entry_count; i++) {
- const auto& target_value = target_values[entry_offset + i];
- const auto& config = configurations[target_value.first];
- target_entry.values[config] = target_value.second;
+ data->target_inline_entries_.reserve(target_inline_entries.size());
+ for (auto&& entry_header : target_inline_entries) {
+ TargetInlineEntry& entry = data->target_inline_entries_.emplace_back();
+ entry.target_id = entry_header.target_id;
+ for (size_t i = 0; i < entry_header.values_count; i++) {
+ const auto& value_header = target_values[entry_header.values_offset + i];
+ const auto& config = configurations[value_header.config_index];
+ auto& value = entry.values[config];
+ value.data_type = value_header.data_type;
+ value.data_value = value_header.data_value;
}
- data->target_inline_entries_.emplace_back(target_entry);
}
// Read the mapping of overlay resource id to target resource id.
+ data->overlay_entries_.resize(data->header_->GetOverlayEntryCount());
for (size_t i = 0; i < data->header_->GetOverlayEntryCount(); i++) {
- OverlayEntry overlay_entry{};
- if (!Read32(stream, &overlay_entry.overlay_id) || !Read32(stream, &overlay_entry.target_id)) {
+ if (!Read32(stream, &data->overlay_entries_[i].overlay_id)) {
return nullptr;
}
- data->overlay_entries_.emplace_back(overlay_entry);
+ }
+ for (size_t i = 0; i < data->header_->GetOverlayEntryCount(); i++) {
+ if (!Read32(stream, &data->overlay_entries_[i].target_id)) {
+ return nullptr;
+ }
}
// Read raw string pool bytes.
@@ -320,7 +338,7 @@
std::unique_ptr<IdmapData> data(new IdmapData());
data->string_pool_data_ = std::string(resource_mapping.GetStringPoolData());
uint32_t inline_value_count = 0;
- std::set<std::string> config_set;
+ std::set<std::string_view> config_set;
for (const auto& mapping : resource_mapping.GetTargetToOverlayMap()) {
if (auto overlay_resource = std::get_if<ResourceId>(&mapping.second)) {
data->target_entries_.push_back({mapping.first, *overlay_resource});
@@ -329,7 +347,9 @@
for (const auto& [config, value] : std::get<ConfigMap>(mapping.second)) {
config_set.insert(config);
ConfigDescription cd;
- ConfigDescription::Parse(config, &cd);
+ if (!ConfigDescription::Parse(config, &cd)) {
+ return Error("failed to parse configuration string '%s'", config.c_str());
+ }
values[cd] = value;
inline_value_count++;
}
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index c85619c..1b656e8 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -68,7 +68,7 @@
std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
ASSERT_THAT(header, NotNull());
ASSERT_EQ(header->GetMagic(), 0x504d4449U);
- ASSERT_EQ(header->GetVersion(), 0x09U);
+ ASSERT_EQ(header->GetVersion(), 10);
ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(header->GetFulfilledPolicies(), 0x11);
@@ -143,7 +143,7 @@
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
- ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x09U);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 10);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies);
@@ -204,7 +204,7 @@
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
- ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x09U);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 10);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC);
ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC);
ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC);
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 68164e2..7fae1c6 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -64,7 +64,7 @@
(*idmap)->accept(&visitor);
ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "00000009 version\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "0000000a version\n", stream.str());
ASSERT_CONTAINS_REGEX(
StringPrintf(ADDRESS "%s target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING),
stream.str());
@@ -113,7 +113,7 @@
(*idmap)->accept(&visitor);
ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "00000009 version\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "0000000a version\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00001234 target crc\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00005678 overlay crc\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000011 fulfilled policies: public|signature\n", stream.str());
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index bf01c32..2b4ebd1 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -34,7 +34,7 @@
0x49, 0x44, 0x4d, 0x50,
// 0x4: version
- 0x09, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00,
// 0x8: target crc
0x34, 0x12, 0x00, 0x00,
@@ -95,19 +95,15 @@
// TARGET ENTRIES
// 0x6c: target id (0x7f020000)
0x00, 0x00, 0x02, 0x7f,
-
- // 0x70: overlay_id (0x7f020000)
- 0x00, 0x00, 0x02, 0x7f,
-
- // 0x74: target id (0x7f030000)
+ // 0x70: target id (0x7f030000)
0x00, 0x00, 0x03, 0x7f,
-
- // 0x78: overlay_id (0x7f030000)
- 0x00, 0x00, 0x03, 0x7f,
-
- // 0x7c: target id (0x7f030002)
+ // 0x74: target id (0x7f030002)
0x02, 0x00, 0x03, 0x7f,
+ // 0x78: overlay_id (0x7f020000)
+ 0x00, 0x00, 0x02, 0x7f,
+ // 0x7c: overlay_id (0x7f030000)
+ 0x00, 0x00, 0x03, 0x7f,
// 0x80: overlay_id (0x7f030001)
0x01, 0x00, 0x03, 0x7f,
@@ -178,16 +174,20 @@
// 0xe1: padding
0x00, 0x00, 0x00,
-
// OVERLAY ENTRIES
- // 0xe4: 0x7f020000 -> 0x7f020000
- 0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
+ // 0xe4: 0x7f020000 -> ...
+ 0x00, 0x00, 0x02, 0x7f,
+ // 0xe8: 0x7f030000 -> ...
+ 0x00, 0x00, 0x03, 0x7f,
+ // 0xec: 0x7f030001 -> ...
+ 0x01, 0x00, 0x03, 0x7f,
- // 0xec: 0x7f030000 -> 0x7f030000
- 0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
-
- // 0xf4: 0x7f030001 -> 0x7f030002
- 0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f,
+ // 0xf0: ... -> 0x7f020000
+ 0x00, 0x00, 0x02, 0x7f,
+ // 0xf4: ... -> 0x7f030000
+ 0x00, 0x00, 0x03, 0x7f,
+ // 0xf8: ... -> 0x7f030002
+ 0x02, 0x00, 0x03, 0x7f,
// 0xfc: string pool
// string length,
diff --git a/core/api/current.txt b/core/api/current.txt
index ec6a20c..65e4f27 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9834,6 +9834,7 @@
public final class AssociationInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.companion.AssociatedDevice getAssociatedDevice();
+ method @FlaggedApi("android.companion.association_device_icon") @Nullable public android.graphics.drawable.Icon getDeviceIcon();
method @Nullable public android.net.MacAddress getDeviceMacAddress();
method @Nullable public String getDeviceProfile();
method @Nullable public CharSequence getDisplayName();
@@ -9847,6 +9848,7 @@
public final class AssociationRequest implements android.os.Parcelable {
method public int describeContents();
+ method @FlaggedApi("android.companion.association_device_icon") @Nullable public android.graphics.drawable.Icon getDeviceIcon();
method @Nullable public String getDeviceProfile();
method @Nullable public CharSequence getDisplayName();
method public boolean isForceConfirmation();
@@ -9866,6 +9868,7 @@
ctor public AssociationRequest.Builder();
method @NonNull public android.companion.AssociationRequest.Builder addDeviceFilter(@Nullable android.companion.DeviceFilter<?>);
method @NonNull public android.companion.AssociationRequest build();
+ method @FlaggedApi("android.companion.association_device_icon") @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setDeviceIcon(@NonNull android.graphics.drawable.Icon);
method @NonNull public android.companion.AssociationRequest.Builder setDeviceProfile(@NonNull String);
method @NonNull public android.companion.AssociationRequest.Builder setDisplayName(@NonNull CharSequence);
method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setForceConfirmation(boolean);
@@ -12286,6 +12289,7 @@
method public int getMemtagMode();
method public int getNativeHeapZeroInitialized();
method public int getRequestRawExternalStorageAccess();
+ method @FlaggedApi("android.content.pm.audio_playback_capture_allowance") public boolean isAudioPlaybackCaptureAllowed();
method public boolean isProfileable();
method public boolean isProfileableByShell();
method public boolean isResourceOverlay();
@@ -34335,6 +34339,7 @@
method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public boolean areEnvelopeEffectsSupported();
method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @Nullable public android.os.vibrator.VibratorFrequencyProfile getFrequencyProfile();
method public int getId();
method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectControlPointDurationMillis();
method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectDurationMillis();
@@ -34688,6 +34693,19 @@
}
+package android.os.vibrator {
+
+ @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public final class VibratorFrequencyProfile {
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.util.SparseArray<java.lang.Float> getFrequenciesOutputAcceleration();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @Nullable public android.util.Range<java.lang.Float> getFrequencyRange(float);
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public float getMaxFrequencyHz();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public float getMaxOutputAccelerationGs();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public float getMinFrequencyHz();
+ method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public float getOutputAccelerationGs(float);
+ }
+
+}
+
package android.preference {
@Deprecated public class CheckBoxPreference extends android.preference.TwoStatePreference {
@@ -52560,10 +52578,12 @@
ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int);
ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int);
method public void applyTransactionToFrame(@NonNull android.view.SurfaceControl.Transaction);
+ method @FlaggedApi("android.view.flags.surface_view_set_composition_order") public int getCompositionOrder();
method public android.view.SurfaceHolder getHolder();
method @Deprecated @Nullable public android.os.IBinder getHostToken();
method public android.view.SurfaceControl getSurfaceControl();
method public void setChildSurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
+ method @FlaggedApi("android.view.flags.surface_view_set_composition_order") public void setCompositionOrder(int);
method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
method public void setSecure(boolean);
method public void setSurfaceLifecycle(int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 287e787..4d1a423 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -321,7 +321,7 @@
package android.net.wifi {
public final class WifiMigration {
- method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static int migrateLegacyKeystoreToWifiBlobstore();
+ method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static void migrateLegacyKeystoreToWifiBlobstore(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
field @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static final int KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION = 2; // 0x2
field @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE = 0; // 0x0
field @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED = 1; // 0x1
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8edfc21..7781f88 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -18559,6 +18559,7 @@
method @Deprecated public abstract void setUserAgent(int);
method public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean);
field public static final long ENABLE_SIMPLIFIED_DARK_MODE = 214741472L; // 0xcccb1e0L
+ field @FlaggedApi("android.webkit.user_agent_reduction") public static final long ENABLE_USER_AGENT_REDUCTION = 371034303L; // 0x161d88bfL
}
public class WebStorage {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 76e9ca0..5e4485c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -914,6 +914,7 @@
ctor public AssociationInfo.Builder(@NonNull android.companion.AssociationInfo);
method @NonNull public android.companion.AssociationInfo build();
method @NonNull public android.companion.AssociationInfo.Builder setAssociatedDevice(@Nullable android.companion.AssociatedDevice);
+ method @FlaggedApi("android.companion.association_device_icon") @NonNull public android.companion.AssociationInfo.Builder setDeviceIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.companion.AssociationInfo.Builder setDeviceMacAddress(@Nullable android.net.MacAddress);
method @NonNull public android.companion.AssociationInfo.Builder setDeviceProfile(@Nullable String);
method @NonNull public android.companion.AssociationInfo.Builder setDisplayName(@Nullable CharSequence);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 93a9489..7eacaac 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -48,6 +48,9 @@
import android.os.TestLooperManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.ravenwood.annotation.RavenwoodKeep;
+import android.ravenwood.annotation.RavenwoodKeepPartialClass;
+import android.ravenwood.annotation.RavenwoodReplace;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.view.Display;
@@ -80,7 +83,7 @@
* implementation is described to the system through an AndroidManifest.xml's
* <instrumentation> tag.
*/
-@android.ravenwood.annotation.RavenwoodKeepPartialClass
+@RavenwoodKeepPartialClass
public class Instrumentation {
/**
@@ -136,7 +139,7 @@
private UiAutomation mUiAutomation;
private final Object mAnimationCompleteLock = new Object();
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public Instrumentation() {
}
@@ -147,7 +150,7 @@
* reflection, but it will serve as noticeable discouragement from
* doing such a thing.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
private void checkInstrumenting(String method) {
// Check if we have an instrumentation context, as init should only get called by
// the system in startup processes that are being instrumented.
@@ -162,7 +165,7 @@
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public boolean isInstrumenting() {
// Check if we have an instrumentation context, as init should only get called by
// the system in startup processes that are being instrumented.
@@ -326,7 +329,7 @@
*
* @see #getTargetContext
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public Context getContext() {
return mInstrContext;
}
@@ -351,7 +354,7 @@
*
* @see #getContext
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public Context getTargetContext() {
return mAppContext;
}
@@ -2407,10 +2410,11 @@
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodKeep
- public final void basicInit(Context instrContext, Context appContext) {
+ @RavenwoodKeep
+ public final void basicInit(Context instrContext, Context appContext, UiAutomation ui) {
mInstrContext = instrContext;
mAppContext = appContext;
+ mUiAutomation = ui;
}
/** @hide */
@@ -2501,6 +2505,7 @@
*
* @see UiAutomation
*/
+ @RavenwoodKeep
public UiAutomation getUiAutomation() {
return getUiAutomation(0);
}
@@ -2539,6 +2544,7 @@
*
* @see UiAutomation
*/
+ @RavenwoodReplace
public UiAutomation getUiAutomation(@UiAutomationFlags int flags) {
boolean mustCreateNewAutomation = (mUiAutomation == null) || (mUiAutomation.isDestroyed());
@@ -2569,11 +2575,15 @@
return null;
}
+ private UiAutomation getUiAutomation$ravenwood(@UiAutomationFlags int flags) {
+ return mUiAutomation;
+ }
+
/**
* Takes control of the execution of messages on the specified looper until
* {@link TestLooperManager#release} is called.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public TestLooperManager acquireLooperManager(Looper looper) {
checkInstrumenting("acquireLooperManager");
return new TestLooperManager(looper);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9be928f..102540c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -470,16 +470,9 @@
* that the user backed-out of provisioning or some precondition for provisioning wasn't met.
*
* <p>If a <a href="#roleholder">device policy management role holder</a> updater is present on
- * the device, an internet connection attempt must be made prior to launching this intent. If
- * an internet connection can not be established, provisioning will fail unless {@link
- * #EXTRA_PROVISIONING_ALLOW_OFFLINE} is explicitly set to {@code true}, in which case
- * provisioning will continue without using the
- * <a href="#roleholder">device policy management role holder</a>. If an internet connection
- * has been established, the <a href="#roleholder">device policy management role holder</a>
- * updater will be launched, which may update the
- * <a href="#roleholder">device policy management role holder</a> before continuing
- * provisioning.
+ * the device, an internet connection attempt must be made prior to launching this intent.
*/
+ // See b/365955253 for additional behaviours of this API.
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PROVISION_MANAGED_PROFILE
= "android.app.action.PROVISION_MANAGED_PROFILE";
@@ -960,23 +953,8 @@
* A boolean extra indicating whether offline provisioning should be used.
*
* <p>The default value is {@code false}.
- *
- * <p>Usually during the <a href="#managedprovisioning">provisioning flow</a>, there will be
- * an attempt to download and install the latest version of the <a href="#roleholder">device
- * policy management role holder</a>. The platform will then
- * delegate provisioning to the <a href="#roleholder">device
- * * policy management role holder</a>.
- *
- * <p>When this extra is set to {@code true}, the
- * <a href="#managedprovisioning">provisioning flow</a> will always be handled by the platform
- * and the <a href="#roleholder">device policy management role holder</a>'s part skipped.
- *
- * <p>On Android versions prior to {@link Build.VERSION_CODES#TIRAMISU}, when this extra is
- * {@code false}, the <a href="#managedprovisioning">provisioning flow</a> will enforce that an
- * internet connection is established, or otherwise fail. When this extra is {@code true}, a
- * connection will still be attempted but when it cannot be established provisioning will
- * continue offline.
*/
+ // See b/365955253 for detailed behaviours of this API.
public static final String EXTRA_PROVISIONING_ALLOW_OFFLINE =
"android.app.extra.PROVISIONING_ALLOW_OFFLINE";
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index b4b96e2..7f30d7c 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Parcel;
import android.os.Parcelable;
@@ -86,6 +87,11 @@
private final int mSystemDataSyncFlags;
/**
+ * A device icon displayed on a selfManaged association dialog.
+ */
+ private final Icon mDeviceIcon;
+
+ /**
* Creates a new Association.
*
* @hide
@@ -95,7 +101,7 @@
@Nullable CharSequence displayName, @Nullable String deviceProfile,
@Nullable AssociatedDevice associatedDevice, boolean selfManaged,
boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs,
- long lastTimeConnectedMs, int systemDataSyncFlags) {
+ long lastTimeConnectedMs, int systemDataSyncFlags, @Nullable Icon deviceIcon) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -119,6 +125,7 @@
mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs;
mSystemDataSyncFlags = systemDataSyncFlags;
+ mDeviceIcon = deviceIcon;
}
/**
@@ -278,6 +285,20 @@
}
/**
+ * Get the device icon of the associated device. The device icon represents the device type.
+ *
+ * @return the device icon, or {@code null} if no device icon is has been set for the
+ * associated device.
+ *
+ * @see AssociationRequest.Builder#setDeviceIcon(Icon)
+ */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
+ @Nullable
+ public Icon getDeviceIcon() {
+ return mDeviceIcon;
+ }
+
+ /**
* Utility method for checking if the association represents a device with the given MAC
* address.
*
@@ -370,14 +391,16 @@
&& Objects.equals(mDisplayName, that.mDisplayName)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
&& Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
- && mSystemDataSyncFlags == that.mSystemDataSyncFlags;
+ && mSystemDataSyncFlags == that.mSystemDataSyncFlags
+ && (mDeviceIcon == null ? that.mDeviceIcon == null
+ : mDeviceIcon.sameAs(that.mDeviceIcon));
}
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName,
mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
- mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
+ mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags, mDeviceIcon);
}
@Override
@@ -402,6 +425,12 @@
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
dest.writeInt(mSystemDataSyncFlags);
+ if (mDeviceIcon != null) {
+ dest.writeInt(1);
+ mDeviceIcon.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
}
private AssociationInfo(@NonNull Parcel in) {
@@ -420,6 +449,11 @@
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
mSystemDataSyncFlags = in.readInt();
+ if (in.readInt() == 1) {
+ mDeviceIcon = Icon.CREATOR.createFromParcel(in);
+ } else {
+ mDeviceIcon = null;
+ }
}
@NonNull
@@ -459,6 +493,7 @@
private long mTimeApprovedMs;
private long mLastTimeConnectedMs;
private int mSystemDataSyncFlags;
+ private Icon mDeviceIcon;
/** @hide */
@TestApi
@@ -486,6 +521,7 @@
mTimeApprovedMs = info.mTimeApprovedMs;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
+ mDeviceIcon = info.mDeviceIcon;
}
/**
@@ -510,6 +546,7 @@
mTimeApprovedMs = info.mTimeApprovedMs;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
+ mDeviceIcon = info.mDeviceIcon;
}
/** @hide */
@@ -625,6 +662,16 @@
/** @hide */
@TestApi
@NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
+ public Builder setDeviceIcon(@Nullable Icon deviceIcon) {
+ mDeviceIcon = deviceIcon;
+ return this;
+ }
+
+ /** @hide */
+ @TestApi
+ @NonNull
public AssociationInfo build() {
if (mId <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
@@ -648,7 +695,8 @@
mPending,
mTimeApprovedMs,
mLastTimeConnectedMs,
- mSystemDataSyncFlags
+ mSystemDataSyncFlags,
+ mDeviceIcon
);
}
}
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 2e969f8..41a6791 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -23,12 +23,14 @@
import static java.util.Objects.requireNonNull;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -234,6 +236,13 @@
private boolean mSkipPrompt;
/**
+ * The device icon displayed in selfManaged association dialog.
+ * @hide
+ */
+ @Nullable
+ private Icon mDeviceIcon;
+
+ /**
* Creates a new AssociationRequest.
*
* @param singleDevice
@@ -258,15 +267,16 @@
@Nullable @DeviceProfile String deviceProfile,
@Nullable CharSequence displayName,
boolean selfManaged,
- boolean forceConfirmation) {
+ boolean forceConfirmation,
+ @Nullable Icon deviceIcon) {
mSingleDevice = singleDevice;
mDeviceFilters = requireNonNull(deviceFilters);
mDeviceProfile = deviceProfile;
mDisplayName = displayName;
mSelfManaged = selfManaged;
mForceConfirmation = forceConfirmation;
-
mCreationTime = System.currentTimeMillis();
+ mDeviceIcon = deviceIcon;
}
/**
@@ -318,6 +328,19 @@
return mSingleDevice;
}
+ /**
+ * Get the device icon of the self-managed association request.
+ *
+ * @return the device icon, or {@code null} if no device icon has been set.
+ *
+ * @see Builder#setDeviceIcon(Icon)
+ */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
+ @Nullable
+ public Icon getDeviceIcon() {
+ return mDeviceIcon;
+ }
+
/** @hide */
public void setPackageName(@NonNull String packageName) {
mPackageName = packageName;
@@ -365,6 +388,7 @@
private CharSequence mDisplayName;
private boolean mSelfManaged = false;
private boolean mForceConfirmation = false;
+ private Icon mDeviceIcon = null;
public Builder() {}
@@ -450,6 +474,23 @@
return this;
}
+ /**
+ * Set the device icon for the self-managed device and this icon will be
+ * displayed in the self-managed association dialog.
+ *
+ * @throws IllegalArgumentException if the icon is not exactly 24dp by 24dp
+ * or if it is {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}.
+ * @see #setSelfManaged(boolean)
+ */
+ @NonNull
+ @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_DEVICE_ICON)
+ public Builder setDeviceIcon(@NonNull Icon deviceIcon) {
+ checkNotUsed();
+ mDeviceIcon = requireNonNull(deviceIcon);
+ return this;
+ }
+
/** @inheritDoc */
@NonNull
@Override
@@ -460,7 +501,7 @@
+ "provide the display name of the device");
}
return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
- mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation);
+ mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mDeviceIcon);
}
}
@@ -561,7 +602,9 @@
&& Objects.equals(mDeviceProfilePrivilegesDescription,
that.mDeviceProfilePrivilegesDescription)
&& mCreationTime == that.mCreationTime
- && mSkipPrompt == that.mSkipPrompt;
+ && mSkipPrompt == that.mSkipPrompt
+ && (mDeviceIcon == null ? that.mDeviceIcon == null
+ : mDeviceIcon.sameAs(that.mDeviceIcon));
}
@Override
@@ -579,6 +622,8 @@
_hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
_hash = 31 * _hash + Long.hashCode(mCreationTime);
_hash = 31 * _hash + Boolean.hashCode(mSkipPrompt);
+ _hash = 31 * _hash + Objects.hashCode(mDeviceIcon);
+
return _hash;
}
@@ -606,6 +651,12 @@
dest.writeString8(mDeviceProfilePrivilegesDescription);
}
dest.writeLong(mCreationTime);
+ if (mDeviceIcon != null) {
+ dest.writeInt(1);
+ mDeviceIcon.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
}
@Override
@@ -650,6 +701,11 @@
this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
this.mCreationTime = creationTime;
this.mSkipPrompt = skipPrompt;
+ if (in.readInt() == 1) {
+ mDeviceIcon = Icon.CREATOR.createFromParcel(in);
+ } else {
+ mDeviceIcon = null;
+ }
}
@NonNull
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1cdf3b1..dfad6de 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -20,6 +20,8 @@
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
+import static android.graphics.drawable.Icon.TYPE_URI;
+import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
import android.annotation.CallbackExecutor;
@@ -49,6 +51,11 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.VectorDrawable;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Handler;
@@ -535,6 +542,13 @@
Objects.requireNonNull(executor, "Executor cannot be null");
Objects.requireNonNull(callback, "Callback cannot be null");
+ final Icon deviceIcon = request.getDeviceIcon();
+
+ if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
+ throw new IllegalArgumentException("The size of the device icon must be 24dp x 24dp to"
+ + "ensure proper display");
+ }
+
try {
mService.associate(request, new AssociationRequestCallbackProxy(executor, callback),
mContext.getOpPackageName(), mContext.getUserId());
@@ -2027,4 +2041,34 @@
}
}
}
+
+ private boolean isValidIcon(Icon icon, Context context) {
+ if (icon.getType() == TYPE_URI_ADAPTIVE_BITMAP || icon.getType() == TYPE_URI) {
+ throw new IllegalArgumentException("The URI based Icon is not supported.");
+ }
+ Drawable drawable = icon.loadDrawable(context);
+ float density = context.getResources().getDisplayMetrics().density;
+
+ if (drawable instanceof BitmapDrawable) {
+ Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
+
+ float widthDp = bitmap.getWidth() / density;
+ float heightDp = bitmap.getHeight() / density;
+
+ if (widthDp != 24 || heightDp != 24) {
+ return false;
+ }
+ } else if (drawable instanceof VectorDrawable) {
+ VectorDrawable vectorDrawable = (VectorDrawable) drawable;
+ float widthDp = vectorDrawable.getIntrinsicWidth() / density;
+ float heightDp = vectorDrawable.getIntrinsicHeight() / density;
+
+ if (widthDp != 24 || heightDp != 24) {
+ return false;
+ }
+ } else {
+ throw new IllegalArgumentException("The format of the device icon is unsupported.");
+ }
+ return true;
+ }
}
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 93d62cf..2539a12 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -55,3 +55,11 @@
description: "Enable association failure code API"
bug: "331459560"
}
+
+flag {
+ name: "association_device_icon"
+ is_exported: true
+ namespace: "companion"
+ description: "Enable set device icon API"
+ bug: "341057668"
+}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 34bea1a..cccfdb0 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -2334,9 +2334,8 @@
* Whether an app allows its playback audio to be captured by other apps.
*
* @return {@code true} if the app indicates that its audio can be captured by other apps.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_AUDIO_PLAYBACK_CAPTURE_ALLOWANCE)
public boolean isAudioPlaybackCaptureAllowed() {
return (privateFlags & PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE) != 0;
}
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index d77b2f5..f7191e6 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -209,6 +209,24 @@
}
/**
+ * @hide
+ * @param name
+ * @param versionMajor
+ */
+ public SharedLibraryInfo(String name, long versionMajor, int type) {
+ mPath = null;
+ mPackageName = null;
+ mName = name;
+ mVersion = versionMajor;
+ mType = type;
+ mDeclaringPackage = null;
+ mDependentPackages = null;
+ mDependencies = null;
+ mIsNative = false;
+ mOptionalDependentPackages = null;
+ }
+
+ /**
* Gets the type of this library.
*
* @return The library type.
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 300740e..c7d7dc1 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -288,6 +288,15 @@
}
flag {
+ name: "audio_playback_capture_allowance"
+ is_exported: true
+ namespace: "package_manager_service"
+ description: "Feature flag to enable the feature to retrieve info about audio playback capture allowance at manifest level."
+ bug: "362425551"
+ is_fixed_read_only: true
+}
+
+flag {
name: "get_packages_from_launcher_apps"
namespace: "package_manager_service"
description: "Feature flag to provide the new methods within launcher apps class to get packages."
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index fa26837..dcf82bf 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -198,46 +198,6 @@
}
flag {
- name: "cache_profile_parent"
- namespace: "multiuser"
- description: "Cache getProfileParent to avoid unnecessary binder calls"
- bug: "350417399"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "cache_profile_ids"
- namespace: "multiuser"
- description: "Cache getProfileIds to avoid unnecessary binder calls"
- bug: "350421409"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "cache_profile_type"
- namespace: "multiuser"
- description: "Cache getProfileType to avoid unnecessary binder calls"
- bug: "350417403"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "cache_profiles"
- namespace: "multiuser"
- description: "Cache getProfiles to avoid unnecessary binder calls"
- bug: "350419395"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "fix_disabling_of_mu_toggle_when_restriction_applied"
namespace: "multiuser"
description: "When no_user_switch is set but no EnforcedAdmin is present, the toggle has to be disabled"
@@ -248,6 +208,50 @@
}
flag {
+ name: "cache_profile_parent_read_only"
+ namespace: "multiuser"
+ description: "Cache getProfileParent to avoid unnecessary binder calls"
+ bug: "350417399"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "cache_profile_ids_read_only"
+ namespace: "multiuser"
+ description: "Cache getProfileIds to avoid unnecessary binder calls"
+ bug: "350421409"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "cache_profile_type_read_only"
+ namespace: "multiuser"
+ description: "Cache getProfileType to avoid unnecessary binder calls"
+ bug: "350417403"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "cache_profiles_read_only"
+ namespace: "multiuser"
+ description: "Cache getProfiles to avoid unnecessary binder calls"
+ bug: "350419395"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+flag {
name: "cache_quiet_mode_state"
namespace: "multiuser"
description: "Optimise quiet mode state retrieval"
@@ -290,6 +294,17 @@
}
flag {
+ name: "invalidate_cache_on_users_changed_read_only"
+ namespace: "multiuser"
+ description: "Invalidate the cache when users are added or removed to improve caches."
+ bug: "372383485"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+flag {
name: "caches_not_invalidated_at_start_read_only"
namespace: "multiuser"
description: "PIC need to be invalidated at start in order to work properly."
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 74ce62c..19a13db 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -21,6 +21,7 @@
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.content.pm.VerifierInfo;
@@ -149,6 +150,8 @@
*/
private final @Nullable String mEmergencyInstaller;
+ private final @NonNull List<SharedLibraryInfo> mDeclaredLibraries;
+
/**
* Archival install info.
*/
@@ -165,7 +168,7 @@
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem,
- String emergencyInstaller) {
+ String emergencyInstaller, List<SharedLibraryInfo> declaredLibraries) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -202,6 +205,7 @@
mUpdatableSystem = updatableSystem;
mEmergencyInstaller = emergencyInstaller;
mArchivedPackage = null;
+ mDeclaredLibraries = declaredLibraries;
}
public ApkLite(String path, ArchivedPackageParcel archivedPackage) {
@@ -241,6 +245,7 @@
mUpdatableSystem = true;
mEmergencyInstaller = null;
mArchivedPackage = archivedPackage;
+ mDeclaredLibraries = null;
}
/**
@@ -565,6 +570,11 @@
return mEmergencyInstaller;
}
+ @DataClass.Generated.Member
+ public @NonNull List<SharedLibraryInfo> getDeclaredLibraries() {
+ return mDeclaredLibraries;
+ }
+
/**
* Archival install info.
*/
@@ -574,10 +584,10 @@
}
@DataClass.Generated(
- time = 1706896661616L,
+ time = 1728333566322L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index ffb69c0..1a7f628 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -24,6 +24,7 @@
import android.app.admin.DeviceAdminReceiver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.content.pm.VerifierInfo;
import android.content.pm.parsing.result.ParseInput;
@@ -92,6 +93,8 @@
private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
private static final String TAG_PROCESSES = "processes";
private static final String TAG_PROCESS = "process";
+ private static final String TAG_STATIC_LIBRARY = "static-library";
+ private static final String TAG_LIBRARY = "library";
/**
* Parse only lightweight details about the package at the given location.
@@ -457,6 +460,7 @@
boolean hasDeviceAdminReceiver = false;
boolean isSdkLibrary = false;
+ List<SharedLibraryInfo> declaredLibraries = new ArrayList<>();
// Only search the tree when the tag is the direct child of <manifest> tag
int type;
@@ -521,6 +525,51 @@
break;
case TAG_SDK_LIBRARY:
isSdkLibrary = true;
+ // Mirrors ParsingPackageUtils#parseSdkLibrary until lite and full
+ // parsing are combined
+ String sdkLibName = parser.getAttributeValue(
+ ANDROID_RES_NAMESPACE, "name");
+ int sdkLibVersionMajor = parser.getAttributeIntValue(
+ ANDROID_RES_NAMESPACE, "versionMajor", -1);
+ if (sdkLibName == null || sdkLibVersionMajor < 0) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Bad uses-sdk-library declaration name: " + sdkLibName
+ + " version: " + sdkLibVersionMajor);
+ }
+ declaredLibraries.add(new SharedLibraryInfo(
+ sdkLibName, sdkLibVersionMajor,
+ SharedLibraryInfo.TYPE_SDK_PACKAGE));
+ break;
+ case TAG_STATIC_LIBRARY:
+ // Mirrors ParsingPackageUtils#parseStaticLibrary until lite and full
+ // parsing are combined
+ String staticLibName = parser.getAttributeValue(
+ ANDROID_RES_NAMESPACE, "name");
+ int staticLibVersion = parser.getAttributeIntValue(
+ ANDROID_RES_NAMESPACE, "version", -1);
+ int staticLibVersionMajor = parser.getAttributeIntValue(
+ ANDROID_RES_NAMESPACE, "versionMajor", 0);
+ if (staticLibName == null || staticLibVersion < 0) {
+ return input.error("Bad static-library declaration name: "
+ + staticLibName + " version: " + staticLibVersion);
+ }
+ declaredLibraries.add(new SharedLibraryInfo(staticLibName,
+ PackageInfo.composeLongVersionCode(staticLibVersionMajor,
+ staticLibVersion), SharedLibraryInfo.TYPE_STATIC));
+ break;
+ case TAG_LIBRARY:
+ // Mirrors ParsingPackageUtils#parseLibrary until lite and full parsing
+ // are combined
+ String libName = parser.getAttributeValue(
+ ANDROID_RES_NAMESPACE, "name");
+ if (libName == null) {
+ return input.error("Bad library declaration name: null");
+ }
+ libName = libName.intern();
+ declaredLibraries.add(new SharedLibraryInfo(libName,
+ SharedLibraryInfo.VERSION_UNDEFINED,
+ SharedLibraryInfo.TYPE_DYNAMIC));
break;
case TAG_PROCESSES:
final int processesDepth = parser.getDepth();
@@ -645,7 +694,8 @@
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller));
+ hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller,
+ declaredLibraries));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index 116dd1f..9a2ee7f 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.PackageInfo;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.content.pm.VerifierInfo;
@@ -114,6 +115,8 @@
*/
private final boolean mIsSdkLibrary;
+ private final @NonNull List<SharedLibraryInfo> mDeclaredLibraries;
+
/**
* Archival install info.
*/
@@ -154,6 +157,7 @@
mSplitApkPaths = splitApkPaths;
mSplitRevisionCodes = splitRevisionCodes;
mTargetSdk = targetSdk;
+ mDeclaredLibraries = baseApk.getDeclaredLibraries();
mArchivedPackage = baseApk.getArchivedPackage();
}
@@ -433,6 +437,11 @@
return mIsSdkLibrary;
}
+ @DataClass.Generated.Member
+ public @NonNull List<SharedLibraryInfo> getDeclaredLibraries() {
+ return mDeclaredLibraries;
+ }
+
/**
* Archival install info.
*/
@@ -442,10 +451,10 @@
}
@DataClass.Generated(
- time = 1694792176268L,
+ time = 1728333569917L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index a69a371..acb48f3 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2270,7 +2270,17 @@
* {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are ignored. The
* application has control over the various
* android.flash.* fields.</p>
+ * <p>If the device supports manual flash strength control, i.e.,
+ * if {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} and
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} are greater than 1, then
+ * the auto-exposure (AE) precapture metering sequence should be
+ * triggered for the configured flash mode and strength to avoid
+ * the image being incorrectly exposed at different
+ * {@link CaptureRequest#FLASH_STRENGTH_LEVEL android.flash.strengthLevel}.</p>
*
+ * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
+ * @see CaptureRequest#FLASH_STRENGTH_LEVEL
+ * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
* @see CaptureRequest#SENSOR_FRAME_DURATION
* @see CaptureRequest#SENSOR_SENSITIVITY
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3f5ae91..a193ee1 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1358,6 +1358,13 @@
* camera device auto-exposure routine for the overridden
* fields for a given capture will be available in its
* CaptureResult.</p>
+ * <p>When {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is AE_MODE_ON and if the device
+ * supports manual flash strength control, i.e.,
+ * if {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} and
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} are greater than 1, then
+ * the auto-exposure (AE) precapture metering sequence should be
+ * triggered to avoid the image being incorrectly exposed at
+ * different {@link CaptureRequest#FLASH_STRENGTH_LEVEL android.flash.strengthLevel}.</p>
* <p><b>Possible values:</b></p>
* <ul>
* <li>{@link #CONTROL_AE_MODE_OFF OFF}</li>
@@ -1373,9 +1380,13 @@
* <p>This key is available on all devices.</p>
*
* @see CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES
+ * @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#FLASH_INFO_AVAILABLE
* @see CaptureRequest#FLASH_MODE
+ * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
+ * @see CaptureRequest#FLASH_STRENGTH_LEVEL
+ * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
* @see CaptureRequest#SENSOR_FRAME_DURATION
* @see CaptureRequest#SENSOR_SENSITIVITY
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index a18a634..e5ca46a 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -759,6 +759,13 @@
* camera device auto-exposure routine for the overridden
* fields for a given capture will be available in its
* CaptureResult.</p>
+ * <p>When {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is AE_MODE_ON and if the device
+ * supports manual flash strength control, i.e.,
+ * if {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} and
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} are greater than 1, then
+ * the auto-exposure (AE) precapture metering sequence should be
+ * triggered to avoid the image being incorrectly exposed at
+ * different {@link CaptureRequest#FLASH_STRENGTH_LEVEL android.flash.strengthLevel}.</p>
* <p><b>Possible values:</b></p>
* <ul>
* <li>{@link #CONTROL_AE_MODE_OFF OFF}</li>
@@ -774,9 +781,13 @@
* <p>This key is available on all devices.</p>
*
* @see CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES
+ * @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#FLASH_INFO_AVAILABLE
* @see CaptureRequest#FLASH_MODE
+ * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
+ * @see CaptureRequest#FLASH_STRENGTH_LEVEL
+ * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
* @see CaptureRequest#SENSOR_FRAME_DURATION
* @see CaptureRequest#SENSOR_SENSITIVITY
diff --git a/core/java/android/hardware/devicestate/DeviceStateInfo.java b/core/java/android/hardware/devicestate/DeviceStateInfo.java
index 28561ec..fd6f0b8 100644
--- a/core/java/android/hardware/devicestate/DeviceStateInfo.java
+++ b/core/java/android/hardware/devicestate/DeviceStateInfo.java
@@ -28,7 +28,6 @@
import java.util.Objects;
import java.util.concurrent.Executor;
-
/**
* Information about the state of the device.
*
@@ -63,11 +62,13 @@
* ignoring any override requests made through a call to {@link DeviceStateManager#requestState(
* DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*/
+ @NonNull
public final DeviceState baseState;
/**
* The state of the device.
*/
+ @NonNull
public final DeviceState currentState;
/**
@@ -78,8 +79,9 @@
*/
// Using the specific types to avoid virtual method calls in binder transactions
@SuppressWarnings("NonApiType")
- public DeviceStateInfo(@NonNull ArrayList<DeviceState> supportedStates, DeviceState baseState,
- DeviceState state) {
+ public DeviceStateInfo(@NonNull ArrayList<DeviceState> supportedStates,
+ @NonNull DeviceState baseState,
+ @NonNull DeviceState state) {
this.supportedStates = supportedStates;
this.baseState = baseState;
this.currentState = state;
@@ -97,7 +99,7 @@
public boolean equals(@Nullable Object other) {
if (this == other) return true;
if (other == null || (getClass() != other.getClass())) return false;
- DeviceStateInfo that = (DeviceStateInfo) other;
+ final DeviceStateInfo that = (DeviceStateInfo) other;
return baseState.equals(that.baseState)
&& currentState.equals(that.currentState)
&& Objects.equals(supportedStates, that.supportedStates);
@@ -126,8 +128,16 @@
return diff;
}
+ // Parcelable implementation
+
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Writes to Parcel. */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(supportedStates.size());
for (int i = 0; i < supportedStates.size(); i++) {
dest.writeTypedObject(supportedStates.get(i).getConfiguration(), flags);
@@ -137,28 +147,27 @@
dest.writeTypedObject(currentState.getConfiguration(), flags);
}
- @Override
- public int describeContents() {
- return 0;
+ /** Reads from Parcel. */
+ private DeviceStateInfo(@NonNull Parcel in) {
+ final int numberOfSupportedStates = in.readInt();
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(numberOfSupportedStates);
+ for (int i = 0; i < numberOfSupportedStates; i++) {
+ final DeviceState.Configuration configuration =
+ Objects.requireNonNull(in.readTypedObject(DeviceState.Configuration.CREATOR));
+ supportedStates.add(i, new DeviceState(configuration));
+ }
+ this.supportedStates = supportedStates;
+
+ this.baseState = new DeviceState(
+ Objects.requireNonNull(in.readTypedObject(DeviceState.Configuration.CREATOR)));
+ this.currentState = new DeviceState(
+ Objects.requireNonNull(in.readTypedObject(DeviceState.Configuration.CREATOR)));
}
public static final @NonNull Creator<DeviceStateInfo> CREATOR = new Creator<>() {
@Override
- public DeviceStateInfo createFromParcel(Parcel source) {
- final int numberOfSupportedStates = source.readInt();
- final ArrayList<DeviceState> supportedStates = new ArrayList<>(numberOfSupportedStates);
- for (int i = 0; i < numberOfSupportedStates; i++) {
- DeviceState.Configuration configuration = source.readTypedObject(
- DeviceState.Configuration.CREATOR);
- supportedStates.add(i, new DeviceState(configuration));
- }
-
- final DeviceState baseState = new DeviceState(
- source.readTypedObject(DeviceState.Configuration.CREATOR));
- final DeviceState currentState = new DeviceState(
- source.readTypedObject(DeviceState.Configuration.CREATOR));
-
- return new DeviceStateInfo(supportedStates, baseState, currentState);
+ public DeviceStateInfo createFromParcel(@NonNull Parcel in) {
+ return new DeviceStateInfo(in);
}
@Override
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 0c84019..1845827 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -46,6 +46,7 @@
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
public final class DeviceStateManagerGlobal {
+ @Nullable
private static DeviceStateManagerGlobal sInstance;
private static final String TAG = "DeviceStateManagerGlobal";
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
@@ -58,7 +59,7 @@
public static DeviceStateManagerGlobal getInstance() {
synchronized (DeviceStateManagerGlobal.class) {
if (sInstance == null) {
- IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
+ final IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
if (b != null) {
sInstance = new DeviceStateManagerGlobal(IDeviceStateManager
.Stub.asInterface(b));
@@ -94,6 +95,7 @@
*
* @see DeviceStateManager#getSupportedDeviceStates()
*/
+ @NonNull
public List<DeviceState> getSupportedDeviceStates() {
synchronized (mLock) {
final DeviceStateInfo currentInfo;
@@ -126,8 +128,8 @@
conditional = true)
public void requestState(@NonNull DeviceStateRequest request,
@Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
- DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
- executor);
+ final DeviceStateRequestWrapper requestWrapper =
+ new DeviceStateRequestWrapper(request, callback, executor);
synchronized (mLock) {
if (findRequestTokenLocked(request) != null) {
// This request has already been submitted.
@@ -136,7 +138,7 @@
// Add the request wrapper to the mRequests array before requesting the state as the
// callback could be triggered immediately if the mDeviceStateManager IBinder is in the
// same process as this instance.
- IBinder token = new Binder();
+ final IBinder token = new Binder();
mRequests.put(token, requestWrapper);
try {
@@ -176,8 +178,8 @@
@RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
@Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
- DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
- executor);
+ final DeviceStateRequestWrapper requestWrapper =
+ new DeviceStateRequestWrapper(request, callback, executor);
synchronized (mLock) {
if (findRequestTokenLocked(request) != null) {
// This request has already been submitted.
@@ -186,7 +188,7 @@
// Add the request wrapper to the mRequests array before requesting the state as the
// callback could be triggered immediately if the mDeviceStateManager IBinder is in the
// same process as this instance.
- IBinder token = new Binder();
+ final IBinder token = new Binder();
mRequests.put(token, requestWrapper);
try {
@@ -225,7 +227,7 @@
public void registerDeviceStateCallback(@NonNull DeviceStateCallback callback,
@NonNull Executor executor) {
synchronized (mLock) {
- int index = findCallbackLocked(callback);
+ final int index = findCallbackLocked(callback);
if (index != -1) {
// This callback is already registered.
return;
@@ -233,7 +235,8 @@
// Add the callback wrapper to the mCallbacks array after registering the callback as
// the callback could be triggered immediately if the mDeviceStateManager IBinder is in
// the same process as this instance.
- DeviceStateCallbackWrapper wrapper = new DeviceStateCallbackWrapper(callback, executor);
+ final DeviceStateCallbackWrapper wrapper =
+ new DeviceStateCallbackWrapper(callback, executor);
mCallbacks.add(wrapper);
if (mLastReceivedInfo != null) {
@@ -253,7 +256,7 @@
@VisibleForTesting(visibility = Visibility.PACKAGE)
public void unregisterDeviceStateCallback(@NonNull DeviceStateCallback callback) {
synchronized (mLock) {
- int indexToRemove = findCallbackLocked(callback);
+ final int indexToRemove = findCallbackLocked(callback);
if (indexToRemove != -1) {
mCallbacks.remove(indexToRemove);
}
@@ -277,14 +280,16 @@
}
private void registerCallbackIfNeededLocked() {
- if (mCallback == null) {
- mCallback = new DeviceStateManagerCallback();
- try {
- mDeviceStateManager.registerCallback(mCallback);
- } catch (RemoteException ex) {
- mCallback = null;
- throw ex.rethrowFromSystemServer();
- }
+ if (mCallback != null) {
+ return;
+ }
+
+ mCallback = new DeviceStateManagerCallback();
+ try {
+ mDeviceStateManager.registerCallback(mCallback);
+ } catch (RemoteException ex) {
+ mCallback = null;
+ throw ex.rethrowFromSystemServer();
}
}
@@ -298,6 +303,7 @@
}
@Nullable
+ @GuardedBy("mLock")
private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
for (int i = 0; i < mRequests.size(); i++) {
if (mRequests.valueAt(i).mRequest.equals(request)) {
@@ -309,8 +315,8 @@
/** Handles a call from the server that the device state info has changed. */
private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
- ArrayList<DeviceStateCallbackWrapper> callbacks;
- DeviceStateInfo oldInfo;
+ final ArrayList<DeviceStateCallbackWrapper> callbacks;
+ final DeviceStateInfo oldInfo;
synchronized (mLock) {
oldInfo = mLastReceivedInfo;
mLastReceivedInfo = info;
@@ -335,8 +341,8 @@
* Handles a call from the server that a request for the supplied {@code token} has become
* active.
*/
- private void handleRequestActive(IBinder token) {
- DeviceStateRequestWrapper request;
+ private void handleRequestActive(@NonNull IBinder token) {
+ final DeviceStateRequestWrapper request;
synchronized (mLock) {
request = mRequests.get(token);
}
@@ -349,8 +355,8 @@
* Handles a call from the server that a request for the supplied {@code token} has become
* canceled.
*/
- private void handleRequestCanceled(IBinder token) {
- DeviceStateRequestWrapper request;
+ private void handleRequestCanceled(@NonNull IBinder token) {
+ final DeviceStateRequestWrapper request;
synchronized (mLock) {
request = mRequests.remove(token);
}
@@ -361,17 +367,17 @@
private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub {
@Override
- public void onDeviceStateInfoChanged(DeviceStateInfo info) {
+ public void onDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
handleDeviceStateInfoChanged(info);
}
@Override
- public void onRequestActive(IBinder token) {
+ public void onRequestActive(@NonNull IBinder token) {
handleRequestActive(token);
}
@Override
- public void onRequestCanceled(IBinder token) {
+ public void onRequestCanceled(@NonNull IBinder token) {
handleRequestCanceled(token);
}
}
@@ -388,7 +394,8 @@
mExecutor = executor;
}
- void notifySupportedDeviceStatesChanged(List<DeviceState> newSupportedDeviceStates) {
+ void notifySupportedDeviceStatesChanged(
+ @NonNull List<DeviceState> newSupportedDeviceStates) {
mExecutor.execute(() ->
mDeviceStateCallback.onSupportedStatesChanged(newSupportedDeviceStates));
}
@@ -398,7 +405,7 @@
() -> mDeviceStateCallback.onDeviceStateChanged(newDeviceState));
}
- private void execute(String traceName, Runnable r) {
+ private void execute(@NonNull String traceName, @NonNull Runnable r) {
mExecutor.execute(() -> {
if (DEBUG) {
Trace.beginSection(
@@ -416,6 +423,7 @@
}
private static final class DeviceStateRequestWrapper {
+ @NonNull
private final DeviceStateRequest mRequest;
@Nullable
private final DeviceStateRequest.Callback mCallback;
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 7327630..c4c4580 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -34,6 +34,7 @@
import android.media.AudioAttributes;
import android.os.vibrator.Flags;
import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibratorFrequencyProfile;
import android.os.vibrator.VibratorFrequencyProfileLegacy;
import android.util.Log;
import android.view.HapticFeedbackConstants;
@@ -303,6 +304,28 @@
}
/**
+ * Gets the profile that describes the vibrator output across the supported frequency range.
+ *
+ * <p>The profile describes the output acceleration that the device can reach when it
+ * vibrates at different frequencies.
+ *
+ * @return The frequency profile for this vibrator, or null if the vibrator does not have
+ * frequency control. If this vibrator is a composite of multiple physical devices then this
+ * will return a profile supported in all devices, or null if the intersection is empty or not
+ * available.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @Nullable
+ public VibratorFrequencyProfile getFrequencyProfile() {
+ VibratorInfo.FrequencyProfile frequencyProfile = getInfo().getFrequencyProfile();
+ if (frequencyProfile.isEmpty()) {
+ return null;
+ }
+
+ return new VibratorFrequencyProfile(frequencyProfile);
+ }
+
+ /**
* Return the maximum amplitude the vibrator can play using the audio haptic channels.
*
* <p>This is a positive value, or {@link Float#NaN NaN} if it's unknown. If this returns a
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index f7fff39..9419032 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -20,8 +20,10 @@
import android.annotation.Nullable;
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
+import android.os.vibrator.Flags;
import android.util.IndentingPrintWriter;
import android.util.MathUtils;
+import android.util.Pair;
import android.util.Range;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -30,8 +32,11 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.TreeMap;
/**
* A VibratorInfo describes the capabilities of a {@link Vibrator}.
@@ -60,6 +65,7 @@
private final int mPwleSizeMax;
private final float mQFactor;
private final FrequencyProfileLegacy mFrequencyProfileLegacy;
+ private final FrequencyProfile mFrequencyProfile;
private final int mMaxEnvelopeEffectSize;
private final int mMinEnvelopeEffectControlPointDurationMillis;
private final int mMaxEnvelopeEffectControlPointDurationMillis;
@@ -76,6 +82,7 @@
mPwleSizeMax = in.readInt();
mQFactor = in.readFloat();
mFrequencyProfileLegacy = FrequencyProfileLegacy.CREATOR.createFromParcel(in);
+ mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in);
mMaxEnvelopeEffectSize = in.readInt();
mMinEnvelopeEffectControlPointDurationMillis = in.readInt();
mMaxEnvelopeEffectControlPointDurationMillis = in.readInt();
@@ -87,7 +94,7 @@
baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax,
baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax,
baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfileLegacy,
- baseVibratorInfo.mMaxEnvelopeEffectSize,
+ baseVibratorInfo.mFrequencyProfile, baseVibratorInfo.mMaxEnvelopeEffectSize,
baseVibratorInfo.mMinEnvelopeEffectControlPointDurationMillis,
baseVibratorInfo.mMaxEnvelopeEffectControlPointDurationMillis);
}
@@ -114,6 +121,17 @@
* @param qFactor The vibrator quality factor.
* @param frequencyProfileLegacy The description of the vibrator supported frequencies and max
* amplitude mappings.
+ * @param frequencyProfile The description of the vibrator supported frequencies and
+ * output acceleration mappings.
+ * @param maxEnvelopeEffectSize The maximum number of control points supported for an
+ * envelope effect.
+ * @param minEnvelopeEffectControlPointDurationMillis The minimum duration supported
+ * between two control points within an
+ * envelope effect.
+ * @param maxEnvelopeEffectControlPointDurationMillis The maximum duration supported
+ * between two control points within an
+ * envelope effect.
+ *
* @hide
*/
public VibratorInfo(int id, long capabilities, @Nullable SparseBooleanArray supportedEffects,
@@ -121,10 +139,12 @@
@NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
float qFactor, @NonNull FrequencyProfileLegacy frequencyProfileLegacy,
- int maxEnvelopeEffectSize, int minEnvelopeEffectControlPointDurationMillis,
+ @NonNull FrequencyProfile frequencyProfile, int maxEnvelopeEffectSize,
+ int minEnvelopeEffectControlPointDurationMillis,
int maxEnvelopeEffectControlPointDurationMillis) {
Preconditions.checkNotNull(supportedPrimitives);
Preconditions.checkNotNull(frequencyProfileLegacy);
+ Preconditions.checkNotNull(frequencyProfile);
mId = id;
mCapabilities = capabilities;
mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
@@ -136,6 +156,7 @@
mPwleSizeMax = pwleSizeMax;
mQFactor = qFactor;
mFrequencyProfileLegacy = frequencyProfileLegacy;
+ mFrequencyProfile = frequencyProfile;
mMaxEnvelopeEffectSize = maxEnvelopeEffectSize;
mMinEnvelopeEffectControlPointDurationMillis =
minEnvelopeEffectControlPointDurationMillis;
@@ -156,6 +177,7 @@
dest.writeInt(mPwleSizeMax);
dest.writeFloat(mQFactor);
mFrequencyProfileLegacy.writeToParcel(dest, flags);
+ mFrequencyProfile.writeToParcel(dest, flags);
dest.writeInt(mMaxEnvelopeEffectSize);
dest.writeInt(mMinEnvelopeEffectControlPointDurationMillis);
dest.writeInt(mMaxEnvelopeEffectControlPointDurationMillis);
@@ -206,6 +228,7 @@
&& Objects.equals(mSupportedBraking, that.mSupportedBraking)
&& Objects.equals(mQFactor, that.mQFactor)
&& Objects.equals(mFrequencyProfileLegacy, that.mFrequencyProfileLegacy)
+ && Objects.equals(mFrequencyProfile, that.mFrequencyProfile)
&& mMaxEnvelopeEffectSize == that.mMaxEnvelopeEffectSize
&& mMinEnvelopeEffectControlPointDurationMillis
== that.mMinEnvelopeEffectControlPointDurationMillis
@@ -216,7 +239,7 @@
@Override
public int hashCode() {
int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
- mQFactor, mFrequencyProfileLegacy);
+ mQFactor, mFrequencyProfileLegacy, mFrequencyProfile);
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
@@ -239,6 +262,7 @@
+ ", mPwleSizeMax=" + mPwleSizeMax
+ ", mQFactor=" + mQFactor
+ ", mFrequencyProfileLegacy=" + mFrequencyProfileLegacy
+ + ", mFrequencyProfile=" + mFrequencyProfile
+ ", mMaxEnvelopeEffectSize=" + mMaxEnvelopeEffectSize
+ ", mMinEnvelopeEffectControlPointDurationMillis="
+ mMinEnvelopeEffectControlPointDurationMillis
@@ -263,6 +287,7 @@
pw.println("pwleSizeMax = " + mPwleSizeMax);
pw.println("q-factor = " + mQFactor);
pw.println("frequencyProfileLegacy = " + mFrequencyProfileLegacy);
+ pw.println("frequencyProfile = " + mFrequencyProfile);
pw.println("mMaxEnvelopeEffectSize = " + mMaxEnvelopeEffectSize);
pw.println("mMinEnvelopeEffectControlPointDurationMillis = "
+ mMinEnvelopeEffectControlPointDurationMillis);
@@ -517,6 +542,9 @@
* this vibrator is a composite of multiple physical devices.
*/
public float getResonantFrequencyHz() {
+ if (Flags.normalizedPwleEffects()) {
+ return mFrequencyProfile.mResonantFrequencyHz;
+ }
return mFrequencyProfileLegacy.mResonantFrequencyHz;
}
@@ -541,6 +569,17 @@
return mFrequencyProfileLegacy;
}
+ /**
+ * Gets the profile of supported frequencies, including the measurements of maximum
+ * output acceleration for supported vibration frequencies.
+ *
+ * <p>If the devices does not have frequency control then the profile should be empty.
+ */
+ @NonNull
+ public FrequencyProfile getFrequencyProfile() {
+ return mFrequencyProfile;
+ }
+
/** Returns a single int representing all the capabilities of the vibrator. */
public long getCapabilities() {
return mCapabilities;
@@ -623,6 +662,304 @@
}
/**
+ * Describes the maximum output acceleration that can be achieved for each supported
+ * frequency in a specific vibrator.
+ *
+ * @hide
+ */
+ public static final class FrequencyProfile implements Parcelable {
+
+ private final float[] mFrequenciesHz;
+ private final float[] mOutputAccelerationsGs;
+ private final float mResonantFrequencyHz;
+ private final float mMaxOutputAccelerationGs;
+ private final float mMinFrequencyHz;
+ private final float mMaxFrequencyHz;
+
+ public FrequencyProfile(Parcel in) {
+ this(in.readFloat(), in.createFloatArray(), in.createFloatArray());
+ }
+
+ /**
+ * Default constructor.
+ *
+ * @param resonantFrequencyHz The vibrator resonant frequency, in hertz.
+ * @param frequenciesHz The supported vibration frequencies, in hertz.
+ * @param outputAccelerationsGs The maximum achievable output acceleration (in Gs) the
+ * device can reach at the supported frequencies.
+ */
+ public FrequencyProfile(float resonantFrequencyHz, float[] frequenciesHz,
+ float[] outputAccelerationsGs) {
+
+ mResonantFrequencyHz = resonantFrequencyHz;
+
+ boolean isValid = !Float.isNaN(resonantFrequencyHz)
+ && (resonantFrequencyHz > 0)
+ && (frequenciesHz != null && outputAccelerationsGs != null)
+ && (frequenciesHz.length == outputAccelerationsGs.length)
+ && (frequenciesHz.length > 0);
+
+ if (!isValid) {
+ mFrequenciesHz = null;
+ mOutputAccelerationsGs = null;
+ mMinFrequencyHz = Float.NaN;
+ mMaxFrequencyHz = Float.NaN;
+ mMaxOutputAccelerationGs = Float.NaN;
+ return;
+ }
+
+ TreeMap<Float, Float> frequencyToOutputAccelerationMap = new TreeMap<>();
+
+ for (int i = 0; i < frequenciesHz.length; i++) {
+ frequencyToOutputAccelerationMap.putIfAbsent(frequenciesHz[i],
+ outputAccelerationsGs[i]);
+ }
+
+ float[] frequencies = new float[frequencyToOutputAccelerationMap.size()];
+ float[] accelerations = new float[frequencyToOutputAccelerationMap.size()];
+ float maxOutputAccelerationGs = 0;
+ int i = 0;
+ for (Map.Entry<Float, Float> entry : frequencyToOutputAccelerationMap.entrySet()) {
+ frequencies[i] = entry.getKey();
+ accelerations[i] = entry.getValue();
+ maxOutputAccelerationGs = Math.max(maxOutputAccelerationGs, entry.getValue());
+ i++;
+ }
+
+ mFrequenciesHz = frequencies;
+ mOutputAccelerationsGs = accelerations;
+ mMinFrequencyHz = mFrequenciesHz[0];
+ mMaxFrequencyHz = mFrequenciesHz[mFrequenciesHz.length - 1];
+ mMaxOutputAccelerationGs = maxOutputAccelerationGs;
+ }
+
+ /** Returns true if the supported frequency range is null. */
+ public boolean isEmpty() {
+ return mFrequenciesHz == null;
+ }
+
+ /**
+ * Returns a list of available frequencies.
+ */
+ @Nullable
+ public float[] getFrequenciesHz() {
+ return mFrequenciesHz;
+ }
+
+ /** Returns the list of available output accelerations */
+ @Nullable
+ public float[] getOutputAccelerationsGs() {
+ return mOutputAccelerationsGs;
+ }
+
+ /** Maximum output acceleration reachable in Gs when amplitude is 1.0f. */
+ public float getMaxOutputAccelerationGs() {
+ return mMaxOutputAccelerationGs;
+ }
+
+ /**
+ * Calculates the maximum output acceleration for a given frequency using linear
+ * interpolation.
+ *
+ * @param frequencyHz frequency, in hertz, for query.
+ * @return the maximum output acceleration for the given frequency.
+ */
+ public float getOutputAccelerationGs(float frequencyHz) {
+ if (mFrequenciesHz == null) {
+ return Float.NaN;
+ }
+
+ if (frequencyHz < mMinFrequencyHz || frequencyHz > mMaxFrequencyHz) {
+ // Outside supported frequency range, not able to vibrate at this frequency.
+ return 0;
+ }
+
+ int idx = Arrays.binarySearch(mFrequenciesHz, frequencyHz);
+ if (idx >= 0) {
+ return mOutputAccelerationsGs[idx];
+ }
+
+ // This indicates that the value was not found in the list. Adjust index of the
+ // insertion point to be at the lower bound.
+ idx = -idx - 2;
+
+ // Linearly interpolate the output acceleration based on the frequency.
+ return MathUtils.constrainedMap(
+ mOutputAccelerationsGs[idx], mOutputAccelerationsGs[idx + 1],
+ mFrequenciesHz[idx], mFrequenciesHz[idx + 1],
+ frequencyHz);
+ }
+
+ /** The minimum frequency supported, in hertz. */
+ public float getMinFrequencyHz() {
+ return mMinFrequencyHz;
+ }
+
+ /** The maximum frequency supported, in hertz. */
+ public float getMaxFrequencyHz() {
+ return mMaxFrequencyHz;
+ }
+
+ /**
+ * Returns the frequency range that supports the specified minimum output
+ * acceleration.
+ *
+ * @return The frequency range, or null if the specified acceleration
+ * is not achievable on the device.
+ */
+ @Nullable
+ public Range<Float> getFrequencyRangeHz(float minOutputAcceleration) {
+ if (mFrequenciesHz == null || mOutputAccelerationsGs == null
+ || minOutputAcceleration > mMaxOutputAccelerationGs) {
+ return null; // No frequency range available
+ }
+
+ if (minOutputAcceleration <= 0) {
+ return new Range<>(mMinFrequencyHz, mMaxFrequencyHz);
+ }
+
+ float minFrequency = Float.NaN;
+ float maxFrequency = Float.NaN;
+ int lowerFrequencyBoundIndex = 0;
+ // Find the lower frequency bound
+ for (int i = 0; i < mOutputAccelerationsGs.length; i++) {
+ if (mOutputAccelerationsGs[i] >= minOutputAcceleration) {
+ if (i == 0) {
+ minFrequency = mMinFrequencyHz;
+ } else {
+ minFrequency = MathUtils.constrainedMap(
+ mFrequenciesHz[i - 1], mFrequenciesHz[i],
+ mOutputAccelerationsGs[i - 1], mOutputAccelerationsGs[i],
+ minOutputAcceleration);
+ }
+ lowerFrequencyBoundIndex = i;
+ break; // Found the lower bound
+ }
+ }
+
+ if (Float.isNaN(minFrequency)) {
+ // Lower bound was not found
+ return null;
+ }
+
+ // Find the upper frequency bound
+ for (int i = lowerFrequencyBoundIndex; i < mOutputAccelerationsGs.length; i++) {
+ if (mOutputAccelerationsGs[i] <= minOutputAcceleration) {
+ maxFrequency = MathUtils.constrainedMap(
+ mFrequenciesHz[i - 1], mFrequenciesHz[i],
+ mOutputAccelerationsGs[i - 1], mOutputAccelerationsGs[i],
+ minOutputAcceleration);
+ break; // Found the upper bound
+ }
+ }
+
+ if (Float.isNaN(maxFrequency)) {
+ // If the upper bound was not found, the specified output acceleration is
+ // achievable at all remaining frequencies.
+ maxFrequency = mMaxFrequencyHz;
+ }
+
+ return new Range<>(minFrequency, maxFrequency);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mResonantFrequencyHz);
+ dest.writeFloatArray(mFrequenciesHz);
+ dest.writeFloatArray(mOutputAccelerationsGs);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FrequencyProfile that)) {
+ return false;
+ }
+ return Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
+ && Arrays.equals(mFrequenciesHz, that.mFrequenciesHz)
+ && Arrays.equals(mOutputAccelerationsGs, that.mOutputAccelerationsGs);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mResonantFrequencyHz, Arrays.hashCode(mFrequenciesHz),
+ Arrays.hashCode(mOutputAccelerationsGs));
+ }
+
+ @Override
+ public String toString() {
+ return "FrequencyProfile{"
+ + "mResonantFrequency=" + mResonantFrequencyHz
+ + ", mFrequenciesHz=" + Arrays.toString(mFrequenciesHz)
+ + ", mOutputAccelerationsGs=" + Arrays.toString(mOutputAccelerationsGs)
+ + ", mMinFrequencyHz=" + mMinFrequencyHz
+ + ", mMaxFrequencyHz=" + mMaxFrequencyHz
+ + ", mMaxOutputAccelerationGs=" + mMaxOutputAccelerationGs
+ + '}';
+ }
+
+ @NonNull
+ public static final Creator<FrequencyProfile> CREATOR =
+ new Creator<FrequencyProfile>() {
+ @Override
+ public FrequencyProfile createFromParcel(Parcel in) {
+ return new FrequencyProfile(in);
+ }
+
+ @Override
+ public FrequencyProfile[] newArray(int size) {
+ return new FrequencyProfile[size];
+ }
+ };
+
+ private static void deduplicateAndSortList(List<Pair<Float, Float>> list) {
+ if (list == null || list.size() < 2) {
+ return; // Nothing to dedupe
+ }
+
+ list.sort(Comparator.comparing(pair -> pair.first));
+
+ // Remove duplicates from the list
+ int writeIndex = 1;
+ for (int i = 1; i < list.size(); i++) {
+ Pair<Float, Float> currentPair = list.get(i);
+ Pair<Float, Float> previousPair = list.get(writeIndex - 1);
+
+ if (currentPair.first.compareTo(previousPair.first) != 0) {
+ list.set(writeIndex++, currentPair);
+ }
+ }
+ list.subList(writeIndex, list.size()).clear();
+ }
+
+ private static ArrayList<Pair<Float, Float>> extractFrequencyToOutputAccelerationData(
+ float[] frequencies, float[] outputAccelerations) {
+
+ if (frequencies == null || outputAccelerations == null
+ || frequencies.length == 0
+ || frequencies.length != outputAccelerations.length) {
+ return new ArrayList<>(); // Return empty list for invalid or mismatched data
+ }
+
+ ArrayList<Pair<Float, Float>> frequencyToOutputAccelerationList = new ArrayList<>(
+ frequencies.length);
+ for (int i = 0; i < frequencies.length; i++) {
+ frequencyToOutputAccelerationList.add(
+ new Pair<>(frequencies[i], outputAccelerations[i]));
+ }
+
+ return frequencyToOutputAccelerationList;
+ }
+ }
+
+ /**
* Describes the maximum relative output acceleration that can be achieved for each supported
* frequency in a specific vibrator.
*
@@ -834,6 +1171,8 @@
private float mQFactor = Float.NaN;
private FrequencyProfileLegacy mFrequencyProfileLegacy =
new FrequencyProfileLegacy(Float.NaN, Float.NaN, Float.NaN, null);
+ private FrequencyProfile mFrequencyProfile = new FrequencyProfile(Float.NaN, null,
+ null);
private int mMaxEnvelopeEffectSize;
private int mMinEnvelopeEffectControlPointDurationMillis;
private int mMaxEnvelopeEffectControlPointDurationMillis;
@@ -914,6 +1253,16 @@
}
/**
+ * Configure the vibrator frequency information like resonant frequency and frequency to
+ * output acceleration data.
+ */
+ @NonNull
+ public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) {
+ mFrequencyProfile = frequencyProfile;
+ return this;
+ }
+
+ /**
* Configure the maximum number of control points supported for envelope effects on this
* device.
*/
@@ -951,7 +1300,8 @@
return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax,
mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfileLegacy,
- mMaxEnvelopeEffectSize, mMinEnvelopeEffectControlPointDurationMillis,
+ mFrequencyProfile, mMaxEnvelopeEffectSize,
+ mMinEnvelopeEffectControlPointDurationMillis,
mMaxEnvelopeEffectControlPointDurationMillis);
}
diff --git a/core/java/android/os/vibrator/MultiVibratorInfo.java b/core/java/android/os/vibrator/MultiVibratorInfo.java
index 37dae56..1ba8d99 100644
--- a/core/java/android/os/vibrator/MultiVibratorInfo.java
+++ b/core/java/android/os/vibrator/MultiVibratorInfo.java
@@ -27,6 +27,9 @@
import android.util.SparseIntArray;
import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
import java.util.function.Function;
/**
@@ -44,13 +47,18 @@
private static final float EPSILON = 1e-5f;
public MultiVibratorInfo(int id, VibratorInfo[] vibrators) {
- this(id, vibrators, frequencyProfileIntersection(vibrators));
+ this(id, vibrators, frequencyProfileLegacyIntersection(vibrators),
+ frequencyProfileIntersection(vibrators));
}
private MultiVibratorInfo(
- int id, VibratorInfo[] vibrators, FrequencyProfileLegacy mergedProfile) {
+ int id, VibratorInfo[] vibrators,
+ VibratorInfo.FrequencyProfileLegacy mergedLegacyProfile,
+ FrequencyProfile mergedProfile) {
super(id,
- capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
+ capabilitiesIntersection(vibrators,
+ Flags.normalizedPwleEffects() ? mergedProfile.isEmpty()
+ : mergedLegacyProfile.isEmpty()),
supportedEffectsIntersection(vibrators),
supportedBrakingIntersection(vibrators),
supportedPrimitivesAndDurationsIntersection(vibrators),
@@ -59,6 +67,7 @@
integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
+ mergedLegacyProfile,
mergedProfile,
integerLimitIntersection(vibrators,
VibratorInfo::getMaxEnvelopeEffectSize),
@@ -209,7 +218,82 @@
}
@NonNull
- private static FrequencyProfileLegacy frequencyProfileIntersection(VibratorInfo[] infos) {
+ private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
+ if (infos == null || infos.length == 0) {
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ float resonantFreq = floatPropertyIntersection(infos, VibratorInfo::getResonantFrequencyHz);
+
+ if (Float.isNaN(resonantFreq)) {
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ float minFrequency = 0.0f;
+ float maxFrequency = Float.MAX_VALUE;
+ Set<Float> allFrequencies = new TreeSet<>(); // Using TreeSet for automatic sorting
+
+ for (VibratorInfo info : infos) {
+ float newMinFrequency = info.getFrequencyProfile().getMinFrequencyHz();
+ float newMaxFrequency = info.getFrequencyProfile().getMaxFrequencyHz();
+
+ if (Float.isNaN(newMinFrequency) || Float.isNaN(newMaxFrequency)) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ minFrequency = Math.max(minFrequency, newMinFrequency);
+ maxFrequency = Math.min(maxFrequency, newMaxFrequency);
+
+ if (info.getFrequencyProfile().getFrequenciesHz() == null) {
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ for (float frequency : info.getFrequencyProfile().getFrequenciesHz()) {
+ allFrequencies.add(frequency);
+ }
+ }
+
+ if (minFrequency > maxFrequency) {
+ // If the range and intersection are disjoint then the intersection is undefined
+ return new FrequencyProfile(Float.NaN,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ }
+
+ // Trim frequencies to the min/max range
+ Iterator<Float> iterator = allFrequencies.iterator();
+ while (iterator.hasNext()) {
+ float frequency = iterator.next();
+ if (frequency < minFrequency || frequency > maxFrequency) {
+ iterator.remove();
+ }
+ }
+
+ float[] frequencies = new float[allFrequencies.size()];
+ float[] accelerations = new float[allFrequencies.size()];
+ int idx = 0;
+
+ for (Float frequency : allFrequencies) {
+ float outputAcceleration = Float.MAX_VALUE;
+ for (VibratorInfo info : infos) {
+ // This will find the mapped value or interpolate it if needed.
+ outputAcceleration = Math.min(outputAcceleration,
+ info.getFrequencyProfile().getOutputAccelerationGs(frequency));
+ }
+ frequencies[idx] = frequency;
+ accelerations[idx] = outputAcceleration;
+ idx++;
+ }
+
+ return new FrequencyProfile(resonantFreq, frequencies, accelerations);
+ }
+
+ @NonNull
+ private static FrequencyProfileLegacy frequencyProfileLegacyIntersection(VibratorInfo[] infos) {
float freqResolution = floatPropertyIntersection(infos,
info -> info.getFrequencyProfileLegacy().getFrequencyResolutionHz());
float resonantFreq = floatPropertyIntersection(infos,
diff --git a/core/java/android/os/vibrator/VibratorFrequencyProfile.java b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
new file mode 100644
index 0000000..2b5f9bf
--- /dev/null
+++ b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.VibratorInfo;
+import android.util.Range;
+import android.util.SparseArray;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Describes the output of a {@link android.os.Vibrator} for different vibration frequencies.
+ *
+ * <p>The profile contains the vibrator's frequency range (minimum/maximum) and maximum
+ * acceleration, enabling retrieval of supported acceleration levels for specific frequencies, if
+ * the device supports independent frequency control.
+ *
+ * <p>It also describes the max output acceleration (Gs), of a vibration at different supported
+ * frequencies (Hz).
+ *
+ * <p>Vibrators without independent frequency control do not have a frequency profile.
+ */
+@FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+public final class VibratorFrequencyProfile {
+
+ private final VibratorInfo.FrequencyProfile mFrequencyProfile;
+ private final SparseArray<Float> mFrequenciesOutputAcceleration;
+
+ /** @hide */
+ public VibratorFrequencyProfile(@NonNull VibratorInfo.FrequencyProfile frequencyProfile) {
+ Objects.requireNonNull(frequencyProfile);
+ Preconditions.checkArgument(!frequencyProfile.isEmpty(),
+ "Frequency profile must not be empty");
+ mFrequencyProfile = frequencyProfile;
+ mFrequenciesOutputAcceleration = generateFrequencyToAccelerationMap(
+ frequencyProfile.getFrequenciesHz(), frequencyProfile.getOutputAccelerationsGs());
+ }
+
+ /**
+ * Returns a {@link SparseArray} representing the vibrator's output acceleration capabilities
+ * across different frequencies. This map defines the maximum acceleration
+ * the vibrator can achieve at each supported frequency.
+ * <p>The map's keys are frequencies in Hz, and the corresponding values
+ * are the maximum achievable output accelerations in Gs.
+ *
+ * @return A map of frequencies (Hz) to maximum accelerations (Gs).
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @NonNull
+ public SparseArray<Float> getFrequenciesOutputAcceleration() {
+ return mFrequenciesOutputAcceleration;
+ }
+
+ /**
+ * Returns the maximum output acceleration (in Gs) supported by the vibrator.
+ * This value represents the highest acceleration the vibrator can achieve
+ * across its entire frequency range.
+ *
+ * @return The maximum output acceleration in Gs.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public float getMaxOutputAccelerationGs() {
+ return mFrequencyProfile.getMaxOutputAccelerationGs();
+ }
+
+ /**
+ * Returns the frequency range (in Hz) where the vibrator can sustain at least
+ * the given minimum output acceleration (Gs).
+ *
+ * @param minOutputAccelerationGs The minimum desired output acceleration in Gs.
+ * @return A {@link Range} object representing the frequency range where the
+ * vibrator can sustain at least the given minimum acceleration, or null if
+ * the minimum output acceleration cannot be achieved.
+ *
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ @Nullable
+ public Range<Float> getFrequencyRange(float minOutputAccelerationGs) {
+ return mFrequencyProfile.getFrequencyRangeHz(minOutputAccelerationGs);
+ }
+
+ /**
+ * Returns the output acceleration (in Gs) for the given frequency (Hz).
+ * This method provides the actual acceleration the vibrator will produce
+ * when operating at the specified frequency, using linear interpolation over
+ * the {@link #getFrequenciesOutputAcceleration()}.
+ *
+ * @param frequencyHz The frequency in Hz.
+ * @return The output acceleration in Gs for the given frequency.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public float getOutputAccelerationGs(float frequencyHz) {
+ return mFrequencyProfile.getOutputAccelerationGs(frequencyHz);
+ }
+
+ /**
+ * Gets the minimum frequency supported by the vibrator.
+ *
+ * @return the minimum frequency supported by the vibrator, in hertz.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public float getMinFrequencyHz() {
+ return mFrequencyProfile.getMinFrequencyHz();
+ }
+
+ /**
+ * Gets the maximum frequency supported by the vibrator.
+ *
+ * @return the maximum frequency supported by the vibrator, in hertz.
+ */
+ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public float getMaxFrequencyHz() {
+ return mFrequencyProfile.getMaxFrequencyHz();
+ }
+
+ private static SparseArray<Float> generateFrequencyToAccelerationMap(
+ float[] frequencies, float[] accelerations) {
+ SparseArray<Float> sparseArray = new SparseArray<>(frequencies.length);
+
+ for (int i = 0; i < frequencies.length; i++) {
+ int frequency = (int) frequencies[i];
+ float acceleration = accelerations[i];
+
+ sparseArray.put(frequency,
+ Math.min(acceleration, sparseArray.get(frequency, Float.MAX_VALUE)));
+
+ }
+
+ return sparseArray;
+ }
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index bca5bcc..feeb339 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -206,17 +206,6 @@
}
flag {
- name: "apex_signature_permission_allowlist_enabled"
- is_fixed_read_only: true
- namespace: "permissions"
- description: "Enable reading signature permission allowlist from APEXes"
- bug: "308573169"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "check_op_validate_package"
namespace: "permissions"
description: "Validate package/uid match in checkOp similar to noteOp"
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 98d58d0..5ecf361 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -3209,7 +3209,11 @@
return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_NOT_SET, null);
}
- private static boolean isCloudOrSimAccount(@DefaultAccountState int state) {
+ /**
+ *
+ * @hide
+ */
+ public static boolean isCloudOrSimAccount(@DefaultAccountState int state) {
return state == DEFAULT_ACCOUNT_STATE_CLOUD
|| state == DEFAULT_ACCOUNT_STATE_SIM;
}
@@ -3287,23 +3291,20 @@
Bundle response = nullSafeCall(resolver, ContactsContract.AUTHORITY_URI,
QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD, null, null);
- int defaultContactsAccountState = response.getInt(KEY_DEFAULT_ACCOUNT_STATE, -1);
- if (defaultContactsAccountState
- == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) {
+ int defaultAccountState = response.getInt(KEY_DEFAULT_ACCOUNT_STATE, -1);
+ if (DefaultAccountAndState.isCloudOrSimAccount(defaultAccountState)) {
String accountName = response.getString(Settings.ACCOUNT_NAME);
String accountType = response.getString(Settings.ACCOUNT_TYPE);
if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
throw new IllegalStateException(
"account name and type cannot be null or empty");
}
- return new DefaultAccountAndState(
- DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD,
+ return new DefaultAccountAndState(defaultAccountState,
new Account(accountName, accountType));
- } else if (defaultContactsAccountState
- == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL
- || defaultContactsAccountState
+ } else if (defaultAccountState == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL
+ || defaultAccountState
== DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET) {
- return new DefaultAccountAndState(defaultContactsAccountState, /*cloudAccount=*/
+ return new DefaultAccountAndState(defaultAccountState, /*account=*/
null);
} else {
throw new IllegalStateException("Invalid default account state");
@@ -3348,12 +3349,11 @@
Bundle extras = new Bundle();
extras.putInt(KEY_DEFAULT_ACCOUNT_STATE, defaultAccountAndState.getState());
- if (defaultAccountAndState.getState()
- == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) {
- Account cloudAccount = defaultAccountAndState.getAccount();
- assert cloudAccount != null;
- extras.putString(Settings.ACCOUNT_NAME, cloudAccount.name);
- extras.putString(Settings.ACCOUNT_TYPE, cloudAccount.type);
+ if (DefaultAccountAndState.isCloudOrSimAccount(defaultAccountAndState.getState())) {
+ Account account = defaultAccountAndState.getAccount();
+ assert account != null;
+ extras.putString(Settings.ACCOUNT_NAME, account.name);
+ extras.putString(Settings.ACCOUNT_TYPE, account.type);
}
nullSafeCall(resolver, ContactsContract.AUTHORITY_URI,
SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD, null, extras);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index f7745d1..83b4971 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.flags.Flags.FLAG_SURFACE_VIEW_SET_COMPOSITION_ORDER;
import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
@@ -770,6 +771,36 @@
}
/**
+ * Controls the composition order of the SurfaceView. A non-negative composition order
+ * value indicates that the SurfaceView is composited on top of the parent window, while
+ * a negative composition order indicates that the SurfaceView is behind the parent
+ * window. A SurfaceView with a higher value appears above its peers with lower values.
+ * For SurfaceViews with the same composition order value, their relative order is
+ * undefined.
+ *
+ * @param compositionOrder the composition order of the surface view.
+ */
+ @FlaggedApi(FLAG_SURFACE_VIEW_SET_COMPOSITION_ORDER)
+ public void setCompositionOrder(int compositionOrder) {
+ mRequestedSubLayer = compositionOrder;
+ if (mSubLayer != mRequestedSubLayer) {
+ updateSurface();
+ }
+ }
+
+ /**
+ * Returns the composition order of the SurfaceView.
+ *
+ * @return composition order of the SurfaceView.
+ *
+ * @see #setCompositionOrder(int)
+ */
+ @FlaggedApi(FLAG_SURFACE_VIEW_SET_COMPOSITION_ORDER)
+ public int getCompositionOrder() {
+ return mRequestedSubLayer;
+ }
+
+ /**
* Control whether the surface view's surface is placed on top of another
* regular surface view in the window (but still behind the window itself).
* This is typically used to place overlays on top of an underlying media
@@ -1257,7 +1288,8 @@
final Transaction surfaceUpdateTransaction = new Transaction();
if (creating) {
updateOpaqueFlag();
- final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+ final String name = Integer.toHexString(System.identityHashCode(this))
+ + " SurfaceView[" + viewRoot.getTitle().toString() + "]";
createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
} else if (mSurfaceControl == null) {
return;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index b9e9750..69cbb9b 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -11,6 +11,13 @@
}
flag {
+ name: "a11y_is_required_api"
+ namespace: "accessibility"
+ description: "Adds an API to indicate whether a form field (or similar element) is required."
+ bug: "362784403"
+}
+
+flag {
name: "a11y_overlay_callbacks"
is_exported: true
namespace: "accessibility"
@@ -210,3 +217,13 @@
description: "Feature flag for declaring system pinch zoom opt-out apis"
bug: "315089687"
}
+
+flag {
+ name: "warning_use_default_dialog_type"
+ namespace: "accessibility"
+ description: "Uses the default type for the A11yService warning dialog, instead of SYSTEM_ALERT_DIALOG"
+ bug: "336719951"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index f570a9a..1cf26ab 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -97,4 +97,23 @@
description: "Disable Draw Wakelock starting U."
bug: "331698645"
is_fixed_read_only: true
+}
+
+flag {
+ name: "calculate_bounds_in_parent_from_bounds_in_screen"
+ namespace: "accessibility"
+ description: "Calculate bounds in parent of each node in ViewStructure from its bounds set relative to screen and its parent's"
+ bug: "366131857"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "surface_view_set_composition_order"
+ namespace: "window_surfaces"
+ description: "Add a SurfaceView composition order control API."
+ bug: "341021569"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 7366b9a..ab5969b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -16,10 +16,12 @@
package android.webkit;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -151,6 +153,24 @@
public static final long ENABLE_SIMPLIFIED_DARK_MODE = 214741472L;
/**
+ * Enable User-Agent Reduction for webview.
+ * The OS, CPU, and Build information in the default User-Agent will be
+ * reduced to the static "Linux; Android 10; K" string.
+ * Minor/build/patch version information in the default User-Agent is
+ * reduced to "0.0.0". The rest of the default User-Agent remains unchanged.
+ *
+ * See https://developers.google.com/privacy-sandbox/protections/user-agent
+ * for details related to User-Agent Reduction.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @FlaggedApi(android.webkit.Flags.FLAG_USER_AGENT_REDUCTION)
+ @SystemApi
+ public static final long ENABLE_USER_AGENT_REDUCTION = 371034303L;
+
+ /**
* Default cache usage mode. If the navigation type doesn't impose any
* specific behavior, use cached resources when they are available
* and not expired, otherwise load resources from the network.
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
index b21a490..d1013a9 100644
--- a/core/java/android/webkit/flags.aconfig
+++ b/core/java/android/webkit/flags.aconfig
@@ -18,3 +18,11 @@
bug: "310653407"
is_fixed_read_only: true
}
+
+flag {
+ name: "user_agent_reduction"
+ is_exported: true
+ namespace: "webview"
+ description: "New feature reduce user-agent for webview"
+ bug: "371034303"
+}
diff --git a/core/java/android/window/OWNERS b/core/java/android/window/OWNERS
index 2c61df9..77c99b9 100644
--- a/core/java/android/window/OWNERS
+++ b/core/java/android/window/OWNERS
@@ -1,3 +1,5 @@
set noparent
include /services/core/java/com/android/server/wm/OWNERS
+
+per-file DesktopModeFlags.java = file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index b22aa22..31bb3a6 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -319,4 +319,11 @@
namespace: "lse_desktop_experience"
description: "Enables custom transitions for alt-tab app launches in Desktop Mode."
bug: "370735595"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "enable_move_to_next_display_shortcut"
+ namespace: "lse_desktop_experience"
+ description: "Add new keyboard shortcut of moving a task into next display"
+ bug: "364154795"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index c9b93c9..622f8c8 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -173,6 +173,16 @@
}
flag {
+ name: "filter_irrelevant_input_device_change"
+ namespace: "windowing_frontend"
+ description: "Recompute display configuration only for necessary input device changes"
+ bug: "368461853"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "respect_non_top_visible_fixed_orientation"
namespace: "windowing_frontend"
description: "If top activity is not opaque, respect the fixed orientation of activity behind it"
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
index 0f8ced2..3557633 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
@@ -29,6 +29,7 @@
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.view.accessibility.Flags;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@@ -58,13 +59,15 @@
@NonNull View.OnClickListener uninstallListener) {
final AlertDialog ad = new AlertDialog.Builder(context)
.setView(createAccessibilityServiceWarningDialogContentView(
- context, info, allowListener, denyListener, uninstallListener))
+ context, info, allowListener, denyListener, uninstallListener))
.setCancelable(true)
.create();
Window window = ad.getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
- params.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+ if (!Flags.warningUseDefaultDialogType()) {
+ params.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+ }
window.setAttributes(params);
return ad;
}
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index 39aadfb..8faaf95 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -53,19 +53,18 @@
* @hide
*/
public class AconfigFlags {
+ private static final boolean DEBUG = false;
private static final String LOG_TAG = "AconfigFlags";
-
- public enum Permission {
- READ_WRITE,
- READ_ONLY
- }
+ private static final String OVERRIDE_PREFIX = "device_config_overrides/";
+ private static final String STAGED_PREFIX = "staged/";
private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
- private final ArrayMap<String, Permission> mFlagPermissions = new ArrayMap<>();
public AconfigFlags() {
if (!Flags.manifestFlagging()) {
- Slog.v(LOG_TAG, "Feature disabled, skipped all loading");
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Feature disabled, skipped all loading");
+ }
return;
}
final var defaultFlagProtoFiles =
@@ -130,19 +129,17 @@
if (!"false".equalsIgnoreCase(value) && !"true".equalsIgnoreCase(value)) {
continue;
}
- final var overridePrefix = "device_config_overrides/";
- final var stagedPrefix = "staged/";
String separator = "/";
String prefix = "default";
int priority = 0;
- if (name.startsWith(overridePrefix)) {
- prefix = overridePrefix;
- name = name.substring(overridePrefix.length());
+ if (name.startsWith(OVERRIDE_PREFIX)) {
+ prefix = OVERRIDE_PREFIX;
+ name = name.substring(OVERRIDE_PREFIX.length());
separator = ":";
priority = 20;
- } else if (name.startsWith(stagedPrefix)) {
- prefix = stagedPrefix;
- name = name.substring(stagedPrefix.length());
+ } else if (name.startsWith(STAGED_PREFIX)) {
+ prefix = STAGED_PREFIX;
+ name = name.substring(STAGED_PREFIX.length());
separator = "*";
priority = 10;
}
@@ -155,12 +152,19 @@
if (!mFlagValues.containsKey(flagPackageAndName)) {
continue;
}
- Slog.d(LOG_TAG, "Found " + prefix
- + " Aconfig flag value for " + flagPackageAndName + " = " + value);
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Found " + prefix
+ + " Aconfig flag value in settings for " + flagPackageAndName
+ + " = " + value);
+ }
final Integer currentPriority = flagPriority.get(flagPackageAndName);
if (currentPriority != null && currentPriority >= priority) {
- Slog.i(LOG_TAG, "Skipping " + prefix + " flag " + flagPackageAndName
- + " because of the existing one with priority " + currentPriority);
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Skipping " + prefix + " flag "
+ + flagPackageAndName
+ + " in settings because of existing one with priority "
+ + currentPriority);
+ }
continue;
}
flagPriority.put(flagPackageAndName, priority);
@@ -185,15 +189,7 @@
for (parsed_flag flag : parsedFlags.parsedFlag) {
String flagPackageAndName = flag.package_ + "." + flag.name;
boolean flagValue = (flag.state == Aconfig.ENABLED);
- Slog.v(LOG_TAG, "Read Aconfig default flag value "
- + flagPackageAndName + " = " + flagValue);
mFlagValues.put(flagPackageAndName, flagValue);
-
- Permission permission = flag.permission == Aconfig.READ_ONLY
- ? Permission.READ_ONLY
- : Permission.READ_WRITE;
-
- mFlagPermissions.put(flagPackageAndName, permission);
}
}
@@ -203,24 +199,15 @@
* @return the current value of the given Aconfig flag, or null if there is no such flag
*/
@Nullable
- public Boolean getFlagValue(@NonNull String flagPackageAndName) {
+ private Boolean getFlagValue(@NonNull String flagPackageAndName) {
Boolean value = mFlagValues.get(flagPackageAndName);
- Slog.d(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
+ }
return value;
}
/**
- * Get the flag permission, or null if the flag doesn't exist.
- * @param flagPackageAndName Full flag name formatted as 'package.flag'
- * @return the current permission of the given Aconfig flag, or null if there is no such flag
- */
- @Nullable
- public Permission getFlagPermission(@NonNull String flagPackageAndName) {
- Permission permission = mFlagPermissions.get(flagPackageAndName);
- return permission;
- }
-
- /**
* Check if the element in {@code parser} should be skipped because of the feature flag.
* @param parser XML parser object currently parsing an element
* @return true if the element is disabled because of its feature flag
@@ -247,7 +234,7 @@
}
// Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
if (flagValue == negated) {
- Slog.v(LOG_TAG, "Skipping element " + parser.getName()
+ Slog.i(LOG_TAG, "Skipping element " + parser.getName()
+ " behind feature flag " + featureFlag + " = " + flagValue);
return true;
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
new file mode 100644
index 0000000..12e1dd9
--- /dev/null
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2024 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.internal.widget;
+
+import android.app.Notification.ProgressStyle;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ProgressBar;
+import android.widget.RemoteViews;
+
+import androidx.annotation.ColorInt;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.NotificationProgressDrawable.Part;
+import com.android.internal.widget.NotificationProgressDrawable.Point;
+import com.android.internal.widget.NotificationProgressDrawable.Segment;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * NotificationProgressBar extends the capabilities of ProgressBar by adding functionalities to
+ * represent Notification ProgressStyle progress, such as for ridesharing and navigation.
+ */
+@RemoteViews.RemoteView
+public class NotificationProgressBar extends ProgressBar {
+ public NotificationProgressBar(Context context) {
+ this(context, null);
+ }
+
+ public NotificationProgressBar(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.progressBarStyle);
+ }
+
+ public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * Processes the ProgressStyle data and convert to list of {@code
+ * NotificationProgressDrawable.Part}.
+ */
+ @VisibleForTesting
+ public static List<Part> processAndConvertToDrawableParts(
+ List<ProgressStyle.Segment> segments,
+ List<ProgressStyle.Point> points,
+ int progress,
+ boolean isStyledByProgress
+ ) {
+ if (segments.isEmpty()) {
+ throw new IllegalArgumentException("List of segments shouldn't be empty");
+ }
+
+ for (ProgressStyle.Segment segment : segments) {
+ final int length = segment.getLength();
+ if (length <= 0) {
+ throw new IllegalArgumentException("Invalid segment length : " + length);
+ }
+ }
+
+ final int progressMax = segments.stream().mapToInt(ProgressStyle.Segment::getLength).sum();
+
+ if (progress < 0 || progress > progressMax) {
+ throw new IllegalArgumentException("Invalid progress : " + progress);
+ }
+ for (ProgressStyle.Point point : points) {
+ final int pos = point.getPosition();
+ if (pos <= 0 || pos >= progressMax) {
+ throw new IllegalArgumentException("Invalid Point position : " + pos);
+ }
+ }
+
+ final Map<Integer, ProgressStyle.Segment> startToSegmentMap = generateStartToSegmentMap(
+ segments);
+ final Map<Integer, ProgressStyle.Point> positionToPointMap = generatePositionToPointMap(
+ points);
+ final SortedSet<Integer> sortedPos = generateSortedPositionSet(startToSegmentMap,
+ positionToPointMap, progress, isStyledByProgress);
+
+ final Map<Integer, ProgressStyle.Segment> startToSplitSegmentMap =
+ splitSegmentsByPointsAndProgress(
+ startToSegmentMap, sortedPos, progressMax);
+
+ return convertToDrawableParts(startToSplitSegmentMap, positionToPointMap, sortedPos,
+ progress, progressMax,
+ isStyledByProgress);
+ }
+
+
+ // Any segment with a point on it gets split by the point.
+ // If isStyledByProgress is true, also split the segment with the progress value in its range.
+ private static Map<Integer, ProgressStyle.Segment> splitSegmentsByPointsAndProgress(
+ Map<Integer, ProgressStyle.Segment> startToSegmentMap,
+ SortedSet<Integer> sortedPos,
+ int progressMax) {
+ int prevSegStart = 0;
+ for (Integer pos : sortedPos) {
+ if (pos == 0 || pos == progressMax) continue;
+ if (startToSegmentMap.containsKey(pos)) {
+ prevSegStart = pos;
+ continue;
+ }
+
+ final ProgressStyle.Segment prevSeg = startToSegmentMap.get(prevSegStart);
+ final ProgressStyle.Segment leftSeg = new ProgressStyle.Segment(
+ pos - prevSegStart).setColor(
+ prevSeg.getColor());
+ final ProgressStyle.Segment rightSeg = new ProgressStyle.Segment(
+ prevSegStart + prevSeg.getLength() - pos).setColor(prevSeg.getColor());
+
+ startToSegmentMap.put(prevSegStart, leftSeg);
+ startToSegmentMap.put(pos, rightSeg);
+
+ prevSegStart = pos;
+ }
+
+ return startToSegmentMap;
+ }
+
+ private static List<Part> convertToDrawableParts(
+ Map<Integer, ProgressStyle.Segment> startToSegmentMap,
+ Map<Integer, ProgressStyle.Point> positionToPointMap,
+ SortedSet<Integer> sortedPos,
+ int progress,
+ int progressMax,
+ boolean isStyledByProgress
+ ) {
+ List<Part> parts = new ArrayList<>();
+ boolean styleRemainingParts = false;
+ for (Integer pos : sortedPos) {
+ if (positionToPointMap.containsKey(pos)) {
+ final ProgressStyle.Point point = positionToPointMap.get(pos);
+ final int color = maybeGetFadedColor(point.getColor(), styleRemainingParts);
+ parts.add(new Point(null, color, styleRemainingParts));
+ }
+ // We want the Point at the current progress to be filled (not faded), but a Segment
+ // starting at this progress to be faded.
+ if (isStyledByProgress && !styleRemainingParts && pos == progress) {
+ styleRemainingParts = true;
+ }
+ if (startToSegmentMap.containsKey(pos)) {
+ final ProgressStyle.Segment seg = startToSegmentMap.get(pos);
+ final int color = maybeGetFadedColor(seg.getColor(), styleRemainingParts);
+ parts.add(new Segment(
+ (float) seg.getLength() / progressMax, color, styleRemainingParts));
+ }
+ }
+
+ return parts;
+ }
+
+ @ColorInt
+ private static int maybeGetFadedColor(@ColorInt int color, boolean fade) {
+ if (!fade) return color;
+
+ return NotificationProgressDrawable.getFadedColor(color);
+ }
+
+ private static Map<Integer, ProgressStyle.Segment> generateStartToSegmentMap(
+ List<ProgressStyle.Segment> segments) {
+ final Map<Integer, ProgressStyle.Segment> startToSegmentMap = new HashMap<>();
+
+ int currentStart = 0; // Initial start position is 0
+
+ for (ProgressStyle.Segment segment : segments) {
+ // Use the current start position as the key, and the segment as the value
+ startToSegmentMap.put(currentStart, segment);
+
+ // Update the start position for the next segment
+ currentStart += segment.getLength();
+ }
+
+ return startToSegmentMap;
+ }
+
+ private static Map<Integer, ProgressStyle.Point> generatePositionToPointMap(
+ List<ProgressStyle.Point> points) {
+ final Map<Integer, ProgressStyle.Point> positionToPointMap = new HashMap<>();
+
+ for (ProgressStyle.Point point : points) {
+ positionToPointMap.put(point.getPosition(), point);
+ }
+
+ return positionToPointMap;
+ }
+
+ private static SortedSet<Integer> generateSortedPositionSet(
+ Map<Integer, ProgressStyle.Segment> startToSegmentMap,
+ Map<Integer, ProgressStyle.Point> positionToPointMap, int progress,
+ boolean isStyledByProgress) {
+ final SortedSet<Integer> sortedPos = new TreeSet<>(startToSegmentMap.keySet());
+ sortedPos.addAll(positionToPointMap.keySet());
+ if (isStyledByProgress) {
+ sortedPos.add(progress);
+ }
+
+ return sortedPos;
+ }
+}
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index 4d88546..89ef875 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -45,6 +45,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Objects;
/**
* This is used by NotificationProgressBar for displaying a custom background. It composes of
@@ -126,7 +127,7 @@
* @see #setStroke(int, int, float, float)
*/
public void setStrokeDefaultColor(@ColorInt int color) {
- mState.mStrokeColor = color;
+ mState.setStrokeColor(color);
}
/**
@@ -138,7 +139,7 @@
* @see #mutate()
*/
public void setPointRectDefaultColor(@ColorInt int color) {
- mState.mPointRectColor = color;
+ mState.setPointRectColor(color);
}
private void setStrokeInternal(int width, float dashWidth, float dashGap) {
@@ -194,7 +195,7 @@
mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
: mState.mStrokeColor);
mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
- : mState.mStrokeColor);
+ : mState.mFadedStrokeColor);
// Leave space for the rounded line cap which extends beyond start/end.
final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
@@ -220,7 +221,8 @@
mPointRectF.inset(inset, inset);
mFillPaint.setColor(point.mColor != Color.TRANSPARENT ? point.mColor
- : mState.mPointRectColor);
+ : (point.mFaded ? mState.mFadedPointRectColor
+ : mState.mPointRectColor));
canvas.drawRoundRect(mPointRectF, cornerRadius, cornerRadius, mFillPaint);
}
@@ -424,8 +426,9 @@
state.mPointRectCornerRadius = a.getDimension(
R.styleable.NotificationProgressDrawablePoints_cornerRadius,
state.mPointRectCornerRadius);
- state.mPointRectColor = a.getColor(R.styleable.NotificationProgressDrawablePoints_color,
+ final int color = a.getColor(R.styleable.NotificationProgressDrawablePoints_color,
state.mPointRectColor);
+ setPointRectDefaultColor(color);
}
static int resolveDensity(@Nullable Resources r, int parentDensity) {
@@ -478,7 +481,6 @@
* {@link Point} with zero length.
*/
public interface Part {
-
}
/**
@@ -521,6 +523,24 @@
return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", dashed="
+ this.mDashed + ')';
}
+
+ // Needed for unit tests
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (this == other) return true;
+
+ if (other == null || getClass() != other.getClass()) return false;
+
+ Segment that = (Segment) other;
+ if (Float.compare(this.mFraction, that.mFraction) != 0) return false;
+ if (this.mColor != that.mColor) return false;
+ return this.mDashed == that.mDashed;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFraction, mColor, mDashed);
+ }
}
/**
@@ -532,14 +552,21 @@
@Nullable
private final Drawable mIcon;
@ColorInt private final int mColor;
+ private final boolean mFaded;
public Point(@Nullable Drawable icon) {
- this(icon, Color.TRANSPARENT);
+ this(icon, Color.TRANSPARENT, false);
}
public Point(@Nullable Drawable icon, @ColorInt int color) {
+ this(icon, color, false);
+
+ }
+
+ public Point(@Nullable Drawable icon, @ColorInt int color, boolean faded) {
mIcon = icon;
mColor = color;
+ mFaded = faded;
}
@Nullable
@@ -547,9 +574,37 @@
return this.mIcon;
}
+ public int getColor() {
+ return this.mColor;
+ }
+
+ public boolean getFaded() {
+ return this.mFaded;
+ }
+
@Override
public String toString() {
- return "Point(icon=" + this.mIcon + ", color=" + this.mColor + ')';
+ return "Point(icon=" + this.mIcon + ", color=" + this.mColor + ", faded=" + this.mFaded
+ + ")";
+ }
+
+ // Needed for unit tests.
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (this == other) return true;
+
+ if (other == null || getClass() != other.getClass()) return false;
+
+ Point that = (Point) other;
+
+ if (!Objects.equals(this.mIcon, that.mIcon)) return false;
+ if (this.mColor != that.mColor) return false;
+ return this.mFaded == that.mFaded;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIcon, mColor, mFaded);
}
}
@@ -576,12 +631,14 @@
float mSegPointGap = 0.0f;
int mStrokeWidth = 0;
int mStrokeColor;
+ int mFadedStrokeColor;
float mStrokeDashWidth = 0.0f;
float mStrokeDashGap = 0.0f;
float mPointRadius;
float mPointRectInset;
float mPointRectCornerRadius;
int mPointRectColor;
+ int mFadedPointRectColor;
int[] mThemeAttrs;
int[] mThemeAttrsSegments;
@@ -595,6 +652,7 @@
State(@NonNull State orig, @Nullable Resources res) {
mChangingConfigurations = orig.mChangingConfigurations;
mStrokeColor = orig.mStrokeColor;
+ mFadedStrokeColor = orig.mFadedStrokeColor;
mStrokeWidth = orig.mStrokeWidth;
mStrokeDashWidth = orig.mStrokeDashWidth;
mStrokeDashGap = orig.mStrokeDashGap;
@@ -602,6 +660,7 @@
mPointRectInset = orig.mPointRectInset;
mPointRectCornerRadius = orig.mPointRectCornerRadius;
mPointRectColor = orig.mPointRectColor;
+ mFadedPointRectColor = orig.mFadedPointRectColor;
mThemeAttrs = orig.mThemeAttrs;
mThemeAttrsSegments = orig.mThemeAttrsSegments;
@@ -683,10 +742,30 @@
public void setStroke(int width, int color, float dashWidth, float dashGap) {
mStrokeWidth = width;
- mStrokeColor = color;
mStrokeDashWidth = dashWidth;
mStrokeDashGap = dashGap;
+
+ setStrokeColor(color);
}
+
+ public void setStrokeColor(int color) {
+ mStrokeColor = color;
+ mFadedStrokeColor = getFadedColor(color);
+ }
+
+ public void setPointRectColor(int color) {
+ mPointRectColor = color;
+ mFadedPointRectColor = getFadedColor(color);
+ }
+ }
+
+ /**
+ * Get a color with an opacity that's 50% of the input color.
+ */
+ @ColorInt
+ static int getFadedColor(@ColorInt int color) {
+ return Color.argb(Color.alpha(color) / 2, Color.red(color), Color.green(color),
+ Color.blue(color));
}
@Override
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index e65b4b6..46855c6 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -16,14 +16,19 @@
package com.android.internal.widget;
+import static java.lang.Float.NaN;
+
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Path;
+import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -40,6 +45,7 @@
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.RoundedCorner;
+import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
@@ -65,18 +71,21 @@
private static final PointerState EMPTY_POINTER_STATE = new PointerState();
public static class PointerState {
- // Trace of previous points.
- private float[] mTraceX = new float[32];
- private float[] mTraceY = new float[32];
- private boolean[] mTraceCurrent = new boolean[32];
- private int mTraceCount;
+ private float mCurrentX = NaN;
+ private float mCurrentY = NaN;
+ private float mPreviousX = NaN;
+ private float mPreviousY = NaN;
+ private float mFirstX = NaN;
+ private float mFirstY = NaN;
+ private boolean mPreviousPointIsHistorical;
+ private boolean mCurrentPointIsHistorical;
// True if the pointer is down.
@UnsupportedAppUsage
private boolean mCurDown;
// Most recent coordinates.
- private PointerCoords mCoords = new PointerCoords();
+ private final PointerCoords mCoords = new PointerCoords();
private int mToolType;
// Most recent velocity.
@@ -96,31 +105,20 @@
public PointerState() {
}
- public void clearTrace() {
- mTraceCount = 0;
- }
-
- public void addTrace(float x, float y, boolean current) {
- int traceCapacity = mTraceX.length;
- if (mTraceCount == traceCapacity) {
- traceCapacity *= 2;
- float[] newTraceX = new float[traceCapacity];
- System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
- mTraceX = newTraceX;
-
- float[] newTraceY = new float[traceCapacity];
- System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
- mTraceY = newTraceY;
-
- boolean[] newTraceCurrent = new boolean[traceCapacity];
- System.arraycopy(mTraceCurrent, 0, newTraceCurrent, 0, mTraceCount);
- mTraceCurrent= newTraceCurrent;
+ void addTrace(float x, float y, boolean isHistorical) {
+ if (Float.isNaN(mFirstX)) {
+ mFirstX = x;
+ }
+ if (Float.isNaN(mFirstY)) {
+ mFirstY = y;
}
- mTraceX[mTraceCount] = x;
- mTraceY[mTraceCount] = y;
- mTraceCurrent[mTraceCount] = current;
- mTraceCount += 1;
+ mPreviousX = mCurrentX;
+ mPreviousY = mCurrentY;
+ mCurrentX = x;
+ mCurrentY = y;
+ mPreviousPointIsHistorical = mCurrentPointIsHistorical;
+ mCurrentPointIsHistorical = isHistorical;
}
}
@@ -149,6 +147,13 @@
private final SparseArray<PointerState> mPointers = new SparseArray<PointerState>();
private final PointerCoords mTempCoords = new PointerCoords();
+ // Draw the trace of all pointers in the current gesture in a separate layer
+ // that is not cleared on every frame so that we don't have to re-draw the
+ // entire trace on each frame. The trace bitmap is in the coordinate space of the unrotated
+ // display.
+ private Bitmap mTraceBitmap;
+ private final Canvas mTraceCanvas;
+
private final Region mSystemGestureExclusion = new Region();
private final Region mSystemGestureExclusionRejected = new Region();
private final Path mSystemGestureExclusionPath = new Path();
@@ -197,6 +202,9 @@
mPathPaint.setARGB(255, 0, 96, 255);
mPathPaint.setStyle(Paint.Style.STROKE);
+ mTraceCanvas = new Canvas();
+ configureTraceBitmap();
+
configureDensityDependentFactors();
mSystemGestureExclusionPaint = new Paint();
@@ -256,7 +264,7 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mTextPaint.getFontMetricsInt(mTextMetrics);
- mHeaderBottom = mHeaderPaddingTop-mTextMetrics.ascent+mTextMetrics.descent+2;
+ mHeaderBottom = mHeaderPaddingTop - mTextMetrics.ascent + mTextMetrics.descent + 2;
if (false) {
Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
+ " descent=" + mTextMetrics.descent
@@ -268,7 +276,8 @@
// Draw an oval. When angle is 0 radians, orients the major axis vertically,
// angles less than or greater than 0 radians rotate the major axis left or right.
- private RectF mReusableOvalRect = new RectF();
+ private final RectF mReusableOvalRect = new RectF();
+
private void drawOval(Canvas canvas, float x, float y, float major, float minor,
float angle, Paint paint) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
@@ -285,6 +294,12 @@
protected void onDraw(Canvas canvas) {
final int NP = mPointers.size();
+ // Pointer trace.
+ canvas.save();
+ rotateCanvasToUnrotatedDisplay(canvas);
+ canvas.drawBitmap(mTraceBitmap, 0, 0, null);
+ canvas.restore();
+
if (!mSystemGestureExclusion.isEmpty()) {
mSystemGestureExclusionPath.reset();
mSystemGestureExclusion.getBoundaryPath(mSystemGestureExclusionPath);
@@ -300,35 +315,14 @@
// Labels
drawLabels(canvas);
- // Pointer trace.
+ // Current pointer states.
+ canvas.save();
+ rotateCanvasToUnrotatedDisplay(canvas);
for (int p = 0; p < NP; p++) {
final PointerState ps = mPointers.valueAt(p);
+ float lastX = ps.mCurrentX, lastY = ps.mCurrentY;
- // Draw path.
- final int N = ps.mTraceCount;
- float lastX = 0, lastY = 0;
- boolean haveLast = false;
- boolean drawn = false;
- mPaint.setARGB(255, 128, 255, 255);
- for (int i=0; i < N; i++) {
- float x = ps.mTraceX[i];
- float y = ps.mTraceY[i];
- if (Float.isNaN(x) || Float.isNaN(y)) {
- haveLast = false;
- continue;
- }
- if (haveLast) {
- canvas.drawLine(lastX, lastY, x, y, mPathPaint);
- final Paint paint = ps.mTraceCurrent[i - 1] ? mCurrentPointPaint : mPaint;
- canvas.drawPoint(lastX, lastY, paint);
- drawn = true;
- }
- lastX = x;
- lastY = y;
- haveLast = true;
- }
-
- if (drawn) {
+ if (!Float.isNaN(lastX) && !Float.isNaN(lastY)) {
// Draw velocity vector.
mPaint.setARGB(255, 255, 64, 128);
float xVel = ps.mXVelocity * (1000 / 60);
@@ -353,7 +347,7 @@
Math.max(getHeight(), getWidth()), mTargetPaint);
// Draw current point.
- int pressureLevel = (int)(ps.mCoords.pressure * 255);
+ int pressureLevel = (int) (ps.mCoords.pressure * 255);
mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
@@ -406,6 +400,7 @@
}
}
}
+ canvas.restore();
}
private void drawLabels(Canvas canvas) {
@@ -424,8 +419,7 @@
.append(" / ").append(mMaxNumPointers)
.toString(), 1, base, mTextPaint);
- final int count = ps.mTraceCount;
- if ((mCurDown && ps.mCurDown) || count == 0) {
+ if ((mCurDown && ps.mCurDown) || Float.isNaN(ps.mCurrentX)) {
canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
mTextBackgroundPaint);
canvas.drawText(mText.clear()
@@ -437,8 +431,8 @@
.append("Y: ").append(ps.mCoords.y, 1)
.toString(), 1 + itemW * 2, base, mTextPaint);
} else {
- float dx = ps.mTraceX[count - 1] - ps.mTraceX[0];
- float dy = ps.mTraceY[count - 1] - ps.mTraceY[0];
+ float dx = ps.mCurrentX - ps.mFirstX;
+ float dy = ps.mCurrentY - ps.mFirstY;
canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
Math.abs(dx) < mVC.getScaledTouchSlop()
? mTextBackgroundPaint : mTextLevelPaint);
@@ -565,9 +559,9 @@
.append(" TouchMinor=").append(coords.touchMinor, 3)
.append(" ToolMajor=").append(coords.toolMajor, 3)
.append(" ToolMinor=").append(coords.toolMinor, 3)
- .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
+ .append(" Orientation=").append((float) (coords.orientation * 180 / Math.PI), 1)
.append("deg")
- .append(" Tilt=").append((float)(
+ .append(" Tilt=").append((float) (
coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1)
.append("deg")
.append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
@@ -586,6 +580,11 @@
@Override
public void onPointerEvent(MotionEvent event) {
+ // PointerLocationView stores and draws events in the unrotated display space, so undo the
+ // event's rotation to bring it back to the unrotated display space.
+ event.transform(MotionEvent.createRotateMatrix(inverseRotation(event.getSurfaceRotation()),
+ mTraceBitmap.getWidth(), mTraceBitmap.getHeight()));
+
final int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN
@@ -598,6 +597,7 @@
mCurNumPointers = 0;
mMaxNumPointers = 0;
mVelocity.clear();
+ mTraceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
if (mAltVelocity != null) {
mAltVelocity.clear();
}
@@ -646,7 +646,8 @@
logCoords("Pointer", action, i, coords, id, event);
}
if (ps != null) {
- ps.addTrace(coords.x, coords.y, false);
+ ps.addTrace(coords.x, coords.y, /*isHistorical*/ true);
+ updateDrawTrace(ps);
}
}
}
@@ -659,7 +660,8 @@
logCoords("Pointer", action, i, coords, id, event);
}
if (ps != null) {
- ps.addTrace(coords.x, coords.y, true);
+ ps.addTrace(coords.x, coords.y, /*isHistorical*/ false);
+ updateDrawTrace(ps);
ps.mXVelocity = mVelocity.getXVelocity(id);
ps.mYVelocity = mVelocity.getYVelocity(id);
if (mAltVelocity != null) {
@@ -702,13 +704,27 @@
if (mActivePointerId == id) {
mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
}
- ps.addTrace(Float.NaN, Float.NaN, false);
+ ps.addTrace(Float.NaN, Float.NaN, true);
}
}
invalidate();
}
+ private void updateDrawTrace(PointerState ps) {
+ mPaint.setARGB(255, 128, 255, 255);
+ float x = ps.mCurrentX;
+ float y = ps.mCurrentY;
+ float lastX = ps.mPreviousX;
+ float lastY = ps.mPreviousY;
+ if (Float.isNaN(x) || Float.isNaN(y) || Float.isNaN(lastX) || Float.isNaN(lastY)) {
+ return;
+ }
+ mTraceCanvas.drawLine(lastX, lastY, x, y, mPathPaint);
+ Paint paint = ps.mPreviousPointIsHistorical ? mPaint : mCurrentPointPaint;
+ mTraceCanvas.drawPoint(lastX, lastY, paint);
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
onPointerEvent(event);
@@ -767,7 +783,7 @@
return true;
default:
return KeyEvent.isGamepadButton(keyCode)
- || KeyEvent.isModifierKey(keyCode);
+ || KeyEvent.isModifierKey(keyCode);
}
}
@@ -887,7 +903,7 @@
public FasterStringBuilder append(int value, int zeroPadWidth) {
final boolean negative = value < 0;
if (negative) {
- value = - value;
+ value = -value;
if (value < 0) {
append("-2147483648");
return this;
@@ -971,32 +987,34 @@
}
}
- private ISystemGestureExclusionListener mSystemGestureExclusionListener =
+ private final ISystemGestureExclusionListener mSystemGestureExclusionListener =
new ISystemGestureExclusionListener.Stub() {
- @Override
- public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion,
- Region systemGestureExclusionUnrestricted) {
- Region exclusion = Region.obtain(systemGestureExclusion);
- Region rejected = Region.obtain();
- if (systemGestureExclusionUnrestricted != null) {
- rejected.set(systemGestureExclusionUnrestricted);
- rejected.op(exclusion, Region.Op.DIFFERENCE);
- }
- Handler handler = getHandler();
- if (handler != null) {
- handler.post(() -> {
- mSystemGestureExclusion.set(exclusion);
- mSystemGestureExclusionRejected.set(rejected);
- exclusion.recycle();
- invalidate();
- });
- }
- }
- };
+ @Override
+ public void onSystemGestureExclusionChanged(int displayId,
+ Region systemGestureExclusion,
+ Region systemGestureExclusionUnrestricted) {
+ Region exclusion = Region.obtain(systemGestureExclusion);
+ Region rejected = Region.obtain();
+ if (systemGestureExclusionUnrestricted != null) {
+ rejected.set(systemGestureExclusionUnrestricted);
+ rejected.op(exclusion, Region.Op.DIFFERENCE);
+ }
+ Handler handler = getHandler();
+ if (handler != null) {
+ handler.post(() -> {
+ mSystemGestureExclusion.set(exclusion);
+ mSystemGestureExclusionRejected.set(rejected);
+ exclusion.recycle();
+ invalidate();
+ });
+ }
+ }
+ };
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ configureTraceBitmap();
configureDensityDependentFactors();
}
@@ -1008,4 +1026,58 @@
mCurrentPointPaint.setStrokeWidth(1 * mDensity);
mPathPaint.setStrokeWidth(1 * mDensity);
}
+
+ private void configureTraceBitmap() {
+ final var display = mContext.getDisplay();
+ final boolean rotated = display.getRotation() == Surface.ROTATION_90
+ || display.getRotation() == Surface.ROTATION_270;
+ int unrotatedWidth = rotated ? display.getHeight() : display.getWidth();
+ int unrotatedHeight = rotated ? display.getWidth() : display.getHeight();
+
+ if (mTraceBitmap != null && mTraceBitmap.getWidth() == unrotatedWidth
+ && mTraceBitmap.getHeight() == unrotatedHeight) {
+ return;
+ }
+ if (unrotatedWidth <= 0 || unrotatedHeight <= 0) {
+ Slog.w(TAG, "Ignoring configuration: invalid display size: " + unrotatedWidth + "x"
+ + unrotatedHeight);
+ // Initialize the bitmap to an arbitrary size. It should be reconfigured with a valid
+ // size in the future.
+ unrotatedWidth = 100;
+ unrotatedHeight = 100;
+ }
+ mTraceBitmap = Bitmap.createBitmap(unrotatedWidth, unrotatedHeight,
+ Bitmap.Config.ARGB_8888);
+ mTraceCanvas.setBitmap(mTraceBitmap);
+ }
+
+ private static int inverseRotation(@Surface.Rotation int rotation) {
+ return switch(rotation) {
+ case Surface.ROTATION_0 -> Surface.ROTATION_0;
+ case Surface.ROTATION_90 -> Surface.ROTATION_270;
+ case Surface.ROTATION_180 -> Surface.ROTATION_180;
+ case Surface.ROTATION_270 -> Surface.ROTATION_90;
+ default -> {
+ Slog.e(TAG, "Received unexpected surface rotation: " + rotation);
+ yield Surface.ROTATION_0;
+ }
+ };
+ }
+
+ private void rotateCanvasToUnrotatedDisplay(Canvas c) {
+ switch (inverseRotation(mContext.getDisplay().getRotation())) {
+ case Surface.ROTATION_90 -> {
+ c.rotate(90);
+ c.translate(0, -mTraceBitmap.getHeight());
+ }
+ case Surface.ROTATION_180 -> {
+ c.rotate(180);
+ c.translate(-mTraceBitmap.getWidth(), -mTraceBitmap.getHeight());
+ }
+ case Surface.ROTATION_270 -> {
+ c.rotate(270);
+ c.translate(-mTraceBitmap.getWidth(), 0);
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index b16c237..be8ecbe 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -470,6 +470,7 @@
@Test
@SmallTest
+ @DisabledOnRavenwood(blockedBy = ResourcesManager.class)
public void testResourceConfigurationAppliedWhenOverrideDoesNotExist() {
final int width = 240;
final int height = 360;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
index 362eeea..8e906fd 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
@@ -25,6 +25,8 @@
import android.app.AlertDialog;
import android.content.Context;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
@@ -33,6 +35,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
+import android.view.accessibility.Flags;
import android.widget.TextView;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -89,7 +92,19 @@
}
@Test
- public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams() {
+ @RequiresFlagsDisabled(Flags.FLAG_WARNING_USE_DEFAULT_DIALOG_TYPE)
+ public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams_isSystemDialog() {
+ createAccessibilityServiceWarningDialog_hasExpectedWindowParams(true);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_WARNING_USE_DEFAULT_DIALOG_TYPE)
+ public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams_notSystemDialog() {
+ createAccessibilityServiceWarningDialog_hasExpectedWindowParams(false);
+ }
+
+ private void createAccessibilityServiceWarningDialog_hasExpectedWindowParams(
+ boolean expectSystemDialog) {
final AlertDialog dialog =
AccessibilityServiceWarning.createAccessibilityServiceWarningDialog(
mContext,
@@ -101,7 +116,11 @@
expect.that(dialogWindow.getAttributes().privateFlags
& SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- expect.that(dialogWindow.getAttributes().type).isEqualTo(TYPE_SYSTEM_DIALOG);
+ if (expectSystemDialog) {
+ expect.that(dialogWindow.getAttributes().type).isEqualTo(TYPE_SYSTEM_DIALOG);
+ } else {
+ expect.that(dialogWindow.getAttributes().type).isNotEqualTo(TYPE_SYSTEM_DIALOG);
+ }
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
new file mode 100644
index 0000000..6419c1e0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2024 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.internal.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Notification.ProgressStyle;
+import android.graphics.Color;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.widget.NotificationProgressDrawable.Part;
+import com.android.internal.widget.NotificationProgressDrawable.Point;
+import com.android.internal.widget.NotificationProgressDrawable.Segment;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationProgressBarTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_segmentsIsEmpty() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 50;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ isStyledByProgress);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_segmentLengthIsNegative() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(-50));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 50;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ isStyledByProgress);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_segmentLengthIsZero() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(0));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 50;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ isStyledByProgress);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_progressIsNegative() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = -50;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ isStyledByProgress);
+ }
+
+ @Test
+ public void processAndConvertToDrawableParts_progressIsZero() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 0;
+ boolean isStyledByProgress = true;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
+ segments, points, progress, isStyledByProgress);
+
+ int fadedRed = 0x7FFF0000;
+ List<Part> expected = new ArrayList<>(List.of(new Segment(1f, fadedRed, true)));
+
+ assertThat(parts).isEqualTo(expected);
+ }
+
+ @Test
+ public void processAndConvertToDrawableParts_progressAtMax() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 100;
+ boolean isStyledByProgress = true;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
+ segments, points, progress, isStyledByProgress);
+
+ List<Part> expected = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
+
+ assertThat(parts).isEqualTo(expected);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_progressAboveMax() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 150;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ isStyledByProgress);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_pointPositionIsNegative() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(-50).setColor(Color.RED));
+ int progress = 50;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ isStyledByProgress);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_pointPositionIsZero() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(0).setColor(Color.RED));
+ int progress = 50;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ isStyledByProgress);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_pointPositionAtMax() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(100).setColor(Color.RED));
+ int progress = 50;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ isStyledByProgress);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void processAndConvertToDrawableParts_pointPositionAboveMax() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(150).setColor(Color.RED));
+ int progress = 50;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.processAndConvertToDrawableParts(segments, points, progress,
+ isStyledByProgress);
+ }
+
+ @Test
+ public void processAndConvertToDrawableParts_multipleSegmentsWithoutPoints() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 60;
+ boolean isStyledByProgress = true;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
+ segments, points, progress, isStyledByProgress);
+
+ // Colors with 50% opacity
+ int fadedGreen = 0x7F00FF00;
+
+ List<Part> expected = new ArrayList<>(List.of(
+ new Segment(0.50f, Color.RED),
+ new Segment(0.10f, Color.GREEN),
+ new Segment(0.40f, fadedGreen, true)));
+
+ assertThat(parts).isEqualTo(expected);
+ }
+
+ @Test
+ public void processAndConvertToDrawableParts_singleSegmentWithPoints() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(15).setColor(Color.RED));
+ points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
+ int progress = 60;
+ boolean isStyledByProgress = true;
+
+ // Colors with 50% opacity
+ int fadedBlue = 0x7F0000FF;
+ int fadedYellow = 0x7FFFFF00;
+
+ List<Part> expected = new ArrayList<>(List.of(
+ new Segment(0.15f, Color.BLUE),
+ new Point(null, Color.RED),
+ new Segment(0.10f, Color.BLUE),
+ new Point(null, Color.BLUE),
+ new Segment(0.35f, Color.BLUE),
+ new Point(null, Color.BLUE),
+ new Segment(0.15f, fadedBlue, true),
+ new Point(null, fadedYellow, true),
+ new Segment(0.25f, fadedBlue, true)));
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
+ segments, points, progress, isStyledByProgress);
+
+ assertThat(parts).isEqualTo(expected);
+ }
+
+ @Test
+ public void processAndConvertToDrawableParts_multipleSegmentsWithPoints() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(15).setColor(Color.RED));
+ points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
+ int progress = 60;
+ boolean isStyledByProgress = true;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
+ segments, points, progress, isStyledByProgress);
+
+ // Colors with 50% opacity
+ int fadedGreen = 0x7F00FF00;
+ int fadedBlue = 0x7F0000FF;
+ int fadedYellow = 0x7FFFFF00;
+
+ List<Part> expected = new ArrayList<>(List.of(
+ new Segment(0.15f, Color.RED),
+ new Point(null, Color.RED),
+ new Segment(0.10f, Color.RED),
+ new Point(null, Color.BLUE),
+ new Segment(0.25f, Color.RED),
+ new Segment(0.10f, Color.GREEN),
+ new Point(null, Color.BLUE),
+ new Segment(0.15f, fadedGreen, true),
+ new Point(null, fadedYellow, true),
+ new Segment(0.25f, fadedGreen, true)));
+
+ assertThat(parts).isEqualTo(expected);
+ }
+
+ @Test
+ public void processAndConvertToDrawableParts_multipleSegmentsWithPoints_notStyledByProgress() {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
+ segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(15).setColor(Color.RED));
+ points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
+ int progress = 60;
+ boolean isStyledByProgress = false;
+
+ List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts(
+ segments, points, progress, isStyledByProgress);
+
+ List<Part> expected = new ArrayList<>(List.of(
+ new Segment(0.15f, Color.RED),
+ new Point(null, Color.RED),
+ new Segment(0.10f, Color.RED),
+ new Point(null, Color.BLUE),
+ new Segment(0.25f, Color.RED),
+ new Segment(0.25f, Color.GREEN),
+ new Point(null, Color.YELLOW),
+ new Segment(0.25f, Color.GREEN)));
+
+ assertThat(parts).isEqualTo(expected);
+ }
+}
diff --git a/core/tests/devicestatetests/Android.bp b/core/tests/devicestatetests/Android.bp
index a3303c6..a2aa62d 100644
--- a/core/tests/devicestatetests/Android.bp
+++ b/core/tests/devicestatetests/Android.bp
@@ -26,11 +26,12 @@
// Include all test java files
srcs: ["src/**/*.java"],
static_libs: [
+ "androidx.test.ext.junit",
"androidx.test.rules",
"frameworks-base-testutils",
"mockito-target-minus-junit4",
"platform-test-annotations",
- "testng",
+ "truth",
],
libs: ["android.test.runner.stubs.system"],
platform_apis: true,
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
index cf7c549..1b78433 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
@@ -22,21 +22,15 @@
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
import java.util.ArrayList;
import java.util.List;
@@ -44,13 +38,13 @@
/**
* Unit tests for {@link DeviceStateInfo}.
- * <p/>
- * Run with <code>atest DeviceStateInfoTest</code>.
+ *
+ * <p> Build/Install/Run:
+ * atest FrameworksCoreDeviceStateManagerTests:DeviceStateInfoTest
*/
-@RunWith(JUnit4.class)
@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class DeviceStateInfoTest {
-
private static final DeviceState DEVICE_STATE_0 = new DeviceState(
new DeviceState.Configuration.Builder(0, "STATE_0")
.setSystemProperties(
@@ -74,88 +68,113 @@
@Test
public void create() {
- final ArrayList<DeviceState> supportedStates = new ArrayList<>(
- List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
+ final ArrayList<DeviceState> supportedStates =
+ new ArrayList<>(List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState baseState = DEVICE_STATE_0;
final DeviceState currentState = DEVICE_STATE_2;
- final DeviceStateInfo info = new DeviceStateInfo(supportedStates, baseState, currentState);
- assertNotNull(info.supportedStates);
- assertEquals(supportedStates, info.supportedStates);
- assertEquals(baseState, info.baseState);
- assertEquals(currentState, info.currentState);
+ final DeviceStateInfo info =
+ new DeviceStateInfo(supportedStates, baseState, currentState);
+
+ assertThat(info.supportedStates).containsExactlyElementsIn(supportedStates).inOrder();
+ assertThat(info.baseState).isEqualTo(baseState);
+ assertThat(info.currentState).isEqualTo(currentState);
}
@Test
public void equals() {
- final ArrayList<DeviceState> supportedStates = new ArrayList<>(
- List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
+ final ArrayList<DeviceState> supportedStates =
+ new ArrayList<>(List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState baseState = DEVICE_STATE_0;
final DeviceState currentState = DEVICE_STATE_2;
final DeviceStateInfo info = new DeviceStateInfo(supportedStates, baseState, currentState);
- Assert.assertEquals(info, info);
+ final DeviceStateInfo sameInstance = info;
+ assertThat(info).isEqualTo(sameInstance);
- final DeviceStateInfo sameInfo = new DeviceStateInfo(supportedStates, baseState,
- currentState);
- Assert.assertEquals(info, sameInfo);
+ final DeviceStateInfo sameInfo =
+ new DeviceStateInfo(supportedStates, baseState, currentState);
+ assertThat(info).isEqualTo(sameInfo);
+
+ final DeviceStateInfo copiedInfo = new DeviceStateInfo(info);
+ assertThat(info).isEqualTo(copiedInfo);
final DeviceStateInfo differentInfo = new DeviceStateInfo(
new ArrayList<>(List.of(DEVICE_STATE_0, DEVICE_STATE_2)), baseState, currentState);
- assertNotEquals(info, differentInfo);
+ assertThat(differentInfo).isNotEqualTo(info);
+ }
+
+ @Test
+ public void hashCode_sameObject() {
+ final ArrayList<DeviceState> supportedStates =
+ new ArrayList<>(List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
+ final DeviceState baseState = DEVICE_STATE_0;
+ final DeviceState currentState = DEVICE_STATE_2;
+ final DeviceStateInfo info =
+ new DeviceStateInfo(supportedStates, baseState, currentState);
+ final DeviceStateInfo copiedInfo = new DeviceStateInfo(info);
+
+ assertThat(info.hashCode()).isEqualTo(copiedInfo.hashCode());
}
@Test
public void diff_sameObject() {
- final ArrayList<DeviceState> supportedStates = new ArrayList<>(
- List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
+ final ArrayList<DeviceState> supportedStates =
+ new ArrayList<>(List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState baseState = DEVICE_STATE_0;
final DeviceState currentState = DEVICE_STATE_2;
final DeviceStateInfo info = new DeviceStateInfo(supportedStates, baseState, currentState);
- assertEquals(0, info.diff(info));
+
+ assertThat(info.diff(info)).isEqualTo(0);
}
@Test
public void diff_differentSupportedStates() {
- final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)),
- DEVICE_STATE_0, DEVICE_STATE_0);
+ final DeviceStateInfo info = new DeviceStateInfo(
+ new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_0, DEVICE_STATE_0);
final DeviceStateInfo otherInfo = new DeviceStateInfo(
new ArrayList<>(List.of(DEVICE_STATE_2)), DEVICE_STATE_0, DEVICE_STATE_0);
+
final int diff = info.diff(otherInfo);
- assertTrue((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
- assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
- assertFalse((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0);
+
+ assertThat(diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES).isGreaterThan(0);
+ assertThat(diff & DeviceStateInfo.CHANGED_BASE_STATE).isEqualTo(0);
+ assertThat(diff & DeviceStateInfo.CHANGED_CURRENT_STATE).isEqualTo(0);
}
@Test
public void diff_differentNonOverrideState() {
- final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)),
- DEVICE_STATE_1, DEVICE_STATE_0);
+ final DeviceStateInfo info = new DeviceStateInfo(
+ new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_1, DEVICE_STATE_0);
final DeviceStateInfo otherInfo = new DeviceStateInfo(
new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_2, DEVICE_STATE_0);
+
final int diff = info.diff(otherInfo);
- assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
- assertTrue((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
- assertFalse((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0);
+
+ assertThat(diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES).isEqualTo(0);
+ assertThat(diff & DeviceStateInfo.CHANGED_BASE_STATE).isGreaterThan(0);
+ assertThat(diff & DeviceStateInfo.CHANGED_CURRENT_STATE).isEqualTo(0);
}
@Test
public void diff_differentState() {
- final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)),
- DEVICE_STATE_0, DEVICE_STATE_1);
+ final DeviceStateInfo info = new DeviceStateInfo(
+ new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_0, DEVICE_STATE_1);
final DeviceStateInfo otherInfo = new DeviceStateInfo(
new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_0, DEVICE_STATE_2);
+
final int diff = info.diff(otherInfo);
- assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
- assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
- assertTrue((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0);
+
+ assertThat(diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES).isEqualTo(0);
+ assertThat(diff & DeviceStateInfo.CHANGED_BASE_STATE).isEqualTo(0);
+ assertThat(diff & DeviceStateInfo.CHANGED_CURRENT_STATE).isGreaterThan(0);
}
@Test
public void writeToParcel() {
- final ArrayList<DeviceState> supportedStates = new ArrayList<>(
- List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
+ final ArrayList<DeviceState> supportedStates =
+ new ArrayList<>(List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState nonOverrideState = DEVICE_STATE_0;
final DeviceState state = DEVICE_STATE_2;
final DeviceStateInfo originalInfo =
@@ -166,6 +185,6 @@
parcel.setDataPosition(0);
final DeviceStateInfo info = DeviceStateInfo.CREATOR.createFromParcel(parcel);
- assertEquals(originalInfo, info);
+ assertThat(info).isEqualTo(originalInfo);
}
}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index f4d3631..7c01ecc 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -16,8 +16,7 @@
package android.hardware.devicestate;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
+import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -31,6 +30,9 @@
import android.os.RemoteException;
import android.os.test.FakePermissionEnforcer;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.util.ConcurrentUtils;
@@ -38,7 +40,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
import java.util.ArrayList;
import java.util.HashSet;
@@ -47,32 +48,35 @@
/**
* Unit tests for {@link DeviceStateManagerGlobal}.
- * <p/>
- * Run with <code>atest DeviceStateManagerGlobalTest</code>.
+ *
+ * <p> Build/Install/Run:
+ * atest FrameworksCoreDeviceStateManagerTests:DeviceStateManagerGlobalTest
*/
-@RunWith(JUnit4.class)
@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class DeviceStateManagerGlobalTest {
private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(
new DeviceState.Configuration.Builder(0 /* identifier */, "" /* name */).build());
private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(
new DeviceState.Configuration.Builder(1 /* identifier */, "" /* name */).build());
+ @NonNull
private TestDeviceStateManagerService mService;
+ @NonNull
private DeviceStateManagerGlobal mDeviceStateManagerGlobal;
@Before
public void setUp() {
- FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer();
+ final FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer();
mService = new TestDeviceStateManagerService(permissionEnforcer);
mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService);
- assertFalse(mService.mCallbacks.isEmpty());
+ assertThat(mService.mCallbacks).isNotEmpty();
}
@Test
public void registerCallback() {
- DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
- DeviceStateCallback callback2 = mock(DeviceStateCallback.class);
+ final DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
+ final DeviceStateCallback callback2 = mock(DeviceStateCallback.class);
mDeviceStateManagerGlobal.registerDeviceStateCallback(callback1,
ConcurrentUtils.DIRECT_EXECUTOR);
@@ -105,8 +109,8 @@
reset(callback2);
// Change the requested state and verify callback
- DeviceStateRequest request = DeviceStateRequest.newBuilder(
- DEFAULT_DEVICE_STATE.getIdentifier()).build();
+ final DeviceStateRequest request =
+ DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
@@ -115,10 +119,10 @@
@Test
public void unregisterCallback() {
- DeviceStateCallback callback = mock(DeviceStateCallback.class);
+ final DeviceStateCallback callback = mock(DeviceStateCallback.class);
- mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
- ConcurrentUtils.DIRECT_EXECUTOR);
+ mDeviceStateManagerGlobal
+ .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
// Verify initial callbacks
verify(callback).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
@@ -134,15 +138,15 @@
@Test
public void submitRequest() {
- DeviceStateCallback callback = mock(DeviceStateCallback.class);
- mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
- ConcurrentUtils.DIRECT_EXECUTOR);
+ final DeviceStateCallback callback = mock(DeviceStateCallback.class);
+ mDeviceStateManagerGlobal
+ .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
reset(callback);
- DeviceStateRequest request = DeviceStateRequest.newBuilder(
- OTHER_DEVICE_STATE.getIdentifier()).build();
+ final DeviceStateRequest request =
+ DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
verify(callback).onDeviceStateChanged(eq(OTHER_DEVICE_STATE));
@@ -155,15 +159,15 @@
@Test
public void submitBaseStateOverrideRequest() {
- DeviceStateCallback callback = mock(DeviceStateCallback.class);
- mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
- ConcurrentUtils.DIRECT_EXECUTOR);
+ final DeviceStateCallback callback = mock(DeviceStateCallback.class);
+ mDeviceStateManagerGlobal
+ .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
reset(callback);
- DeviceStateRequest request = DeviceStateRequest.newBuilder(
- OTHER_DEVICE_STATE.getIdentifier()).build();
+ final DeviceStateRequest request =
+ DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
null /* callback */);
@@ -177,28 +181,28 @@
@Test
public void submitBaseAndEmulatedStateOverride() {
- DeviceStateCallback callback = mock(DeviceStateCallback.class);
- mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
- ConcurrentUtils.DIRECT_EXECUTOR);
+ final DeviceStateCallback callback = mock(DeviceStateCallback.class);
+ mDeviceStateManagerGlobal
+ .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
reset(callback);
- DeviceStateRequest request = DeviceStateRequest.newBuilder(
- OTHER_DEVICE_STATE.getIdentifier()).build();
+ final DeviceStateRequest request =
+ DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
null /* callback */);
verify(callback).onDeviceStateChanged(eq(OTHER_DEVICE_STATE));
- assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
+ assertThat(mService.getBaseState()).isEqualTo(OTHER_DEVICE_STATE);
reset(callback);
- DeviceStateRequest secondRequest = DeviceStateRequest.newBuilder(
- DEFAULT_DEVICE_STATE.getIdentifier()).build();
+ final DeviceStateRequest secondRequest =
+ DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestState(secondRequest, null, null);
- assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
+ assertThat(mService.getBaseState()).isEqualTo(OTHER_DEVICE_STATE);
verify(callback).onDeviceStateChanged(eq(DEFAULT_DEVICE_STATE));
reset(callback);
@@ -214,10 +218,10 @@
@Test
public void verifyDeviceStateRequestCallbacksCalled() {
- DeviceStateRequest.Callback callback = mock(TestDeviceStateRequestCallback.class);
+ final DeviceStateRequest.Callback callback = mock(TestDeviceStateRequestCallback.class);
- DeviceStateRequest request = DeviceStateRequest.newBuilder(
- OTHER_DEVICE_STATE.getIdentifier()).build();
+ final DeviceStateRequest request =
+ DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestState(request,
ConcurrentUtils.DIRECT_EXECUTOR /* executor */,
callback /* callback */);
@@ -232,52 +236,55 @@
public static class TestDeviceStateRequestCallback implements DeviceStateRequest.Callback {
@Override
- public void onRequestActivated(DeviceStateRequest request) { }
+ public void onRequestActivated(@NonNull DeviceStateRequest request) { }
@Override
- public void onRequestCanceled(DeviceStateRequest request) { }
+ public void onRequestCanceled(@NonNull DeviceStateRequest request) { }
@Override
- public void onRequestSuspended(DeviceStateRequest request) { }
+ public void onRequestSuspended(@NonNull DeviceStateRequest request) { }
}
private static final class TestDeviceStateManagerService extends IDeviceStateManager.Stub {
- public static final class Request {
- public final IBinder token;
- public final int state;
- public final int flags;
+ static final class Request {
+ @NonNull
+ final IBinder mToken;
+ final int mState;
- private Request(IBinder token, int state, int flags) {
- this.token = token;
- this.state = state;
- this.flags = flags;
+ private Request(@NonNull IBinder token, int state) {
+ this.mToken = token;
+ this.mState = state;
}
}
- private List<DeviceState> mSupportedDeviceStates = List.of(DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE);
-
+ @NonNull
+ private List<DeviceState> mSupportedDeviceStates =
+ List.of(DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE);
+ @NonNull
private DeviceState mBaseState = DEFAULT_DEVICE_STATE;
+ @Nullable
private Request mRequest;
+ @Nullable
private Request mBaseStateRequest;
private final Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
- TestDeviceStateManagerService(FakePermissionEnforcer enforcer) {
+ TestDeviceStateManagerService(@NonNull FakePermissionEnforcer enforcer) {
super(enforcer);
}
+ @NonNull
private DeviceStateInfo getInfo() {
final int mergedBaseState = mBaseStateRequest == null
- ? mBaseState.getIdentifier() : mBaseStateRequest.state;
- final int mergedState = mRequest == null
- ? mergedBaseState : mRequest.state;
+ ? mBaseState.getIdentifier() : mBaseStateRequest.mState;
+ final int mergedState = mRequest == null ? mergedBaseState : mRequest.mState;
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(mSupportedDeviceStates);
final DeviceState baseState = new DeviceState(
new DeviceState.Configuration.Builder(mergedBaseState, "" /* name */).build());
final DeviceState state = new DeviceState(
new DeviceState.Configuration.Builder(mergedState, "" /* name */).build());
- return new DeviceStateInfo(new ArrayList<>(mSupportedDeviceStates), baseState, state);
+ return new DeviceStateInfo(supportedStates, baseState, state);
}
private void notifyDeviceStateInfoChanged() {
@@ -291,6 +298,7 @@
}
}
+ @NonNull
@Override
public DeviceStateInfo getDeviceStateInfo() {
return getInfo();
@@ -311,18 +319,18 @@
}
@Override
- public void requestState(IBinder token, int state, int flags) {
+ public void requestState(@NonNull IBinder token, int state, int unusedFlags) {
if (mRequest != null) {
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
- callback.onRequestCanceled(mRequest.token);
+ callback.onRequestCanceled(mRequest.mToken);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
}
- final Request request = new Request(token, state, flags);
+ final Request request = new Request(token, state);
mRequest = request;
notifyDeviceStateInfoChanged();
@@ -337,7 +345,7 @@
@Override
public void cancelStateRequest() {
- IBinder token = mRequest.token;
+ final IBinder token = mRequest.mToken;
mRequest = null;
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
@@ -350,19 +358,18 @@
}
@Override
- public void requestBaseStateOverride(IBinder token, int state, int flags) {
+ public void requestBaseStateOverride(@NonNull IBinder token, int state, int unusedFlags) {
if (mBaseStateRequest != null) {
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
- callback.onRequestCanceled(mBaseStateRequest.token);
+ callback.onRequestCanceled(mBaseStateRequest.mToken);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
}
- final Request request = new Request(token, state, flags);
- mBaseStateRequest = request;
+ mBaseStateRequest = new Request(token, state);
notifyDeviceStateInfoChanged();
for (IDeviceStateManagerCallback callback : mCallbacks) {
@@ -376,7 +383,7 @@
@Override
public void cancelBaseStateOverride() throws RemoteException {
- IBinder token = mBaseStateRequest.token;
+ final IBinder token = mBaseStateRequest.mToken;
mBaseStateRequest = null;
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
@@ -396,24 +403,27 @@
onStateRequestOverlayDismissed_enforcePermission();
}
- public void setSupportedStates(List<DeviceState> states) {
+ public void setSupportedStates(@NonNull List<DeviceState> states) {
mSupportedDeviceStates = states;
notifyDeviceStateInfoChanged();
}
+ @NonNull
public List<DeviceState> getSupportedDeviceStates() {
return mSupportedDeviceStates;
}
- public void setBaseState(DeviceState state) {
+ public void setBaseState(@NonNull DeviceState state) {
mBaseState = state;
notifyDeviceStateInfoChanged();
}
+ @NonNull
public DeviceState getBaseState() {
return getInfo().baseState;
}
+ @NonNull
public DeviceState getMergedState() {
return getInfo().currentState;
}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index 78d4324..83b5ff3 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -21,17 +21,16 @@
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
-import junit.framework.Assert;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
import java.util.HashSet;
import java.util.List;
@@ -39,28 +38,32 @@
/**
* Unit tests for {@link android.hardware.devicestate.DeviceState}.
- * <p/>
- * Run with <code>atest DeviceStateTest</code>.
+ *
+ * <p> Build/Install/Run:
+ * atest FrameworksCoreDeviceStateManagerTests:DeviceStateTest
*/
@Presubmit
-@RunWith(JUnit4.class)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class DeviceStateTest {
@Test
public void testConstruct() {
- DeviceState.Configuration config = new DeviceState.Configuration.Builder(
+ final DeviceState.Configuration config = new DeviceState.Configuration.Builder(
MINIMUM_DEVICE_STATE_IDENTIFIER, "TEST_CLOSED")
.setSystemProperties(
new HashSet<>(List.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)))
.build();
+
final DeviceState state = new DeviceState(config);
- assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE_IDENTIFIER);
- assertEquals(state.getName(), "TEST_CLOSED");
- assertTrue(state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS));
+
+ assertThat(state.getIdentifier()).isEqualTo(MINIMUM_DEVICE_STATE_IDENTIFIER);
+ assertThat(state.getName()).isEqualTo("TEST_CLOSED");
+ assertThat(state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)).isTrue();
}
@Test
public void testHasProperties() {
- DeviceState.Configuration config = new DeviceState.Configuration.Builder(
+ final DeviceState.Configuration config = new DeviceState.Configuration.Builder(
MINIMUM_DEVICE_STATE_IDENTIFIER, "TEST")
.setSystemProperties(new HashSet<>(List.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST)))
@@ -68,10 +71,10 @@
final DeviceState state = new DeviceState(config);
- assertTrue(state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS));
- assertTrue(state.hasProperty(PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST));
- assertTrue(state.hasProperties(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
- PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST));
+ assertThat(state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)).isTrue();
+ assertThat(state.hasProperty(PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST)).isTrue();
+ assertThat(state.hasProperties(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST)).isTrue();
}
@Test
@@ -91,7 +94,7 @@
final DeviceState.Configuration stateConfiguration =
DeviceState.Configuration.CREATOR.createFromParcel(parcel);
- Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
+ assertThat(originalState).isEqualTo(new DeviceState(stateConfiguration));
}
@Test
@@ -109,6 +112,6 @@
final DeviceState.Configuration stateConfiguration =
DeviceState.Configuration.CREATOR.createFromParcel(parcel);
- Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
+ assertThat(originalState).isEqualTo(new DeviceState(stateConfiguration));
}
}
diff --git a/core/tests/vibrator/src/android/os/VibratorInfoTest.java b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
index 47d01c4..04945f3 100644
--- a/core/tests/vibrator/src/android/os/VibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
@@ -16,6 +16,8 @@
package android.os;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -24,6 +26,7 @@
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
+import android.util.Range;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,12 +42,19 @@
private static final float TEST_FREQUENCY_RESOLUTION = 25;
private static final float[] TEST_AMPLITUDE_MAP = new float[]{
/* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+ private static final float[] TEST_FREQUENCIES =
+ new float[]{90f, 120f, 150f, 60f, 30f, 210f, 270f, 300f, 240f, 180f};
+ private static final float[] TEST_OUTPUT_ACCELERATIONS =
+ new float[]{1.2f, 1.8f, 2.4f, 0.6f, 0.1f, 2.2f, 1.0f, 0.5f, 1.9f, 3.0f};
private static final VibratorInfo.FrequencyProfileLegacy EMPTY_FREQUENCY_PROFILE =
new VibratorInfo.FrequencyProfileLegacy(Float.NaN, Float.NaN, Float.NaN, null);
private static final VibratorInfo.FrequencyProfileLegacy TEST_FREQUENCY_PROFILE_LEGACY =
new VibratorInfo.FrequencyProfileLegacy(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP);
+ private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_FREQUENCIES,
+ TEST_OUTPUT_ACCELERATIONS);
@Test
public void testHasAmplitudeControl() {
@@ -179,13 +189,123 @@
}
@Test
+ public void testGetFrequencyProfile_unsetProfileIsEmpty() {
+ assertTrue(new VibratorInfo.Builder(
+ TEST_VIBRATOR_ID).build().getFrequencyProfile().isEmpty());
+ }
+
+ @Test
+ public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() {
+ // Invalid resonant frequency.
+ assertThat(new VibratorInfo.FrequencyProfile(Float.NaN,
+ TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isTrue();
+ assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/-1f,
+ TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isTrue();
+ // No frequency-acceleration data
+ assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null).isEmpty()).isTrue();
+ // Mismatching frequency and output acceleration lists
+ assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ new float[]{30f, 40f, 50f, 100f},
+ /*outputAccelerationsGs=*/ new float[]{0.8f, 1.0f, 2.0f}).isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testGetFrequenciesAndOutputAccelerations_noFrequencyAccelerationData_returnNull() {
+ VibratorInfo.FrequencyProfile emptyFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ assertThat(emptyFrequencyProfile.getFrequenciesHz()).isNull();
+ assertThat(emptyFrequencyProfile.getOutputAccelerationsGs()).isNull();
+ }
+
+ @Test
+ public void testGetFrequenciesAndOutputAccelerations_mismatchingDataLength_returnNull() {
+ VibratorInfo.FrequencyProfile emptyFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ new float[]{150f, 200f},
+ /*outputAccelerationsGs=*/ new float[]{1.2f, 2.2f, 3.0f});
+ assertThat(emptyFrequencyProfile.getFrequenciesHz()).isNull();
+ assertThat(emptyFrequencyProfile.getOutputAccelerationsGs()).isNull();
+ }
+
+ @Test
+ public void testGetFrequenciesAndOutputAccelerations_dataIsDedupedAndSorted() {
+ VibratorInfo.FrequencyProfile frequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ new float[]{150f, 150f, 150f, 130f, 200f, 160f},
+ /*outputAccelerationsGs=*/ new float[]{1.2f, 1.5f, 1.9f, 1.0f, 2.2f, 3.0f});
+ float[] frequencies = frequencyProfile.getFrequenciesHz();
+ assertThat(frequencies).isEqualTo(
+ new float[]{130f, 150f, 160f, 200f});
+ assertThat(frequencyProfile.getOutputAccelerationsGs()).isEqualTo(
+ new float[]{1.0f, 1.2f, 3.0f, 2.2f});
+ }
+
+ @Test
+ public void testGetFrequencyRangeHz_emptyProfileReturnsNull() {
+ VibratorInfo.FrequencyProfile emptyFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null);
+ assertThat(
+ emptyFrequencyProfile.getFrequencyRangeHz(/*minOutputAcceleration=*/0.2f)).isNull();
+ }
+
+ @Test
+ public void testGetFrequencyRangeHz_validProfileReturnsMappedValues() {
+ VibratorInfo.FrequencyProfile frequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f,
+ /*frequenciesHz=*/new float[]{90f, 120f, 150f, 60f, 30f, 210f, 180f},
+ /*outputAccelerationsGs=*/ new float[]{1.2f, 1.8f, 2.4f, 0.6f, 0.4f, 2.2f, 3.0f});
+
+ // lower and upper bounds are min and max frequencies
+ assertThat(frequencyProfile.getFrequencyRangeHz(/*minOutputAcceleration=*/0.33f)).isEqualTo(
+ new Range<>(frequencyProfile.getMinFrequencyHz(),
+ frequencyProfile.getMaxFrequencyHz()));
+
+ // lower and upper bounds are within frequency range and use interpolation
+ assertThat(frequencyProfile.getFrequencyRangeHz(/*minOutputAcceleration=*/2.6f))
+ .isEqualTo(new Range<>(160f, 195f));
+
+ // upper bound is max frequency
+ assertThat(frequencyProfile.getFrequencyRangeHz(/*minOutputAcceleration=*/2.0f))
+ .isEqualTo(new Range<>(130f, frequencyProfile.getMaxFrequencyHz()));
+ }
+
+ @Test
+ public void testFrequencyProfile_emptyProfileReturnsNanValues() {
+ VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
+ /*resonantFrequencyHz=*/150f, /*frequenciesHz=*/ null,
+ /*outputAccelerationsGs=*/ null);
+
+ assertThat(frequencyProfile.getMaxOutputAccelerationGs()).isNaN();
+ assertThat(frequencyProfile.getMinFrequencyHz()).isNaN();
+ assertThat(frequencyProfile.getMaxFrequencyHz()).isNaN();
+ assertThat(frequencyProfile.getOutputAccelerationGs(/*frequencyHz=*/150f)).isNaN();
+ }
+
+ @Test
+ public void testFrequencyProfile_validProfileReturnsAppropriateValues() {
+ VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
+ /*resonantFrequencyHz=*/150f, TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS);
+
+ assertThat(frequencyProfile.getMaxOutputAccelerationGs()).isEqualTo(3f);
+ assertThat(frequencyProfile.getMinFrequencyHz()).isEqualTo(30f);
+ assertThat(frequencyProfile.getMaxFrequencyHz()).isEqualTo(300f);
+ assertThat(frequencyProfile.getOutputAccelerationGs(/*frequencyHz=*/150f)).isEqualTo(2.4f);
+ // Test getting output acceleration using linear interpolation
+ assertThat(frequencyProfile.getOutputAccelerationGs(/*frequencyHz=*/166f)).isEqualTo(
+ 2.72f);
+ }
+
+ @Test
public void testGetFrequencyProfileLegacy_unsetProfileIsEmpty() {
assertTrue(new VibratorInfo.Builder(
TEST_VIBRATOR_ID).build().getFrequencyProfileLegacy().isEmpty());
}
@Test
- public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() {
+ public void testFrequencyProfileLegacy_invalidValuesCreatesEmptyProfile() {
// Invalid, contains NaN values or empty array.
assertTrue(new VibratorInfo.FrequencyProfileLegacy(
Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).isEmpty());
@@ -216,7 +336,7 @@
}
@Test
- public void testGetFrequencyRangeHz_emptyProfileReturnsNull() {
+ public void testLegacyGetFrequencyRangeHz_emptyProfileReturnsNull() {
assertNull(new VibratorInfo.FrequencyProfileLegacy(
Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz());
assertNull(new VibratorInfo.FrequencyProfileLegacy(
@@ -228,7 +348,7 @@
}
@Test
- public void testGetFrequencyRangeHz_validProfileReturnsMappedValues() {
+ public void testLegacyGetFrequencyRangeHz_validProfileReturnsMappedValues() {
VibratorInfo.FrequencyProfileLegacy profile = new VibratorInfo.FrequencyProfileLegacy(
/* resonantFrequencyHz= */ 150,
/* minFrequencyHz= */ 50,
@@ -306,6 +426,7 @@
.setPwleSizeMax(20)
.setQFactor(2f)
.setFrequencyProfileLegacy(TEST_FREQUENCY_PROFILE_LEGACY)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.setMaxEnvelopeEffectSize(16)
.setMinEnvelopeEffectControlPointDurationMillis(20)
.setMaxEnvelopeEffectControlPointDurationMillis(1_000);
@@ -347,18 +468,33 @@
assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
assertFalse(complete.equalContent(completeWithDifferentPrimitiveDuration));
- VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
+ VibratorInfo completeWithDifferentFrequencyProfileLegacy = completeBuilder
.setFrequencyProfileLegacy(new VibratorInfo.FrequencyProfileLegacy(
TEST_RESONANT_FREQUENCY + 20,
TEST_MIN_FREQUENCY + 10,
TEST_FREQUENCY_RESOLUTION + 5,
TEST_AMPLITUDE_MAP))
.build();
+ assertNotEquals(complete, completeWithDifferentFrequencyProfileLegacy);
+ assertFalse(complete.equalContent(completeWithDifferentFrequencyProfileLegacy));
+
+ VibratorInfo completeWithEmptyFrequencyProfileLegacy = completeBuilder
+ .setFrequencyProfileLegacy(EMPTY_FREQUENCY_PROFILE)
+ .build();
+ assertNotEquals(complete, completeWithEmptyFrequencyProfileLegacy);
+ assertFalse(complete.equalContent(completeWithEmptyFrequencyProfileLegacy));
+
+ VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
+ .setFrequencyProfile(
+ new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY + 20,
+ new float[]{90f, 150f}, new float[]{1.2f, 2.2f}))
+ .build();
assertNotEquals(complete, completeWithDifferentFrequencyProfile);
assertFalse(complete.equalContent(completeWithDifferentFrequencyProfile));
VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder
- .setFrequencyProfileLegacy(EMPTY_FREQUENCY_PROFILE)
+ .setFrequencyProfile(
+ new VibratorInfo.FrequencyProfile(Float.NaN, null, null))
.build();
assertNotEquals(complete, completeWithEmptyFrequencyProfile);
assertFalse(complete.equalContent(completeWithEmptyFrequencyProfile));
@@ -396,6 +532,7 @@
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setQFactor(Float.NaN)
.setFrequencyProfileLegacy(TEST_FREQUENCY_PROFILE_LEGACY)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
.build();
Parcel parcel = Parcel.obtain();
diff --git a/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java b/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
index f192b89..c9ab297 100644
--- a/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
@@ -16,6 +16,8 @@
package android.os.vibrator;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
@@ -24,7 +26,11 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -33,6 +39,9 @@
public class MultiVibratorInfoTest {
private static final float TEST_TOLERANCE = 1e-5f;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testGetId() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
@@ -157,6 +166,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetQFactorAndResonantFrequency_differentValues_returnsNaN() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -187,6 +197,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetQFactorAndResonantFrequency_sameValues_returnsValue() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -212,6 +223,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetFrequencyProfileLegacy_differentResonantFreqOrResolutions_returnsEmpty() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -240,6 +252,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetFrequencyProfileLegacy_missingValues_returnsEmpty() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -288,6 +301,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetFrequencyProfileLegacy_unalignedMaxAmplitudes_returnsEmpty() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -312,6 +326,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testGetFrequencyProfileLegacy_alignedProfiles_returnsIntersection() {
VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
.setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
@@ -353,6 +368,132 @@
assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
}
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_alignedProfiles_returnsIntersection() {
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{30f, 60f, 120f, 150f, 180f, 210f, 270f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 1.8f, 2.4f, 3.0f, 2.2f, 1.0f, 0.5f});
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{120f, 150f, 180f, 210f},
+ /*accelerations=*/new float[]{1.5f, 2.6f, 2.7f, 2.1f});
+
+ VibratorInfo.FrequencyProfile expectedFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/
+ 180f, /*frequenciesHz=*/new float[]{120.0f, 150.0f, 180.0f, 210.0f},
+ /*outputAccelerationsGs=*/new float[]{1.5f, 2.4f, 2.7f, 2.1f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(expectedFrequencyProfile);
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_alignedProfilesUsingInterpolation_returnsIntersection() {
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{30f, 60f, 120f},
+ /*accelerations=*/new float[]{0.25f, 1.0f, 4.0f});
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{40f, 70f, 110f},
+ /*accelerations=*/new float[]{1.0f, 2.5f, 4.0f});
+
+ VibratorInfo.FrequencyProfile expectedFrequencyProfile =
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/
+ 180f, /*frequenciesHz=*/new float[]{40f, 60f, 70f, 110f},
+ /*outputAccelerationsGs=*/new float[]{0.5f, 1.0f, 1.5f, 3.5f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(expectedFrequencyProfile);
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_disjointFrequencyRange_returnsEmpty() {
+
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{30f, 60f, 120f, 150f, 180f, 210f, 270f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 1.8f, 2.4f, 3.0f, 2.2f, 1.0f, 0.5f});
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{310f, 320f, 350f, 380f, 410f, 440f},
+ /*accelerations=*/new float[]{0.3f, 0.75f, 1.82f, 2.11f, 2.8f, 2.12f, 1.4f, 0.42f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/ Float.NaN,
+ /*frequenciesHz=*/null, /*outputAccelerationsGs=*/null));
+ assertThat(info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_emptyFrequencyRange_returnsEmpty() {
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/180f,
+ /*frequencies=*/null, /*accelerations=*/null);
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/180f,
+ /*frequencies=*/new float[]{30f, 60f, 150f, 180f, 210f, 240f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 2.4f, 3.0f, 2.2f, 1.9f, 0.5f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/ Float.NaN,
+ /*frequenciesHz=*/null,
+ /*outputAccelerationsGs=*/null));
+ assertThat(info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testGetFrequencyProfile_differentResonantFrequency_returnsEmpty() {
+ VibratorInfo firstInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 1,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 160f,
+ /*frequencies=*/new float[]{30f, 60f, 120f, 150f, 180f, 210f, 270f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 1.8f, 2.4f, 3.0f, 2.2f, 1.0f, 0.5f});
+
+ VibratorInfo secondInfo = createVibratorInfoWithFrequencyProfile(/*id=*/ 2,
+ IVibrator.CAP_FREQUENCY_CONTROL, /*resonantFrequencyHz=*/ 180f,
+ /*frequencies=*/new float[]{30f, 60f, 120f, 150f, 180f, 210f, 270f, 300f},
+ /*accelerations=*/new float[]{0.1f, 0.6f, 1.8f, 2.4f, 3.0f, 2.2f, 1.0f, 0.5f});
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertThat(info.getFrequencyProfile()).isEqualTo(
+ new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/ Float.NaN,
+ /*frequenciesHz=*/null,
+ /*outputAccelerationsGs=*/null));
+ assertThat(info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)).isFalse();
+ }
+
+ private VibratorInfo createVibratorInfoWithFrequencyProfile(int id, long capabilities,
+ float resonantFrequencyHz, float[] frequencies, float[] accelerations) {
+ return new VibratorInfo.Builder(id)
+ .setCapabilities(capabilities)
+ .setFrequencyProfile(
+ new VibratorInfo.FrequencyProfile(resonantFrequencyHz, frequencies,
+ accelerations))
+ .build();
+ }
+
/**
* Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
*/
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 3b739c3..1260796 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -24,6 +24,7 @@
<uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
+ <uses-permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION" />
<application>
<activity
diff --git a/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_confirm_button_background.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_dismiss_button_background.xml
rename to libs/WindowManager/Shell/res/drawable/open_by_default_settings_dialog_confirm_button_background.xml
diff --git a/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
index 8ff382b..b5bceda 100644
--- a/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
+++ b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
@@ -111,7 +111,7 @@
</RadioGroup>
<Button
- android:id="@+id/open_by_default_settings_dialog_dismiss_button"
+ android:id="@+id/open_by_default_settings_dialog_confirm_button"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:text="@string/open_by_default_dialog_dismiss_button_text"
@@ -122,7 +122,7 @@
android:textSize="14sp"
android:textFontWeight="500"
android:textColor="?androidprv:attr/materialColorOnPrimary"
- android:background="@drawable/open_by_default_settings_dialog_dismiss_button_background"/>
+ android:background="@drawable/open_by_default_settings_dialog_confirm_button_background"/>
</LinearLayout>
</ScrollView>
</FrameLayout>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java
index 26aae2d..02a7991 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java
@@ -26,5 +26,11 @@
/**
* Called when a transition changes the top, focused display.
*/
- void onFocusedDisplayChanged(int displayId);
+ default void onFocusedDisplayChanged(int displayId) {}
+
+ /**
+ * Called when the per-app or system-wide focus state has changed for a task.
+ */
+ default void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay,
+ boolean isFocusedGlobally) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
index 71bcb59..65132fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
@@ -22,7 +22,13 @@
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationUserState
import android.net.Uri
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+
+private const val TAG = "AppToWebUtils"
private val GenericBrowserIntent = Intent()
.setAction(Intent.ACTION_VIEW)
@@ -58,4 +64,25 @@
val component = intent.resolveActivity(packageManager) ?: return null
intent.setComponent(component)
return intent
-}
\ No newline at end of file
+}
+
+/**
+ * Returns the [DomainVerificationUserState] of the user associated with the given
+ * [DomainVerificationManager] and the given package.
+ */
+fun getDomainVerificationUserState(
+ manager: DomainVerificationManager,
+ packageName: String
+): DomainVerificationUserState? {
+ try {
+ return manager.getDomainVerificationUserState(packageName)
+ } catch (e: PackageManager.NameNotFoundException) {
+ ProtoLog.w(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "%s: Failed to get domain verification user state: %s",
+ TAG,
+ e.message!!
+ )
+ return null
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
index 4926cbd..a727b54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
@@ -19,6 +19,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.TaskInfo
import android.content.Context
+import android.content.pm.verify.domain.DomainVerificationManager
import android.graphics.Bitmap
import android.graphics.PixelFormat
import android.view.LayoutInflater
@@ -30,6 +31,7 @@
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
import android.view.WindowlessWindowManager
import android.widget.ImageView
+import android.widget.RadioButton
import android.widget.TextView
import android.window.TaskConstants
import com.android.wm.shell.R
@@ -58,8 +60,17 @@
private lateinit var appIconView: ImageView
private lateinit var appNameView: TextView
+ private lateinit var openInAppButton: RadioButton
+ private lateinit var openInBrowserButton: RadioButton
+
+ private val domainVerificationManager =
+ context.getSystemService(DomainVerificationManager::class.java)!!
+ private val packageName = taskInfo.baseActivity?.packageName!!
+
+
init {
createDialog()
+ initializeRadioButtons()
bindAppInfo(appIconBitmap, appName)
}
@@ -111,9 +122,30 @@
closeMenu()
}
+ dialog.setConfirmButtonClickListener {
+ setDefaultLinkHandlingSetting()
+ closeMenu()
+ }
+
listener.onDialogCreated()
}
+ private fun initializeRadioButtons() {
+ openInAppButton = dialog.requireViewById(R.id.open_in_app_button)
+ openInBrowserButton = dialog.requireViewById(R.id.open_in_browser_button)
+
+ val userState =
+ getDomainVerificationUserState(domainVerificationManager, packageName) ?: return
+ val openInApp = userState.isLinkHandlingAllowed
+ openInAppButton.isChecked = openInApp
+ openInBrowserButton.isChecked = !openInApp
+ }
+
+ private fun setDefaultLinkHandlingSetting() {
+ domainVerificationManager.setDomainVerificationLinkHandlingAllowed(
+ packageName, openInAppButton.isChecked)
+ }
+
private fun closeMenu() {
dialogContainer?.releaseView()
dialogContainer = null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt
index d03a38e..1b914f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialogView.kt
@@ -36,9 +36,6 @@
private lateinit var backgroundDim: Drawable
fun setDismissOnClickListener(callback: (View) -> Unit) {
- val dismissButton = dialogContainer.requireViewById<Button>(
- R.id.open_by_default_settings_dialog_dismiss_button)
- dismissButton.setOnClickListener(callback)
// Clicks on the background dim should also dismiss the dialog.
setOnClickListener(callback)
// We add a no-op on-click listener to the dialog container so that clicks on it won't
@@ -46,6 +43,13 @@
dialogContainer.setOnClickListener { }
}
+ fun setConfirmButtonClickListener(callback: (View) -> Unit) {
+ val dismissButton = dialogContainer.requireViewById<Button>(
+ R.id.open_by_default_settings_dialog_confirm_button
+ )
+ dismissButton.setOnClickListener(callback)
+ }
+
override fun onFinishInflate() {
super.onFinishInflate()
dialogContainer = requireViewById(R.id.open_by_default_dialog_container)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index b3491ba..b83b5f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -177,26 +177,84 @@
}
/**
+ * Calculates the transform to apply on a UNTRANSFORMED (config-at-end) Activity surface in
+ * order for it's hint-rect to occupy the same task-relative position/dimensions as it would
+ * have at the end of the transition (post-configuration).
+ *
+ * This is intended to be used in tandem with [calcStartTransform] below applied to the parent
+ * task. Applying both transforms simultaneously should result in the appearance of nothing
+ * having happened yet.
+ *
+ * Only the task should be animated (into it's identity state) and then WMCore will reset the
+ * activity transform in sync with its new configuration upon finish.
+ *
+ * Usage example:
+ * calcEndTransform(pipActivity, pipTask, scale, pos);
+ * t.setScale(pipActivity.getLeash(), scale.x, scale.y);
+ * t.setPosition(pipActivity.getLeash(), pos.x, pos.y);
+ *
+ * @see calcStartTransform
+ */
+ @JvmStatic
+ fun calcEndTransform(pipActivity: TransitionInfo.Change, pipTask: TransitionInfo.Change,
+ outScale: PointF, outPos: PointF) {
+ val actStartBounds = pipActivity.startAbsBounds
+ val actEndBounds = pipActivity.endAbsBounds
+ val taskEndBounds = pipTask.endAbsBounds
+
+ var hintRect = pipTask.taskInfo?.pictureInPictureParams?.sourceRectHint
+ if (hintRect == null) {
+ hintRect = Rect(actStartBounds)
+ hintRect.offsetTo(0, 0)
+ }
+
+ // FA = final activity bounds (absolute)
+ // FT = final task bounds (absolute)
+ // SA = start activity bounds (absolute)
+ // H = source hint (relative to start activity bounds)
+ // We want to transform the activity so that when the task is at FT, H overlaps with FA
+
+ // This scales the activity such that the hint rect has the same dimensions
+ // as the final activity bounds.
+ val hintToEndScaleX = (actEndBounds.width().toFloat()) / (hintRect.width().toFloat())
+ val hintToEndScaleY = (actEndBounds.height().toFloat()) / (hintRect.height().toFloat())
+ // top-left needs to be (FA.tl - FT.tl) - H.tl * hintToEnd . H is relative to the
+ // activity; so, for example, if shrinking H to FA (hintToEnd < 1), then the tl of the
+ // shrunk SA is closer to H than expected, so we need to reduce how much we offset SA
+ // to get H.tl to match.
+ val startActPosInTaskEndX =
+ (actEndBounds.left - taskEndBounds.left) - hintRect.left * hintToEndScaleX
+ val startActPosInTaskEndY =
+ (actEndBounds.top - taskEndBounds.top) - hintRect.top * hintToEndScaleY
+ outScale.set(hintToEndScaleX, hintToEndScaleY)
+ outPos.set(startActPosInTaskEndX, startActPosInTaskEndY)
+ }
+
+ /**
* Calculates the transform and crop to apply on a Task surface in order for the config-at-end
* activity inside it (original-size activity transformed to match it's hint rect to the final
* Task bounds) to occupy the same world-space position/dimensions as it had before the
* transition.
*
+ * Intended to be used in tandem with [calcEndTransform].
+ *
* Usage example:
- * calcStartTransform(pipChange, scale, pos, crop);
- * t.setScale(pipChange.getLeash(), scale.x, scale.y);
- * t.setPosition(pipChange.getLeash(), pos.x, pos.y);
- * t.setCrop(pipChange.getLeash(), crop);
+ * calcStartTransform(pipTask, scale, pos, crop);
+ * t.setScale(pipTask.getLeash(), scale.x, scale.y);
+ * t.setPosition(pipTask.getLeash(), pos.x, pos.y);
+ * t.setCrop(pipTask.getLeash(), crop);
+ *
+ * @see calcEndTransform
*/
@JvmStatic
- fun calcStartTransform(pipChange: TransitionInfo.Change, outScale: PointF,
+ fun calcStartTransform(pipTask: TransitionInfo.Change, outScale: PointF,
outPos: PointF, outCrop: Rect) {
- val startBounds = pipChange.startAbsBounds
- val taskEndBounds = pipChange.endAbsBounds
+ val startBounds = pipTask.startAbsBounds
+ val taskEndBounds = pipTask.endAbsBounds
// For now, pip activity bounds always matches task bounds. If this ever changes, we'll
// need to get the activity offset.
val endBounds = taskEndBounds
- var hintRect = pipChange.taskInfo?.pictureInPictureParams?.sourceRectHint
+ var hintRect = pipTask.taskInfo?.pictureInPictureParams?.sourceRectHint
if (hintRect == null) {
hintRect = Rect(startBounds)
hintRect.offsetTo(0, 0)
@@ -226,8 +284,8 @@
+ startBounds.left + hintRect.left)
val endTaskPosForStartY = (-(endBounds.top - taskEndBounds.top) * endToHintScaleY
+ startBounds.top + hintRect.top)
- outScale[endToHintScaleX] = endToHintScaleY
- outPos[endTaskPosForStartX] = endTaskPosForStartY
+ outScale.set(endToHintScaleX, endToHintScaleY)
+ outPos.set(endTaskPosForStartX, endTaskPosForStartY)
// now need to set crop to reveal the non-hint stuff. Again, hintrect is relative, so
// we must apply outsets to reveal the *activity* content which is *inside* the task
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 d457a0f..52262e6 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
@@ -111,6 +111,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.DefaultMixedHandler;
+import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.MixedTransitionHandler;
import com.android.wm.shell.transition.Transitions;
@@ -134,14 +135,14 @@
import dagger.Module;
import dagger.Provides;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
import kotlinx.coroutines.MainCoroutineDispatcher;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
/**
* Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
* from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
@@ -263,7 +264,8 @@
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
- Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) {
+ Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler,
+ FocusTransitionObserver focusTransitionObserver) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return new DesktopModeWindowDecorViewModel(
context,
@@ -290,7 +292,8 @@
desktopTasksLimiter,
appHandleEducationController,
windowDecorCaptionHandleRepository,
- desktopActivityOrientationHandler);
+ desktopActivityOrientationHandler,
+ focusTransitionObserver);
}
return new CaptionWindowDecorViewModel(
context,
@@ -304,7 +307,8 @@
displayController,
rootTaskDisplayAreaOrganizer,
syncQueue,
- transitions);
+ transitions,
+ focusTransitionObserver);
}
@WMSingleton
@@ -391,10 +395,11 @@
Transitions transitions,
Optional<DesktopFullImmersiveTransitionHandler> desktopImmersiveTransitionHandler,
WindowDecorViewModel windowDecorViewModel,
- Optional<TaskChangeListener> taskChangeListener) {
+ Optional<TaskChangeListener> taskChangeListener,
+ FocusTransitionObserver focusTransitionObserver) {
return new FreeformTaskTransitionObserver(
context, shellInit, transitions, desktopImmersiveTransitionHandler,
- windowDecorViewModel, taskChangeListener);
+ windowDecorViewModel, taskChangeListener, focusTransitionObserver);
}
@WMSingleton
@@ -693,10 +698,16 @@
static Optional<DesktopFullImmersiveTransitionHandler> provideDesktopImmersiveHandler(
Context context,
Transitions transitions,
- @DynamicOverride DesktopRepository desktopRepository) {
+ @DynamicOverride DesktopRepository desktopRepository,
+ DisplayController displayController,
+ ShellTaskOrganizer shellTaskOrganizer) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(
- new DesktopFullImmersiveTransitionHandler(transitions, desktopRepository));
+ new DesktopFullImmersiveTransitionHandler(
+ transitions,
+ desktopRepository,
+ displayController,
+ shellTaskOrganizer));
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
index f749aa1..679179a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt
@@ -27,8 +27,12 @@
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.core.animation.addListener
+import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
-import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
@@ -41,16 +45,29 @@
class DesktopFullImmersiveTransitionHandler(
private val transitions: Transitions,
private val desktopRepository: DesktopRepository,
+ private val displayController: DisplayController,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
private val transactionSupplier: () -> SurfaceControl.Transaction,
) : TransitionHandler {
constructor(
transitions: Transitions,
desktopRepository: DesktopRepository,
- ) : this(transitions, desktopRepository, { SurfaceControl.Transaction() })
+ displayController: DisplayController,
+ shellTaskOrganizer: ShellTaskOrganizer,
+ ) : this(
+ transitions,
+ desktopRepository,
+ displayController,
+ shellTaskOrganizer,
+ { SurfaceControl.Transaction() }
+ )
private var state: TransitionState? = null
+ @VisibleForTesting
+ val pendingExternalExitTransitions = mutableSetOf<ExternalPendingExit>()
+
/** Whether there is an immersive transition that hasn't completed yet. */
private val inProgress: Boolean
get() = state != null
@@ -61,15 +78,15 @@
var onTaskResizeAnimationListener: OnTaskResizeAnimationListener? = null
/** Starts a transition to enter full immersive state inside the desktop. */
- fun enterImmersive(taskInfo: RunningTaskInfo, wct: WindowContainerTransaction) {
+ fun moveTaskToImmersive(taskInfo: RunningTaskInfo) {
if (inProgress) {
- ProtoLog.v(
- ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "FullImmersive: cannot start entry because transition already in progress."
- )
+ logV("Cannot start entry because transition already in progress.")
return
}
-
+ val wct = WindowContainerTransaction().apply {
+ setBounds(taskInfo.token, Rect())
+ }
+ logV("Moving task ${taskInfo.taskId} into immersive mode")
val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
state = TransitionState(
transition = transition,
@@ -79,15 +96,18 @@
)
}
- fun exitImmersive(taskInfo: RunningTaskInfo, wct: WindowContainerTransaction) {
+ fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo) {
if (inProgress) {
- ProtoLog.v(
- ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "$TAG: cannot start exit because transition already in progress."
- )
+ logV("Cannot start exit because transition already in progress.")
return
}
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ val destinationBounds = calculateMaximizeBounds(displayLayout, taskInfo)
+ val wct = WindowContainerTransaction().apply {
+ setBounds(taskInfo.token, destinationBounds)
+ }
+ logV("Moving task ${taskInfo.taskId} out of immersive mode")
val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
state = TransitionState(
transition = transition,
@@ -97,6 +117,82 @@
)
}
+ /**
+ * Bring the immersive app of the given [displayId] out of immersive mode, if applicable.
+ *
+ * @param transition that will apply this transaction
+ * @param wct that will apply these changes
+ * @param displayId of the display that should exit immersive mode
+ */
+ fun exitImmersiveIfApplicable(
+ transition: IBinder,
+ wct: WindowContainerTransaction,
+ displayId: Int
+ ) {
+ if (!Flags.enableFullyImmersiveInDesktop()) return
+ exitImmersiveIfApplicable(wct, displayId)?.invoke(transition)
+ }
+
+ /**
+ * Bring the immersive app of the given [displayId] out of immersive mode, if applicable.
+ *
+ * @param wct that will apply these changes
+ * @param displayId of the display that should exit immersive mode
+ * @return a function to apply once the transition that will apply these changes is started
+ */
+ fun exitImmersiveIfApplicable(
+ wct: WindowContainerTransaction,
+ displayId: Int
+ ): ((IBinder) -> Unit)? {
+ if (!Flags.enableFullyImmersiveInDesktop()) return null
+ val displayLayout = displayController.getDisplayLayout(displayId) ?: return null
+ val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) ?: return null
+ val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return null
+ logV("Appending immersive exit for task: $immersiveTask in display: $displayId")
+ wct.setBounds(taskInfo.token, calculateMaximizeBounds(displayLayout, taskInfo))
+ return { transition -> addPendingImmersiveExit(immersiveTask, displayId, transition) }
+ }
+
+ /**
+ * Bring the given [taskInfo] out of immersive mode, if applicable.
+ *
+ * @param wct that will apply these changes
+ * @param taskInfo of the task that should exit immersive mode
+ * @return a function to apply once the transition that will apply these changes is started
+ */
+ fun exitImmersiveIfApplicable(
+ wct: WindowContainerTransaction,
+ taskInfo: RunningTaskInfo
+ ): ((IBinder) -> Unit)? {
+ if (!Flags.enableFullyImmersiveInDesktop()) return null
+ if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
+ // A full immersive task is being minimized, make sure the immersive state is broken
+ // (i.e. resize back to max bounds).
+ displayController.getDisplayLayout(taskInfo.displayId)?.let { displayLayout ->
+ wct.setBounds(taskInfo.token, calculateMaximizeBounds(displayLayout, taskInfo))
+ logV("Appending immersive exit for task: ${taskInfo.taskId}")
+ return { transition ->
+ addPendingImmersiveExit(
+ taskId = taskInfo.taskId,
+ displayId = taskInfo.displayId,
+ transition = transition
+ )
+ }
+ }
+ }
+ return null
+ }
+
+ private fun addPendingImmersiveExit(taskId: Int, displayId: Int, transition: IBinder) {
+ pendingExternalExitTransitions.add(
+ ExternalPendingExit(
+ taskId = taskId,
+ displayId = displayId,
+ transition = transition
+ )
+ )
+ }
+
override fun startAnimation(
transition: IBinder,
info: TransitionInfo,
@@ -190,15 +286,31 @@
* Called when any transition in the system is ready to play. This is needed to update the
* repository state before window decorations are drawn (which happens immediately after
* |onTransitionReady|, before this transition actually animates) because drawing decorations
- * depends in whether the task is in full immersive state or not.
+ * depends on whether the task is in full immersive state or not.
*/
- fun onTransitionReady(transition: IBinder) {
+ fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
+ // Check if this is a pending external exit transition.
+ val pendingExit = pendingExternalExitTransitions
+ .firstOrNull { pendingExit -> pendingExit.transition == transition }
+ if (pendingExit != null) {
+ pendingExternalExitTransitions.remove(pendingExit)
+ if (info.hasTaskChange(taskId = pendingExit.taskId)) {
+ if (desktopRepository.isTaskInFullImmersiveState(pendingExit.taskId)) {
+ logV("Pending external exit for task ${pendingExit.taskId} verified")
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = pendingExit.displayId,
+ taskId = pendingExit.taskId,
+ immersive = false
+ )
+ }
+ }
+ return
+ }
+
+ // Check if this is a direct immersive enter/exit transition.
val state = this.state ?: return
- // TODO: b/369443668 - this assumes invoking the exit transition is the only way to exit
- // immersive, which isn't realistic. The app could crash, the user could dismiss it from
- // overview, etc. This (or its caller) should search all transitions to look for any
- // immersive task exiting that state to keep the repository properly updated.
if (transition == state.transition) {
+ logV("Direct move for task ${state.taskId} in ${state.direction} direction verified")
when (state.direction) {
Direction.ENTER -> {
desktopRepository.setTaskInFullImmersiveState(
@@ -225,6 +337,9 @@
private fun requireState(): TransitionState =
state ?: error("Expected non-null transition state")
+ private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean =
+ changes.any { c -> c.taskInfo?.taskId == taskId }
+
/** The state of the currently running transition. */
private data class TransitionState(
val transition: IBinder,
@@ -233,12 +348,28 @@
val direction: Direction
)
+ /**
+ * Tracks state of a transition involving an immersive exit that is external to this class' own
+ * transitions. This usually means transitions that exit immersive mode as a side-effect and
+ * not the primary action (for example, minimizing the immersive task or launching a new task
+ * on top of the immersive task).
+ */
+ data class ExternalPendingExit(
+ val taskId: Int,
+ val displayId: Int,
+ val transition: IBinder,
+ )
+
private enum class Direction {
ENTER, EXIT
}
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
private companion object {
- private const val TAG = "FullImmersiveHandler"
+ private const val TAG = "DesktopImmersive"
private const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index bd61722..6d47922 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -123,6 +123,29 @@
}
/**
+ * Calculates the maximized bounds of a task given in the given [DisplayLayout], taking
+ * resizability into consideration.
+ */
+fun calculateMaximizeBounds(
+ displayLayout: DisplayLayout,
+ taskInfo: RunningTaskInfo,
+): Rect {
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+ if (taskInfo.isResizeable) {
+ // if resizable then expand to entire stable bounds (full display minus insets)
+ return Rect(stableBounds)
+ } else {
+ // if non-resizable then calculate max bounds according to aspect ratio
+ val activityAspectRatio = calculateAspectRatio(taskInfo)
+ val newSize = maximizeSizeGivenAspectRatio(taskInfo,
+ Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
+ return centerInArea(
+ newSize, stableBounds, stableBounds.left, stableBounds.top)
+ }
+}
+
+/**
* Calculates the largest size that can fit in a given area while maintaining a specific aspect
* ratio.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index c175133..5ac4ef5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -328,6 +328,10 @@
return desktopTaskDataSequence().any { taskId == it.fullImmersiveTaskId }
}
+ /** Returns the task that is currently in immersive mode in this display, or null. */
+ fun getTaskInFullImmersiveState(displayId: Int): Int? =
+ desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId
+
private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
visibleTasksListeners.forEach { (listener, executor) ->
executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 75c795b..3f6dc94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -91,6 +91,7 @@
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
@@ -190,6 +191,7 @@
private var recentsAnimationRunning = false
private lateinit var splitScreenController: SplitScreenController
+ lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
// Launch cookie used to identify a drag and drop transition to fullscreen after it has begun.
// Used to prevent handleRequest from moving the new fullscreen task to freeform.
private var dragAndDropFullscreenCookie: Binder? = null
@@ -354,6 +356,8 @@
// TODO(342378842): Instead of using default display, support multiple displays
val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
DEFAULT_DISPLAY, wct, taskId)
+ val runOnTransit = immersiveTransitionHandler
+ .exitImmersiveIfApplicable(wct, DEFAULT_DISPLAY)
wct.startTask(
taskId,
ActivityOptions.makeBasic().apply {
@@ -363,6 +367,7 @@
// TODO(343149901): Add DPI changes for task launch
val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
addPendingMinimizeTransition(transition, taskToMinimize)
+ runOnTransit?.invoke(transition)
return true
}
@@ -379,6 +384,7 @@
}
logV("moveRunningTaskToDesktop taskId=%d", task.taskId)
exitSplitIfApplicable(wct, task)
+ val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable(wct, task.displayId)
// Bring other apps to front first
val taskToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
@@ -386,6 +392,7 @@
val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
addPendingMinimizeTransition(transition, taskToMinimize)
+ runOnTransit?.invoke(transition)
}
/**
@@ -422,8 +429,13 @@
val taskToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
+ val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable(
+ wct, taskInfo.displayId)
val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
- transition?.let { addPendingMinimizeTransition(it, taskToMinimize) }
+ transition?.let {
+ addPendingMinimizeTransition(it, taskToMinimize)
+ runOnTransit?.invoke(transition)
+ }
}
/**
@@ -455,18 +467,28 @@
taskRepository.addClosingTask(displayId, taskId)
}
- /**
- * Perform clean up of the desktop wallpaper activity if the minimized window task is the last
- * active task.
- *
- * @param wct transaction to modify if the last active task is minimized
- * @param taskId task id of the window that's being minimized
- */
- fun onDesktopWindowMinimize(wct: WindowContainerTransaction, taskId: Int) {
+ fun minimizeTask(taskInfo: RunningTaskInfo) {
+ val taskId = taskInfo.taskId
+ val displayId = taskInfo.displayId
+ val wct = WindowContainerTransaction()
if (taskRepository.isOnlyVisibleNonClosingTask(taskId)) {
+ // Perform clean up of the desktop wallpaper activity if the minimized window task is
+ // the last active task.
removeWallpaperActivity(wct)
}
- // Do not call taskRepository.minimizeTask because it will be called by DekstopTasksLimiter.
+ // Notify immersive handler as it might need to exit immersive state.
+ val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable(wct, taskInfo)
+
+ wct.reorder(taskInfo.token, false)
+ val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct)
+ desktopTasksLimiter.ifPresent {
+ it.addPendingMinimizeChange(
+ transition = transition,
+ displayId = displayId,
+ taskId = taskId
+ )
+ }
+ runOnTransit?.invoke(transition)
}
/** Move a task with given `taskId` to fullscreen */
@@ -552,6 +574,8 @@
// TODO: b/342378842 - Instead of using default display, support multiple displays
val taskToMinimize: RunningTaskInfo? =
addAndGetMinimizeChangesIfNeeded(DEFAULT_DISPLAY, wct, taskId)
+ val runOnTransit = immersiveTransitionHandler
+ .exitImmersiveIfApplicable(wct, DEFAULT_DISPLAY)
wct.startTask(
taskId,
ActivityOptions.makeBasic().apply {
@@ -560,6 +584,7 @@
)
val transition = transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
addPendingMinimizeTransition(transition, taskToMinimize)
+ runOnTransit?.invoke(transition)
}
/** Move a task to the front */
@@ -567,11 +592,14 @@
logV("moveTaskToFront taskId=%s", taskInfo.taskId)
val wct = WindowContainerTransaction()
wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */)
+ val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable(
+ wct, taskInfo.displayId)
val taskToMinimize =
addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo.taskId)
val transition = transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
addPendingMinimizeTransition(transition, taskToMinimize)
+ runOnTransit?.invoke(transition)
}
/**
@@ -643,22 +671,12 @@
private fun moveDesktopTaskToFullImmersive(taskInfo: RunningTaskInfo) {
check(taskInfo.isFreeform) { "Task must already be in freeform" }
- val wct = WindowContainerTransaction().apply {
- setBounds(taskInfo.token, Rect())
- }
- immersiveTransitionHandler.enterImmersive(taskInfo, wct)
+ immersiveTransitionHandler.moveTaskToImmersive(taskInfo)
}
private fun exitDesktopTaskFromFullImmersive(taskInfo: RunningTaskInfo) {
check(taskInfo.isFreeform) { "Task must already be in freeform" }
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
- val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
- val destinationBounds = getMaximizeBounds(taskInfo, stableBounds)
-
- val wct = WindowContainerTransaction().apply {
- setBounds(taskInfo.token, destinationBounds)
- }
- immersiveTransitionHandler.exitImmersive(taskInfo, wct)
+ immersiveTransitionHandler.moveTaskToNonImmersive(taskInfo)
}
/**
@@ -697,7 +715,7 @@
// and toggle to the stable bounds.
taskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds)
- destinationBounds.set(getMaximizeBounds(taskInfo, stableBounds))
+ destinationBounds.set(calculateMaximizeBounds(displayLayout, taskInfo))
}
@@ -1285,8 +1303,10 @@
if (useDesktopOverrideDensity()) {
wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE)
}
- // Desktop Mode is showing and we're launching a new Task - we might need to minimize
- // a Task.
+ // Desktop Mode is showing and we're launching a new Task:
+ // 1) Exit immersive if needed.
+ immersiveTransitionHandler.exitImmersiveIfApplicable(transition, wct, task.displayId)
+ // 2) minimize a Task if needed.
val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId)
if (taskToMinimize != null) {
addPendingMinimizeTransition(transition, taskToMinimize)
@@ -1316,6 +1336,9 @@
val taskToMinimize =
addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId)
addPendingMinimizeTransition(transition, taskToMinimize)
+ immersiveTransitionHandler.exitImmersiveIfApplicable(
+ transition, wct, task.displayId
+ )
}
}
return null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 1090a46..86351e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -51,5 +51,5 @@
void moveToDesktop(int taskId, in DesktopModeTransitionSource transitionSource);
/** Remove desktop on the given display */
- void removeDesktop(int displayId);
+ oneway void removeDesktop(int displayId);
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index ae65892..a16446ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -126,6 +126,7 @@
|| repository.isClosingTask(taskInfo.taskId)) {
// A task that's vanishing should be removed:
// - If it's closed by the X button which means it's marked as a closing task.
+ repository.removeClosingTask(taskInfo.taskId);
repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
} else {
repository.updateTaskVisibility(taskInfo.displayId, taskInfo.taskId, false);
@@ -150,8 +151,6 @@
mDesktopRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
- } else if (repository.isClosingTask(taskInfo.taskId)) {
- repository.removeClosingTask(taskInfo.taskId);
}
repository.updateTaskVisibility(taskInfo.displayId, taskInfo.taskId,
taskInfo.isVisible);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index c9eccc3..771573d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -30,6 +30,7 @@
import com.android.window.flags.Flags;
import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -50,6 +51,7 @@
private final Optional<DesktopFullImmersiveTransitionHandler> mImmersiveTransitionHandler;
private final WindowDecorViewModel mWindowDecorViewModel;
private final Optional<TaskChangeListener> mTaskChangeListener;
+ private final FocusTransitionObserver mFocusTransitionObserver;
private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
new HashMap<>();
@@ -60,11 +62,13 @@
Transitions transitions,
Optional<DesktopFullImmersiveTransitionHandler> immersiveTransitionHandler,
WindowDecorViewModel windowDecorViewModel,
- Optional<TaskChangeListener> taskChangeListener) {
+ Optional<TaskChangeListener> taskChangeListener,
+ FocusTransitionObserver focusTransitionObserver) {
mTransitions = transitions;
mImmersiveTransitionHandler = immersiveTransitionHandler;
mWindowDecorViewModel = windowDecorViewModel;
mTaskChangeListener = taskChangeListener;
+ mFocusTransitionObserver = focusTransitionObserver;
if (FreeformComponents.isFreeformEnabled(context)) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -85,8 +89,11 @@
// TODO(b/367268953): Remove when DesktopTaskListener is introduced and the repository
// is updated from there **before** the |mWindowDecorViewModel| methods are invoked.
// Otherwise window decoration relayout won't run with the immersive state up to date.
- mImmersiveTransitionHandler.ifPresent(h -> h.onTransitionReady(transition));
+ mImmersiveTransitionHandler.ifPresent(h -> h.onTransitionReady(transition, info));
}
+ // Update focus state first to ensure the correct state can be queried from listeners.
+ // TODO(371503964): Remove this once the unified task repository is ready.
+ mFocusTransitionObserver.updateFocusState(info);
final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>();
final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
index 399e39a..6d01e24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
@@ -16,7 +16,8 @@
package com.android.wm.shell.transition;
-import static android.view.Display.INVALID_DISPLAY;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -24,10 +25,11 @@
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
import android.annotation.NonNull;
-import android.os.IBinder;
+import android.app.ActivityManager.RunningTaskInfo;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.util.Slog;
-import android.view.SurfaceControl;
+import android.util.SparseArray;
import android.window.TransitionInfo;
import com.android.wm.shell.shared.FocusTransitionListener;
@@ -43,44 +45,64 @@
* It reports transitions to callers outside of the process via {@link IFocusTransitionListener},
* and callers within the process via {@link FocusTransitionListener}.
*/
-public class FocusTransitionObserver implements TransitionObserver {
+public class FocusTransitionObserver {
private static final String TAG = FocusTransitionObserver.class.getSimpleName();
private IFocusTransitionListener mRemoteListener;
private final Map<FocusTransitionListener, Executor> mLocalListeners =
new HashMap<>();
- private int mFocusedDisplayId = INVALID_DISPLAY;
+ private int mFocusedDisplayId = DEFAULT_DISPLAY;
+ private final SparseArray<RunningTaskInfo> mFocusedTaskOnDisplay = new SparseArray<>();
+
+ private final ArraySet<RunningTaskInfo> mTmpTasksToBeNotified = new ArraySet<>();
public FocusTransitionObserver() {}
- @Override
- public void onTransitionReady(@NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction) {
+ /**
+ * Update display/window focus state from the given transition info and notifies changes if any.
+ */
+ public void updateFocusState(@NonNull TransitionInfo info) {
+ if (!enableDisplayFocusInShellTransitions()) {
+ return;
+ }
final List<TransitionInfo.Change> changes = info.getChanges();
for (int i = changes.size() - 1; i >= 0; i--) {
final TransitionInfo.Change change = changes.get(i);
+
+ final RunningTaskInfo task = change.getTaskInfo();
+ if (task != null
+ && (change.hasFlags(FLAG_MOVED_TO_TOP) || change.getMode() == TRANSIT_OPEN)) {
+ final RunningTaskInfo lastFocusedTaskOnDisplay =
+ mFocusedTaskOnDisplay.get(task.displayId);
+ if (lastFocusedTaskOnDisplay != null) {
+ mTmpTasksToBeNotified.add(lastFocusedTaskOnDisplay);
+ }
+ mTmpTasksToBeNotified.add(task);
+ mFocusedTaskOnDisplay.put(task.displayId, task);
+ }
+
if (change.hasFlags(FLAG_IS_DISPLAY) && change.hasFlags(FLAG_MOVED_TO_TOP)) {
if (mFocusedDisplayId != change.getEndDisplayId()) {
+ final RunningTaskInfo lastGloballyFocusedTask =
+ mFocusedTaskOnDisplay.get(mFocusedDisplayId);
+ if (lastGloballyFocusedTask != null) {
+ mTmpTasksToBeNotified.add(lastGloballyFocusedTask);
+ }
mFocusedDisplayId = change.getEndDisplayId();
notifyFocusedDisplayChanged();
+ final RunningTaskInfo currentGloballyFocusedTask =
+ mFocusedTaskOnDisplay.get(mFocusedDisplayId);
+ if (currentGloballyFocusedTask != null) {
+ mTmpTasksToBeNotified.add(currentGloballyFocusedTask);
+ }
}
- return;
}
}
+ mTmpTasksToBeNotified.forEach(this::notifyTaskFocusChanged);
+ mTmpTasksToBeNotified.clear();
}
- @Override
- public void onTransitionStarting(@NonNull IBinder transition) {}
-
- @Override
- public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {}
-
- @Override
- public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {}
-
/**
* Sets the focus transition listener that receives any transitions resulting in focus switch.
* This is for calls from outside the Shell, within the host process.
@@ -92,7 +114,10 @@
return;
}
mLocalListeners.put(listener, executor);
- executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId));
+ executor.execute(() -> {
+ listener.onFocusedDisplayChanged(mFocusedDisplayId);
+ mTmpTasksToBeNotified.forEach(this::notifyTaskFocusChanged);
+ });
}
/**
@@ -120,13 +145,20 @@
notifyFocusedDisplayChangedToRemote();
}
- /**
- * Notifies the listener that display focus has changed.
- */
- public void notifyFocusedDisplayChanged() {
+ private void notifyTaskFocusChanged(RunningTaskInfo task) {
+ final boolean isFocusedOnDisplay = isFocusedOnDisplay(task);
+ final boolean isFocusedGlobally = hasGlobalFocus(task);
+ mLocalListeners.forEach((listener, executor) ->
+ executor.execute(() -> listener.onFocusedTaskChanged(task.taskId,
+ isFocusedOnDisplay, isFocusedGlobally)));
+ }
+
+ private void notifyFocusedDisplayChanged() {
notifyFocusedDisplayChangedToRemote();
mLocalListeners.forEach((listener, executor) ->
- executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId)));
+ executor.execute(() -> {
+ listener.onFocusedDisplayChanged(mFocusedDisplayId);
+ }));
}
private void notifyFocusedDisplayChangedToRemote() {
@@ -138,4 +170,23 @@
}
}
}
+
+ private boolean isFocusedOnDisplay(@NonNull RunningTaskInfo task) {
+ if (!enableDisplayFocusInShellTransitions()) {
+ return task.isFocused;
+ }
+ final RunningTaskInfo focusedTaskOnDisplay = mFocusedTaskOnDisplay.get(task.displayId);
+ return focusedTaskOnDisplay != null && focusedTaskOnDisplay.taskId == task.taskId;
+ }
+
+ /**
+ * Checks whether the given task has focused globally on the system.
+ * (Note {@link RunningTaskInfo#isFocused} represents per-display focus.)
+ */
+ public boolean hasGlobalFocus(@NonNull RunningTaskInfo task) {
+ if (!enableDisplayFocusInShellTransitions()) {
+ return task.isFocused;
+ }
+ return task.displayId == mFocusedDisplayId && isFocusedOnDisplay(task);
+ }
}
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 d5e92e6..346f21b 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
@@ -392,8 +392,6 @@
mShellCommandHandler.addCommandCallback("transitions", this, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
-
- registerObserver(mFocusTransitionObserver);
}
public boolean isRegistered() {
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 c540ede..be4fd7c 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
@@ -58,9 +58,11 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
@@ -68,7 +70,7 @@
* View model for the window decoration with a caption and shadows. Works with
* {@link CaptionWindowDecoration}.
*/
-public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
+public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusTransitionListener {
private static final String TAG = "CaptionWindowDecorViewModel";
private final ShellTaskOrganizer mTaskOrganizer;
@@ -85,6 +87,7 @@
private final Region mExclusionRegion = Region.obtain();
private final InputManager mInputManager;
private TaskOperations mTaskOperations;
+ private FocusTransitionObserver mFocusTransitionObserver;
/**
* Whether to pilfer the next motion event to send cancellations to the windows below.
@@ -121,7 +124,8 @@
DisplayController displayController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SyncTransactionQueue syncQueue,
- Transitions transitions) {
+ Transitions transitions,
+ FocusTransitionObserver focusTransitionObserver) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -133,6 +137,7 @@
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mSyncQueue = syncQueue;
mTransitions = transitions;
+ mFocusTransitionObserver = focusTransitionObserver;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
@@ -148,6 +153,16 @@
} catch (RemoteException e) {
Log.e(TAG, "Failed to register window manager callbacks", e);
}
+ mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
+ }
+
+ @Override
+ public void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay,
+ boolean isFocusedGlobally) {
+ final WindowDecoration decor = mWindowDecorByTaskId.get(taskId);
+ if (decor != null) {
+ decor.relayout(decor.mTaskInfo, isFocusedGlobally);
+ }
}
@Override
@@ -180,7 +195,7 @@
return;
}
- decoration.relayout(taskInfo);
+ decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
}
@Override
@@ -217,7 +232,8 @@
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
- false /* setTaskCropAndPosition */);
+ false /* setTaskCropAndPosition */,
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo));
}
}
@@ -230,7 +246,8 @@
if (decoration == null) return;
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
- false /* setTaskCropAndPosition */);
+ false /* setTaskCropAndPosition */,
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo));
}
@Override
@@ -308,7 +325,8 @@
windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setTaskDragResizer(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */);
+ false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */,
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo));
}
private class CaptionTouchEventListener implements
@@ -359,7 +377,7 @@
}
if (e.getAction() == MotionEvent.ACTION_DOWN) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (!taskInfo.isFocused) {
+ if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mTaskToken, true /* onTop */, true /* includingParents */);
mSyncQueue.queue(wct);
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 576c911..509cb85 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
@@ -174,7 +174,7 @@
}
@Override
- void relayout(RunningTaskInfo taskInfo) {
+ void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
// The crop and position of the task should only be set when a task is fluid resizing. In
// all other cases, it is expected that the transition handler positions and crops the task
@@ -185,7 +185,7 @@
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
- shouldSetTaskPositionAndCrop);
+ shouldSetTaskPositionAndCrop, hasGlobalFocus);
}
@VisibleForTesting
@@ -196,12 +196,13 @@
boolean setTaskCropAndPosition,
boolean isStatusBarVisible,
boolean isKeyguardVisibleAndOccluded,
- InsetsState displayInsetsState) {
+ InsetsState displayInsetsState,
+ boolean hasGlobalFocus) {
relayoutParams.reset();
relayoutParams.mRunningTaskInfo = taskInfo;
relayoutParams.mLayoutResId = R.layout.caption_window_decor;
relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
- relayoutParams.mShadowRadiusId = taskInfo.isFocused
+ relayoutParams.mShadowRadiusId = hasGlobalFocus
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
@@ -233,7 +234,8 @@
@SuppressLint("MissingPermission")
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
+ boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition,
+ boolean hasGlobalFocus) {
final boolean isFreeform =
taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = ENABLE_WINDOWING_SCALED_RESIZING.isTrue()
@@ -245,7 +247,7 @@
updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
setTaskCropAndPosition, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded,
- mDisplayController.getInsetsState(taskInfo.displayId));
+ mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index e55bc67..9e089b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -56,7 +56,6 @@
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -112,6 +111,7 @@
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -124,6 +124,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
@@ -133,20 +134,21 @@
import kotlin.Pair;
import kotlin.Unit;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
/**
* View model for the window decoration with a caption and shadows. Works with
* {@link DesktopModeWindowDecoration}.
*/
-public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
+public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
+ FocusTransitionListener {
private static final String TAG = "DesktopModeWindowDecorViewModel";
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
@@ -216,6 +218,7 @@
}
};
private final TaskPositionerFactory mTaskPositionerFactory;
+ private final FocusTransitionObserver mFocusTransitionObserver;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -242,7 +245,8 @@
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
- Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler) {
+ Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
+ FocusTransitionObserver focusTransitionObserver) {
this(
context,
shellExecutor,
@@ -274,7 +278,8 @@
appHandleEducationController,
windowDecorCaptionHandleRepository,
activityOrientationChangeHandler,
- new TaskPositionerFactory());
+ new TaskPositionerFactory(),
+ focusTransitionObserver);
}
@VisibleForTesting
@@ -309,7 +314,8 @@
AppHandleEducationController appHandleEducationController,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
- TaskPositionerFactory taskPositionerFactory) {
+ TaskPositionerFactory taskPositionerFactory,
+ FocusTransitionObserver focusTransitionObserver) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -369,6 +375,7 @@
}
};
mTaskPositionerFactory = taskPositionerFactory;
+ mFocusTransitionObserver = focusTransitionObserver;
shellInit.addInitCallback(this::onInit, this);
}
@@ -402,11 +409,22 @@
return Unit.INSTANCE;
});
}
+ mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
+ }
+
+ @Override
+ public void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay,
+ boolean isFocusedGlobally) {
+ final WindowDecoration decor = mWindowDecorByTaskId.get(taskId);
+ if (decor != null) {
+ decor.relayout(decor.mTaskInfo, isFocusedGlobally);
+ }
}
@Override
public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
+ mDesktopTasksController.setFreeformTaskTransitionStarter(transitionStarter);
}
@Override
@@ -447,7 +465,7 @@
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
- decoration.relayout(taskInfo);
+ decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
mActivityOrientationChangeHandler.ifPresent(handler ->
handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
}
@@ -486,7 +504,8 @@
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
- false /* shouldSetTaskPositionAndCrop */);
+ false /* shouldSetTaskPositionAndCrop */,
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo));
}
}
@@ -499,7 +518,8 @@
if (decoration == null) return;
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
- false /* shouldSetTaskPositionAndCrop */);
+ false /* shouldSetTaskPositionAndCrop */,
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo));
}
@Override
@@ -774,11 +794,7 @@
onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button");
}
} else if (id == R.id.minimize_window) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mDesktopTasksController.onDesktopWindowMinimize(wct, mTaskId);
- final IBinder transition = mTaskOperations.minimizeTask(mTaskToken, wct);
- mDesktopTasksLimiter.ifPresent(limiter ->
- limiter.addPendingMinimizeChange(transition, mDisplayId, mTaskId));
+ mDesktopTasksController.minimizeTask(decoration.mTaskInfo);
}
}
@@ -895,7 +911,7 @@
}
private void moveTaskToFront(RunningTaskInfo taskInfo) {
- if (!taskInfo.isFocused) {
+ if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) {
mDesktopTasksController.moveTaskToFront(taskInfo);
}
}
@@ -1516,7 +1532,8 @@
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */);
+ false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo));
if (!Flags.enableHandleInputFix()) {
incrementEventReceiverTasks(taskInfo.displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index c1c3f9b..2c621b1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -352,7 +352,7 @@
}
@Override
- void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+ void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
// The crop and position of the task should only be set when a task is fluid resizing. In
// all other cases, it is expected that the transition handler positions and crops the task
@@ -365,7 +365,8 @@
// the View). Both will be shown on screen at the same, whereas applying them independently
// causes flickering. See b/270202228.
final boolean applyTransactionOnDraw = taskInfo.isFreeform();
- relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop);
+ relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop,
+ hasGlobalFocus);
if (!applyTransactionOnDraw) {
t.apply();
}
@@ -373,18 +374,19 @@
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+ boolean hasGlobalFocus) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
if (taskInfo.isFreeform()) {
// The Task is in Freeform mode -> show its header in sync since it's an integral part
// of the window itself - a delayed header might cause bad UX.
relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskPositionAndCrop);
+ shouldSetTaskPositionAndCrop, hasGlobalFocus);
} else {
// The Task is outside Freeform mode -> allow the handle view to be delayed since the
// handle is just a small addition to the window.
relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskPositionAndCrop);
+ shouldSetTaskPositionAndCrop, hasGlobalFocus);
}
Trace.endSection();
}
@@ -392,11 +394,12 @@
/** Run the whole relayout phase immediately without delay. */
private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+ boolean hasGlobalFocus) {
// Clear the current ViewHost runnable as we will update the ViewHost here
clearCurrentViewHostRunnable();
updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskPositionAndCrop);
+ shouldSetTaskPositionAndCrop, hasGlobalFocus);
if (mResult.mRootView != null) {
updateViewHost(mRelayoutParams, startT, mResult);
}
@@ -418,7 +421,8 @@
*/
private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+ boolean hasGlobalFocus) {
if (applyStartTransactionOnDraw) {
throw new IllegalArgumentException(
"We cannot both sync viewhost ondraw and delay viewhost creation.");
@@ -426,7 +430,8 @@
// Clear the current ViewHost runnable as we will update the ViewHost here
clearCurrentViewHostRunnable();
updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop);
+ false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop,
+ hasGlobalFocus);
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
// Nothing is set up in this case including the decoration surface.
@@ -440,7 +445,8 @@
@SuppressLint("MissingPermission")
private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+ boolean hasGlobalFocus) {
Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
if (Flags.enableDesktopWindowingAppToWeb()) {
@@ -459,7 +465,8 @@
.isTaskInFullImmersiveState(taskInfo.taskId);
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
shouldSetTaskPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded,
- inFullImmersive, mDisplayController.getInsetsState(taskInfo.displayId));
+ inFullImmersive, mDisplayController.getInsetsState(taskInfo.displayId),
+ hasGlobalFocus);
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -507,12 +514,13 @@
));
} else {
mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData(
- mTaskInfo, TaskInfoKt.getRequestingImmersive(mTaskInfo), inFullImmersive
+ mTaskInfo, TaskInfoKt.getRequestingImmersive(mTaskInfo), inFullImmersive,
+ hasGlobalFocus
));
}
Trace.endSection();
- if (!mTaskInfo.isFocused) {
+ if (!hasGlobalFocus) {
closeHandleMenu();
closeManageWindowsMenu();
closeMaximizeMenu();
@@ -780,7 +788,8 @@
boolean isStatusBarVisible,
boolean isKeyguardVisibleAndOccluded,
boolean inFullImmersiveMode,
- @NonNull InsetsState displayInsetsState) {
+ @NonNull InsetsState displayInsetsState,
+ boolean hasGlobalFocus) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
final boolean isAppHeader =
captionLayoutId == R.layout.desktop_mode_app_header;
@@ -790,6 +799,7 @@
relayoutParams.mLayoutResId = captionLayoutId;
relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
+ relayoutParams.mHasGlobalFocus = hasGlobalFocus;
final boolean showCaption;
if (Flags.enableFullyImmersiveInDesktop()) {
@@ -812,7 +822,7 @@
|| (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
}
relayoutParams.mIsCaptionVisible = showCaption;
-
+ relayoutParams.mIsInsetSource = isAppHeader && !inFullImmersiveMode;
if (isAppHeader) {
if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
// If the app is requesting to customize the caption bar, allow input to fall
@@ -837,7 +847,6 @@
WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
false /* ignoreVisibility */);
relayoutParams.mCaptionTopPadding = systemBarInsets.top;
- relayoutParams.mIsInsetSource = false;
}
// Report occluding elements as bounding rects to the insets system so that apps can
// draw in the empty space in the center:
@@ -865,8 +874,8 @@
relayoutParams.mInputFeatures
|= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
}
- if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ taskInfo.isFocused)) {
- relayoutParams.mShadowRadiusId = taskInfo.isFocused
+ if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) {
+ relayoutParams.mShadowRadiusId = hasGlobalFocus
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
}
@@ -1408,7 +1417,7 @@
}
boolean isFocused() {
- return mTaskInfo.isFocused;
+ return mHasGlobalFocus;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index f4c7fe3..ccf329c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -93,7 +93,7 @@
mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
mRepositionStartPoint.set(x, y);
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
- if (mCtrlType != CTRL_TYPE_UNDEFINED && !mWindowDecoration.mTaskInfo.isFocused) {
+ if (mCtrlType != CTRL_TYPE_UNDEFINED && !mWindowDecoration.mHasGlobalFocus) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mWindowDecoration.mTaskInfo.token, true /* onTop */,
true /* includingParents */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index a1f76d2..ff3b455 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -106,7 +106,7 @@
// Capture CUJ for re-sizing window in DW mode.
mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
- if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
+ if (!mDesktopWindowDecoration.mHasGlobalFocus) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true /* onTop */,
true /* includingParents */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index f8aed41..ce5cfd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -125,7 +125,7 @@
}
mDisplayController.removeDisplayWindowListener(this);
- relayout(mTaskInfo);
+ relayout(mTaskInfo, mHasGlobalFocus);
}
};
@@ -146,6 +146,7 @@
boolean mIsStatusBarVisible;
boolean mIsKeyguardVisibleAndOccluded;
+ boolean mHasGlobalFocus;
/** The most recent set of insets applied to this window decoration. */
private WindowDecorationInsets mWindowDecorationInsets;
@@ -199,8 +200,9 @@
*
* @param taskInfo The previous {@link RunningTaskInfo} passed into {@link #relayout} or the
* constructor.
+ * @param hasGlobalFocus Whether the task is focused
*/
- abstract void relayout(RunningTaskInfo taskInfo);
+ abstract void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus);
/**
* Used by the {@link DragPositioningCallback} associated with the implementing class to
@@ -225,6 +227,7 @@
if (params.mRunningTaskInfo != null) {
mTaskInfo = params.mRunningTaskInfo;
}
+ mHasGlobalFocus = params.mHasGlobalFocus;
final int oldLayoutResId = mLayoutResId;
mLayoutResId = params.mLayoutResId;
@@ -246,7 +249,7 @@
final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
outResult.mWidth = taskBounds.width();
outResult.mHeight = taskBounds.height();
- outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
+ outResult.mRootView.setTaskFocusState(mHasGlobalFocus);
final Resources resources = mDecorWindowContext.getResources();
outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId)
+ params.mCaptionTopPadding;
@@ -391,11 +394,11 @@
final WindowDecorationInsets newInsets = new WindowDecorationInsets(
mTaskInfo.token, mOwner, captionInsetsRect, boundingRects,
- params.mInsetSourceFlags);
+ params.mInsetSourceFlags, params.mIsInsetSource);
if (!newInsets.equals(mWindowDecorationInsets)) {
// Add or update this caption as an insets source.
mWindowDecorationInsets = newInsets;
- mWindowDecorationInsets.addOrUpdate(wct);
+ mWindowDecorationInsets.update(wct);
}
}
@@ -512,7 +515,7 @@
mIsKeyguardVisibleAndOccluded = visible && occluded;
final boolean changed = prevVisAndOccluded != mIsKeyguardVisibleAndOccluded;
if (changed) {
- relayout(mTaskInfo);
+ relayout(mTaskInfo, mHasGlobalFocus);
}
}
@@ -522,7 +525,7 @@
final boolean changed = prevStatusBarVisibility != mIsStatusBarVisible;
if (changed) {
- relayout(mTaskInfo);
+ relayout(mTaskInfo, mHasGlobalFocus);
}
}
@@ -710,10 +713,11 @@
final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
final WindowDecorationInsets newInsets = new WindowDecorationInsets(mTaskInfo.token,
- mOwner, captionInsets, null /* boundingRets */, 0 /* flags */);
+ mOwner, captionInsets, null /* boundingRets */, 0 /* flags */,
+ true /* shouldAddCaptionInset */);
if (!newInsets.equals(mWindowDecorationInsets)) {
mWindowDecorationInsets = newInsets;
- mWindowDecorationInsets.addOrUpdate(wct);
+ mWindowDecorationInsets.update(wct);
}
}
@@ -737,6 +741,7 @@
boolean mApplyStartTransactionOnDraw;
boolean mSetTaskPositionAndCrop;
+ boolean mHasGlobalFocus;
void reset() {
mLayoutResId = Resources.ID_NULL;
@@ -756,6 +761,7 @@
mApplyStartTransactionOnDraw = false;
mSetTaskPositionAndCrop = false;
mWindowDecorConfig = null;
+ mHasGlobalFocus = false;
}
boolean hasInputFeatureSpy() {
@@ -814,21 +820,26 @@
private final Rect mFrame;
private final Rect[] mBoundingRects;
private final @InsetsSource.Flags int mFlags;
+ private final boolean mShouldAddCaptionInset;
private WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame,
- Rect[] boundingRects, @InsetsSource.Flags int flags) {
+ Rect[] boundingRects, @InsetsSource.Flags int flags,
+ boolean shouldAddCaptionInset) {
mToken = token;
mOwner = owner;
mFrame = frame;
mBoundingRects = boundingRects;
mFlags = flags;
+ mShouldAddCaptionInset = shouldAddCaptionInset;
}
- void addOrUpdate(WindowContainerTransaction wct) {
- wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
- mFlags);
- wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
- mBoundingRects, 0 /* flags */);
+ void update(WindowContainerTransaction wct) {
+ if (mShouldAddCaptionInset) {
+ wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
+ mFlags);
+ wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
+ mBoundingRects, 0 /* flags */);
+ }
}
void remove(WindowContainerTransaction wct) {
@@ -843,7 +854,8 @@
return Objects.equals(mToken, that.mToken) && Objects.equals(mOwner,
that.mOwner) && Objects.equals(mFrame, that.mFrame)
&& Objects.deepEquals(mBoundingRects, that.mBoundingRects)
- && mFlags == that.mFlags;
+ && mFlags == that.mFlags
+ && mShouldAddCaptionInset == that.mShouldAddCaptionInset;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index c2af1d4..cf03b3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -81,6 +81,7 @@
val taskInfo: RunningTaskInfo,
val isRequestingImmersive: Boolean,
val inFullImmersiveState: Boolean,
+ val hasGlobalFocus: Boolean
) : Data()
private val decorThemeUtil = DecorThemeUtil(context)
@@ -159,24 +160,27 @@
}
override fun bindData(data: HeaderData) {
- bindData(data.taskInfo, data.isRequestingImmersive, data.inFullImmersiveState)
+ bindData(data.taskInfo, data.isRequestingImmersive, data.inFullImmersiveState,
+ data.hasGlobalFocus)
}
private fun bindData(
taskInfo: RunningTaskInfo,
isRequestingImmersive: Boolean,
inFullImmersiveState: Boolean,
+ hasGlobalFocus: Boolean
) {
if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) {
- bindDataWithThemedHeaders(taskInfo, isRequestingImmersive, inFullImmersiveState)
+ bindDataWithThemedHeaders(taskInfo, isRequestingImmersive, inFullImmersiveState,
+ hasGlobalFocus)
} else {
- bindDataLegacy(taskInfo)
+ bindDataLegacy(taskInfo, hasGlobalFocus)
}
}
- private fun bindDataLegacy(taskInfo: RunningTaskInfo) {
- captionView.setBackgroundColor(getCaptionBackgroundColor(taskInfo))
- val color = getAppNameAndButtonColor(taskInfo)
+ private fun bindDataLegacy(taskInfo: RunningTaskInfo, hasGlobalFocus: Boolean) {
+ captionView.setBackgroundColor(getCaptionBackgroundColor(taskInfo, hasGlobalFocus))
+ val color = getAppNameAndButtonColor(taskInfo, hasGlobalFocus)
val alpha = Color.alpha(color)
closeWindowButton.imageTintList = ColorStateList.valueOf(color)
maximizeWindowButton.imageTintList = ColorStateList.valueOf(color)
@@ -210,9 +214,10 @@
private fun bindDataWithThemedHeaders(
taskInfo: RunningTaskInfo,
requestingImmersive: Boolean,
- inFullImmersiveState: Boolean
+ inFullImmersiveState: Boolean,
+ hasGlobalFocus: Boolean
) {
- val header = fillHeaderInfo(taskInfo)
+ val header = fillHeaderInfo(taskInfo, hasGlobalFocus)
val headerStyle = getHeaderStyle(header)
// Caption Background
@@ -455,7 +460,7 @@
}
}
- private fun fillHeaderInfo(taskInfo: RunningTaskInfo): Header {
+ private fun fillHeaderInfo(taskInfo: RunningTaskInfo, hasGlobalFocus: Boolean): Header {
return Header(
type = if (taskInfo.isTransparentCaptionBarAppearance) {
Header.Type.CUSTOM
@@ -463,7 +468,7 @@
Header.Type.DEFAULT
},
appTheme = decorThemeUtil.getAppTheme(taskInfo),
- isFocused = taskInfo.isFocused,
+ isFocused = hasGlobalFocus,
isAppearanceCaptionLight = taskInfo.isLightCaptionBarAppearance
)
}
@@ -544,19 +549,19 @@
}
@ColorInt
- private fun getCaptionBackgroundColor(taskInfo: RunningTaskInfo): Int {
+ private fun getCaptionBackgroundColor(taskInfo: RunningTaskInfo, hasGlobalFocus: Boolean): Int {
if (taskInfo.isTransparentCaptionBarAppearance) {
return Color.TRANSPARENT
}
val materialColorAttr: Int =
if (isDarkMode()) {
- if (!taskInfo.isFocused) {
+ if (!hasGlobalFocus) {
materialColorSurfaceContainerHigh
} else {
materialColorSurfaceDim
}
} else {
- if (!taskInfo.isFocused) {
+ if (!hasGlobalFocus) {
materialColorSurfaceContainerLow
} else {
materialColorSecondaryContainer
@@ -569,7 +574,7 @@
}
@ColorInt
- private fun getAppNameAndButtonColor(taskInfo: RunningTaskInfo): Int {
+ private fun getAppNameAndButtonColor(taskInfo: RunningTaskInfo, hasGlobalFocus: Boolean): Int {
val materialColorAttr = when {
taskInfo.isTransparentCaptionBarAppearance &&
taskInfo.isLightCaptionBarAppearance -> materialColorOnSecondaryContainer
@@ -579,8 +584,8 @@
else -> materialColorOnSecondaryContainer
}
val appDetailsOpacity = when {
- isDarkMode() && !taskInfo.isFocused -> DARK_THEME_UNFOCUSED_OPACITY
- !isDarkMode() && !taskInfo.isFocused -> LIGHT_THEME_UNFOCUSED_OPACITY
+ isDarkMode() && !hasGlobalFocus -> DARK_THEME_UNFOCUSED_OPACITY
+ !isDarkMode() && !hasGlobalFocus -> LIGHT_THEME_UNFOCUSED_OPACITY
else -> FOCUSED_OPACITY
}
context.withStyledAttributes(null, intArrayOf(materialColorAttr), 0, 0) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
index cae6095..2e9effb4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt
@@ -15,23 +15,39 @@
*/
package com.android.wm.shell.desktopmode
+import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS
+import android.os.Binder
import android.os.IBinder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TransitionFlags
+import android.view.WindowManager.TransitionType
+import android.window.TransitionInfo
+import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@@ -40,14 +56,18 @@
/**
* Tests for [DesktopFullImmersiveTransitionHandler].
*
- * Usage: atest WMShellUnitTests:DesktopFullImmersiveTransitionHandler
+ * Usage: atest WMShellUnitTests:DesktopFullImmersiveTransitionHandlerTest
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() {
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
@Mock private lateinit var mockTransitions: Transitions
private lateinit var desktopRepository: DesktopRepository
+ @Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
private val transactionSupplier = { SurfaceControl.Transaction() }
private lateinit var immersiveHandler: DesktopFullImmersiveTransitionHandler
@@ -57,19 +77,22 @@
desktopRepository = DesktopRepository(
context, ShellInit(TestShellExecutor()), mock(), mock()
)
+ whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY))
+ .thenReturn(DisplayLayout())
immersiveHandler = DesktopFullImmersiveTransitionHandler(
transitions = mockTransitions,
desktopRepository = desktopRepository,
- transactionSupplier = transactionSupplier
+ displayController = mockDisplayController,
+ shellTaskOrganizer = mockShellTaskOrganizer,
+ transactionSupplier = transactionSupplier,
)
}
@Test
fun enterImmersive_transitionReady_updatesRepository() {
val task = createFreeformTask()
- val wct = WindowContainerTransaction()
val mockBinder = mock(IBinder::class.java)
- whenever(mockTransitions.startTransition(TRANSIT_CHANGE, wct, immersiveHandler))
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)))
.thenReturn(mockBinder)
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
@@ -77,8 +100,8 @@
immersive = false
)
- immersiveHandler.enterImmersive(task, wct)
- immersiveHandler.onTransitionReady(mockBinder)
+ immersiveHandler.moveTaskToImmersive(task)
+ immersiveHandler.onTransitionReady(mockBinder, createTransitionInfo())
assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isTrue()
}
@@ -86,9 +109,8 @@
@Test
fun exitImmersive_transitionReady_updatesRepository() {
val task = createFreeformTask()
- val wct = WindowContainerTransaction()
val mockBinder = mock(IBinder::class.java)
- whenever(mockTransitions.startTransition(TRANSIT_CHANGE, wct, immersiveHandler))
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)))
.thenReturn(mockBinder)
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
@@ -96,8 +118,8 @@
immersive = true
)
- immersiveHandler.exitImmersive(task, wct)
- immersiveHandler.onTransitionReady(mockBinder)
+ immersiveHandler.moveTaskToNonImmersive(task)
+ immersiveHandler.onTransitionReady(mockBinder, createTransitionInfo())
assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse()
}
@@ -105,28 +127,251 @@
@Test
fun enterImmersive_inProgress_ignores() {
val task = createFreeformTask()
- val wct = WindowContainerTransaction()
val mockBinder = mock(IBinder::class.java)
- whenever(mockTransitions.startTransition(TRANSIT_CHANGE, wct, immersiveHandler))
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)))
.thenReturn(mockBinder)
- immersiveHandler.enterImmersive(task, wct)
- immersiveHandler.enterImmersive(task, wct)
+ immersiveHandler.moveTaskToImmersive(task)
+ immersiveHandler.moveTaskToImmersive(task)
- verify(mockTransitions, times(1)).startTransition(TRANSIT_CHANGE, wct, immersiveHandler)
+ verify(mockTransitions, times(1))
+ .startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))
}
@Test
fun exitImmersive_inProgress_ignores() {
val task = createFreeformTask()
- val wct = WindowContainerTransaction()
val mockBinder = mock(IBinder::class.java)
- whenever(mockTransitions.startTransition(TRANSIT_CHANGE, wct, immersiveHandler))
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)))
.thenReturn(mockBinder)
- immersiveHandler.exitImmersive(task, wct)
- immersiveHandler.exitImmersive(task, wct)
+ immersiveHandler.moveTaskToNonImmersive(task)
+ immersiveHandler.moveTaskToNonImmersive(task)
- verify(mockTransitions, times(1)).startTransition(TRANSIT_CHANGE, wct, immersiveHandler)
+ verify(mockTransitions, times(1))
+ .startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersiveIfApplicable_inImmersive_addsPendingExit() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ val transition = Binder()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = true
+ )
+
+ immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY)
+
+ assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit ->
+ exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
+ && exit.taskId == task.taskId
+ }).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersiveIfApplicable_notInImmersive_doesNotAddPendingExit() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ val transition = Binder()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = false
+ )
+
+ immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY)
+
+ assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit ->
+ exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
+ && exit.taskId == task.taskId
+ }).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersiveIfApplicable_byDisplay_inImmersive_changesTaskBounds() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ val transition = Binder()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = true
+ )
+
+ immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY)
+
+ assertThat(wct.hasBoundsChange(task.token)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersiveIfApplicable_byDisplay_notInImmersive_doesNotChangeTaskBounds() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ val transition = Binder()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = false
+ )
+
+ immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY)
+
+ assertThat(wct.hasBoundsChange(task.token)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersiveIfApplicable_byTask_inImmersive_changesTaskBounds() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = true
+ )
+
+ immersiveHandler.exitImmersiveIfApplicable(wct = wct, taskInfo = task)
+
+ assertThat(wct.hasBoundsChange(task.token)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersiveIfApplicable_byTask_notInImmersive_doesNotChangeTaskBounds() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = false
+ )
+
+ immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)
+
+ assertThat(wct.hasBoundsChange(task.token)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersiveIfApplicable_byTask_inImmersive_addsPendingExitOnRun() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ val transition = Binder()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = true
+ )
+
+ immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)?.invoke(transition)
+
+ assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit ->
+ exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
+ && exit.taskId == task.taskId
+ }).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun exitImmersiveIfApplicable_byTask_notInImmersive_doesNotAddPendingExitOnRun() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ val transition = Binder()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = false
+ )
+
+ immersiveHandler.exitImmersiveIfApplicable(wct, task.taskId)?.invoke(transition)
+
+ assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit ->
+ exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
+ && exit.taskId == task.taskId
+ }).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTransitionReady_pendingExit_removesPendingExit() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ val transition = Binder()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = true
+ )
+ immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY)
+
+ immersiveHandler.onTransitionReady(
+ transition = transition,
+ info = createTransitionInfo(
+ changes = listOf(
+ TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }
+ )
+ )
+ )
+
+ assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit ->
+ exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
+ && exit.taskId == task.taskId
+ }).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTransitionReady_pendingExit_updatesRepository() {
+ val task = createFreeformTask()
+ whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ val wct = WindowContainerTransaction()
+ val transition = Binder()
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ immersive = true
+ )
+ immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY)
+
+ immersiveHandler.onTransitionReady(
+ transition = transition,
+ info = createTransitionInfo(
+ changes = listOf(
+ TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }
+ )
+ )
+ )
+
+ assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse()
+ }
+
+ private fun createTransitionInfo(
+ @TransitionType type: Int = TRANSIT_CHANGE,
+ @TransitionFlags flags: Int = 0,
+ changes: List<TransitionInfo.Change> = emptyList()
+ ): TransitionInfo = TransitionInfo(type, flags).apply {
+ changes.forEach { change -> addChange(change) }
+ }
+
+ private fun WindowContainerTransaction.hasBoundsChange(token: WindowContainerToken): Boolean =
+ this.changes.any { change ->
+ change.key == token.asBinder()
+ && (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 1308114..e20f0ec 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -957,6 +957,15 @@
assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
}
+ @Test
+ fun getTaskInFullImmersiveState_byDisplay() {
+ repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1, taskId = 2, immersive = true)
+
+ assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID)).isEqualTo(1)
+ assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1)).isEqualTo(2)
+ }
+
class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 27deb0b..b3c10d6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -42,6 +42,7 @@
import android.os.Binder
import android.os.Bundle
import android.os.Handler
+import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -99,6 +100,7 @@
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.draganddrop.DragAndDropController
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
@@ -144,13 +146,11 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.times
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
-import org.mockito.kotlin.argThat
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
@@ -201,6 +201,7 @@
private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
@Mock private lateinit var mockSurface: SurfaceControl
@Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
+ @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
@Mock private lateinit var mockHandler: Handler
@Mock lateinit var persistentRepository: DesktopPersistentRepository
@@ -266,6 +267,7 @@
controller = createController()
controller.setSplitScreenController(splitScreenController)
+ controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter
shellInit.init()
@@ -1542,75 +1544,142 @@
}
@Test
- fun onDesktopWindowMinimize_noActiveTask_doesntUpdateTransaction() {
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowMinimize(wct, taskId = 1)
- // Nothing happens.
- assertThat(wct.hierarchyOps).isEmpty()
+ fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() {
+ val task = setUpFreeformTask(active = false)
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+
+ controller.minimizeTask(task)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ captor.value.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
+ }
}
@Test
- fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntUpdateTransaction() {
- val task = setUpFreeformTask()
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowMinimize(wct, taskId = task.taskId)
- // Nothing happens.
- assertThat(wct.hierarchyOps).isEmpty()
+ fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
+ val task = setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+
+ controller.minimizeTask(task)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ captor.value.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK
+ }
}
@Test
fun onDesktopWindowMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() {
val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
val wallpaperToken = MockToken().token()
taskRepository.wallpaperActivityToken = wallpaperToken
- val wct = WindowContainerTransaction()
// The only active task is being minimized.
- controller.onDesktopWindowMinimize(wct, taskId = task.taskId)
+ controller.minimizeTask(task)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
// Adds remove wallpaper operation
- wct.assertRemoveAt(index = 0, wallpaperToken)
+ captor.value.assertRemoveAt(index = 0, wallpaperToken)
}
@Test
- fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntUpdateTransaction() {
+ fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntRemoveWallpaper() {
val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
val wallpaperToken = MockToken().token()
taskRepository.wallpaperActivityToken = wallpaperToken
taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
- val wct = WindowContainerTransaction()
// The only active task is already minimized.
- controller.onDesktopWindowMinimize(wct, taskId = task.taskId)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
+ controller.minimizeTask(task)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ captor.value.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
+ }
}
@Test
- fun onDesktopWindowMinimize_multipleActiveTasks_doesntUpdateTransaction() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
+ fun onDesktopWindowMinimize_multipleActiveTasks_doesntRemoveWallpaper() {
+ val task1 = setUpFreeformTask(active = true)
+ setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
val wallpaperToken = MockToken().token()
taskRepository.wallpaperActivityToken = wallpaperToken
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowMinimize(wct, taskId = task1.taskId)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
+ controller.minimizeTask(task1)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ captor.value.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
+ }
}
@Test
fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_removesWallpaper() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
+ val task1 = setUpFreeformTask(active = true)
+ val task2 = setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
val wallpaperToken = MockToken().token()
taskRepository.wallpaperActivityToken = wallpaperToken
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
- val wct = WindowContainerTransaction()
// task1 is the only visible task as task2 is minimized.
- controller.onDesktopWindowMinimize(wct, taskId = task1.taskId)
+ controller.minimizeTask(task1)
// Adds remove wallpaper operation
- wct.assertRemoveAt(index = 0, wallpaperToken)
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ // Adds remove wallpaper operation
+ captor.value.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_triesToExitImmersive() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+
+ controller.minimizeTask(task)
+
+ verify(mockDesktopFullImmersiveTransitionHandler).exitImmersiveIfApplicable(any(), eq(task))
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_invokesImmersiveTransitionStartCallback() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ val runOnTransit = RunOnStartTransitionCallback()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+ whenever(mockDesktopFullImmersiveTransitionHandler.exitImmersiveIfApplicable(any(), eq(task)))
+ .thenReturn(runOnTransit)
+
+ controller.minimizeTask(task)
+
+ assertThat(runOnTransit.invocations).isEqualTo(1)
+ assertThat(runOnTransit.lastInvoked).isEqualTo(transition)
}
@Test
@@ -3166,27 +3235,23 @@
}
@Test
- fun toggleImmersive_enter_resizesToDisplayBounds() {
+ fun toggleImmersive_enter_movesToImmersive() {
val task = setUpFreeformTask(DEFAULT_DISPLAY)
taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, false /* immersive */)
controller.toggleDesktopTaskFullImmersiveState(task)
- verify(mockDesktopFullImmersiveTransitionHandler).enterImmersive(eq(task), argThat { wct ->
- wct.hasBoundsChange(task.token, Rect())
- })
+ verify(mockDesktopFullImmersiveTransitionHandler).moveTaskToImmersive(task)
}
@Test
- fun toggleImmersive_exit_resizesToStableBounds() {
+ fun toggleImmersive_exit_movesToNonImmersive() {
val task = setUpFreeformTask(DEFAULT_DISPLAY)
taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, true /* immersive */)
controller.toggleDesktopTaskFullImmersiveState(task)
- verify(mockDesktopFullImmersiveTransitionHandler).exitImmersive(eq(task), argThat { wct ->
- wct.hasBoundsChange(task.token, STABLE_BOUNDS)
- })
+ verify(mockDesktopFullImmersiveTransitionHandler).moveTaskToNonImmersive(task)
}
@Test
@@ -3198,7 +3263,7 @@
task.requestedVisibleTypes = WindowInsets.Type.statusBars()
controller.onTaskInfoChanged(task)
- verify(mockDesktopFullImmersiveTransitionHandler).exitImmersive(eq(task), any())
+ verify(mockDesktopFullImmersiveTransitionHandler).moveTaskToNonImmersive(task)
}
@Test
@@ -3210,7 +3275,113 @@
task.requestedVisibleTypes = WindowInsets.Type.statusBars()
controller.onTaskInfoChanged(task)
- verify(mockDesktopFullImmersiveTransitionHandler, never()).exitImmersive(eq(task), any())
+ verify(mockDesktopFullImmersiveTransitionHandler, never()).moveTaskToNonImmersive(task)
+ }
+
+ @Test
+ fun moveTaskToDesktop_background_attemptsImmersiveExit() {
+ val task = setUpFreeformTask(background = true)
+ val wct = WindowContainerTransaction()
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(mockDesktopFullImmersiveTransitionHandler
+ .exitImmersiveIfApplicable(wct, task.displayId)).thenReturn(runOnStartTransit)
+ whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
+
+ controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
+
+ verify(mockDesktopFullImmersiveTransitionHandler).exitImmersiveIfApplicable(wct, task.displayId)
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun moveTaskToDesktop_foreground_attemptsImmersiveExit() {
+ val task = setUpFreeformTask(background = false)
+ val wct = WindowContainerTransaction()
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(mockDesktopFullImmersiveTransitionHandler
+ .exitImmersiveIfApplicable(wct, task.displayId)).thenReturn(runOnStartTransit)
+ whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
+
+ controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
+
+ verify(mockDesktopFullImmersiveTransitionHandler).exitImmersiveIfApplicable(wct, task.displayId)
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun moveTaskToFront_background_attemptsImmersiveExit() {
+ val task = setUpFreeformTask(background = true)
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(mockDesktopFullImmersiveTransitionHandler
+ .exitImmersiveIfApplicable(any(), eq(task.displayId))).thenReturn(runOnStartTransit)
+ whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+
+ controller.moveTaskToFront(task.taskId)
+
+ verify(mockDesktopFullImmersiveTransitionHandler)
+ .exitImmersiveIfApplicable(any(), eq(task.displayId))
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun moveTaskToFront_foreground_attemptsImmersiveExit() {
+ val task = setUpFreeformTask(background = false)
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(mockDesktopFullImmersiveTransitionHandler
+ .exitImmersiveIfApplicable(any(), eq(task.displayId))).thenReturn(runOnStartTransit)
+ whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+
+ controller.moveTaskToFront(task.taskId)
+
+ verify(mockDesktopFullImmersiveTransitionHandler)
+ .exitImmersiveIfApplicable(any(), eq(task.displayId))
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun handleRequest_freeformLaunchToDesktop_attemptsImmersiveExit() {
+ markTaskVisible(setUpFreeformTask())
+ val task = setUpFreeformTask()
+ markTaskVisible(task)
+ val binder = Binder()
+
+ controller.handleRequest(binder, createTransition(task))
+
+ verify(mockDesktopFullImmersiveTransitionHandler)
+ .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId))
+ }
+
+ @Test
+ fun handleRequest_fullscreenLaunchToDesktop_attemptsImmersiveExit() {
+ setUpFreeformTask()
+ val task = setUpFullscreenTask()
+ val binder = Binder()
+
+ controller.handleRequest(binder, createTransition(task))
+
+ verify(mockDesktopFullImmersiveTransitionHandler)
+ .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId))
+ }
+
+ private class RunOnStartTransitionCallback : ((IBinder) -> Unit) {
+ var invocations = 0
+ private set
+ var lastInvoked: IBinder? = null
+ private set
+
+ override fun invoke(transition: IBinder) {
+ invocations++
+ lastInvoked = transition
+ }
+ }
+
+ private fun RunOnStartTransitionCallback.assertOnlyInvocation(transition: IBinder) {
+ assertThat(invocations).isEqualTo(1)
+ assertThat(lastInvoked).isEqualTo(transition)
}
/**
@@ -3291,18 +3462,27 @@
private fun setUpFreeformTask(
displayId: Int = DEFAULT_DISPLAY,
bounds: Rect? = null,
- active: Boolean = true
+ active: Boolean = true,
+ background: Boolean = false,
): RunningTaskInfo {
val task = createFreeformTask(displayId, bounds)
val activityInfo = ActivityInfo()
task.topActivityInfo = activityInfo
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ if (background) {
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(task.taskId))
+ .thenReturn(createTaskInfo(task.taskId))
+ } else {
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ }
if (active) {
taskRepository.addActiveTask(displayId, task.taskId)
taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true)
}
taskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId)
- runningTasks.add(task)
+ if (!background) {
+ runningTasks.add(task)
+ }
return task
}
@@ -3556,6 +3736,21 @@
assertThat(op.container).isEqualTo(token.asBinder())
}
+private fun WindowContainerTransaction.assertNoRemoveAt(index: Int, token: WindowContainerToken) {
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
+}
+
+private fun WindowContainerTransaction.hasRemoveAt(index: Int, token: WindowContainerToken) {
+
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
+}
+
private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) {
assertIndexInBounds(index)
val op = hierarchyOps[index]
@@ -3578,13 +3773,6 @@
.isEqualTo(windowingMode)
}
-private fun WindowContainerTransaction.hasBoundsChange(
- token: WindowContainerToken,
- bounds: Rect
-): Boolean = this.changes.any { change ->
- change.key == token.asBinder() && change.value.configuration.windowConfiguration.bounds == bounds
-}
-
private fun WindowContainerTransaction?.anyDensityConfigChange(
token: WindowContainerToken
): Boolean {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 36e0427..f95b0d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -178,6 +178,7 @@
mFreeformTaskListener.onTaskVanished(task);
verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopRepository).removeClosingTask(task.taskId);
verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index d4a319e..7ae0bcd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -22,7 +22,6 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_CHANGE;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -44,18 +43,14 @@
import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
-
-import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
-import java.util.Optional;
-
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -80,6 +75,9 @@
private WindowDecorViewModel mWindowDecorViewModel;
@Mock
private TaskChangeListener mTaskChangeListener;
+ @Mock
+ private FocusTransitionObserver mFocusTransitionObserver;
+
private FreeformTaskTransitionObserver mTransitionObserver;
@Before
@@ -95,7 +93,7 @@
mTransitionObserver = new FreeformTaskTransitionObserver(
context, mShellInit, mTransitions,
Optional.of(mDesktopFullImmersiveTransitionHandler),
- mWindowDecorViewModel, Optional.of(mTaskChangeListener));
+ mWindowDecorViewModel, Optional.of(mTaskChangeListener), mFocusTransitionObserver);
final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(
Runnable.class);
@@ -331,7 +329,7 @@
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
- verify(mDesktopFullImmersiveTransitionHandler).onTransitionReady(transition);
+ verify(mDesktopFullImmersiveTransitionHandler).onTransitionReady(transition, info);
}
private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
index d63158c..015ea20 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
@@ -23,6 +23,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -30,9 +31,6 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -43,17 +41,11 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
import com.android.window.flags.Flags;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.shared.IFocusTransitionListener;
-import com.android.wm.shell.shared.TransactionPool;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.shared.FocusTransitionListener;
import org.junit.Before;
import org.junit.Rule;
@@ -75,57 +67,64 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
- private IFocusTransitionListener mListener;
- private Transitions mTransition;
+ private FocusTransitionListener mListener;
+ private final TestShellExecutor mShellExecutor = new TestShellExecutor();
private FocusTransitionObserver mFocusTransitionObserver;
@Before
public void setUp() {
- mListener = mock(IFocusTransitionListener.class);
- when(mListener.asBinder()).thenReturn(mock(IBinder.class));
-
+ mListener = mock(FocusTransitionListener.class);
mFocusTransitionObserver = new FocusTransitionObserver();
- mTransition =
- new Transitions(InstrumentationRegistry.getInstrumentation().getTargetContext(),
- mock(ShellInit.class), mock(ShellController.class),
- mock(ShellTaskOrganizer.class), mock(TransactionPool.class),
- mock(DisplayController.class), new TestShellExecutor(),
- new Handler(Looper.getMainLooper()), new TestShellExecutor(),
- mock(HomeTransitionObserver.class),
- mFocusTransitionObserver);
- mFocusTransitionObserver.setRemoteFocusTransitionListener(mTransition, mListener);
+ mFocusTransitionObserver.setLocalFocusTransitionListener(mListener, mShellExecutor);
+ mShellExecutor.flushAll();
+ clearInvocations(mListener);
}
@Test
- public void testOnlyDisplayChangeAffectsDisplayFocus() throws RemoteException {
- final IBinder binder = mock(IBinder.class);
+ public void testBasicTaskAndDisplayFocusSwitch() throws RemoteException {
final SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
- // Open a task on the secondary display, but it doesn't change display focus because it only
- // has a task change.
+ // First, open a task on the default display.
TransitionInfo info = mock(TransitionInfo.class);
final List<TransitionInfo.Change> changes = new ArrayList<>();
- setupTaskChange(changes, 123 /* taskId */, TRANSIT_OPEN, SECONDARY_DISPLAY_ID,
- true /* focused */);
+ setupTaskChange(changes, 1 /* taskId */, TRANSIT_OPEN,
+ DEFAULT_DISPLAY, true /* focused */);
when(info.getChanges()).thenReturn(changes);
- mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
- verify(mListener, never()).onFocusedDisplayChanged(SECONDARY_DISPLAY_ID);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
+ verify(mListener, never()).onFocusedDisplayChanged(anyInt());
+ verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
clearInvocations(mListener);
- // Moving the secondary display to front must change display focus to it.
- changes.clear();
+ // Open a task on the secondary display.
+ setupTaskChange(changes, 2 /* taskId */, TRANSIT_OPEN,
+ SECONDARY_DISPLAY_ID, true /* focused */);
setupDisplayToTopChange(changes, SECONDARY_DISPLAY_ID);
when(info.getChanges()).thenReturn(changes);
- mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
verify(mListener, times(1))
.onFocusedDisplayChanged(SECONDARY_DISPLAY_ID);
+ verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
+ true /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
+ verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
+ clearInvocations(mListener);
- // Moving the secondary display to front must change display focus back to it.
+ // Moving only the default display back to front, and verify that affected tasks are also
+ // notified.
changes.clear();
setupDisplayToTopChange(changes, DEFAULT_DISPLAY);
when(info.getChanges()).thenReturn(changes);
- mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
- verify(mListener, times(1)).onFocusedDisplayChanged(DEFAULT_DISPLAY);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
+ verify(mListener, times(1))
+ .onFocusedDisplayChanged(DEFAULT_DISPLAY);
+ verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
+ verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
+ true /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
}
private void setupTaskChange(List<TransitionInfo.Change> changes, int taskId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
index 0f16b9d..5ebf517 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
@@ -49,7 +49,8 @@
false,
true /* isStatusBarVisible */,
false /* isKeyguardVisibleAndOccluded */,
- InsetsState()
+ InsetsState(),
+ true /* hasGlobalFocus */
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue()
@@ -70,7 +71,8 @@
false,
true /* isStatusBarVisible */,
false /* isKeyguardVisibleAndOccluded */,
- InsetsState()
+ InsetsState(),
+ true /* hasGlobalFocus */
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse()
@@ -87,7 +89,8 @@
false,
true /* isStatusBarVisible */,
false /* isKeyguardVisibleAndOccluded */,
- InsetsState()
+ InsetsState(),
+ true /* hasGlobalFocus */
)
Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2)
Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 4aa7e18..175fbd2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -100,6 +100,7 @@
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
@@ -126,7 +127,6 @@
import org.mockito.Mockito.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.any
-import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doNothing
@@ -192,6 +192,7 @@
DesktopModeWindowDecorViewModel.TaskPositionerFactory
@Mock private lateinit var mockTaskPositioner: TaskPositioner
@Mock private lateinit var mockAppHandleEducationController: AppHandleEducationController
+ @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
@Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
private lateinit var spyContext: TestableContext
@@ -254,7 +255,8 @@
mockAppHandleEducationController,
mockCaptionHandleRepository,
Optional.of(mockActivityOrientationChangeHandler),
- mockTaskPositionerFactory
+ mockTaskPositionerFactory,
+ mockFocusTransitionObserver
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -455,24 +457,13 @@
onClickListenerCaptor.value.onClick(view)
- val transactionCaptor = argumentCaptor<WindowContainerTransaction>()
- verify(mockFreeformTaskTransitionStarter)
- .startMinimizedModeTransition(transactionCaptor.capture())
- val wct = transactionCaptor.firstValue
-
- verify(mockTasksLimiter).addPendingMinimizeChange(
- anyOrNull(), eq(DEFAULT_DISPLAY), eq(decor.mTaskInfo.taskId))
-
- assertEquals(1, wct.getHierarchyOps().size)
- assertEquals(HierarchyOp.HIERARCHY_OP_TYPE_REORDER, wct.getHierarchyOps().get(0).getType())
- assertFalse(wct.getHierarchyOps().get(0).getToTop())
- assertEquals(decor.mTaskInfo.token.asBinder(), wct.getHierarchyOps().get(0).getContainer())
+ verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
isTopActivityTransparent = true
isTopActivityStyleFloating = true
numActivities = 1
@@ -487,7 +478,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
isTopActivityTransparent = true
isTopActivityStyleFloating = false
numActivities = 1
@@ -500,7 +491,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForSystemUIActivities() {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
// Set task as systemUI package
val systemUIPackageName = context.resources.getString(
@@ -573,7 +564,7 @@
// Simulate default enforce device restrictions system property
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
// Simulate device that doesn't support desktop mode
doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
@@ -589,7 +580,7 @@
// Simulate device that doesn't support desktop mode
doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
setUpMockDecorationsForTasks(task)
onTaskOpening(task)
@@ -602,7 +593,7 @@
// Simulate default enforce device restrictions system property
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
setUpMockDecorationsForTasks(task)
@@ -1045,7 +1036,7 @@
@Test
fun testOnDisplayRotation_tasksOutOfValidArea_taskBoundsUpdated() {
- val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
val secondTask =
createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
val thirdTask =
@@ -1073,7 +1064,7 @@
@Test
fun testOnDisplayRotation_taskInValidArea_taskBoundsNotUpdated() {
- val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
val secondTask =
createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
val thirdTask =
@@ -1100,7 +1091,7 @@
@Test
fun testOnDisplayRotation_sameOrientationRotation_taskBoundsNotUpdated() {
- val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
val secondTask =
createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
val thirdTask =
@@ -1124,7 +1115,7 @@
@Test
fun testOnDisplayRotation_differentDisplayId_taskBoundsNotUpdated() {
- val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FREEFORM)
val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_FREEFORM)
@@ -1149,7 +1140,7 @@
@Test
fun testOnDisplayRotation_nonFreeformTask_taskBoundsNotUpdated() {
- val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FULLSCREEN)
val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_PINNED)
@@ -1322,7 +1313,6 @@
displayId: Int = DEFAULT_DISPLAY,
@WindowingMode windowingMode: Int,
activityType: Int = ACTIVITY_TYPE_STANDARD,
- focused: Boolean = true,
activityInfo: ActivityInfo = ActivityInfo(),
requestingImmersive: Boolean = false
): RunningTaskInfo {
@@ -1333,7 +1323,6 @@
.setActivityType(activityType)
.build().apply {
topActivityInfo = activityInfo
- isFocused = focused
isResizeable = true
requestedVisibleTypes = if (requestingImmersive) {
statusBars().inv()
@@ -1351,7 +1340,6 @@
any(), any(), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
- whenever(decoration.isFocused).thenReturn(task.isFocused)
whenever(decoration.user).thenReturn(mockUserHandle)
if (task.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
whenever(mockSplitScreenController.isTaskInSplitScreen(task.taskId))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 35be80e..1d11d2e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -279,7 +279,7 @@
final DesktopModeWindowDecoration spyWindowDecor =
spy(createWindowDecoration(taskInfo));
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
// Menus should close if open before the task being invisible causes relayout to return.
verify(spyWindowDecor).closeHandleMenu();
@@ -298,7 +298,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
}
@@ -318,7 +319,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mCornerRadius).isGreaterThan(0);
}
@@ -343,7 +345,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(customTaskDensity);
}
@@ -369,7 +372,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(systemDensity);
}
@@ -391,7 +395,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.hasInputFeatureSpy()).isTrue();
}
@@ -412,7 +417,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.hasInputFeatureSpy()).isFalse();
}
@@ -432,7 +438,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.hasInputFeatureSpy()).isFalse();
}
@@ -452,7 +459,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(hasNoInputChannelFeature(relayoutParams)).isFalse();
}
@@ -473,7 +481,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue();
}
@@ -494,7 +503,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue();
}
@@ -516,7 +526,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
}
@@ -539,7 +550,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
}
@@ -560,7 +572,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(
(relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0)
@@ -583,7 +596,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(
(relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) == 0)
@@ -612,7 +626,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
- insetsState);
+ insetsState,
+ /* hasGlobalFocus= */ true);
// Takes status bar inset as padding, ignores caption bar inset.
assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -634,7 +649,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mIsInsetSource).isFalse();
}
@@ -655,7 +671,8 @@
/* isStatusBarVisible */ false,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
// Header is always shown because it's assumed the status bar is always visible.
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -676,7 +693,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -696,7 +714,8 @@
/* isStatusBarVisible */ false,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -716,7 +735,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ true,
/* inFullImmersiveMode */ false,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -737,7 +757,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -750,7 +771,8 @@
/* isStatusBarVisible */ false,
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -771,7 +793,8 @@
/* isStatusBarVisible */ true,
/* isKeyguardVisibleAndOccluded */ true,
/* inFullImmersiveMode */ true,
- new InsetsState());
+ new InsetsState(),
+ /* hasGlobalFocus= */ true);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -782,7 +805,7 @@
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockTransaction).apply();
verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any());
@@ -797,7 +820,7 @@
// Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
taskInfo.isResizeable = false;
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockTransaction, never()).apply();
verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction);
@@ -809,7 +832,7 @@
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
}
@@ -821,7 +844,7 @@
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
// Once for view host, the other for the AppHandle input layer.
verify(mMockHandler, times(2)).post(runnableArgument.capture());
@@ -838,7 +861,7 @@
// Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
taskInfo.isResizeable = false;
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
verify(mMockHandler, never()).post(any());
@@ -850,11 +873,11 @@
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
// Once for view host, the other for the AppHandle input layer.
verify(mMockHandler, times(2)).post(runnableArgument.capture());
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
}
@@ -865,7 +888,7 @@
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
// Once for view host, the other for the AppHandle input layer.
verify(mMockHandler, times(2)).post(runnableArgument.capture());
@@ -998,7 +1021,7 @@
runnableArgument.getValue().run();
// Relayout decor with same captured link
- decor.relayout(taskInfo);
+ decor.relayout(taskInfo, true /* hasGlobalFocus */);
// Verify handle menu's browser link not set to captured link since link is expired
createHandleMenu(decor);
@@ -1147,7 +1170,7 @@
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockCaptionHandleRepository, never()).notifyCaptionChanged(any());
}
@@ -1164,7 +1187,7 @@
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
captionStateArgumentCaptor.capture());
@@ -1191,7 +1214,7 @@
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockAppHeaderViewHolder, atLeastOnce()).runOnAppChipGlobalLayout(
runnableArgumentCaptor.capture());
runnableArgumentCaptor.getValue().invoke();
@@ -1214,7 +1237,7 @@
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
captionStateArgumentCaptor.capture());
@@ -1234,7 +1257,7 @@
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
createHandleMenu(spyWindowDecor);
verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
@@ -1259,7 +1282,7 @@
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
createHandleMenu(spyWindowDecor);
spyWindowDecor.closeHandleMenu();
@@ -1356,7 +1379,7 @@
windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
windowDecor.mDecorWindowContext = mContext;
if (relayout) {
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
}
return windowDecor;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 7543fed..ca1f9ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -624,7 +624,7 @@
@Test
fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() {
- mockWindowDecoration.mTaskInfo.isFocused = false
+ mockWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
STARTING_BOUNDS.left.toFloat(),
@@ -640,7 +640,7 @@
@Test
fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() {
- mockWindowDecoration.mTaskInfo.isFocused = true
+ mockWindowDecoration.mHasGlobalFocus = true
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
STARTING_BOUNDS.left.toFloat(),
@@ -656,7 +656,7 @@
@Test
fun testDragResize_drag_draggedTaskNotReorderedToTop() {
- mockWindowDecoration.mTaskInfo.isFocused = false
+ mockWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
STARTING_BOUNDS.left.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 1273ee8..1dfbd67 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -323,7 +323,7 @@
@Test
fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread {
- mockDesktopWindowDecoration.mTaskInfo.isFocused = false
+ mockDesktopWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
STARTING_BOUNDS.left.toFloat(),
@@ -339,7 +339,7 @@
@Test
fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread {
- mockDesktopWindowDecoration.mTaskInfo.isFocused = true
+ mockDesktopWindowDecoration.mHasGlobalFocus = true
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
STARTING_BOUNDS.left.toFloat(),
@@ -355,7 +355,7 @@
@Test
fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread {
- mockDesktopWindowDecoration.mTaskInfo.isFocused = false
+ mockDesktopWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
STARTING_BOUNDS.left.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 54dd15ba..bb41e9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -203,13 +203,12 @@
.setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
.setVisible(false)
.build();
- taskInfo.isFocused = false;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
verify(decorContainerSurfaceBuilder, never()).build();
verify(taskBackgroundSurfaceBuilder, never()).build();
@@ -243,13 +242,12 @@
.setVisible(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.build();
- taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
mRelayoutParams.mIsCaptionVisible = true;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(decorContainerSurfaceBuilder).setParent(mMockTaskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
@@ -316,14 +314,13 @@
.setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
.setVisible(true)
.build();
- taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
mRelayoutParams.mIsCaptionVisible = true;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockSurfaceControlViewHost, never()).release();
verify(t, never()).apply();
@@ -333,7 +330,7 @@
final SurfaceControl.Transaction t2 = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t2);
taskInfo.isVisible = false;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost);
releaseOrder.verify(mMockSurfaceControlViewHost).release();
@@ -361,7 +358,7 @@
.build();
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
// It shouldn't show the window decoration when it can't obtain the display instance.
assertThat(mRelayoutResult.mRootView).isNull();
@@ -417,10 +414,9 @@
.setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
.setVisible(true)
.build();
- taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
final SurfaceControl.Builder additionalWindowSurfaceBuilder =
@@ -470,11 +466,10 @@
.setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
.setVisible(true)
.build();
- taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
@@ -510,11 +505,11 @@
.setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
.setVisible(true)
.build();
- taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */);
+ windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */,
+ true /* hasGlobalFocus */);
verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
}
@@ -549,10 +544,9 @@
.setVisible(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.build();
- taskInfo.isFocused = true;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockSurfaceControlStartT).setColor(mMockTaskSurface, new float[]{1.f, 1.f, 0.f});
@@ -575,7 +569,7 @@
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
mRelayoutParams.mIsCaptionVisible = true;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
@@ -611,10 +605,9 @@
.setVisible(true)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.build();
- taskInfo.isFocused = true;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockSurfaceControlStartT).unsetColor(mMockTaskSurface);
@@ -635,7 +628,7 @@
// Hidden from the beginning, so no insets were ever added.
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
mRelayoutParams.mIsCaptionVisible = false;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
// Never added.
verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
@@ -663,7 +656,7 @@
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
mRelayoutParams.mIsInsetSource = false;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
// Never added.
verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(),
@@ -687,11 +680,11 @@
mRelayoutParams.mIsCaptionVisible = true;
mRelayoutParams.mIsInsetSource = true;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
mRelayoutParams.mIsCaptionVisible = true;
mRelayoutParams.mIsInsetSource = false;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
// Insets should be removed.
verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
@@ -715,7 +708,7 @@
// Relayout will add insets.
mRelayoutParams.mIsCaptionVisible = true;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
eq(0) /* index */, eq(captionBar()), any(), any(), anyInt());
verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
@@ -768,10 +761,10 @@
final ActivityManager.RunningTaskInfo firstTaskInfo =
builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
final TestWindowDecoration windowDecor = createWindowDecoration(firstTaskInfo);
- windowDecor.relayout(firstTaskInfo);
+ windowDecor.relayout(firstTaskInfo, true /* hasGlobalFocus */);
final ActivityManager.RunningTaskInfo secondTaskInfo =
builder.setToken(token).setBounds(new Rect(50, 50, 1000, 1000)).build();
- windowDecor.relayout(secondTaskInfo);
+ windowDecor.relayout(secondTaskInfo, true /* hasGlobalFocus */);
// Insets should be applied twice.
verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(),
@@ -796,10 +789,10 @@
final ActivityManager.RunningTaskInfo firstTaskInfo =
builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
final TestWindowDecoration windowDecor = createWindowDecoration(firstTaskInfo);
- windowDecor.relayout(firstTaskInfo);
+ windowDecor.relayout(firstTaskInfo, true /* hasGlobalFocus */);
final ActivityManager.RunningTaskInfo secondTaskInfo =
builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
- windowDecor.relayout(secondTaskInfo);
+ windowDecor.relayout(secondTaskInfo, true /* hasGlobalFocus */);
// Insets should only need to be applied once.
verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(),
@@ -824,7 +817,7 @@
mRelayoutParams.mIsCaptionVisible = true;
mRelayoutParams.mInsetSourceFlags =
FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
// Caption inset source should add params' flags.
verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(),
@@ -845,14 +838,13 @@
.setVisible(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.build();
- taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
mRelayoutParams.mSetTaskPositionAndCrop = false;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockSurfaceControlStartT, never()).setWindowCrop(
eq(mMockTaskSurface), anyInt(), anyInt());
@@ -875,13 +867,12 @@
.setVisible(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.build();
- taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
mRelayoutParams.mSetTaskPositionAndCrop = true;
- windowDecor.relayout(taskInfo);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
verify(mMockSurfaceControlStartT).setWindowCrop(
eq(mMockTaskSurface), anyInt(), anyInt());
@@ -932,12 +923,12 @@
when(mMockDisplayController.getInsetsState(task.displayId))
.thenReturn(createInsetsState(statusBars(), true /* visible */));
final TestWindowDecoration decor = spy(createWindowDecoration(task));
- decor.relayout(task);
+ decor.relayout(task, true /* hasGlobalFocus */);
assertTrue(decor.mIsStatusBarVisible);
decor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */));
- verify(decor, times(2)).relayout(task);
+ verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */);
}
@Test
@@ -947,11 +938,11 @@
when(mMockDisplayController.getInsetsState(task.displayId))
.thenReturn(createInsetsState(statusBars(), true /* visible */));
final TestWindowDecoration decor = spy(createWindowDecoration(task));
- decor.relayout(task);
+ decor.relayout(task, true /* hasGlobalFocus */);
decor.onInsetsStateChanged(createInsetsState(statusBars(), true /* visible */));
- verify(decor, times(1)).relayout(task);
+ verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */);
}
@Test
@@ -960,13 +951,13 @@
when(mMockDisplayController.getInsetsState(task.displayId))
.thenReturn(createInsetsState(statusBars(), true /* visible */));
final TestWindowDecoration decor = spy(createWindowDecoration(task));
- decor.relayout(task);
+ decor.relayout(task, true /* hasGlobalFocus */);
assertFalse(decor.mIsKeyguardVisibleAndOccluded);
decor.onKeyguardStateChanged(true /* visible */, true /* occluding */);
assertTrue(decor.mIsKeyguardVisibleAndOccluded);
- verify(decor, times(2)).relayout(task);
+ verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */);
}
@Test
@@ -975,19 +966,18 @@
when(mMockDisplayController.getInsetsState(task.displayId))
.thenReturn(createInsetsState(statusBars(), true /* visible */));
final TestWindowDecoration decor = spy(createWindowDecoration(task));
- decor.relayout(task);
+ decor.relayout(task, true /* hasGlobalFocus */);
assertFalse(decor.mIsKeyguardVisibleAndOccluded);
decor.onKeyguardStateChanged(false /* visible */, true /* occluding */);
- verify(decor, times(1)).relayout(task);
+ verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */);
}
private ActivityManager.RunningTaskInfo createTaskInfo() {
final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
.setVisible(true)
.build();
- taskInfo.isFocused = true;
return taskInfo;
}
@@ -1055,8 +1045,8 @@
}
@Override
- void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- relayout(taskInfo, false /* applyStartTransactionOnDraw */);
+ void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
+ relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus);
}
@Override
@@ -1078,10 +1068,11 @@
}
void relayout(ActivityManager.RunningTaskInfo taskInfo,
- boolean applyStartTransactionOnDraw) {
+ boolean applyStartTransactionOnDraw, boolean hasGlobalFocus) {
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
mRelayoutParams.mLayoutResId = R.layout.caption_layout;
+ mRelayoutParams.mHasGlobalFocus = hasGlobalFocus;
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index f066e46..3ecd82b 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -65,13 +65,7 @@
uint32_t string_pool_index_offset;
};
-struct Idmap_target_entry {
- uint32_t target_id;
- uint32_t overlay_id;
-};
-
struct Idmap_target_entry_inline {
- uint32_t target_id;
uint32_t start_value_index;
uint32_t value_count;
};
@@ -81,10 +75,9 @@
Res_value value;
};
-struct Idmap_overlay_entry {
- uint32_t overlay_id;
- uint32_t target_id;
-};
+static constexpr uint32_t convert_dev_target_id(uint32_t dev_target_id) {
+ return (0x00FFFFFFU & dtohl(dev_target_id));
+}
OverlayStringPool::OverlayStringPool(const LoadedIdmap* loaded_idmap)
: data_header_(loaded_idmap->data_header_),
@@ -117,27 +110,29 @@
}
OverlayDynamicRefTable::OverlayDynamicRefTable(const Idmap_data_header* data_header,
- const Idmap_overlay_entry* entries,
+ Idmap_overlay_entries entries,
uint8_t target_assigned_package_id)
: data_header_(data_header),
entries_(entries),
- target_assigned_package_id_(target_assigned_package_id) {}
+ target_assigned_package_id_(target_assigned_package_id) {
+}
status_t OverlayDynamicRefTable::lookupResourceId(uint32_t* resId) const {
- const Idmap_overlay_entry* first_entry = entries_;
- const Idmap_overlay_entry* end_entry = entries_ + dtohl(data_header_->overlay_entry_count);
- auto entry = std::lower_bound(first_entry, end_entry, *resId,
- [](const Idmap_overlay_entry& e1, const uint32_t overlay_id) {
- return dtohl(e1.overlay_id) < overlay_id;
- });
+ const auto count = dtohl(data_header_->overlay_entry_count);
+ const auto overlay_it_end = entries_.overlay_id + count;
+ const auto entry_it = std::lower_bound(entries_.overlay_id, overlay_it_end, *resId,
+ [](uint32_t dev_overlay_id, uint32_t overlay_id) {
+ return dtohl(dev_overlay_id) < overlay_id;
+ });
- if (entry == end_entry || dtohl(entry->overlay_id) != *resId) {
+ if (entry_it == overlay_it_end || dtohl(*entry_it) != *resId) {
// A mapping for the target resource id could not be found.
return DynamicRefTable::lookupResourceId(resId);
}
- *resId = (0x00FFFFFFU & dtohl(entry->target_id))
- | (((uint32_t) target_assigned_package_id_) << 24U);
+ const auto index = entry_it - entries_.overlay_id;
+ *resId = convert_dev_target_id(entries_.target_id[index]) |
+ (((uint32_t)target_assigned_package_id_) << 24U);
return NO_ERROR;
}
@@ -145,12 +140,10 @@
return DynamicRefTable::lookupResourceId(resId);
}
-IdmapResMap::IdmapResMap(const Idmap_data_header* data_header,
- const Idmap_target_entry* entries,
- const Idmap_target_entry_inline* inline_entries,
+IdmapResMap::IdmapResMap(const Idmap_data_header* data_header, Idmap_target_entries entries,
+ Idmap_target_inline_entries inline_entries,
const Idmap_target_entry_inline_value* inline_entry_values,
- const ConfigDescription* configs,
- uint8_t target_assigned_package_id,
+ const ConfigDescription* configs, uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table)
: data_header_(data_header),
entries_(entries),
@@ -158,7 +151,8 @@
inline_entry_values_(inline_entry_values),
configurations_(configs),
target_assigned_package_id_(target_assigned_package_id),
- overlay_ref_table_(overlay_ref_table) { }
+ overlay_ref_table_(overlay_ref_table) {
+}
IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const {
if ((target_res_id >> 24U) != target_assigned_package_id_) {
@@ -171,15 +165,15 @@
target_res_id &= 0x00FFFFFFU;
// Check if the target resource is mapped to an overlay resource.
- auto first_entry = entries_;
- auto end_entry = entries_ + dtohl(data_header_->target_entry_count);
- auto entry = std::lower_bound(first_entry, end_entry, target_res_id,
- [](const Idmap_target_entry& e, const uint32_t target_id) {
- return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
- });
+ const auto target_end = entries_.target_id + dtohl(data_header_->target_entry_count);
+ auto target_it = std::lower_bound(entries_.target_id, target_end, target_res_id,
+ [](uint32_t dev_target_id, uint32_t target_id) {
+ return convert_dev_target_id(dev_target_id) < target_id;
+ });
- if (entry != end_entry && (0x00FFFFFFU & dtohl(entry->target_id)) == target_res_id) {
- uint32_t overlay_resource_id = dtohl(entry->overlay_id);
+ if (target_it != target_end && convert_dev_target_id(*target_it) == target_res_id) {
+ const auto index = target_it - entries_.target_id;
+ uint32_t overlay_resource_id = dtohl(entries_.overlay_id[index]);
// Lookup the resource without rewriting the overlay resource id back to the target resource id
// being looked up.
overlay_ref_table_->lookupResourceIdNoRewrite(&overlay_resource_id);
@@ -187,20 +181,22 @@
}
// Check if the target resources is mapped to an inline table entry.
- auto first_inline_entry = inline_entries_;
- auto end_inline_entry = inline_entries_ + dtohl(data_header_->target_inline_entry_count);
- auto inline_entry = std::lower_bound(first_inline_entry, end_inline_entry, target_res_id,
- [](const Idmap_target_entry_inline& e,
- const uint32_t target_id) {
- return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
- });
+ const auto inline_entry_target_end =
+ inline_entries_.target_id + dtohl(data_header_->target_inline_entry_count);
+ const auto inline_entry_target_it =
+ std::lower_bound(inline_entries_.target_id, inline_entry_target_end, target_res_id,
+ [](uint32_t dev_target_id, uint32_t target_id) {
+ return convert_dev_target_id(dev_target_id) < target_id;
+ });
- if (inline_entry != end_inline_entry &&
- (0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) {
+ if (inline_entry_target_it != inline_entry_target_end &&
+ convert_dev_target_id(*inline_entry_target_it) == target_res_id) {
+ const auto index = inline_entry_target_it - inline_entries_.target_id;
std::map<ConfigDescription, Res_value> values_map;
- for (int i = 0; i < inline_entry->value_count; i++) {
- const auto& value = inline_entry_values_[inline_entry->start_value_index + i];
- const auto& config = configurations_[value.config_index];
+ const auto& inline_entry = inline_entries_.entry[index];
+ for (int i = 0; i < dtohl(inline_entry.value_count); i++) {
+ const auto& value = inline_entry_values_[dtohl(inline_entry.start_value_index) + i];
+ const auto& config = configurations_[dtohl(value.config_index)];
values_map[config] = value.value;
}
return Result(std::move(values_map));
@@ -210,15 +206,15 @@
namespace {
template <typename T>
-const T* ReadType(const uint8_t** in_out_data_ptr, size_t* in_out_size, const std::string& label,
+const T* ReadType(const uint8_t** in_out_data_ptr, size_t* in_out_size, const char* label,
size_t count = 1) {
if (!util::IsFourByteAligned(*in_out_data_ptr)) {
- LOG(ERROR) << "Idmap " << label << " is not word aligned.";
+ LOG(ERROR) << "Idmap " << label << " in " << __func__ << " is not word aligned.";
return {};
}
if ((*in_out_size / sizeof(T)) < count) {
- LOG(ERROR) << "Idmap too small for the number of " << label << " entries ("
- << count << ").";
+ LOG(ERROR) << "Idmap too small for the number of " << label << " in " << __func__
+ << " entries (" << count << ").";
return nullptr;
}
auto data_ptr = *in_out_data_ptr;
@@ -229,8 +225,8 @@
}
std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size_t* in_out_size,
- const std::string& label) {
- const auto* len = ReadType<uint32_t>(in_out_data_ptr, in_out_size, label + " length");
+ const char* label) {
+ const auto* len = ReadType<uint32_t>(in_out_data_ptr, in_out_size, label);
if (len == nullptr) {
return {};
}
@@ -242,7 +238,7 @@
const uint32_t padding_size = (4U - ((size_t)*in_out_data_ptr & 0x3U)) % 4U;
for (uint32_t i = 0; i < padding_size; i++) {
if (**in_out_data_ptr != 0) {
- LOG(ERROR) << " Idmap padding of " << label << " is non-zero.";
+ LOG(ERROR) << " Idmap padding of " << label << " in " << __func__ << " is non-zero.";
return {};
}
*in_out_data_ptr += sizeof(uint8_t);
@@ -258,12 +254,10 @@
#endif
LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* header,
- const Idmap_data_header* data_header,
- const Idmap_target_entry* target_entries,
- const Idmap_target_entry_inline* target_inline_entries,
+ const Idmap_data_header* data_header, Idmap_target_entries target_entries,
+ Idmap_target_inline_entries target_inline_entries,
const Idmap_target_entry_inline_value* inline_entry_values,
- const ConfigDescription* configs,
- const Idmap_overlay_entry* overlay_entries,
+ const ConfigDescription* configs, Idmap_overlay_entries overlay_entries,
std::unique_ptr<ResStringPool>&& string_pool,
std::string_view overlay_apk_path, std::string_view target_apk_path)
: header_(header),
@@ -274,10 +268,12 @@
configurations_(configs),
overlay_entries_(overlay_entries),
string_pool_(std::move(string_pool)),
- idmap_fd_(android::base::utf8::open(idmap_path.c_str(), O_RDONLY|O_CLOEXEC|O_BINARY|O_PATH)),
+ idmap_fd_(
+ android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)),
overlay_apk_path_(overlay_apk_path),
target_apk_path_(target_apk_path),
- idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {}
+ idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {
+}
std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
ATRACE_CALL();
@@ -319,14 +315,21 @@
if (data_header == nullptr) {
return {};
}
- auto target_entries = ReadType<Idmap_target_entry>(&data_ptr, &data_size, "target",
- dtohl(data_header->target_entry_count));
- if (target_entries == nullptr) {
+ Idmap_target_entries target_entries{
+ .target_id = ReadType<uint32_t>(&data_ptr, &data_size, "entries.target_id",
+ dtohl(data_header->target_entry_count)),
+ .overlay_id = ReadType<uint32_t>(&data_ptr, &data_size, "entries.overlay_id",
+ dtohl(data_header->target_entry_count)),
+ };
+ if (!target_entries.target_id || !target_entries.overlay_id) {
return {};
}
- auto target_inline_entries = ReadType<Idmap_target_entry_inline>(
- &data_ptr, &data_size, "target inline", dtohl(data_header->target_inline_entry_count));
- if (target_inline_entries == nullptr) {
+ Idmap_target_inline_entries target_inline_entries{
+ .target_id = ReadType<uint32_t>(&data_ptr, &data_size, "target inline.target_id",
+ dtohl(data_header->target_inline_entry_count)),
+ .entry = ReadType<Idmap_target_entry_inline>(&data_ptr, &data_size, "target inline.entry",
+ dtohl(data_header->target_inline_entry_count))};
+ if (!target_inline_entries.target_id || !target_inline_entries.entry) {
return {};
}
@@ -344,9 +347,13 @@
return {};
}
- auto overlay_entries = ReadType<Idmap_overlay_entry>(&data_ptr, &data_size, "target inline",
- dtohl(data_header->overlay_entry_count));
- if (overlay_entries == nullptr) {
+ Idmap_overlay_entries overlay_entries{
+ .overlay_id = ReadType<uint32_t>(&data_ptr, &data_size, "overlay entries.overlay_id",
+ dtohl(data_header->overlay_entry_count)),
+ .target_id = ReadType<uint32_t>(&data_ptr, &data_size, "overlay entries.target_id",
+ dtohl(data_header->overlay_entry_count)),
+ };
+ if (!overlay_entries.overlay_id || !overlay_entries.target_id) {
return {};
}
std::optional<std::string_view> string_pool = ReadString(&data_ptr, &data_size, "string pool");
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 64b1f0c..e213fbd 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -40,6 +40,19 @@
struct Idmap_target_entry_inline_value;
struct Idmap_overlay_entry;
+struct Idmap_target_entries {
+ const uint32_t* target_id = nullptr;
+ const uint32_t* overlay_id = nullptr;
+};
+struct Idmap_target_inline_entries {
+ const uint32_t* target_id = nullptr;
+ const Idmap_target_entry_inline* entry = nullptr;
+};
+struct Idmap_overlay_entries {
+ const uint32_t* overlay_id = nullptr;
+ const uint32_t* target_id = nullptr;
+};
+
// A string pool for overlay apk assets. The string pool holds the strings of the overlay resources
// table and additionally allows for loading strings from the idmap string pool. The idmap string
// pool strings are offset after the end of the overlay resource table string pool entries so
@@ -67,7 +80,7 @@
private:
explicit OverlayDynamicRefTable(const Idmap_data_header* data_header,
- const Idmap_overlay_entry* entries,
+ Idmap_overlay_entries entries,
uint8_t target_assigned_package_id);
// Rewrites a compile-time overlay resource id to the runtime resource id of corresponding target
@@ -75,8 +88,8 @@
status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
const Idmap_data_header* data_header_;
- const Idmap_overlay_entry* entries_;
- const int8_t target_assigned_package_id_;
+ Idmap_overlay_entries entries_;
+ uint8_t target_assigned_package_id_;
friend LoadedIdmap;
friend IdmapResMap;
@@ -131,17 +144,15 @@
}
private:
- explicit IdmapResMap(const Idmap_data_header* data_header,
- const Idmap_target_entry* entries,
- const Idmap_target_entry_inline* inline_entries,
+ explicit IdmapResMap(const Idmap_data_header* data_header, Idmap_target_entries entries,
+ Idmap_target_inline_entries inline_entries,
const Idmap_target_entry_inline_value* inline_entry_values,
- const ConfigDescription* configs,
- uint8_t target_assigned_package_id,
+ const ConfigDescription* configs, uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table);
const Idmap_data_header* data_header_;
- const Idmap_target_entry* entries_;
- const Idmap_target_entry_inline* inline_entries_;
+ Idmap_target_entries entries_;
+ Idmap_target_inline_entries inline_entries_;
const Idmap_target_entry_inline_value* inline_entry_values_;
const ConfigDescription* configurations_;
const uint8_t target_assigned_package_id_;
@@ -192,11 +203,11 @@
const Idmap_header* header_;
const Idmap_data_header* data_header_;
- const Idmap_target_entry* target_entries_;
- const Idmap_target_entry_inline* target_inline_entries_;
+ Idmap_target_entries target_entries_;
+ Idmap_target_inline_entries target_inline_entries_;
const Idmap_target_entry_inline_value* inline_entry_values_;
const ConfigDescription* configurations_;
- const Idmap_overlay_entry* overlay_entries_;
+ const Idmap_overlay_entries overlay_entries_;
const std::unique_ptr<ResStringPool> string_pool_;
android::base::unique_fd idmap_fd_;
@@ -207,17 +218,13 @@
private:
DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
- explicit LoadedIdmap(const std::string& idmap_path,
- const Idmap_header* header,
- const Idmap_data_header* data_header,
- const Idmap_target_entry* target_entries,
- const Idmap_target_entry_inline* target_inline_entries,
+ explicit LoadedIdmap(const std::string& idmap_path, const Idmap_header* header,
+ const Idmap_data_header* data_header, Idmap_target_entries target_entries,
+ Idmap_target_inline_entries target_inline_entries,
const Idmap_target_entry_inline_value* inline_entry_values_,
- const ConfigDescription* configs,
- const Idmap_overlay_entry* overlay_entries,
+ const ConfigDescription* configs, Idmap_overlay_entries overlay_entries,
std::unique_ptr<ResStringPool>&& string_pool,
- std::string_view overlay_apk_path,
- std::string_view target_apk_path);
+ std::string_view overlay_apk_path, std::string_view target_apk_path);
friend OverlayStringPool;
};
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index c264890..e330410 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -48,7 +48,7 @@
namespace android {
constexpr const uint32_t kIdmapMagic = 0x504D4449u;
-constexpr const uint32_t kIdmapCurrentVersion = 0x00000009u;
+constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Au;
// This must never change.
constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian)
diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap
index 8e847e8..7e4b261 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index b10019a..67d5774 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -29,6 +29,10 @@
libs: [
"androidx.annotation_annotation",
],
+ stub_only_libs: [
+ // Needed for javadoc references.
+ "framework-location.stubs.system",
+ ],
api_packages: [
"android.location",
"com.android.location.provider",
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index 80b606c..5e55f64 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -34,7 +34,7 @@
/**
* MediaMuxer facilitates muxing elementary streams. Currently MediaMuxer supports MP4, Webm
- * and 3GP file as the output. It also supports muxing B-frames in MP4 since Android Nougat.
+ * and 3GP file as the output. It also supports muxing B-frames in MP4 since Android Nougat MR1.
* <p>
* It is generally used like this:
*
@@ -191,14 +191,14 @@
<td>○</td>
<td>●</td>
</tr>
- <td align="center">Muxing B-Frames(bi-directional predicted frames)</td>
+ <td align="center">Muxing B-Frames (bi-directional predicted frames)</td>
<td>○</td>
<td>○</td>
<td>○</td>
<td>○</td>
<td>○</td>
<td>○</td>
- <td>⁕</td>
+ <td>○</td>
<td>⁕</td>
<td>⁕</td>
</tr>
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 08155dd..5805332 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -31,6 +31,12 @@
<!-- A header for selfManaged devices only. -->
<include layout="@layout/vendor_header" />
+ <!-- A device icon for selfManaged devices only. -->
+ <ImageView
+ android:id="@+id/device_icon"
+ android:visibility="gone"
+ android:contentDescription="@null"
+ style="@style/DeviceIcon" />
<!-- Do NOT change the ID of the root LinearLayout above:
it's referenced in CTS tests. -->
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index fe7cfc6..a161a50 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -137,4 +137,10 @@
<item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
</style>
+ <style name="DeviceIcon">
+ <item name="android:layout_width">24dp</item>
+ <item name="android:layout_height">24dp</item>
+ <item name="android:layout_gravity">center</item>
+ </style>
+
</resources>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 7974a37..50419f7 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -61,6 +61,7 @@
import android.graphics.BlendModeColorFilter;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Bundle;
import android.os.Handler;
@@ -130,6 +131,8 @@
// Present for single device and multiple device only.
private ImageView mProfileIcon;
+ // Present for self managed association only;
+ private ImageView mDeviceIcon;
// Only present for selfManaged devices.
private ImageView mVendorHeaderImage;
@@ -260,8 +263,8 @@
}
@Override
- protected void onStop() {
- super.onStop();
+ protected void onDestroy() {
+ super.onDestroy();
// TODO: handle config changes without cancelling.
if (!isDone()) {
@@ -306,6 +309,8 @@
mVendorHeaderName = findViewById(R.id.vendor_header_name);
mVendorHeaderButton = findViewById(R.id.vendor_header_button);
+ mDeviceIcon = findViewById(R.id.device_icon);
+
mDeviceListRecyclerView = findViewById(R.id.device_list);
mMultipleDeviceSpinner = findViewById(R.id.spinner_multiple_device);
@@ -430,6 +435,7 @@
final Drawable vendorIcon;
final CharSequence vendorName;
final Spanned title;
+ final Icon deviceIcon = mRequest.getDeviceIcon();
if (!SUPPORTED_SELF_MANAGED_PROFILES.contains(deviceProfile)) {
throw new RuntimeException("Unsupported profile " + deviceProfile);
@@ -452,6 +458,11 @@
title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), mAppLabel,
getString(R.string.device_type), deviceName);
+ if (deviceIcon != null) {
+ mDeviceIcon.setImageIcon(deviceIcon);
+ mDeviceIcon.setVisibility(View.VISIBLE);
+ }
+
if (PROFILE_SUMMARIES.containsKey(deviceProfile)) {
final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
final Spanned summary = getHtmlFromResources(this, summaryResourceId,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index c48e7e4..8df8a07 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -33,7 +33,6 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.rememberSystemUiController
-import com.android.compose.theme.LocalAndroidColorScheme
import androidx.compose.ui.unit.dp
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
@@ -57,7 +56,7 @@
)
androidx.compose.material3.ModalBottomSheet(
onDismissRequest = onDismiss,
- containerColor = LocalAndroidColorScheme.current.surfaceBright,
+ containerColor = MaterialTheme.colorScheme.surfaceBright,
sheetState = state,
content = {
Box(
@@ -91,7 +90,7 @@
setBottomSheetSystemBarsColor(sysUiController)
}
ModalBottomSheetLayout(
- sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
+ sheetBackgroundColor = MaterialTheme.colorScheme.surfaceBright,
modifier = Modifier.background(Color.Transparent),
sheetState = state,
sheetContent = { sheetContent() },
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index 006a2d9..426fec2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -29,12 +29,12 @@
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.ui.theme.Shapes
/**
@@ -54,7 +54,7 @@
modifier = modifier.fillMaxWidth().wrapContentHeight(),
border = null,
colors = CardDefaults.cardColors(
- containerColor = LocalAndroidColorScheme.current.surfaceBright,
+ containerColor = MaterialTheme.colorScheme.surfaceBright,
),
) {
if (topAppBar != null) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 2c3c63b..84078c4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -31,6 +31,7 @@
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.SuggestionChipDefaults
import androidx.compose.runtime.Composable
@@ -52,7 +53,6 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.ui.theme.Shapes
@@ -172,7 +172,7 @@
// Decorative purpose only.
contentDescription = null,
modifier = Modifier.size(24.dp),
- tint = LocalAndroidColorScheme.current.onSurfaceVariant,
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
@@ -186,7 +186,7 @@
Icon(
modifier = iconSize,
bitmap = iconImageBitmap,
- tint = LocalAndroidColorScheme.current.onSurfaceVariant,
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -210,7 +210,7 @@
Icon(
modifier = iconSize,
imageVector = iconImageVector,
- tint = LocalAndroidColorScheme.current.onSurfaceVariant,
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -222,7 +222,7 @@
Icon(
modifier = iconSize,
painter = iconPainter,
- tint = LocalAndroidColorScheme.current.onSurfaceVariant,
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -233,9 +233,9 @@
},
border = null,
colors = SuggestionChipDefaults.suggestionChipColors(
- containerColor = LocalAndroidColorScheme.current.surfaceContainerHigh,
- labelColor = LocalAndroidColorScheme.current.onSurfaceVariant,
- iconContentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
+ containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
+ labelColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
)
}
@@ -338,7 +338,7 @@
imageVector = navigationIcon,
contentDescription = navigationIconContentDescription,
modifier = Modifier.size(24.dp).autoMirrored(),
- tint = LocalAndroidColorScheme.current.onSurfaceVariant,
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 342af3b..37268ad 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -21,23 +21,23 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
+import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
-import com.android.compose.theme.LocalAndroidColorScheme
@Composable
fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
InternalSectionHeader(
text = text,
- color = LocalAndroidColorScheme.current.onSurfaceVariant,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
applyTopPadding = !isFirstSection
)
}
@Composable
fun MoreAboutPasskeySectionHeader(text: String) {
- InternalSectionHeader(text, LocalAndroidColorScheme.current.onSurface)
+ InternalSectionHeader(text, MaterialTheme.colorScheme.onSurface)
}
@Composable
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
index b4075f1..d325ebb 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
@@ -17,9 +17,9 @@
package com.android.credentialmanager.common.ui
import androidx.compose.runtime.Composable
+import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.graphics.Color
import com.android.compose.SystemUiController
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
@Composable
@@ -34,7 +34,7 @@
darkIcons = false
)
sysUiController.setNavigationBarColor(
- color = LocalAndroidColorScheme.current.surfaceBright,
+ color = MaterialTheme.colorScheme.surfaceBright,
darkIcons = false
)
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 68c2244..3e999cb 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -26,7 +26,6 @@
import androidx.compose.ui.text.style.Hyphens
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
-import com.android.compose.theme.LocalAndroidColorScheme
/**
* The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X".
@@ -38,7 +37,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.onSurface,
+ color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.headlineSmall.copy(hyphens = Hyphens.Auto),
)
@@ -52,7 +51,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.onSurfaceVariant,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium.copy(hyphens = Hyphens.Auto),
)
}
@@ -70,7 +69,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.onSurfaceVariant,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall.copy(hyphens = Hyphens.Auto),
overflow = TextOverflow.Ellipsis,
maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -86,7 +85,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.onSurface,
+ color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleLarge.copy(hyphens = Hyphens.Auto),
)
}
@@ -104,7 +103,7 @@
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = LocalAndroidColorScheme.current.onSurface,
+ color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleSmall.copy(hyphens = Hyphens.Auto),
overflow = TextOverflow.Ellipsis,
maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
@@ -160,7 +159,7 @@
modifier = modifier.wrapContentSize(),
text = text,
textAlign = TextAlign.Center,
- color = LocalAndroidColorScheme.current.onSurfaceVariant,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.labelLarge.copy(hyphens = Hyphens.Auto),
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 4993a1f..d788891 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -30,6 +30,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Divider
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.filled.Add
@@ -46,7 +47,6 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
import com.android.credentialmanager.common.BiometricError
@@ -448,7 +448,7 @@
item {
Divider(
thickness = 1.dp,
- color = LocalAndroidColorScheme.current.outlineVariant,
+ color = MaterialTheme.colorScheme.outlineVariant,
modifier = Modifier.padding(vertical = 16.dp)
)
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
index a270681..debaf3e 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.os.Bundle
+import android.util.Log
import androidx.annotation.XmlRes
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
@@ -42,7 +43,9 @@
override fun createPreferenceScreen(factory: PreferenceScreenFactory): PreferenceScreen? {
val context = factory.context
fun createPreferenceScreenFromResource() =
- factory.inflate(getPreferenceScreenResId(context))
+ factory.inflate(getPreferenceScreenResId(context))?.also {
+ Log.i(TAG, "Load screen " + it.key + " from resource")
+ }
val screenCreator =
getPreferenceScreenCreator(context) ?: return createPreferenceScreenFromResource()
@@ -50,10 +53,12 @@
val preferenceHierarchy = screenCreator.getPreferenceHierarchy(context)
val preferenceScreen =
if (screenCreator.hasCompleteHierarchy()) {
+ Log.i(TAG, "Load screen " + screenCreator.key + " from hierarchy")
factory.getOrCreatePreferenceScreen().apply {
inflatePreferenceHierarchy(preferenceBindingFactory, preferenceHierarchy)
}
} else {
+ Log.i(TAG, "Screen " + screenCreator.key + " is hybrid")
createPreferenceScreenFromResource()?.also {
bindRecursively(it, preferenceBindingFactory, preferenceHierarchy)
} ?: return null
@@ -83,4 +88,8 @@
preferenceScreenBindingHelper?.close()
super.onDestroy()
}
+
+ companion object {
+ private const val TAG = "PreferenceFragment"
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index feaf7fb..b94e906 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.media;
+import static android.content.pm.PackageManager.FEATURE_PC;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_DOCK;
import static android.media.MediaRoute2Info.TYPE_HDMI;
@@ -29,8 +30,6 @@
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -43,6 +42,8 @@
import android.util.Log;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.settingslib.R;
import com.android.settingslib.media.flags.Flags;
@@ -72,7 +73,7 @@
return context.getString(R.string.media_transfer_this_device_name_tv);
} else if (isTablet()) {
return context.getString(R.string.media_transfer_this_device_name_tablet);
- } else if (inputRoutingEnabledAndIsDesktop()) {
+ } else if (inputRoutingEnabledAndIsDesktop(context)) {
return context.getString(R.string.media_transfer_this_device_name_desktop);
} else {
return context.getString(R.string.media_transfer_this_device_name);
@@ -88,7 +89,7 @@
case TYPE_WIRED_HEADSET:
case TYPE_WIRED_HEADPHONES:
name =
- inputRoutingEnabledAndIsDesktop()
+ inputRoutingEnabledAndIsDesktop(context)
? context.getString(R.string.media_transfer_headphone_name)
: context.getString(R.string.media_transfer_wired_headphone_name);
break;
@@ -96,7 +97,7 @@
case TYPE_USB_HEADSET:
case TYPE_USB_ACCESSORY:
name =
- inputRoutingEnabledAndIsDesktop()
+ inputRoutingEnabledAndIsDesktop(context)
? context.getString(R.string.media_transfer_usb_audio_name)
: context.getString(R.string.media_transfer_wired_headphone_name);
break;
@@ -149,14 +150,13 @@
.contains("tablet");
}
- static boolean isDesktop() {
- return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
- .contains("desktop");
+ public static boolean isDesktop(@NonNull Context context) {
+ return context.getPackageManager().hasSystemFeature(FEATURE_PC);
}
- static boolean inputRoutingEnabledAndIsDesktop() {
+ public static boolean inputRoutingEnabledAndIsDesktop(@NonNull Context context) {
return com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl()
- && isDesktop();
+ && isDesktop(context);
}
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 65b2275..1a99d25 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -39,7 +39,6 @@
"configinfra_framework_flags_java_lib",
"device_config_service_flags_java",
"libaconfig_java_proto_lite",
- "notification_flags_lib",
"SettingsLibDeviceStateRotationLock",
"SettingsLibDisplayUtils",
],
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index ec3bd90..6c31831 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -29,7 +29,6 @@
import android.icu.util.ULocale;
import android.media.AudioManager;
import android.media.RingtoneManager;
-import android.media.Utils;
import android.net.Uri;
import android.os.LocaleList;
import android.os.RemoteException;
@@ -310,13 +309,6 @@
return SILENT_RINGTONE;
}
} else {
- // If the ringtone/notification support the vibration, use the original value.
- final int ringtoneType = getRingtoneType(name);
- if ((Settings.System.RINGTONE.equals(name)
- || Settings.System.NOTIFICATION_SOUND.equals(name))
- && hasVibrationSettings(value, ringtoneType)) {
- return value;
- }
return getCanonicalRingtoneValue(value);
}
}
@@ -370,15 +362,6 @@
return;
}
- // If the ringtone/notification has vibration, we backup original value in onBackupValue.
- // So use the value directly for restoring.
- if ((ringtoneType == RingtoneManager.TYPE_RINGTONE
- || ringtoneType == RingtoneManager.TYPE_NOTIFICATION)
- && hasVibrationSettings(value, ringtoneType)) {
- RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, Uri.parse(value));
- return;
- }
-
Uri ringtoneUri = null;
try {
ringtoneUri =
@@ -634,19 +617,6 @@
return allLocales.remove(toFullLocale(filteredLocale));
}
- private boolean hasVibrationSettings(String value, int type) {
- if (Utils.hasVibration(Uri.parse(value)) && mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)) {
- if (type == RingtoneManager.TYPE_RINGTONE) {
- return android.media.audio.Flags.enableRingtoneHapticsCustomization();
- }
- if (type == RingtoneManager.TYPE_NOTIFICATION) {
- return com.android.server.notification.Flags.notificationVibrationInSoundUri();
- }
- }
- return false;
- }
-
/**
* Sets the locale specified. Input data is the byte representation of comma separated
* multiple BCP-47 language tags. For backwards compatibility, strings of the form
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index babc1a3..4b10b56 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -37,12 +37,9 @@
import android.database.Cursor;
import android.database.MatrixCursor;
import android.media.AudioManager;
-import android.media.Utils;
import android.net.Uri;
import android.os.Bundle;
import android.os.LocaleList;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -57,7 +54,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -78,13 +74,9 @@
"content://media/internal/audio/media/20?title=DefaultNotification&canonical=1";
private static final String DEFAULT_ALARM_VALUE =
"content://media/internal/audio/media/30?title=DefaultAlarm&canonical=1";
- private static final String VIBRATION_FILE_NAME = "haptics.xml";
private SettingsHelper mSettingsHelper;
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
@Mock private Context mContext;
@Mock private Resources mResources;
@Mock private AudioManager mAudioManager;
@@ -128,22 +120,6 @@
}
@Test
- @EnableFlags({android.media.audio.Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION,
- com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI})
- public void testOnBackupValue_ringtoneVibrationSupport_returnsSameValue() {
- when(mResources.getBoolean(
- com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)).thenReturn(
- true);
- String testRingtoneVibrationValue = createUriWithVibration(DEFAULT_RINGTONE_VALUE);
- String testNotificationVibrationValue = createUriWithVibration(DEFAULT_NOTIFICATION_VALUE);
-
- assertEquals(testRingtoneVibrationValue, mSettingsHelper.onBackupValue(
- Settings.System.RINGTONE, testRingtoneVibrationValue));
- assertEquals(testNotificationVibrationValue, mSettingsHelper.onBackupValue(
- Settings.System.NOTIFICATION_SOUND, testNotificationVibrationValue));
- }
-
- @Test
public void testGetRealValue_settingNotReplaced_returnsSameValue() {
when(mSettingsHelper.isReplacedSystemSetting(eq(SETTING_KEY))).thenReturn(false);
@@ -699,30 +675,6 @@
.isEqualTo(null);
}
- @Test
- @EnableFlags({android.media.audio.Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION,
- com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI})
- public void testRestoreValue_ringtoneVibrationSupport_restoreValue() {
- when(mResources.getBoolean(
- com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)).thenReturn(
- true);
- String testRingtoneVibrationValue = createUriWithVibration(DEFAULT_RINGTONE_VALUE);
- String testNotificationVibrationValue = createUriWithVibration(DEFAULT_NOTIFICATION_VALUE);
- ContentProvider mockMediaContentProvider =
- new MockContentProvider(mContext) {
- @Override
- public String getType(Uri url) {
- return "audio/ogg";
- }
- };
- mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
- resetRingtoneSettingsToDefault();
-
- assertRingtoneSettingsRestoring(Settings.System.RINGTONE, testRingtoneVibrationValue);
- assertRingtoneSettingsRestoring(
- Settings.System.NOTIFICATION_SOUND, testNotificationVibrationValue);
- }
-
private static class MockSettingsProvider extends MockContentProvider {
private final ArrayMap<String, String> mKeyValueStore = new ArrayMap<>();
MockSettingsProvider(Context context) {
@@ -814,25 +766,4 @@
assertThat(Settings.System.getString(mContentResolver, Settings.System.ALARM_ALERT))
.isEqualTo(DEFAULT_ALARM_VALUE);
}
-
- private String createUriWithVibration(String defaultUriString) {
- return Uri.parse(defaultUriString).buildUpon()
- .appendQueryParameter(
- Utils.VIBRATION_URI_PARAM, VIBRATION_FILE_NAME).build().toString();
- }
-
- private void assertRingtoneSettingsRestoring(
- String settings, String testRingtoneSettingsValue) {
- mSettingsHelper.restoreValue(
- mContext,
- mContentResolver,
- new ContentValues(),
- Uri.EMPTY,
- settings,
- testRingtoneSettingsValue,
- 0);
-
- assertThat(Settings.System.getString(mContentResolver, settings))
- .isEqualTo(testRingtoneSettingsValue);
- }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 408ed1e..456fedf 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -743,6 +743,12 @@
<uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" />
<uses-permission android:name="android.permission.MANAGE_SAFETY_CENTER" />
+ <!-- Permissions required for CTS test - CtsVirtualDevicesTestCases -->
+ <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
+ <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" />
+ <uses-permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY" />
+
+
<!-- Permission required for CTS test - Notification test suite -->
<uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index fc4cf1d..3dc0657 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -27,6 +27,8 @@
import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import android.view.ViewOverlay
import android.view.animation.Interpolator
import android.window.WindowAnimationState
import androidx.annotation.VisibleForTesting
@@ -197,10 +199,24 @@
}
interface Animation {
+ /** Start the animation. */
+ fun start()
+
/** Cancel the animation. */
fun cancel()
}
+ @VisibleForTesting
+ class InterpolatedAnimation(@get:VisibleForTesting val animator: Animator) : Animation {
+ override fun start() {
+ animator.start()
+ }
+
+ override fun cancel() {
+ animator.cancel()
+ }
+ }
+
/** The timings (durations and delays) used by this animator. */
data class Timings(
/** The total duration of the animation. */
@@ -270,33 +286,73 @@
alpha = 0
}
- val animator =
- createAnimator(
+ return createAnimation(
controller,
+ controller.createAnimatorState(),
endState,
windowBackgroundLayer,
fadeWindowBackgroundLayer,
drawHole,
)
- animator.start()
-
- return object : Animation {
- override fun cancel() {
- animator.cancel()
- }
- }
+ .apply { start() }
}
@VisibleForTesting
- fun createAnimator(
+ fun createAnimation(
controller: Controller,
+ startState: State,
endState: State,
windowBackgroundLayer: GradientDrawable,
fadeWindowBackgroundLayer: Boolean = true,
drawHole: Boolean = false,
- ): ValueAnimator {
- val state = controller.createAnimatorState()
+ ): Animation {
+ val transitionContainer = controller.transitionContainer
+ val transitionContainerOverlay = transitionContainer.overlay
+ val openingWindowSyncView = controller.openingWindowSyncView
+ val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
+ // Whether we should move the [windowBackgroundLayer] into the overlay of
+ // [Controller.openingWindowSyncView] once the opening app window starts to be visible, or
+ // from it once the closing app window stops being visible.
+ // This is necessary as a one-off sync so we can avoid syncing at every frame, especially
+ // in complex interactions like launching an activity from a dialog. See
+ // b/214961273#comment2 for more details.
+ val moveBackgroundLayerWhenAppVisibilityChanges =
+ openingWindowSyncView != null &&
+ openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl
+
+ return createInterpolatedAnimation(
+ controller,
+ startState,
+ endState,
+ windowBackgroundLayer,
+ transitionContainer,
+ transitionContainerOverlay,
+ openingWindowSyncView,
+ openingWindowSyncViewOverlay,
+ fadeWindowBackgroundLayer,
+ drawHole,
+ moveBackgroundLayerWhenAppVisibilityChanges,
+ )
+ }
+
+ /**
+ * Creates an interpolator-based animator that uses [timings] and [interpolators] to calculate
+ * the new bounds and corner radiuses at each frame.
+ */
+ private fun createInterpolatedAnimation(
+ controller: Controller,
+ state: State,
+ endState: State,
+ windowBackgroundLayer: GradientDrawable,
+ transitionContainer: View,
+ transitionContainerOverlay: ViewGroupOverlay,
+ openingWindowSyncView: View? = null,
+ openingWindowSyncViewOverlay: ViewOverlay? = null,
+ fadeWindowBackgroundLayer: Boolean = true,
+ drawHole: Boolean = false,
+ moveBackgroundLayerWhenAppVisibilityChanges: Boolean = false,
+ ): Animation {
// Start state.
val startTop = state.top
val startBottom = state.bottom
@@ -333,45 +389,24 @@
}
}
- val transitionContainer = controller.transitionContainer
val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState)
+ var movedBackgroundLayer = false
// Update state.
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = timings.totalDuration
animator.interpolator = LINEAR
- // Whether we should move the [windowBackgroundLayer] into the overlay of
- // [Controller.openingWindowSyncView] once the opening app window starts to be visible, or
- // from it once the closing app window stops being visible.
- // This is necessary as a one-off sync so we can avoid syncing at every frame, especially
- // in complex interactions like launching an activity from a dialog. See
- // b/214961273#comment2 for more details.
- val openingWindowSyncView = controller.openingWindowSyncView
- val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
- val moveBackgroundLayerWhenAppVisibilityChanges =
- openingWindowSyncView != null &&
- openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl
-
- val transitionContainerOverlay = transitionContainer.overlay
- var movedBackgroundLayer = false
-
animator.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator, isReverse: Boolean) {
- if (DEBUG) {
- Log.d(TAG, "Animation started")
- }
- controller.onTransitionAnimationStart(isExpandingFullyAbove)
-
- // Add the drawable to the transition container overlay. Overlays always draw
- // drawables after views, so we know that it will be drawn above any view added
- // by the controller.
- if (controller.isLaunching || openingWindowSyncViewOverlay == null) {
- transitionContainerOverlay.add(windowBackgroundLayer)
- } else {
- openingWindowSyncViewOverlay.add(windowBackgroundLayer)
- }
+ onAnimationStart(
+ controller,
+ isExpandingFullyAbove,
+ windowBackgroundLayer,
+ transitionContainerOverlay,
+ openingWindowSyncViewOverlay,
+ )
}
override fun onAnimationEnd(animation: Animator) {
@@ -413,63 +448,20 @@
state.bottomCornerRadius =
MathUtils.lerp(startBottomCornerRadius, endBottomCornerRadius, progress)
- state.visible =
- if (controller.isLaunching) {
- // The expanding view can/should be hidden once it is completely covered by the
- // opening window.
- getProgress(
- timings,
- linearProgress,
- timings.contentBeforeFadeOutDelay,
- timings.contentBeforeFadeOutDuration,
- ) < 1
- } else {
- getProgress(
- timings,
- linearProgress,
- timings.contentAfterFadeInDelay,
- timings.contentAfterFadeInDuration,
- ) > 0
- }
+ state.visible = checkVisibility(timings, linearProgress, controller.isLaunching)
- if (
- controller.isLaunching &&
- moveBackgroundLayerWhenAppVisibilityChanges &&
- !state.visible &&
- !movedBackgroundLayer
- ) {
- // The expanding view is not visible, so the opening app is visible. If this is
- // the first frame when it happens, trigger a one-off sync and move the
- // background layer in its new container.
- movedBackgroundLayer = true
-
- transitionContainerOverlay.remove(windowBackgroundLayer)
- openingWindowSyncViewOverlay!!.add(windowBackgroundLayer)
-
- ViewRootSync.synchronizeNextDraw(
- transitionContainer,
- openingWindowSyncView,
- then = {},
- )
- } else if (
- !controller.isLaunching &&
- moveBackgroundLayerWhenAppVisibilityChanges &&
- state.visible &&
- !movedBackgroundLayer
- ) {
- // The contracting view is now visible, so the closing app is not. If this is
- // the first frame when it happens, trigger a one-off sync and move the
- // background layer in its new container.
- movedBackgroundLayer = true
-
- openingWindowSyncViewOverlay!!.remove(windowBackgroundLayer)
- transitionContainerOverlay.add(windowBackgroundLayer)
-
- ViewRootSync.synchronizeNextDraw(
- openingWindowSyncView,
- transitionContainer,
- then = {},
- )
+ if (!movedBackgroundLayer) {
+ movedBackgroundLayer =
+ maybeMoveBackgroundLayer(
+ controller,
+ state,
+ windowBackgroundLayer,
+ transitionContainer,
+ transitionContainerOverlay,
+ openingWindowSyncView,
+ openingWindowSyncViewOverlay,
+ moveBackgroundLayerWhenAppVisibilityChanges,
+ )
}
val container =
@@ -478,7 +470,6 @@
} else {
controller.transitionContainer
}
-
applyStateToWindowBackgroundLayer(
windowBackgroundLayer,
state,
@@ -488,10 +479,131 @@
drawHole,
controller.isLaunching,
)
+
controller.onTransitionAnimationProgress(state, progress, linearProgress)
}
- return animator
+ return InterpolatedAnimation(animator)
+ }
+
+ private fun onAnimationStart(
+ controller: Controller,
+ isExpandingFullyAbove: Boolean,
+ windowBackgroundLayer: GradientDrawable,
+ transitionContainerOverlay: ViewGroupOverlay,
+ openingWindowSyncViewOverlay: ViewOverlay?,
+ ) {
+ if (DEBUG) {
+ Log.d(TAG, "Animation started")
+ }
+ controller.onTransitionAnimationStart(isExpandingFullyAbove)
+
+ // Add the drawable to the transition container overlay. Overlays always draw
+ // drawables after views, so we know that it will be drawn above any view added
+ // by the controller.
+ if (controller.isLaunching || openingWindowSyncViewOverlay == null) {
+ transitionContainerOverlay.add(windowBackgroundLayer)
+ } else {
+ openingWindowSyncViewOverlay.add(windowBackgroundLayer)
+ }
+ }
+
+ private fun onAnimationEnd(
+ controller: Controller,
+ isExpandingFullyAbove: Boolean,
+ windowBackgroundLayer: GradientDrawable,
+ transitionContainerOverlay: ViewGroupOverlay,
+ openingWindowSyncViewOverlay: ViewOverlay?,
+ moveBackgroundLayerWhenAppVisibilityChanges: Boolean,
+ ) {
+ if (DEBUG) {
+ Log.d(TAG, "Animation ended")
+ }
+
+ // TODO(b/330672236): Post this to the main thread instead so that it does not
+ // flicker with Flexiglass enabled.
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
+
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ }
+ }
+
+ /** Returns whether is the controller's view should be visible with the given [timings]. */
+ private fun checkVisibility(timings: Timings, progress: Float, isLaunching: Boolean): Boolean {
+ return if (isLaunching) {
+ // The expanding view can/should be hidden once it is completely covered by the opening
+ // window.
+ getProgress(
+ timings,
+ progress,
+ timings.contentBeforeFadeOutDelay,
+ timings.contentBeforeFadeOutDuration,
+ ) < 1
+ } else {
+ // The shrinking view can/should be hidden while it is completely covered by the closing
+ // window.
+ getProgress(
+ timings,
+ progress,
+ timings.contentAfterFadeInDelay,
+ timings.contentAfterFadeInDuration,
+ ) > 0
+ }
+ }
+
+ /**
+ * If necessary, moves the background layer from the view container's overlay to the window sync
+ * view overlay, or vice versa.
+ *
+ * @return true if the background layer vwas moved, false otherwise.
+ */
+ private fun maybeMoveBackgroundLayer(
+ controller: Controller,
+ state: State,
+ windowBackgroundLayer: GradientDrawable,
+ transitionContainer: View,
+ transitionContainerOverlay: ViewGroupOverlay,
+ openingWindowSyncView: View?,
+ openingWindowSyncViewOverlay: ViewOverlay?,
+ moveBackgroundLayerWhenAppVisibilityChanges: Boolean,
+ ): Boolean {
+ if (
+ controller.isLaunching && moveBackgroundLayerWhenAppVisibilityChanges && !state.visible
+ ) {
+ // The expanding view is not visible, so the opening app is visible. If this is the
+ // first frame when it happens, trigger a one-off sync and move the background layer
+ // in its new container.
+ transitionContainerOverlay.remove(windowBackgroundLayer)
+ openingWindowSyncViewOverlay!!.add(windowBackgroundLayer)
+
+ ViewRootSync.synchronizeNextDraw(
+ transitionContainer,
+ openingWindowSyncView!!,
+ then = {},
+ )
+
+ return true
+ } else if (
+ !controller.isLaunching && moveBackgroundLayerWhenAppVisibilityChanges && state.visible
+ ) {
+ // The contracting view is now visible, so the closing app is not. If this is the first
+ // frame when it happens, trigger a one-off sync and move the background layer in its
+ // new container.
+ openingWindowSyncViewOverlay!!.remove(windowBackgroundLayer)
+ transitionContainerOverlay.add(windowBackgroundLayer)
+
+ ViewRootSync.synchronizeNextDraw(
+ openingWindowSyncView!!,
+ transitionContainer,
+ then = {},
+ )
+
+ return true
+ }
+
+ return false
}
/** Return whether we are expanding fully above the [transitionContainer]. */
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
index 37c37b0..6b3223d 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
@@ -16,8 +16,8 @@
package com.android.compose.theme
-import android.annotation.ColorInt
import android.content.Context
+import androidx.annotation.ColorRes
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import com.android.internal.R
@@ -34,62 +34,27 @@
/**
* 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.
+ * This scheme contains the Material3 colors that are not available on
+ * [androidx.compose.material3.MaterialTheme]. For other colors (e.g. primary), use
+ * `MaterialTheme.colorScheme` instead.
*/
-class AndroidColorScheme(context: Context) {
- val onSecondaryFixedVariant = getColor(context, R.attr.materialColorOnSecondaryFixedVariant)
- val onTertiaryFixedVariant = getColor(context, R.attr.materialColorOnTertiaryFixedVariant)
- val surfaceContainerLowest = getColor(context, R.attr.materialColorSurfaceContainerLowest)
- val onPrimaryFixedVariant = getColor(context, R.attr.materialColorOnPrimaryFixedVariant)
- val onSecondaryContainer = getColor(context, R.attr.materialColorOnSecondaryContainer)
- val onTertiaryContainer = getColor(context, R.attr.materialColorOnTertiaryContainer)
- val surfaceContainerLow = getColor(context, R.attr.materialColorSurfaceContainerLow)
- val onPrimaryContainer = getColor(context, R.attr.materialColorOnPrimaryContainer)
- val secondaryFixedDim = getColor(context, R.attr.materialColorSecondaryFixedDim)
- val onErrorContainer = getColor(context, R.attr.materialColorOnErrorContainer)
- val onSecondaryFixed = getColor(context, R.attr.materialColorOnSecondaryFixed)
- val onSurfaceInverse = getColor(context, R.attr.materialColorOnSurfaceInverse)
- val tertiaryFixedDim = getColor(context, R.attr.materialColorTertiaryFixedDim)
- val onTertiaryFixed = getColor(context, R.attr.materialColorOnTertiaryFixed)
- val primaryFixedDim = getColor(context, R.attr.materialColorPrimaryFixedDim)
- val secondaryContainer = getColor(context, R.attr.materialColorSecondaryContainer)
- val errorContainer = getColor(context, R.attr.materialColorErrorContainer)
- val onPrimaryFixed = getColor(context, R.attr.materialColorOnPrimaryFixed)
- val primaryInverse = getColor(context, R.attr.materialColorPrimaryInverse)
- val secondaryFixed = getColor(context, R.attr.materialColorSecondaryFixed)
- val surfaceInverse = getColor(context, R.attr.materialColorSurfaceInverse)
- val surfaceVariant = getColor(context, R.attr.materialColorSurfaceVariant)
- val tertiaryContainer = getColor(context, R.attr.materialColorTertiaryContainer)
- val tertiaryFixed = getColor(context, R.attr.materialColorTertiaryFixed)
- val primaryContainer = getColor(context, R.attr.materialColorPrimaryContainer)
- val onBackground = getColor(context, R.attr.materialColorOnBackground)
- val primaryFixed = getColor(context, R.attr.materialColorPrimaryFixed)
- val onSecondary = getColor(context, R.attr.materialColorOnSecondary)
- val onTertiary = getColor(context, R.attr.materialColorOnTertiary)
- val surfaceDim = getColor(context, R.attr.materialColorSurfaceDim)
- val surfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
- val error = getColor(context, R.attr.materialColorError)
- val onError = getColor(context, R.attr.materialColorOnError)
- val surface = getColor(context, R.attr.materialColorSurface)
- val surfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
- val surfaceContainerHighest = getColor(context, R.attr.materialColorSurfaceContainerHighest)
- val onSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant)
- val outline = getColor(context, R.attr.materialColorOutline)
- val outlineVariant = getColor(context, R.attr.materialColorOutlineVariant)
- val onPrimary = getColor(context, R.attr.materialColorOnPrimary)
- val onSurface = getColor(context, R.attr.materialColorOnSurface)
- val surfaceContainer = getColor(context, R.attr.materialColorSurfaceContainer)
- val primary = getColor(context, R.attr.materialColorPrimary)
- val secondary = getColor(context, R.attr.materialColorSecondary)
- val tertiary = getColor(context, R.attr.materialColorTertiary)
+class AndroidColorScheme(val context: Context) {
+ val primaryFixed = color(context, R.color.system_primary_fixed)
+ val primaryFixedDim = color(context, R.color.system_primary_fixed_dim)
+ val onPrimaryFixed = color(context, R.color.system_on_primary_fixed)
+ val onPrimaryFixedVariant = color(context, R.color.system_on_primary_fixed_variant)
+ val secondaryFixed = color(context, R.color.system_secondary_fixed)
+ val secondaryFixedDim = color(context, R.color.system_secondary_fixed_dim)
+ val onSecondaryFixed = color(context, R.color.system_on_secondary_fixed)
+ val onSecondaryFixedVariant = color(context, R.color.system_on_secondary_fixed_variant)
+ val tertiaryFixed = color(context, R.color.system_tertiary_fixed)
+ val tertiaryFixedDim = color(context, R.color.system_tertiary_fixed_dim)
+ val onTertiaryFixed = color(context, R.color.system_on_tertiary_fixed)
+ val onTertiaryFixedVariant = color(context, R.color.system_on_tertiary_fixed_variant)
companion object {
- internal 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)
+ internal fun color(context: Context, @ColorRes id: Int): Color {
+ return Color(context.resources.getColor(id, context.theme))
}
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/Color.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/Color.kt
index 5dbaff6..a499447 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/Color.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/Color.kt
@@ -17,6 +17,8 @@
package com.android.compose.theme
import android.annotation.AttrRes
+import android.annotation.ColorInt
+import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Color
@@ -26,5 +28,13 @@
@Composable
@ReadOnlyComposable
fun colorAttr(@AttrRes attribute: Int): Color {
- return AndroidColorScheme.getColor(LocalContext.current, attribute)
+ return colorAttr(LocalContext.current, attribute)
+}
+
+/** Return the [Color] from the given [attribute]. */
+fun colorAttr(context: Context, @AttrRes 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/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
index 0661870..d31d7aa 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
@@ -16,7 +16,9 @@
package com.android.compose.theme
+import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
@@ -24,6 +26,7 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
+import com.android.compose.theme.AndroidColorScheme.Companion.color
import com.android.compose.theme.typography.TypeScaleTokens
import com.android.compose.theme.typography.TypefaceNames
import com.android.compose.theme.typography.TypefaceTokens
@@ -31,23 +34,15 @@
import com.android.compose.theme.typography.platformTypography
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.compose.windowsizeclass.calculateWindowSizeClass
+import com.android.internal.R
/** The Material 3 theme that should wrap all Platform Composables. */
@Composable
-fun PlatformTheme(
- isDarkTheme: Boolean = isSystemInDarkTheme(),
- content: @Composable () -> Unit,
-) {
+fun PlatformTheme(isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
val context = LocalContext.current
- // TODO(b/230605885): Define our color scheme.
- val colorScheme =
- if (isDarkTheme) {
- dynamicDarkColorScheme(context)
- } else {
- dynamicLightColorScheme(context)
- }
- val androidColorScheme = AndroidColorScheme(context)
+ val colorScheme = remember(context, isDarkTheme) { platformColorScheme(isDarkTheme, context) }
+ val androidColorScheme = remember(context) { AndroidColorScheme(context) }
val typefaceNames = remember(context) { TypefaceNames.get(context) }
val typography =
remember(typefaceNames) {
@@ -55,12 +50,31 @@
}
val windowSizeClass = calculateWindowSizeClass()
- MaterialTheme(colorScheme, typography = typography) {
+ MaterialTheme(colorScheme = colorScheme, typography = typography) {
CompositionLocalProvider(
LocalAndroidColorScheme provides androidColorScheme,
LocalWindowSizeClass provides windowSizeClass,
- ) {
- content()
- }
+ content = content,
+ )
+ }
+}
+
+private fun platformColorScheme(isDarkTheme: Boolean, context: Context): ColorScheme {
+ return if (isDarkTheme) {
+ dynamicDarkColorScheme(context)
+ .copy(
+ error = color(context, R.color.system_error_dark),
+ onError = color(context, R.color.system_on_error_dark),
+ errorContainer = color(context, R.color.system_error_container_dark),
+ onErrorContainer = color(context, R.color.system_on_error_container_dark),
+ )
+ } else {
+ dynamicLightColorScheme(context)
+ .copy(
+ error = color(context, R.color.system_error_light),
+ onError = color(context, R.color.system_on_error_light),
+ errorContainer = color(context, R.color.system_error_container_light),
+ onErrorContainer = color(context, R.color.system_on_error_container_light),
+ )
}
}
diff --git a/packages/SystemUI/compose/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp
index 6e7a142..6a824d8 100644
--- a/packages/SystemUI/compose/core/tests/Android.bp
+++ b/packages/SystemUI/compose/core/tests/Android.bp
@@ -27,7 +27,6 @@
name: "PlatformComposeCoreTests",
manifest: "AndroidManifest.xml",
test_suites: ["device-tests"],
- sdk_version: "current",
certificate: "platform",
srcs: [
diff --git a/packages/SystemUI/compose/core/tests/AndroidManifest.xml b/packages/SystemUI/compose/core/tests/AndroidManifest.xml
index 1016340..28f80d4 100644
--- a/packages/SystemUI/compose/core/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/core/tests/AndroidManifest.xml
@@ -19,6 +19,11 @@
<application>
<uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="androidx.activity.ComponentActivity"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
+ android:exported="true" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/PlatformThemeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/PlatformThemeTest.kt
index 23538e3..de021a0 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/PlatformThemeTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/PlatformThemeTest.kt
@@ -16,11 +16,21 @@
package com.android.compose.theme
+import android.content.Context
+import androidx.annotation.AttrRes
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.R
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
@@ -54,4 +64,145 @@
}
}
}
+
+ @Test
+ fun testMaterialColorsMatchAttributeValue() {
+ val colorValues = mutableListOf<ColorValue>()
+
+ fun onLaunch(colorScheme: ColorScheme, context: Context) {
+ fun addValue(name: String, materialValue: Color, @AttrRes attr: Int) {
+ colorValues.add(ColorValue(name, materialValue, colorAttr(context, attr)))
+ }
+
+ addValue("primary", colorScheme.primary, R.attr.materialColorPrimary)
+ addValue("onPrimary", colorScheme.onPrimary, R.attr.materialColorOnPrimary)
+ addValue(
+ "primaryContainer",
+ colorScheme.primaryContainer,
+ R.attr.materialColorPrimaryContainer,
+ )
+ addValue(
+ "onPrimaryContainer",
+ colorScheme.onPrimaryContainer,
+ R.attr.materialColorOnPrimaryContainer,
+ )
+ addValue(
+ "inversePrimary",
+ colorScheme.inversePrimary,
+ R.attr.materialColorPrimaryInverse,
+ )
+ addValue("secondary", colorScheme.secondary, R.attr.materialColorSecondary)
+ addValue("onSecondary", colorScheme.onSecondary, R.attr.materialColorOnSecondary)
+ addValue(
+ "secondaryContainer",
+ colorScheme.secondaryContainer,
+ R.attr.materialColorSecondaryContainer,
+ )
+ addValue(
+ "onSecondaryContainer",
+ colorScheme.onSecondaryContainer,
+ R.attr.materialColorOnSecondaryContainer,
+ )
+ addValue("tertiary", colorScheme.tertiary, R.attr.materialColorTertiary)
+ addValue("onTertiary", colorScheme.onTertiary, R.attr.materialColorOnTertiary)
+ addValue(
+ "tertiaryContainer",
+ colorScheme.tertiaryContainer,
+ R.attr.materialColorTertiaryContainer,
+ )
+ addValue(
+ "onTertiaryContainer",
+ colorScheme.onTertiaryContainer,
+ R.attr.materialColorOnTertiaryContainer,
+ )
+ addValue("onBackground", colorScheme.onBackground, R.attr.materialColorOnBackground)
+ addValue("surface", colorScheme.surface, R.attr.materialColorSurface)
+ addValue("onSurface", colorScheme.onSurface, R.attr.materialColorOnSurface)
+ addValue(
+ "surfaceVariant",
+ colorScheme.surfaceVariant,
+ R.attr.materialColorSurfaceVariant,
+ )
+ addValue(
+ "onSurfaceVariant",
+ colorScheme.onSurfaceVariant,
+ R.attr.materialColorOnSurfaceVariant,
+ )
+ addValue(
+ "inverseSurface",
+ colorScheme.inverseSurface,
+ R.attr.materialColorSurfaceInverse,
+ )
+ addValue(
+ "inverseOnSurface",
+ colorScheme.inverseOnSurface,
+ R.attr.materialColorOnSurfaceInverse,
+ )
+ addValue("error", colorScheme.error, R.attr.materialColorError)
+ addValue("onError", colorScheme.onError, R.attr.materialColorOnError)
+ addValue(
+ "errorContainer",
+ colorScheme.errorContainer,
+ R.attr.materialColorErrorContainer,
+ )
+ addValue(
+ "onErrorContainer",
+ colorScheme.onErrorContainer,
+ R.attr.materialColorOnErrorContainer,
+ )
+ addValue("outline", colorScheme.outline, R.attr.materialColorOutline)
+ addValue(
+ "outlineVariant",
+ colorScheme.outlineVariant,
+ R.attr.materialColorOutlineVariant,
+ )
+ addValue("surfaceBright", colorScheme.surfaceBright, R.attr.materialColorSurfaceBright)
+ addValue("surfaceDim", colorScheme.surfaceDim, R.attr.materialColorSurfaceDim)
+ addValue(
+ "surfaceContainer",
+ colorScheme.surfaceContainer,
+ R.attr.materialColorSurfaceContainer,
+ )
+ addValue(
+ "surfaceContainerHigh",
+ colorScheme.surfaceContainerHigh,
+ R.attr.materialColorSurfaceContainerHigh,
+ )
+ addValue(
+ "surfaceContainerHighest",
+ colorScheme.surfaceContainerHighest,
+ R.attr.materialColorSurfaceContainerHighest,
+ )
+ addValue(
+ "surfaceContainerLow",
+ colorScheme.surfaceContainerLow,
+ R.attr.materialColorSurfaceContainerLow,
+ )
+ addValue(
+ "surfaceContainerLowest",
+ colorScheme.surfaceContainerLowest,
+ R.attr.materialColorSurfaceContainerLowest,
+ )
+ }
+
+ composeRule.setContent {
+ PlatformTheme {
+ val colorScheme = MaterialTheme.colorScheme
+ val context = LocalContext.current
+
+ LaunchedEffect(Unit) { onLaunch(colorScheme, context) }
+ }
+ }
+
+ assertThat(colorValues).hasSize(33)
+ colorValues.forEach { colorValue ->
+ assertWithMessage(
+ "MaterialTheme.colorScheme.${colorValue.name} matches attribute color"
+ )
+ .that(colorValue.materialValue)
+ .isEqualTo(colorValue.attrValue)
+ }
+ }
+
+ private data class ColorValue(val name: String, val materialValue: Color, val attrValue: Color)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 571b366..6d30398 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -13,6 +13,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
@@ -44,8 +45,6 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
-import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.internal.R.attr.focusable
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -270,7 +269,7 @@
/** Experimental hub background, static linear gradient */
@Composable
private fun BoxScope.StaticLinearGradient() {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
Box(
Modifier.matchParentSize()
.background(
@@ -283,7 +282,7 @@
/** Experimental hub background, animated linear gradient */
@Composable
private fun BoxScope.AnimatedLinearGradient() {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
Box(
Modifier.matchParentSize()
.background(colors.primary)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 6fca178..9392b1a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -19,12 +19,12 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
@@ -71,7 +71,7 @@
}
with(lockSection) {
LockIcon(
- overrideColor = LocalAndroidColorScheme.current.onPrimaryContainer,
+ overrideColor = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.element(Communal.Elements.LockIcon)
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 7fb4c53..96c47cc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -161,7 +161,6 @@
import androidx.window.layout.WindowMetricsCalculator
import com.android.compose.animation.Easings.Emphasized
import com.android.compose.modifiers.thenIf
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.internal.R.dimen.system_app_widget_background_radius
import com.android.systemui.Flags
@@ -473,7 +472,7 @@
if (showBottomSheet) {
val scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState()
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
ModalBottomSheet(
onDismissRequest = viewModel::onDisclaimerDismissed,
@@ -501,7 +500,7 @@
@Composable
private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
Column(
modifier = Modifier.fillMaxWidth().padding(horizontal = 32.dp, vertical = 24.dp),
@@ -817,7 +816,7 @@
*/
@Composable
private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunalViewModel) {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
Card(
modifier = Modifier.height(hubDimensions.GridHeight).padding(contentPadding),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
@@ -963,7 +962,7 @@
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit,
) {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
AnimatedVisibility(
visible = isPrimary,
modifier = modifier,
@@ -1010,7 +1009,7 @@
@Composable
private fun filledButtonColors(): ButtonColors {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
return ButtonDefaults.buttonColors(
containerColor = colors.primary,
contentColor = colors.onPrimary,
@@ -1058,7 +1057,7 @@
/** Creates an empty card used to highlight a particular spot on the grid. */
@Composable
fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) {
- val brush = SolidColor(LocalAndroidColorScheme.current.primary)
+ val brush = SolidColor(MaterialTheme.colorScheme.primary)
Box(
modifier =
// drawBehind lets us draw outside the bounds of the widgets so that we don't need to
@@ -1085,7 +1084,7 @@
viewModel: BaseCommunalViewModel,
modifier: Modifier = Modifier,
) {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
Card(
modifier = modifier,
colors =
@@ -1301,7 +1300,7 @@
modifier: Modifier = Modifier,
widgetConfigurator: WidgetConfigurator,
) {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
val scope = rememberCoroutineScope()
AnimatedVisibility(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
index df11206..b2407fa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
@@ -41,7 +41,6 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -93,7 +92,7 @@
Box(
Modifier.fillMaxWidth()
.padding(top = 18.dp, bottom = 8.dp)
- .background(LocalAndroidColorScheme.current.surfaceBright, RoundedCornerShape(28.dp))
+ .background(MaterialTheme.colorScheme.surfaceBright, RoundedCornerShape(28.dp))
) {
Column(
modifier = Modifier.fillMaxWidth(),
@@ -106,7 +105,7 @@
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
- color = LocalAndroidColorScheme.current.onSurface,
+ color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
maxLines = 1,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
index ef62eb7..97ad4f1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt
@@ -28,6 +28,7 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -47,7 +48,6 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastIsFinite
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.ui.viewmodel.DragHandle
import com.android.systemui.communal.ui.viewmodel.ResizeInfo
import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel
@@ -169,7 +169,7 @@
modifier: Modifier = Modifier,
enabled: Boolean = true,
outlinePadding: Dp = 8.dp,
- outlineColor: Color = LocalAndroidColorScheme.current.primary,
+ outlineColor: Color = MaterialTheme.colorScheme.primary,
cornerRadius: Dp = 37.dp,
strokeWidth: Dp = 3.dp,
alpha: () -> Float = { 1f },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
index b4c1a2e..868e136 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -55,7 +55,6 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.PopupType
import com.android.systemui.res.R
@@ -112,7 +111,7 @@
offset = IntOffset(0, 40),
onDismissRequest = onDismissRequest,
) {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
Button(
modifier =
Modifier.height(56.dp)
@@ -182,7 +181,7 @@
offset = IntOffset(0, 40),
onDismissRequest = onDismissRequest
) {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
Row(
modifier =
Modifier.height(56.dp)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 3d8ca1e..b5d7839 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -34,7 +34,6 @@
import com.android.compose.animation.scene.MovableElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.compose.windowsizeclass.LocalWindowSizeClass
-import com.android.internal.R.attr.layout
import com.android.systemui.media.controls.ui.composable.MediaCarouselStateLoader.stateForMediaCarouselContent
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -60,12 +59,12 @@
carouselController: MediaCarouselController,
offsetProvider: (() -> IntOffset)? = null,
usingCollapsedLandscapeMedia: Boolean = false,
+ isInSplitShade: Boolean = false,
) {
if (!isVisible || carouselController.isLockedAndHidden()) {
return
}
-
- val carouselState = remember { { stateForMediaCarouselContent() } }
+ val carouselState = remember { { stateForMediaCarouselContent(isInSplitShade) } }
val isCollapsed = usingCollapsedLandscapeMedia && isLandscape()
val mediaHeight =
if (isCollapsed && mediaHost.expansion == MediaHostState.COLLAPSED) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt
index 4a0136c..bad7405 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt
@@ -43,10 +43,13 @@
/** Returns the corresponding media location for the given [scene] */
@MediaLocation
- private fun getMediaLocation(scene: SceneKey): Int {
+ private fun getMediaLocation(scene: SceneKey, isSplitShade: Boolean): Int {
return when (scene) {
Scenes.QuickSettings -> MediaHierarchyManager.LOCATION_QS
- Scenes.Shade -> MediaHierarchyManager.LOCATION_QQS
+ Scenes.Shade -> {
+ if (isSplitShade) MediaHierarchyManager.LOCATION_QS
+ else MediaHierarchyManager.LOCATION_QQS
+ }
Scenes.Lockscreen -> MediaHierarchyManager.LOCATION_LOCKSCREEN
Scenes.Communal -> MediaHierarchyManager.LOCATION_COMMUNAL_HUB
else -> MediaHierarchyManager.LOCATION_UNKNOWN
@@ -97,11 +100,11 @@
}
/** Returns the state of media carousel */
- fun SceneScope.stateForMediaCarouselContent(): State {
+ fun SceneScope.stateForMediaCarouselContent(isInSplitShade: Boolean): State {
return when (val transitionState = layoutState.transitionState) {
is TransitionState.Idle -> {
if (MediaContentPicker.contents.contains(transitionState.currentScene)) {
- State.Idle(getMediaLocation(transitionState.currentScene))
+ State.Idle(getMediaLocation(transitionState.currentScene, isInSplitShade))
} else {
State.Gone
}
@@ -114,14 +117,14 @@
) {
State.InProgress(
min(progress, 1.0F),
- getMediaLocation(fromScene),
- getMediaLocation(toScene),
+ getMediaLocation(fromScene, isInSplitShade),
+ getMediaLocation(toScene, isInSplitShade),
)
} else if (MediaContentPicker.contents.contains(toScene)) {
State.InProgress(
transitionProgress = 1.0F,
startLocation = MediaHierarchyManager.LOCATION_UNKNOWN,
- getMediaLocation(toScene),
+ getMediaLocation(toScene, isInSplitShade),
)
} else {
State.Gone
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index e8da4bd..e382e16 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -73,7 +73,6 @@
import com.android.compose.animation.Expandable
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.background
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.colorAttr
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
@@ -163,7 +162,7 @@
}
val backgroundColor = colorAttr(R.attr.underSurface)
- val contentColor = LocalAndroidColorScheme.current.onSurface
+ val contentColor = MaterialTheme.colorScheme.onSurface
val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
val backgroundModifier =
remember(
@@ -344,7 +343,7 @@
@Composable
private fun NewChangesDot(modifier: Modifier = Modifier) {
val contentDescription = stringResource(R.string.fgs_dot_content_description)
- val color = LocalAndroidColorScheme.current.tertiary
+ val color = MaterialTheme.colorScheme.tertiary
Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
drawCircle(color)
@@ -363,7 +362,7 @@
Expandable(
shape = CircleShape,
color = colorAttr(R.attr.underSurface),
- contentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
borderStroke = BorderStroke(1.dp, colorAttr(R.attr.shadeInactive)),
modifier = modifier.padding(horizontal = 4.dp),
onClick = onClick,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index edc4cba..58801e0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -24,7 +24,9 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.layout.layout
@@ -38,6 +40,7 @@
import com.android.compose.animation.scene.MovableElementContentPicker
import com.android.compose.animation.scene.MovableElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
@@ -158,12 +161,10 @@
squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default },
) {
val contentState = { stateForQuickSettingsContent(isSplitShade, squishiness) }
- val transitionState = layoutState.transitionState
- val isClosing =
- transitionState is TransitionState.Transition &&
- transitionState.progress >= 0.9f && // almost done closing
- !(layoutState.isTransitioning(to = Scenes.Shade) ||
- layoutState.isTransitioning(to = Scenes.QuickSettings))
+
+ // Note: We use derivedStateOf {} here because isClosing() is reading the current transition
+ // progress and we don't want to recompose this scene each time the progress has changed.
+ val isClosing by remember(layoutState) { derivedStateOf { isClosing(layoutState) } }
if (isClosing) {
DisposableEffect(Unit) {
@@ -188,6 +189,14 @@
}
}
+private fun isClosing(layoutState: SceneTransitionLayoutState): Boolean {
+ val transitionState = layoutState.transitionState
+ return transitionState is TransitionState.Transition &&
+ !(layoutState.isTransitioning(to = Scenes.Shade) ||
+ layoutState.isTransitioning(to = Scenes.QuickSettings)) &&
+ transitionState.progress >= 0.9f // almost done closing
+}
+
@Composable
private fun QuickSettingsContent(
qsSceneAdapter: QSSceneAdapter,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 3ec057b..491221f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -361,6 +361,7 @@
carouselController = mediaCarouselController,
modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.Media),
usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,
+ isInSplitShade = false,
)
NotificationScrollingStack(
@@ -565,6 +566,7 @@
Modifier.zIndex(1f)
},
carouselController = mediaCarouselController,
+ isInSplitShade = true,
)
}
FooterActionsWithAnimatedVisibility(
@@ -619,6 +621,7 @@
mediaOffsetProvider: ShadeMediaOffsetProvider,
modifier: Modifier = Modifier,
usingCollapsedLandscapeMedia: Boolean = false,
+ isInSplitShade: Boolean,
) {
MediaCarousel(
modifier = modifier.fillMaxWidth(),
@@ -632,5 +635,6 @@
{ mediaOffsetProvider.offset }
},
usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,
+ isInSplitShade = isInSplitShade,
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index 9d3f25e..3bd59df 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -21,6 +21,7 @@
import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import com.android.compose.animation.scene.ContentKey
@@ -249,18 +250,29 @@
private var fromOverscrollSpec: OverscrollSpecImpl? = null
private var toOverscrollSpec: OverscrollSpecImpl? = null
- /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */
- internal val currentOverscrollSpec: OverscrollSpecImpl?
- get() {
- if (this !is HasOverscrollProperties) return null
- val progress = progress
- val bouncingContent = bouncingContent
- return when {
- progress < 0f || bouncingContent == fromContent -> fromOverscrollSpec
- progress > 1f || bouncingContent == toContent -> toOverscrollSpec
- else -> null
+ /**
+ * The current [OverscrollSpecImpl], if this transition is currently overscrolling.
+ *
+ * Note: This is backed by a State<OverscrollSpecImpl?> because the overscroll spec is
+ * derived from progress, and we don't want readers of currentOverscrollSpec to recompose
+ * every time progress is changed.
+ */
+ private val _currentOverscrollSpec: State<OverscrollSpecImpl?>? =
+ if (this !is HasOverscrollProperties) {
+ null
+ } else {
+ derivedStateOf {
+ val progress = progress
+ val bouncingContent = bouncingContent
+ when {
+ progress < 0f || bouncingContent == fromContent -> fromOverscrollSpec
+ progress > 1f || bouncingContent == toContent -> toOverscrollSpec
+ else -> null
+ }
}
}
+ internal val currentOverscrollSpec: OverscrollSpecImpl?
+ get() = _currentOverscrollSpec?.value
/**
* An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index a2c2729..39d4699 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -46,6 +46,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.approachLayout
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertIsDisplayed
@@ -70,11 +71,13 @@
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.setContentAndCreateMainScope
import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.junit.Assert.assertThrows
@@ -2581,4 +2584,61 @@
}
}
}
+
+ @Test
+ fun staticSharedElementShouldNotRemeasureOrReplaceDuringOverscrollableTransition() {
+ val size = 30.dp
+ var numberOfMeasurements = 0
+ var numberOfPlacements = 0
+
+ // Foo is a simple element that does not move or resize during the transition.
+ @Composable
+ fun SceneScope.Foo(modifier: Modifier = Modifier) {
+ Box(
+ modifier
+ .element(TestElements.Foo)
+ .layout { measurable, constraints ->
+ numberOfMeasurements++
+ measurable.measure(constraints).run {
+ numberOfPlacements++
+ layout(width, height) { place(0, 0) }
+ }
+ }
+ .size(size)
+ )
+ }
+
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) { Foo() } }
+ }
+ }
+
+ // Start an overscrollable transition driven by progress.
+ var progress by mutableFloatStateOf(0f)
+ val transition = transition(from = SceneA, to = SceneB, progress = { progress })
+ assertThat(transition).isInstanceOf(TransitionState.HasOverscrollProperties::class.java)
+ scope.launch { state.startTransition(transition) }
+
+ // Reset the counters after the first animation frame.
+ rule.waitForIdle()
+ numberOfMeasurements = 0
+ numberOfPlacements = 0
+
+ // Change the progress a bunch of times.
+ val nFrames = 20
+ repeat(nFrames) { i ->
+ progress = i / nFrames.toFloat()
+ rule.waitForIdle()
+
+ // We shouldn't have remeasured or replaced Foo.
+ assertWithMessage("Frame $i didn't remeasure Foo")
+ .that(numberOfMeasurements)
+ .isEqualTo(0)
+ assertWithMessage("Frame $i didn't replace Foo").that(numberOfPlacements).isEqualTo(0)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
index a676c7d..0983105 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
@@ -31,12 +31,11 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.core.FakeLogBuffer
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,7 +77,7 @@
displayId,
displayManager,
FakeLogBuffer.Factory.create(),
- mock<TableLogBuffer>(),
+ logcatTableLogBuffer(kosmos, "screenBrightness"),
kosmos.applicationCoroutineScope,
kosmos.testDispatcher,
)
@@ -163,7 +162,7 @@
changeBrightnessInfoAndNotify(
BrightnessInfo(0.5f, 0.1f, 0.7f),
- listenerCaptor.value
+ listenerCaptor.value,
)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
index b6616bf..18e7a7e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
@@ -27,9 +27,8 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -49,7 +48,7 @@
ScreenBrightnessInteractor(
screenBrightnessRepository,
applicationCoroutineScope,
- mock<TableLogBuffer>()
+ logcatTableLogBuffer(this, "screenBrightness"),
)
}
@@ -112,7 +111,7 @@
BrightnessUtils.convertGammaToLinearFloat(
gammaBrightness,
min.floatValue,
- max.floatValue
+ max.floatValue,
)
assertThat(temporaryBrightness!!.floatValue)
.isWithin(1e-5f)
@@ -136,7 +135,7 @@
BrightnessUtils.convertGammaToLinearFloat(
gammaBrightness,
min.floatValue,
- max.floatValue
+ max.floatValue,
)
assertThat(brightness!!.floatValue).isWithin(1e-5f).of(expectedBrightness)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index dd5ad17..2b0928f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -21,7 +21,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.testKosmos
@@ -44,7 +44,6 @@
class CommunalMediaRepositoryImplTest : SysuiTestCase() {
private val mediaDataManager = mock<MediaDataManager>()
private val mediaData = mock<MediaData>()
- private val tableLogBuffer = mock<TableLogBuffer>()
private lateinit var underTest: CommunalMediaRepositoryImpl
@@ -52,14 +51,11 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val tableLogBuffer = logcatTableLogBuffer(kosmos, "CommunalMediaRepositoryImplTest")
@Before
fun setUp() {
- underTest =
- CommunalMediaRepositoryImpl(
- mediaDataManager,
- tableLogBuffer,
- )
+ underTest = CommunalMediaRepositoryImpl(mediaDataManager, tableLogBuffer)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index 13f30f5..945e44a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -46,6 +46,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.isNull
@ExperimentalCoroutinesApi
@SmallTest
@@ -96,7 +97,7 @@
.sendVolumeKeyEvent(
eq(actionDownVolumeDownKeyEvent),
eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
- eq(true)
+ eq(true),
)
assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isTrue()
@@ -104,7 +105,7 @@
.sendVolumeKeyEvent(
eq(actionDownVolumeUpKeyEvent),
eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
- eq(true)
+ eq(true),
)
}
@@ -117,7 +118,7 @@
.sendVolumeKeyEvent(
eq(actionDownVolumeDownKeyEvent),
eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
- eq(true)
+ eq(true),
)
assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isFalse()
@@ -125,7 +126,7 @@
.sendVolumeKeyEvent(
eq(actionDownVolumeUpKeyEvent),
eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
- eq(true)
+ eq(true),
)
}
@@ -135,7 +136,9 @@
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
- verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU)
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(statusBarKeyguardViewManager).dismissWithAction(any(), isNull(), eq(false))
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 12039c1..e079619 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -118,11 +118,11 @@
LOCKSCREEN,
0f,
STARTED,
- ownerName = "KeyguardTransitionRepository(boot)"
+ ownerName = "KeyguardTransitionRepository(boot)",
),
steps[0],
steps[3],
- steps[6]
+ steps[6],
)
)
}
@@ -253,51 +253,20 @@
true, // The repo is seeded with a transition from OFF to LOCKSCREEN.
false,
),
- inTransition
+ inTransition,
)
- sendSteps(
- TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
- )
+ sendSteps(TransitionStep(LOCKSCREEN, GONE, 0f, STARTED))
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- ),
- inTransition
- )
+ assertEquals(listOf(false, true, false, true), inTransition)
- sendSteps(
- TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
- )
+ sendSteps(TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING))
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- ),
- inTransition
- )
+ assertEquals(listOf(false, true, false, true), inTransition)
- sendSteps(
- TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
- )
+ sendSteps(TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED))
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- false,
- ),
- inTransition
- )
+ assertEquals(listOf(false, true, false, true, false), inTransition)
}
@Test
@@ -312,33 +281,16 @@
true, // The repo is seeded with a transition from OFF to LOCKSCREEN.
false,
),
- inTransition
+ inTransition,
)
kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Bouncer))
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- ),
- inTransition
- )
+ assertEquals(listOf(false, true, false, true), inTransition)
kosmos.setSceneTransition(Idle(Scenes.Bouncer))
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- false,
- ),
- inTransition
- )
+ assertEquals(listOf(false, true, false, true, false), inTransition)
}
@Test
@@ -346,14 +298,7 @@
testScope.runTest {
val inTransition by collectValues(underTest.isInTransition)
- assertEquals(
- listOf(
- false,
- true,
- false,
- ),
- inTransition
- )
+ assertEquals(listOf(false, true, false), inTransition)
// Start FINISHED in GONE.
sendSteps(
@@ -362,32 +307,11 @@
TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
)
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- false,
- ),
- inTransition
- )
+ assertEquals(listOf(false, true, false, true, false), inTransition)
- sendSteps(
- TransitionStep(GONE, DOZING, 0f, STARTED),
- )
+ sendSteps(TransitionStep(GONE, DOZING, 0f, STARTED))
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- false,
- true,
- ),
- inTransition
- )
+ assertEquals(listOf(false, true, false, true, false, true), inTransition)
sendSteps(
TransitionStep(GONE, DOZING, 0.5f, RUNNING),
@@ -410,7 +334,7 @@
// transitioning to GONE, the state we're also FINISHED in.
true,
),
- inTransition
+ inTransition,
)
sendSteps(
@@ -418,18 +342,7 @@
TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
)
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- false,
- true,
- false,
- ),
- inTransition
- )
+ assertEquals(listOf(false, true, false, true, false, true, false), inTransition)
}
@Test
@@ -440,7 +353,7 @@
collectValues(
underTest.isInTransition(
edge = Edge.create(OFF, OFF),
- edgeWithoutSceneContainer = Edge.create(to = LOCKSCREEN)
+ edgeWithoutSceneContainer = Edge.create(to = LOCKSCREEN),
)
)
@@ -450,49 +363,19 @@
TransitionStep(AOD, DOZING, 1f, FINISHED),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
- sendSteps(
- TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
- )
+ sendSteps(TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
- sendSteps(
- TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING),
- )
+ sendSteps(TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
- sendSteps(
- TransitionStep(DOZING, LOCKSCREEN, 0f, FINISHED),
- )
+ sendSteps(TransitionStep(DOZING, LOCKSCREEN, 0f, FINISHED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(
TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED),
@@ -500,29 +383,14 @@
TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(
TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false, true))
}
@Test
@@ -535,33 +403,15 @@
kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen))
kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
kosmos.setSceneTransition(Transition(from = Scenes.Lockscreen, to = Scenes.Shade))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
kosmos.setSceneTransition(Idle(Scenes.Shade))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
}
@Test
@@ -575,14 +425,7 @@
kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
kosmos.setSceneTransition(Transition(from = Scenes.Lockscreen, to = Scenes.Gone))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
}
@Test
@@ -602,14 +445,7 @@
kosmos.setSceneTransition(Idle(Scenes.Gone))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
}
@Test
@@ -623,49 +459,19 @@
TransitionStep(AOD, DOZING, 1f, FINISHED),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
- sendSteps(
- TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
- )
+ sendSteps(TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
- sendSteps(
- TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING),
- )
+ sendSteps(TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
- sendSteps(
- TransitionStep(DOZING, LOCKSCREEN, 0f, FINISHED),
- )
+ sendSteps(TransitionStep(DOZING, LOCKSCREEN, 0f, FINISHED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(
TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED),
@@ -673,29 +479,14 @@
TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(
TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false, true))
}
@Test
@@ -715,49 +506,19 @@
TransitionStep(AOD, DOZING, 1f, FINISHED),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
- sendSteps(
- TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
- sendSteps(
- TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
- sendSteps(
- TransitionStep(DOZING, GONE, 0f, FINISHED),
- )
+ sendSteps(TransitionStep(DOZING, GONE, 0f, FINISHED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
@@ -765,29 +526,14 @@
TransitionStep(GONE, DOZING, 1f, FINISHED),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false, true))
}
@Test
@@ -807,48 +553,19 @@
TransitionStep(AOD, DOZING, 1f, FINISHED),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
- sendSteps(
- TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
- sendSteps(
- TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
- sendSteps(
- TransitionStep(DOZING, GONE, 0f, CANCELED),
- )
+ sendSteps(TransitionStep(DOZING, GONE, 0f, CANCELED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
@@ -856,29 +573,14 @@
TransitionStep(GONE, DOZING, 1f, FINISHED),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false, true))
}
@Test
@@ -895,87 +597,43 @@
assertThat(results)
.isEqualTo(
listOf(
- false, // Finished in DOZING, not GONE.
+ false // Finished in DOZING, not GONE.
)
)
sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false, true))
}
@Test
@@ -993,87 +651,43 @@
assertThat(results)
.isEqualTo(
listOf(
- false, // Finished in DOZING, not GONE.
+ false // Finished in DOZING, not GONE.
)
)
sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
)
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false, true))
}
@Test
@@ -1091,72 +705,33 @@
assertThat(results)
.isEqualTo(
listOf(
- false, // Finished in DOZING, not GONE.
+ false // Finished in DOZING, not GONE.
)
)
kosmos.setSceneTransition(Transition(from = Scenes.Lockscreen, to = Scenes.Gone))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false))
kosmos.setSceneTransition(Idle(Scenes.Gone))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true))
kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
kosmos.setSceneTransition(Transition(from = Scenes.Lockscreen, to = Scenes.Gone))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false))
kosmos.setSceneTransition(Idle(Scenes.Gone))
- assertThat(results)
- .isEqualTo(
- listOf(
- false,
- true,
- false,
- true,
- )
- )
+ assertThat(results).isEqualTo(listOf(false, true, false, true))
}
@Test
@@ -1165,68 +740,29 @@
val currentStates by collectValues(underTest.currentKeyguardState)
// We init the repo with a transition from OFF -> LOCKSCREEN.
- assertEquals(
- listOf(
- OFF,
- LOCKSCREEN,
- ),
- currentStates
- )
+ assertEquals(listOf(OFF, LOCKSCREEN), currentStates)
- sendSteps(
- TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
- )
+ sendSteps(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
// The current state should continue to be LOCKSCREEN as we transition to AOD.
- assertEquals(
- listOf(
- OFF,
- LOCKSCREEN,
- ),
- currentStates
- )
+ assertEquals(listOf(OFF, LOCKSCREEN), currentStates)
- sendSteps(
- TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
- )
+ sendSteps(TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING))
// The current state should continue to be LOCKSCREEN as we transition to AOD.
- assertEquals(
- listOf(
- OFF,
- LOCKSCREEN,
- ),
- currentStates
- )
+ assertEquals(listOf(OFF, LOCKSCREEN), currentStates)
- sendSteps(
- TransitionStep(LOCKSCREEN, AOD, 0.6f, CANCELED),
- )
+ sendSteps(TransitionStep(LOCKSCREEN, AOD, 0.6f, CANCELED))
// Once CANCELED, we're still currently in LOCKSCREEN...
- assertEquals(
- listOf(
- OFF,
- LOCKSCREEN,
- ),
- currentStates
- )
+ assertEquals(listOf(OFF, LOCKSCREEN), currentStates)
- sendSteps(
- TransitionStep(AOD, LOCKSCREEN, 0.6f, STARTED),
- )
+ sendSteps(TransitionStep(AOD, LOCKSCREEN, 0.6f, STARTED))
// ...until STARTING back to LOCKSCREEN, at which point the "current" state should be
// the
// one we're transitioning from, despite never FINISHING in that state.
- assertEquals(
- listOf(
- OFF,
- LOCKSCREEN,
- AOD,
- ),
- currentStates
- )
+ assertEquals(listOf(OFF, LOCKSCREEN, AOD), currentStates)
sendSteps(
TransitionStep(AOD, LOCKSCREEN, 0.8f, RUNNING),
@@ -1234,15 +770,7 @@
)
// FINSHING in LOCKSCREEN should update the current state to LOCKSCREEN.
- assertEquals(
- listOf(
- OFF,
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ),
- currentStates
- )
+ assertEquals(listOf(OFF, LOCKSCREEN, AOD, LOCKSCREEN), currentStates)
}
@Test
@@ -1251,13 +779,7 @@
val currentStates by collectValues(underTest.currentKeyguardState)
// We init the repo with a transition from OFF -> LOCKSCREEN.
- assertEquals(
- listOf(
- OFF,
- LOCKSCREEN,
- ),
- currentStates
- )
+ assertEquals(listOf(OFF, LOCKSCREEN), currentStates)
sendSteps(
TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
@@ -1273,7 +795,7 @@
// Transitioned to GONE
GONE,
),
- currentStates
+ currentStates,
)
sendSteps(
@@ -1290,12 +812,10 @@
// Current state should not be DOZING until the post-cancelation transition is
// STARTED
),
- currentStates
+ currentStates,
)
- sendSteps(
- TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
- )
+ sendSteps(TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED))
assertEquals(
listOf(
@@ -1305,7 +825,7 @@
// DOZING -> LS STARTED, DOZING is now the current state.
DOZING,
),
- currentStates
+ currentStates,
)
sendSteps(
@@ -1313,19 +833,9 @@
TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED),
)
- assertEquals(
- listOf(
- OFF,
- LOCKSCREEN,
- GONE,
- DOZING,
- ),
- currentStates
- )
+ assertEquals(listOf(OFF, LOCKSCREEN, GONE, DOZING), currentStates)
- sendSteps(
- TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
- )
+ sendSteps(TransitionStep(LOCKSCREEN, GONE, 0f, STARTED))
assertEquals(
listOf(
@@ -1336,7 +846,7 @@
// LS -> GONE STARTED, LS is now the current state.
LOCKSCREEN,
),
- currentStates
+ currentStates,
)
sendSteps(
@@ -1354,7 +864,7 @@
// FINISHED in GONE, GONE is now the current state.
GONE,
),
- currentStates
+ currentStates,
)
}
@@ -1504,6 +1014,126 @@
}
}
+ @Test
+ @EnableSceneContainer
+ fun simulateTransitionStepsForSceneTransitions_emits_correct_values_for_wildcard_from_edge() =
+ testScope.runTest {
+ val sceneToSceneSteps by
+ collectValues(underTest.transition(Edge.create(from = Scenes.Gone)))
+ val progress = MutableSharedFlow<Float>()
+
+ kosmos.setSceneTransition(
+ Transition(Scenes.Gone, Scenes.Lockscreen, progress = progress)
+ )
+
+ progress.emit(0.2f)
+ runCurrent()
+ progress.emit(0.6f)
+ runCurrent()
+
+ kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Bouncer, progress = progress))
+
+ progress.emit(0.1f)
+ runCurrent()
+
+ kosmos.setSceneTransition(
+ Transition(Scenes.Bouncer, Scenes.Lockscreen, progress = progress)
+ )
+
+ progress.emit(0.3f)
+ runCurrent()
+
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ assertEquals(
+ listOf(
+ TransitionStep(UNDEFINED, UNDEFINED, 0f, STARTED),
+ TransitionStep(UNDEFINED, UNDEFINED, 0.2f, RUNNING),
+ TransitionStep(UNDEFINED, UNDEFINED, 0.6f, RUNNING),
+ TransitionStep(UNDEFINED, UNDEFINED, 1f, FINISHED),
+ TransitionStep(UNDEFINED, UNDEFINED, 0f, STARTED),
+ TransitionStep(UNDEFINED, UNDEFINED, 0.1f, RUNNING),
+ TransitionStep(UNDEFINED, UNDEFINED, 1f, FINISHED),
+ ),
+ sceneToSceneSteps,
+ )
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun simulateTransitionStepsForSceneTransitions_emits_correct_values_for_wildcard_to_edge() =
+ testScope.runTest {
+ val sceneToSceneSteps by
+ collectValues(underTest.transition(Edge.create(to = Scenes.Gone)))
+ val progress = MutableSharedFlow<Float>()
+
+ kosmos.setSceneTransition(
+ Transition(Scenes.Gone, Scenes.Lockscreen, progress = progress)
+ )
+
+ progress.emit(0.2f)
+ runCurrent()
+
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Bouncer, progress = progress))
+
+ progress.emit(0.1f)
+ runCurrent()
+
+ kosmos.setSceneTransition(Transition(Scenes.Bouncer, Scenes.Gone, progress = progress))
+
+ progress.emit(0.3f)
+ runCurrent()
+
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ assertEquals(
+ listOf(
+ TransitionStep(UNDEFINED, UNDEFINED, 0f, STARTED),
+ TransitionStep(UNDEFINED, UNDEFINED, 0.3f, RUNNING),
+ TransitionStep(UNDEFINED, UNDEFINED, 1f, FINISHED),
+ ),
+ sceneToSceneSteps,
+ )
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun flatMapLatestWithFinished_emission_of_previous_progress_flow_is_not_interleaving() =
+ testScope.runTest {
+ val sceneToSceneSteps by
+ collectValues(underTest.transition(Edge.create(from = Scenes.Gone)))
+ val progress1 = MutableSharedFlow<Float>()
+ val progress2 = MutableSharedFlow<Float>()
+
+ kosmos.setSceneTransition(
+ Transition(Scenes.Gone, Scenes.Lockscreen, progress = progress1)
+ )
+
+ progress1.emit(0.1f)
+ runCurrent()
+
+ kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Bouncer, progress = progress2))
+
+ progress2.emit(0.3f)
+ runCurrent()
+
+ progress1.emit(0.2f)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ TransitionStep(UNDEFINED, UNDEFINED, 0f, STARTED),
+ TransitionStep(UNDEFINED, UNDEFINED, 0.1f, RUNNING),
+ TransitionStep(UNDEFINED, UNDEFINED, 1f, FINISHED),
+ TransitionStep(UNDEFINED, UNDEFINED, 0f, STARTED),
+ TransitionStep(UNDEFINED, UNDEFINED, 0.3f, RUNNING),
+ ),
+ sceneToSceneSteps,
+ )
+ }
+
private suspend fun sendSteps(vararg steps: TransitionStep) {
steps.forEach {
repository.sendTransitionStep(it)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index de3dc57..1d80826 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -144,13 +145,13 @@
// Tile starts with the generic Modes icon.
runCurrent()
- assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.icon).isEqualTo(MODES_RESOURCE_ICON)
assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
// Add an inactive mode -> Still modes icon
zenModeRepository.addMode(id = "Mode", active = false)
runCurrent()
- assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.icon).isEqualTo(MODES_RESOURCE_ICON)
assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
// Add an active mode with a default icon: icon should be the mode icon, and the
@@ -158,7 +159,7 @@
zenModeRepository.addMode(
id = "Bedtime with default icon",
type = AutomaticZenRule.TYPE_BEDTIME,
- active = true
+ active = true,
)
runCurrent()
assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
@@ -189,7 +190,7 @@
// Deactivate remaining mode: back to the default modes icon
zenModeRepository.deactivateMode("Driving with custom icon")
runCurrent()
- assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.icon).isEqualTo(MODES_RESOURCE_ICON)
assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
}
@@ -204,18 +205,18 @@
)
runCurrent()
- assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.icon).isEqualTo(MODES_RESOURCE_ICON)
assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
// Activate a Mode -> Icon doesn't change.
zenModeRepository.addMode(id = "Mode", active = true)
runCurrent()
- assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.icon).isEqualTo(MODES_RESOURCE_ICON)
assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
zenModeRepository.deactivateMode(id = "Mode")
runCurrent()
- assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+ assertThat(tileData?.icon).isEqualTo(MODES_RESOURCE_ICON)
assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
}
@@ -263,7 +264,7 @@
val BEDTIME_DRAWABLE = TestStubDrawable("bedtime")
val CUSTOM_DRAWABLE = TestStubDrawable("custom")
- val MODES_ICON = MODES_DRAWABLE.asIcon()
+ val MODES_RESOURCE_ICON = Icon.Resource(MODES_DRAWABLE_ID, null)
val BEDTIME_ICON = BEDTIME_DRAWABLE.asIcon()
val CUSTOM_ICON = CUSTOM_DRAWABLE.asIcon()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index c3d45db..a58cb9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -22,7 +22,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.qs.tiles.ModesTile
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
import com.android.systemui.qs.tiles.viewmodel.QSTileState
@@ -51,6 +53,11 @@
.apply {
addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable())
addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable())
+ addOverride(
+ ModesTile.ICON_RES_ID,
+ TestStubDrawable(ModesTile.ICON_RES_ID.toString()),
+ )
+ addOverride(123, TestStubDrawable("123"))
}
.resources,
context.theme,
@@ -59,12 +66,7 @@
@Test
fun inactiveState() {
val icon = TestStubDrawable("res123").asIcon()
- val model =
- ModesTileModel(
- isActivated = false,
- activeModes = emptyList(),
- icon = icon,
- )
+ val model = ModesTileModel(isActivated = false, activeModes = emptyList(), icon = icon)
val state = underTest.map(config, model)
@@ -76,12 +78,7 @@
@Test
fun activeState_oneMode() {
val icon = TestStubDrawable("res123").asIcon()
- val model =
- ModesTileModel(
- isActivated = true,
- activeModes = listOf("DND"),
- icon = icon,
- )
+ val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"), icon = icon)
val state = underTest.map(config, model)
@@ -108,19 +105,36 @@
}
@Test
- fun state_modelHasIconResId_includesIconResId() {
- val icon = TestStubDrawable("res123").asIcon()
+ fun resourceIconModel_whenResIdsIdentical_mapsToLoadedIconWithInputResId() {
+ val icon = Icon.Resource(123, null)
val model =
ModesTileModel(
isActivated = false,
activeModes = emptyList(),
icon = icon,
- iconResId = 123
+ iconResId = 123,
)
val state = underTest.map(config, model)
- assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.icon()).isEqualTo(TestStubDrawable("123").asIcon())
+ assertThat(state.iconRes).isEqualTo(123)
+ }
+
+ @Test
+ fun resourceIconModel_whenResIdsNonIdentical_mapsToLoadedIconWithIconResourceId() {
+ val icon = Icon.Resource(123, null)
+ val model =
+ ModesTileModel(
+ isActivated = false,
+ activeModes = emptyList(),
+ icon = icon,
+ iconResId = 321, // Note: NOT 123. This will be ignored.
+ )
+
+ val state = underTest.map(config, model)
+
+ assertThat(state.icon()).isEqualTo(TestStubDrawable("123").asIcon())
assertThat(state.iconRes).isEqualTo(123)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationListenerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationListenerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLoggerFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/DisplaySwitchNotificationsHiderTrackerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconListTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconListTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconListTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconListTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/FakeDeviceBasedSatelliteViewModel.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt
index 5036e77..46f822a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt
@@ -21,6 +21,7 @@
import android.app.StatusBarManager.DISABLE_NONE
import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -31,8 +32,10 @@
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith;
@SmallTest
+@RunWith(AndroidJUnit4::class)
class CollapsedStatusBarInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 2588f1f..e3bd885 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -61,7 +61,7 @@
import java.util.List;
import java.util.concurrent.Executor;
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@RunWithLooper
@SmallTest
public class BluetoothControllerImplTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ClockTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ClockTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 9d93a9c..39836e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -19,6 +19,7 @@
package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
import android.content.Intent
+import android.content.applicationContext
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -38,9 +39,11 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
+import org.mockito.MockitoAnnotations
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@@ -55,14 +58,20 @@
private val mockDialogDelegate = kosmos.mockModesDialogDelegate
private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger
- private val underTest =
- ModesDialogViewModel(
- context,
- interactor,
- kosmos.testDispatcher,
- mockDialogDelegate,
- mockDialogEventLogger,
- )
+ private lateinit var underTest: ModesDialogViewModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ ModesDialogViewModel(
+ kosmos.applicationContext,
+ interactor,
+ kosmos.testDispatcher,
+ kosmos.mockModesDialogDelegate,
+ kosmos.mockModesDialogEventLogger,
+ )
+ }
@Test
fun tiles_filtersOutUserDisabledModes() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepositoryTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepositoryTest.kt
index 0c27e58..c7c7fdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStatePerDisplayRepositoryTest.kt
@@ -21,6 +21,7 @@
import android.app.StatusBarManager.WINDOW_STATE_HIDING
import android.app.StatusBarManager.WINDOW_STATE_SHOWING
import android.app.StatusBarManager.WINDOW_STATUS_BAR
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -34,11 +35,13 @@
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.kotlin.argumentCaptor
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
class StatusBarWindowStatePerDisplayRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreTest.kt
index b6a3f36..e23e88c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreTest.kt
@@ -19,6 +19,7 @@
import android.app.StatusBarManager.WINDOW_STATE_HIDDEN
import android.app.StatusBarManager.WINDOW_STATE_SHOWING
import android.app.StatusBarManager.WINDOW_STATUS_BAR
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -33,12 +34,14 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.reset
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
class StatusBarWindowStateRepositoryStoreTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
index 7ce421a..06a3e8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
@@ -27,7 +27,7 @@
import com.android.systemui.plugins.fakeVolumeDialogController
import com.android.systemui.testKosmos
import com.android.systemui.volume.Events
-import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.seconds
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
index fa7f37c..449dc20 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
@@ -16,11 +16,16 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+import android.content.mockedContext
+import android.content.packageManager
+import android.content.pm.PackageManager.FEATURE_PC
import android.graphics.drawable.TestStubDrawable
import android.media.AudioManager
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.media.flags.Flags;
import com.android.settingslib.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -42,6 +47,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.whenever
private const val builtInDeviceName = "This phone"
@@ -79,6 +85,8 @@
fun inCall_stateIs_Calling() =
with(kosmos) {
testScope.runTest {
+ whenever(mockedContext.getPackageManager()).thenReturn(packageManager)
+ whenever(packageManager.hasSystemFeature(FEATURE_PC)).thenReturn(false)
with(audioRepository) {
setMode(AudioManager.MODE_IN_CALL)
setCommunicationDevice(TestAudioDevicesFactory.builtInDevice())
@@ -98,6 +106,33 @@
}
}
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ fun inCall_stateIs_Calling_enableInputRouting_desktop() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(mockedContext.getPackageManager()).thenReturn(packageManager)
+ whenever(packageManager.hasSystemFeature(FEATURE_PC)).thenReturn(true)
+
+ with(audioRepository) {
+ setMode(AudioManager.MODE_IN_CALL)
+ setCommunicationDevice(TestAudioDevicesFactory.builtInDevice())
+ }
+
+ val model by collectLastValue(underTest.mediaOutputModel.filterData())
+ runCurrent()
+
+ assertThat(model)
+ .isEqualTo(
+ MediaOutputComponentModel.Calling(
+ device = AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon),
+ isInAudioSharing = false,
+ canOpenAudioSwitcher = true,
+ )
+ )
+ }
+ }
+
@Test
fun hasSession_stateIs_MediaSession() =
with(kosmos) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 275147e..41b9d33 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -226,7 +226,11 @@
mBtnTargets =
mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
mHandler.post(
- () -> handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets));
+ () -> {
+ // Force a refresh by destroying the menu if it exists.
+ destroyFloatingMenu();
+ handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
+ });
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 13b4aa9..6228ac5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -32,9 +32,9 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.PlatformTheme
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags.communalEditWidgetsActivityFinishFix
@@ -227,7 +227,7 @@
Box(
modifier =
Modifier.fillMaxSize()
- .background(LocalAndroidColorScheme.current.surfaceDim),
+ .background(MaterialTheme.colorScheme.surfaceDim),
) {
CommunalHub(
viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index 462e820..b56ed8c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -16,16 +16,25 @@
package com.android.systemui.display
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayRepositoryImpl
+import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.display.data.repository.DisplayScopeRepositoryImpl
import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import dagger.Binds
+import dagger.Lazy
import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
/** Module binding display related classes. */
@Module
@@ -46,4 +55,22 @@
fun bindsFocusedDisplayRepository(
focusedDisplayRepository: FocusedDisplayRepositoryImpl
): FocusedDisplayRepository
+
+ @Binds fun displayScopeRepository(impl: DisplayScopeRepositoryImpl): DisplayScopeRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(DisplayScopeRepositoryImpl::class)
+ fun displayScopeRepoCoreStartable(
+ repoImplLazy: Lazy<DisplayScopeRepositoryImpl>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ repoImplLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index e68aba5..6a69136 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -61,8 +61,11 @@
/** Display addition event indicating a new display has been added. */
val displayAdditionEvent: Flow<Display?>
+ /** Display removal event indicating a display has been removed. */
+ val displayRemovalEvent: Flow<Int>
+
/** Provides the current set of displays. */
- val displays: Flow<Set<Display>>
+ val displays: StateFlow<Set<Display>>
/**
* Pending display id that can be enabled/disabled.
@@ -79,8 +82,8 @@
*
* This method is guaranteed to not result in any binder call.
*/
- suspend fun getDisplay(displayId: Int): Display? =
- displays.first().firstOrNull { it.displayId == displayId }
+ fun getDisplay(displayId: Int): Display? =
+ displays.value.firstOrNull { it.displayId == displayId }
/** Represents a connected display that has not been enabled yet. */
interface PendingDisplay {
@@ -148,6 +151,9 @@
getDisplayFromDisplayManager(it.displayId)
}
+ override val displayRemovalEvent: Flow<Int> =
+ allDisplayEvents.filterIsInstance<DisplayEvent.Removed>().map { it.displayId }
+
// This is necessary because there might be multiple displays, and we could
// have missed events for those added before this process or flow started.
// Note it causes a binder call from the main thread (it's traced).
@@ -180,7 +186,7 @@
*
* Those are commonly the ones provided by [DisplayManager.getDisplays] by default.
*/
- private val enabledDisplays: Flow<Set<Display>> =
+ private val enabledDisplays: StateFlow<Set<Display>> =
enabledDisplayIds
.mapElementsLazily { displayId -> getDisplayFromDisplayManager(displayId) }
.onEach {
@@ -204,7 +210,7 @@
*
* Those are commonly the ones provided by [DisplayManager.getDisplays] by default.
*/
- override val displays: Flow<Set<Display>> = enabledDisplays
+ override val displays: StateFlow<Set<Display>> = enabledDisplays
val _ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
private val ignoredDisplayIds: Flow<Set<Int>> = _ignoredDisplayIds.debugLog("ignoredDisplayIds")
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt
new file mode 100644
index 0000000..3062475
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 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.display.data.repository
+
+import android.view.Display
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+
+/**
+ * Provides per display instances of [CoroutineScope]. These will remain active as long as the
+ * display is connected, and automatically cancelled when the display is removed.
+ */
+interface DisplayScopeRepository {
+ fun scopeForDisplay(displayId: Int): CoroutineScope
+}
+
+@SysUISingleton
+class DisplayScopeRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val displayRepository: DisplayRepository,
+) : DisplayScopeRepository, CoreStartable {
+
+ private val perDisplayScopes = ConcurrentHashMap<Int, CoroutineScope>()
+
+ override fun scopeForDisplay(displayId: Int): CoroutineScope {
+ return perDisplayScopes.computeIfAbsent(displayId) { createScopeForDisplay(displayId) }
+ }
+
+ override fun start() {
+ StatusBarConnectedDisplays.assertInNewMode()
+ backgroundApplicationScope.launch {
+ displayRepository.displayRemovalEvent.collect { displayId ->
+ val scope = perDisplayScopes.remove(displayId)
+ scope?.cancel("Display $displayId has been removed.")
+ }
+ }
+ }
+
+ private fun createScopeForDisplay(displayId: Int): CoroutineScope {
+ return if (displayId == Display.DEFAULT_DISPLAY) {
+ // The default display is connected all the time, therefore we can optimise by reusing
+ // the application scope, and don't need to create a new scope.
+ backgroundApplicationScope
+ } else {
+ CoroutineScope(
+ backgroundDispatcher + createCoroutineTracingContext("DisplayScope$displayId")
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 5e05dab..b2acc2a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -24,6 +24,7 @@
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
@@ -80,25 +81,18 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.geometry.CornerRadius
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
-import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
@@ -113,9 +107,9 @@
import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
-import androidx.compose.ui.zIndex
+import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
-import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
@@ -405,7 +399,7 @@
if (index > 0) {
HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHigh)
}
- ShortcutView(Modifier.padding(vertical = 24.dp), searchQuery, shortcut)
+ Shortcut(Modifier.padding(vertical = 24.dp), searchQuery, shortcut)
}
}
@@ -444,9 +438,9 @@
NoSearchResultsText(horizontalPadding = 24.dp, fillHeight = false)
return
}
- LazyColumn(modifier.nestedScroll(rememberNestedScrollInteropConnection())) {
- items(items = category.subCategories, key = { item -> item.label }) {
- SubCategoryContainerDualPane(searchQuery, it)
+ LazyColumn(modifier = modifier) {
+ items(category.subCategories) { subcategory ->
+ SubCategoryContainerDualPane(searchQuery = searchQuery, subCategory = subcategory)
Spacer(modifier = Modifier.height(8.dp))
}
}
@@ -477,14 +471,21 @@
shape = RoundedCornerShape(28.dp),
color = MaterialTheme.colorScheme.surfaceBright,
) {
- Column(Modifier.padding(24.dp)) {
+ Column(Modifier.padding(16.dp)) {
SubCategoryTitle(subCategory.label)
Spacer(Modifier.height(8.dp))
subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
if (index > 0) {
- HorizontalDivider(color = MaterialTheme.colorScheme.surfaceContainerHigh)
+ HorizontalDivider(
+ modifier = Modifier.padding(horizontal = 8.dp),
+ color = MaterialTheme.colorScheme.surfaceContainerHigh,
+ )
}
- ShortcutView(Modifier.padding(vertical = 16.dp), searchQuery, shortcut)
+ Shortcut(
+ modifier = Modifier.padding(vertical = 8.dp),
+ searchQuery = searchQuery,
+ shortcut = shortcut,
+ )
}
}
}
@@ -500,18 +501,17 @@
}
@Composable
-private fun ShortcutView(modifier: Modifier, searchQuery: String, shortcut: Shortcut) {
+private fun Shortcut(modifier: Modifier, searchQuery: String, shortcut: ShortcutModel) {
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
+ val focusColor = MaterialTheme.colorScheme.secondary
Row(
modifier
+ .thenIf(isFocused) {
+ Modifier.border(width = 3.dp, color = focusColor, shape = RoundedCornerShape(16.dp))
+ }
.focusable(interactionSource = interactionSource)
- .outlineFocusModifier(
- isFocused = isFocused,
- focusColor = MaterialTheme.colorScheme.secondary,
- padding = 8.dp,
- cornerRadius = 16.dp,
- )
+ .padding(8.dp)
) {
Row(
modifier = Modifier.width(128.dp).align(Alignment.CenterVertically),
@@ -523,7 +523,7 @@
}
ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
}
- Spacer(modifier = Modifier.width(16.dp))
+ Spacer(modifier = Modifier.width(24.dp))
ShortcutKeyCombinations(modifier = Modifier.weight(1f), shortcut = shortcut)
}
}
@@ -548,7 +548,7 @@
@OptIn(ExperimentalLayoutApi::class)
@Composable
-private fun ShortcutKeyCombinations(modifier: Modifier = Modifier, shortcut: Shortcut) {
+private fun ShortcutKeyCombinations(modifier: Modifier = Modifier, shortcut: ShortcutModel) {
FlowRow(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
@@ -628,7 +628,7 @@
@Composable
private fun ShortcutDescriptionText(
searchQuery: String,
- shortcut: Shortcut,
+ shortcut: ShortcutModel,
modifier: Modifier = Modifier,
) {
Text(
@@ -751,39 +751,6 @@
}
}
-private fun Modifier.outlineFocusModifier(
- isFocused: Boolean,
- focusColor: Color,
- padding: Dp,
- cornerRadius: Dp,
-): Modifier {
- if (isFocused) {
- return this.drawWithContent {
- val focusOutline =
- Rect(Offset.Zero, size).let {
- if (padding > 0.dp) {
- it.inflate(padding.toPx())
- } else {
- it.deflate(padding.unaryMinus().toPx())
- }
- }
- drawContent()
- drawRoundRect(
- color = focusColor,
- style = Stroke(width = 3.dp.toPx()),
- topLeft = focusOutline.topLeft,
- size = focusOutline.size,
- cornerRadius = CornerRadius(cornerRadius.toPx()),
- )
- }
- // Increasing Z-Index so focus outline is drawn on top of "selected" category
- // background.
- .zIndex(1f)
- } else {
- return this
- }
-}
-
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun TitleBar() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2052459..d28b08f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2460,6 +2460,12 @@
android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)");
return;
}
+
+ if (mKeyguardStateController.isKeyguardGoingAway()) {
+ Log.i(TAG, "Ignoring dismiss because we're already going away.");
+ return;
+ }
+
mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget();
}
@@ -3428,6 +3434,12 @@
return;
}
+ if (mIsKeyguardExitAnimationCanceled) {
+ Log.d(TAG, "Ignoring exitKeyguardAndFinishSurfaceBehindRemoteAnimation. "
+ + "mIsKeyguardExitAnimationCanceled==true");
+ return;
+ }
+
// Block the panel from expanding, in case we were doing a swipe to dismiss gesture.
mKeyguardViewControllerLazy.get().blockPanelExpansionFromCurrentTouch();
final boolean wasShowing = mShowing;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index 65b42e6..fcf486b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -23,6 +23,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler.Companion.handleAction
import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.ShadeController
@@ -105,7 +106,15 @@
(statusBarStateController.state != StatusBarState.SHADE) &&
statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
if (shouldUnlockOnMenuPressed) {
- shadeController.animateCollapseShadeForced()
+ statusBarKeyguardViewManager.dismissWithAction(
+ object : OnDismissAction {
+ override fun onDismiss(): Boolean {
+ return false
+ }
+ },
+ null,
+ false,
+ )
return true
}
return false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index c4f231d..a0000f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint
import android.util.Log
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -37,15 +38,22 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart
@@ -206,44 +214,155 @@
)
}
- return if (SceneContainerFlag.isEnabled) {
- flow.filter { step ->
- val fromScene =
- when (edge) {
- is Edge.StateToState -> edge.from?.mapToSceneContainerScene()
- is Edge.StateToScene -> edge.from?.mapToSceneContainerScene()
- is Edge.SceneToState -> edge.from
+ if (!SceneContainerFlag.isEnabled) {
+ return flow
+ }
+ if (edge.isSceneWildcardEdge()) {
+ return simulateTransitionStepsForSceneTransitions(edge)
+ }
+ return flow.filter { step ->
+ val fromScene =
+ when (edge) {
+ is Edge.StateToState -> edge.from?.mapToSceneContainerScene()
+ is Edge.StateToScene -> edge.from?.mapToSceneContainerScene()
+ is Edge.SceneToState -> edge.from
+ }
+
+ val toScene =
+ when (edge) {
+ is Edge.StateToState -> edge.to?.mapToSceneContainerScene()
+ is Edge.StateToScene -> edge.to
+ is Edge.SceneToState -> edge.to?.mapToSceneContainerScene()
+ }
+
+ val isTransitioningBetweenLockscreenStates =
+ fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()
+ val isTransitioningBetweenDesiredScenes =
+ sceneInteractor.transitionState.value.isTransitioning(fromScene, toScene)
+
+ // We can't compare the terminal step with the current sceneTransition because
+ // a) STL has no guarantee that it will settle in Idle() when finished/canceled
+ // b) Comparing to Idle(toScene) would make any other FINISHED step settling in
+ // toScene pass as well
+ val terminalStepBelongsToPreviousTransition =
+ (step.transitionState == TransitionState.FINISHED ||
+ step.transitionState == TransitionState.CANCELED) &&
+ sceneTransitionPair.value.previousValue.isTransitioning(fromScene, toScene)
+
+ return@filter isTransitioningBetweenLockscreenStates ||
+ isTransitioningBetweenDesiredScenes ||
+ terminalStepBelongsToPreviousTransition
+ }
+ }
+
+ private fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null
+
+ /**
+ * This function will return a flow that simulates TransitionSteps based on STL movements
+ * filtered by [edge].
+ *
+ * STL transitions outside of Lockscreen Transitions are not tracked in KTI. This is an issue
+ * for wildcard edges, as this means that Scenes.Bouncer -> Scenes.Gone would not appear while
+ * AOD -> Scenes.Bouncer would appear.
+ *
+ * This function will track STL transitions only when a wildcard edge is provided and emit a
+ * RUNNING step for each update to [Transition.progress]. It will also emit a STARTED and
+ * FINISHED step when the transitions starts and finishes.
+ *
+ * All TransitionSteps will have UNDEFINED as to and from state even when one of them is the
+ * Lockscreen Scene. It indicates that both are scenes but it should not be relevant to
+ * consumers of the [transition] API as usually all viewModels are just interested in the
+ * progress value. The correct filtering based on the provided [edge] is always the
+ * responsibility of KTI and therefore only proper [TransitionStep]s are emitted. The filter is
+ * applied within this function.
+ */
+ private fun simulateTransitionStepsForSceneTransitions(edge: Edge) =
+ sceneInteractor.transitionState.flatMapLatestWithFinished {
+ when (it) {
+ is ObservableTransitionState.Idle -> {
+ flowOf()
+ }
+ is ObservableTransitionState.Transition -> {
+ val isMatchingTransition =
+ when (edge) {
+ is Edge.StateToState ->
+ throw IllegalStateException("Should not be reachable.")
+ is Edge.SceneToState -> it.isTransitioning(from = edge.from)
+ is Edge.StateToScene -> it.isTransitioning(to = edge.to)
+ }
+ if (!isMatchingTransition) {
+ return@flatMapLatestWithFinished flowOf()
}
-
- val toScene =
- when (edge) {
- is Edge.StateToState -> edge.to?.mapToSceneContainerScene()
- is Edge.StateToScene -> edge.to
- is Edge.SceneToState -> edge.to?.mapToSceneContainerScene()
+ flow {
+ emit(
+ TransitionStep(
+ from = UNDEFINED,
+ to = UNDEFINED,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ emitAll(
+ it.progress.map { progress ->
+ TransitionStep(
+ from = UNDEFINED,
+ to = UNDEFINED,
+ value = progress,
+ transitionState = TransitionState.RUNNING,
+ )
+ }
+ )
}
-
- fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null
-
- val isTransitioningBetweenLockscreenStates =
- fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()
- val isTransitioningBetweenDesiredScenes =
- sceneInteractor.transitionState.value.isTransitioning(fromScene, toScene)
-
- // We can't compare the terminal step with the current sceneTransition because
- // a) STL has no guarantee that it will settle in Idle() when finished/canceled
- // b) Comparing to Idle(toScene) would make any other FINISHED step settling in
- // toScene pass as well
- val terminalStepBelongsToPreviousTransition =
- (step.transitionState == TransitionState.FINISHED ||
- step.transitionState == TransitionState.CANCELED) &&
- sceneTransitionPair.value.previousValue.isTransitioning(fromScene, toScene)
-
- return@filter isTransitioningBetweenLockscreenStates ||
- isTransitioningBetweenDesiredScenes ||
- terminalStepBelongsToPreviousTransition
+ }
}
- } else {
- flow
+ }
+
+ /**
+ * This function is similar to flatMapLatest but it will additionally emit a FINISHED
+ * TransitionStep whenever the flattened innerFlow emitted a STARTED step and is now being
+ * replaced by a new innerFlow.
+ *
+ * This is to make sure that every STARTED step will receive a corresponding FINISHED step.
+ *
+ * We can't simply write this into a flow {} block because Transition.progress doesn't complete.
+ * We also can't emit the FINISHED step simply when an Idle state is reached because a)
+ * Transitions are not guaranteed to finish in Idle and b) There can be multiple Idle
+ * transitions after another
+ */
+ private fun <T> Flow<T>.flatMapLatestWithFinished(
+ transform: suspend (T) -> Flow<TransitionStep>
+ ): Flow<TransitionStep> = channelFlow {
+ var job: Job? = null
+ var startedEmitted = false
+
+ coroutineScope {
+ collect { value ->
+ job?.cancelAndJoin()
+
+ job = launch {
+ val innerFlow = transform(value)
+ try {
+ innerFlow.collect { step ->
+ if (step.transitionState == TransitionState.STARTED) {
+ startedEmitted = true
+ }
+ send(step)
+ }
+ } finally {
+ if (startedEmitted) {
+ send(
+ TransitionStep(
+ from = UNDEFINED,
+ to = UNDEFINED,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ startedEmitted = false
+ }
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt b/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt
index ef7e7eb..62694ce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt
@@ -22,13 +22,18 @@
/**
* Creates a [QSTile.Icon] from an [Icon].
- * * [Icon.Loaded] -> [QSTileImpl.DrawableIcon]
+ * * [Icon.Loaded] && [resId] null -> [QSTileImpl.DrawableIcon]
+ * * [Icon.Loaded] && [resId] available -> [QSTileImpl.DrawableIconWithRes]
* * [Icon.Resource] -> [QSTileImpl.ResourceIcon]
*/
-fun Icon.asQSTileIcon(): QSTile.Icon {
+fun Icon.asQSTileIcon(resId: Int?): QSTile.Icon {
return when (this) {
is Icon.Loaded -> {
- QSTileImpl.DrawableIcon(this.drawable)
+ if (resId != null) {
+ QSTileImpl.DrawableIconWithRes(this.drawable, resId)
+ } else {
+ QSTileImpl.DrawableIcon(this.drawable)
+ }
}
is Icon.Resource -> {
QSTileImpl.ResourceIcon.get(this.res)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index cf2db6c..3bbe624 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -121,7 +121,7 @@
state?.apply {
this.state = tileState.activationState.legacyState
val tileStateIcon = tileState.icon()
- icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
+ icon = tileStateIcon?.asQSTileIcon(tileState.iconRes) ?: ResourceIcon.get(ICON_RES_ID)
label = tileLabel
secondaryLabel = tileState.secondaryLabel
contentDescription = tileState.contentDescription
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 5d44ead..40591bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -76,14 +76,14 @@
} else {
return ModesTileModel(
isActivated = activeModes.isAnyActive(),
- icon = context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(),
+ icon = Icon.Resource(ModesTile.ICON_RES_ID, null),
iconResId = ModesTile.ICON_RES_ID,
activeModes = activeModes.modeNames,
)
}
}
- private data class TileIcon(val icon: Icon.Loaded, val resId: Int?)
+ private data class TileIcon(val icon: Icon, val resId: Int?)
private fun getTileIcon(activeMode: ZenModeInfo?): TileIcon {
return if (activeMode != null) {
@@ -94,7 +94,7 @@
TileIcon(activeMode.icon.drawable.asIcon(), null)
}
} else {
- TileIcon(context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), ModesTile.ICON_RES_ID)
+ TileIcon(Icon.Resource(ModesTile.ICON_RES_ID, null), ModesTile.ICON_RES_ID)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
index db48123..9c31e32 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
@@ -21,12 +21,12 @@
data class ModesTileModel(
val isActivated: Boolean,
val activeModes: List<String>,
- val icon: Icon.Loaded,
+ val icon: Icon,
/**
* Resource id corresponding to [icon]. Will only be present if it's know to correspond to a
* resource with a known id in SystemUI (such as resources from `android.R`,
* `com.android.internal.R`, or `com.android.systemui.res` itself).
*/
- val iconResId: Int? = null
+ val iconResId: Int? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 69da313..801a0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -18,7 +18,9 @@
import android.content.res.Resources
import android.icu.text.MessageFormat
+import android.util.Log
import android.widget.Button
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
@@ -30,14 +32,30 @@
class ModesTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ModesTileModel> {
+constructor(@Main private val resources: Resources, val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ModesTileModel> {
override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- iconRes = data.iconResId
- icon = { data.icon }
+ val loadedIcon: Icon.Loaded =
+ when (val dataIcon = data.icon) {
+ is Icon.Resource -> {
+ if (iconRes != dataIcon.res) {
+ Log.wtf(
+ "ModesTileMapper",
+ "Icon.Resource.res & iconResId are not identical",
+ )
+ }
+ iconRes = dataIcon.res
+ Icon.Loaded(resources.getDrawable(dataIcon.res, theme), null)
+ }
+ is Icon.Loaded -> {
+ iconRes = data.iconResId
+ dataIcon
+ }
+ }
+
+ icon = { loadedIcon }
+
activationState =
if (data.isActivated) {
QSTileState.ActivationState.ACTIVE
@@ -47,10 +65,7 @@
secondaryLabel = getModesStatus(data, resources)
contentDescription = "$label. $secondaryLabel"
supportedActions =
- setOf(
- QSTileState.UserAction.CLICK,
- QSTileState.UserAction.LONG_CLICK,
- )
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
sideViewIcon = QSTileState.SideViewIcon.Chevron
expandedAccessibilityClass = Button::class
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
index 7b56688..6d3b9aa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt
@@ -19,7 +19,6 @@
import android.net.Uri
import android.os.UserManager
import android.util.Log
-import android.view.WindowManager
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
@@ -51,10 +50,6 @@
finisher: Consumer<Uri?>,
requestCallback: TakeScreenshotService.RequestCallback,
) {
- if (screenshot.type == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
- screenshot.bitmap = imageCapture.captureDisplay(screenshot.displayId, crop = null)
- }
-
if (screenshot.bitmap == null) {
Log.e(TAG, "handleScreenshot: Screenshot bitmap was null")
notificationsControllerFactory
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
index ab614f9..acfcd13 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -255,12 +255,6 @@
Assert.isMainThread();
mCurrentRequestCallback = requestCallback;
- Rect bounds = screenshot.getOriginalScreenBounds();
- if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN
- && screenshot.getBitmap() == null) {
- bounds = getFullScreenRect();
- screenshot.setBitmap(mImageCapture.captureDisplay(mDisplay.getDisplayId(), bounds));
- }
if (screenshot.getBitmap() == null) {
Log.e(TAG, "handleScreenshot: Screenshot bitmap was null");
@@ -323,6 +317,7 @@
attachWindow();
+ Rect bounds = screenshot.getOriginalScreenBounds();
boolean showFlash;
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
if (bounds != null
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
index e3fbb60..f5c6052 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt
@@ -35,7 +35,6 @@
import android.view.Display
import android.view.ScrollCaptureResponse
import android.view.ViewRootImpl.ActivityConfigCallback
-import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import android.widget.Toast
import android.window.WindowContext
@@ -162,11 +161,6 @@
Assert.isMainThread()
screenshotHandler.resetTimeout()
- if (screenshot.type == TAKE_SCREENSHOT_FULLSCREEN && screenshot.bitmap == null) {
- val bounds = fullScreenRect
- screenshot.bitmap = imageCapture.captureDisplay(display.displayId, bounds)
- }
-
val currentBitmap = screenshot.bitmap
if (currentBitmap == null) {
Log.e(TAG, "handleScreenshot: Screenshot bitmap was null")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index a94393b..039143a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -126,7 +126,7 @@
)
}
- suspend fun replaceWithTaskSnapshot(
+ private suspend fun replaceWithTaskSnapshot(
original: ScreenshotData,
componentName: ComponentName?,
owner: UserHandle,
@@ -134,7 +134,7 @@
taskBounds: Rect?,
): ScreenshotData {
Log.i(TAG, "Capturing task snapshot: $componentName / $owner")
- val taskSnapshot = capture.captureTask(taskId)
+ val taskSnapshot = capture.captureTask(taskId) ?: error("Failed to capture task")
return original.copy(
type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
bitmap = taskSnapshot,
@@ -153,13 +153,13 @@
taskId: Int? = null,
): ScreenshotData {
Log.i(TAG, "Capturing screenshot: $componentName / $owner")
- val screenshot = captureDisplay(displayId)
+ val screenshot = captureDisplay(displayId) ?: error("Failed to capture screenshot")
return original.copy(
type = TAKE_SCREENSHOT_FULLSCREEN,
bitmap = screenshot,
userHandle = owner,
topComponent = componentName,
- originalScreenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0),
+ originalScreenBounds = Rect(0, 0, screenshot.width, screenshot.height),
taskId = taskId ?: -1,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 0ad22e0..f39af18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -68,6 +68,8 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
@@ -75,6 +77,7 @@
import com.android.systemui.util.kotlin.FlowDumperImpl
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -137,8 +140,10 @@
private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
private val primaryBouncerToLockscreenTransitionViewModel:
PrimaryBouncerToLockscreenTransitionViewModel,
- private val aodBurnInViewModel: AodBurnInViewModel,
+ aodBurnInViewModel: AodBurnInViewModel,
private val communalSceneInteractor: CommunalSceneInteractor,
+ // Lazy because it's only used in the SceneContainer + Dual Shade configuration.
+ headsUpNotificationInteractor: Lazy<HeadsUpNotificationInteractor>,
unfoldTransitionInteractor: UnfoldTransitionInteractor,
) : FlowDumperImpl(dumpManager) {
@@ -390,20 +395,36 @@
* notifications unless in splitshade.
*/
private val alphaForShadeAndQsExpansion: Flow<Float> =
- interactor.configurationBasedDimensions
- .flatMapLatest { configurationBasedDimensions ->
- combineTransform(shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion) {
- shadeExpansion,
- qsExpansion ->
- if (shadeExpansion > 0f || qsExpansion > 0f) {
- if (configurationBasedDimensions.useSplitShade) {
- emit(1f)
- } else if (qsExpansion == 1f) {
- // Ensure HUNs will be visible in QS shade (at least while unlocked)
- emit(1f)
- } else {
- // Fade as QS shade expands
- emit(1f - qsExpansion)
+ if (DualShade.isEnabled) {
+ combineTransform(
+ headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
+ shadeInteractor.shadeExpansion,
+ shadeInteractor.qsExpansion,
+ ) { isHeadsUpOrAnimatingAway, shadeExpansion, qsExpansion ->
+ if (isHeadsUpOrAnimatingAway) {
+ // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ emit(1f)
+ } else if (shadeExpansion > 0f || qsExpansion > 0f) {
+ // Fade out as QS shade expands
+ emit(1f - qsExpansion)
+ }
+ }
+ } else {
+ interactor.configurationBasedDimensions.flatMapLatest { configurationBasedDimensions
+ ->
+ combineTransform(shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion) {
+ shadeExpansion,
+ qsExpansion ->
+ if (shadeExpansion > 0f || qsExpansion > 0f) {
+ if (configurationBasedDimensions.useSplitShade) {
+ emit(1f)
+ } else if (qsExpansion == 1f) {
+ // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ emit(1f)
+ } else {
+ // Fade as QS shade expands
+ emit(1f - qsExpansion)
+ }
}
}
}
@@ -427,7 +448,7 @@
private fun alphaForTransitions(viewState: ViewStateAccessor): Flow<Float> {
return merge(
keyguardInteractor.dismissAlpha.dumpWhileCollecting("keyguardInteractor.dismissAlpha"),
- // All transition view models are mututally exclusive, and safe to merge
+ // All transition view models are mutually exclusive, and safe to merge
bouncerToGoneNotificationAlpha(viewState),
aodToGoneTransitionViewModel.notificationAlpha(viewState),
aodToLockscreenTransitionViewModel.notificationAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index bef552c..ff7c143 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -151,7 +151,11 @@
startSideContainer = mView.requireViewById(R.id.status_bar_start_side_content)
startSideContainer.setOnHoverListener(
- statusOverlayHoverListenerFactory.createDarkAwareListener(startSideContainer)
+ statusOverlayHoverListenerFactory.createDarkAwareListener(
+ startSideContainer,
+ topHoverMargin = 6,
+ bottomHoverMargin = 6,
+ )
)
startSideContainer.setOnTouchListener(iconsOnTouchListener)
}
@@ -210,7 +214,7 @@
event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL
centralSurfaces.setInteracting(
WINDOW_STATUS_BAR,
- !upOrCancel || shadeController.isExpandedVisible
+ !upOrCancel || shadeController.isExpandedVisible,
)
}
}
@@ -247,7 +251,7 @@
String.format(
"onTouchForwardedFromStatusBar: panel disabled, " +
"ignoring touch at (${event.x.toInt()},${event.y.toInt()})"
- )
+ ),
)
}
return false
@@ -266,7 +270,7 @@
if (!shadeViewController.isViewEnabled) {
shadeLogger.logMotionEvent(
event,
- "onTouchForwardedFromStatusBar: panel view disabled"
+ "onTouchForwardedFromStatusBar: panel view disabled",
)
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
index 640ec28..c40822d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
@@ -20,6 +20,7 @@
import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.PaintDrawable
+import android.util.TypedValue
import android.view.MotionEvent
import android.view.View
import android.view.View.OnHoverListener
@@ -27,10 +28,10 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
@@ -65,6 +66,26 @@
createDarkAwareListener(view, darkIconDispatcher.darkChangeFlow())
/**
+ * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay
+ * Also sets margins for hover background relative to view bounds
+ */
+ fun createDarkAwareListener(
+ view: View,
+ leftHoverMargin: Int = 0,
+ rightHoverMargin: Int = 0,
+ topHoverMargin: Int = 0,
+ bottomHoverMargin: Int = 0,
+ ) =
+ createDarkAwareListener(
+ view,
+ darkIconDispatcher.darkChangeFlow(),
+ leftHoverMargin,
+ rightHoverMargin,
+ topHoverMargin,
+ bottomHoverMargin,
+ )
+
+ /**
* Creates listener using provided [DarkChange] producer to determine light or dark color of the
* overlay
*/
@@ -76,6 +97,25 @@
darkFlow.map { toHoverTheme(view, it) },
)
+ private fun createDarkAwareListener(
+ view: View,
+ darkFlow: StateFlow<DarkChange>,
+ leftHoverMargin: Int = 0,
+ rightHoverMargin: Int = 0,
+ topHoverMargin: Int = 0,
+ bottomHoverMargin: Int = 0,
+ ) =
+ StatusOverlayHoverListener(
+ view,
+ configurationController,
+ resources,
+ darkFlow.map { toHoverTheme(view, it) },
+ leftHoverMargin,
+ rightHoverMargin,
+ topHoverMargin,
+ bottomHoverMargin,
+ )
+
private fun toHoverTheme(view: View, darkChange: DarkChange): HoverTheme {
val calculatedTint = DarkIconDispatcher.getTint(darkChange.areas, view, darkChange.tint)
// currently calculated tint is either white or some shade of black.
@@ -91,7 +131,7 @@
*/
enum class HoverTheme {
LIGHT,
- DARK
+ DARK,
}
/**
@@ -103,11 +143,19 @@
configurationController: ConfigurationController,
private val resources: Resources,
private val themeFlow: Flow<HoverTheme>,
+ private val leftHoverMargin: Int = 0,
+ private val rightHoverMargin: Int = 0,
+ private val topHoverMargin: Int = 0,
+ private val bottomHoverMargin: Int = 0,
) : OnHoverListener {
@ColorInt private var darkColor: Int = 0
@ColorInt private var lightColor: Int = 0
private var cornerRadius = 0f
+ private var leftHoverMarginInPx: Int = 0
+ private var rightHoverMarginInPx: Int = 0
+ private var topHoverMarginInPx: Int = 0
+ private var bottomHoverMarginInPx: Int = 0
private var lastTheme = HoverTheme.LIGHT
@@ -138,7 +186,12 @@
val drawable =
PaintDrawable(backgroundColor).apply {
setCornerRadius(cornerRadius)
- setBounds(0, 0, v.width, v.height)
+ setBounds(
+ /*left = */ 0 + leftHoverMarginInPx,
+ /*top = */ 0 + topHoverMarginInPx,
+ /*right = */ v.width - rightHoverMarginInPx,
+ /*bottom = */ v.height - bottomHoverMarginInPx,
+ )
}
v.overlay.add(drawable)
} else if (event.action == MotionEvent.ACTION_HOVER_EXIT) {
@@ -151,5 +204,18 @@
lightColor = resources.getColor(R.color.status_bar_icons_hover_color_light)
darkColor = resources.getColor(R.color.status_bar_icons_hover_color_dark)
cornerRadius = resources.getDimension(R.dimen.status_icons_hover_state_background_radius)
+ leftHoverMarginInPx = leftHoverMargin.dpToPx(resources)
+ rightHoverMarginInPx = rightHoverMargin.dpToPx(resources)
+ topHoverMarginInPx = topHoverMargin.dpToPx(resources)
+ bottomHoverMarginInPx = bottomHoverMargin.dpToPx(resources)
+ }
+
+ private fun Int.dpToPx(resources: Resources): Int {
+ return TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ toFloat(),
+ resources.displayMetrics,
+ )
+ .toInt()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 03ec41d..470abe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -182,7 +182,7 @@
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- TelephonyManager.RADIO_POWER_UNAVAILABLE
+ TelephonyManager.RADIO_POWER_UNAVAILABLE,
)
/**
@@ -265,9 +265,10 @@
var registered = false
try {
+ logBuffer.i { "registerForCommunicationAllowedStateChanged" }
sm.registerForCommunicationAllowedStateChanged(
bgDispatcher.asExecutor(),
- callback
+ callback,
)
registered = true
} catch (e: Exception) {
@@ -276,6 +277,7 @@
awaitClose {
if (registered) {
+ logBuffer.i { "unRegisterForCommunicationAllowedStateChanged" }
sm.unregisterForCommunicationAllowedStateChanged(callback)
}
}
@@ -321,9 +323,10 @@
var registered = false
try {
+ logBuffer.i { "registerForSupportedStateChanged" }
satelliteManager.registerForSupportedStateChanged(
bgDispatcher.asExecutor(),
- callback
+ callback,
)
registered = true
} catch (e: Exception) {
@@ -332,6 +335,7 @@
awaitClose {
if (registered) {
+ logBuffer.i { "unregisterForSupportedStateChanged" }
satelliteManager.unregisterForSupportedStateChanged(callback)
}
}
@@ -366,10 +370,7 @@
var registered = false
try {
logBuffer.i { "registerForProvisionStateChanged" }
- sm.registerForProvisionStateChanged(
- bgDispatcher.asExecutor(),
- callback,
- )
+ sm.registerForProvisionStateChanged(bgDispatcher.asExecutor(), callback)
registered = true
} catch (e: Exception) {
logBuffer.e("error registering for provisioning state callback", e)
@@ -377,6 +378,7 @@
awaitClose {
if (registered) {
+ logBuffer.i { "unregisterForProvisionStateChanged" }
sm.unregisterForProvisionStateChanged(callback)
}
}
@@ -526,17 +528,10 @@
uptime - (clock.uptimeMillis() - android.os.Process.getStartUptimeMillis())
/** A couple of convenience logging methods rather than a whole class */
- private fun LogBuffer.i(
- initializer: MessageInitializer = {},
- printer: MessagePrinter,
- ) = this.log(TAG, LogLevel.INFO, initializer, printer)
+ private fun LogBuffer.i(initializer: MessageInitializer = {}, printer: MessagePrinter) =
+ this.log(TAG, LogLevel.INFO, initializer, printer)
private fun LogBuffer.e(message: String, exception: Throwable? = null) =
- this.log(
- tag = TAG,
- level = LogLevel.ERROR,
- message = message,
- exception = exception,
- )
+ this.log(tag = TAG, level = LogLevel.ERROR, message = message, exception = exception)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 411ff8b..bfc5429 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -16,6 +16,7 @@
package com.android.systemui.touchpad.tutorial.ui.composable
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.airbnb.lottie.compose.rememberLottieDynamicProperties
@@ -57,7 +58,7 @@
@Composable
private fun rememberScreenColors(): TutorialScreenConfig.Colors {
- val onTertiary = LocalAndroidColorScheme.current.onTertiary
+ val onTertiary = MaterialTheme.colorScheme.onTertiary
val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
index 084da2c..ecb5574 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
@@ -16,26 +16,24 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
+import android.view.MotionEvent
import kotlin.math.abs
/** Monitors for touchpad back gesture, that is three fingers swiping left or right */
class BackGestureMonitor(
- override val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit
-) :
- TouchpadGestureMonitor by ThreeFingerDistanceBasedGestureMonitor(
- gestureDistanceThresholdPx = gestureDistanceThresholdPx,
- gestureStateChangedCallback = gestureStateChangedCallback,
- donePredicate =
- object : GestureDonePredicate {
- override fun wasGestureDone(
- startX: Float,
- startY: Float,
- endX: Float,
- endY: Float
- ): Boolean {
- val distance = abs(endX - startX)
- return distance >= gestureDistanceThresholdPx
- }
- }
- )
+ private val gestureDistanceThresholdPx: Int,
+ override val gestureStateChangedCallback: (GestureState) -> Unit,
+) : TouchpadGestureMonitor {
+ private val distanceTracker = DistanceTracker()
+
+ override fun processTouchpadEvent(event: MotionEvent) {
+ if (!isThreeFingerTouchpadSwipe(event)) return
+ val distanceState = distanceTracker.processEvent(event)
+ updateGestureStateBasedOnDistance(
+ gestureStateChangedCallback,
+ distanceState,
+ isFinished = { abs(it.deltaX) >= gestureDistanceThresholdPx },
+ progress = { 0f },
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
new file mode 100644
index 0000000..70d9366
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 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.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+
+/**
+ * Tracks distance change for processed MotionEvents. Useful for recognizing gestures based on
+ * distance travelled instead of specific position on the screen.
+ */
+class DistanceTracker(var startX: Float = 0f, var startY: Float = 0f) {
+ fun processEvent(event: MotionEvent): DistanceGestureState? {
+ val action = event.actionMasked
+ return when (action) {
+ MotionEvent.ACTION_DOWN -> {
+ startX = event.x
+ startY = event.y
+ Started(event.x, event.y)
+ }
+ MotionEvent.ACTION_MOVE -> Moving(event.x - startX, event.y - startY)
+ MotionEvent.ACTION_UP -> Finished(event.x - startX, event.y - startY)
+ else -> null
+ }
+ }
+}
+
+sealed interface DistanceGestureState
+
+class Started(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+
+class Moving(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+
+class Finished(val deltaX: Float, val deltaY: Float) : DistanceGestureState
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
new file mode 100644
index 0000000..c1caeb3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.touchpad.tutorial.ui.gesture
+
+/**
+ * Helper function for gesture recognizers to have common state triggering logic based on distance
+ * only.
+ */
+inline fun updateGestureStateBasedOnDistance(
+ gestureStateChangedCallback: (GestureState) -> Unit,
+ gestureState: DistanceGestureState?,
+ isFinished: (Finished) -> Boolean,
+ progress: (Moving) -> Float,
+) {
+ when (gestureState) {
+ is Finished -> {
+ if (isFinished(gestureState)) {
+ gestureStateChangedCallback(GestureState.Finished)
+ } else {
+ gestureStateChangedCallback(GestureState.NotStarted)
+ }
+ }
+ is Moving -> {
+ gestureStateChangedCallback(GestureState.InProgress(progress(gestureState)))
+ }
+ is Started -> gestureStateChangedCallback(GestureState.InProgress())
+ else -> {}
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
index a9aa5c8..fdcf9de 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
@@ -16,24 +16,23 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
+import android.view.MotionEvent
+
/** Monitors for touchpad home gesture, that is three fingers swiping up */
class HomeGestureMonitor(
- override val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit
-) :
- TouchpadGestureMonitor by ThreeFingerDistanceBasedGestureMonitor(
- gestureDistanceThresholdPx = gestureDistanceThresholdPx,
- gestureStateChangedCallback = gestureStateChangedCallback,
- donePredicate =
- object : GestureDonePredicate {
- override fun wasGestureDone(
- startX: Float,
- startY: Float,
- endX: Float,
- endY: Float
- ): Boolean {
- val distance = startY - endY
- return distance >= gestureDistanceThresholdPx
- }
- }
- )
+ private val gestureDistanceThresholdPx: Int,
+ override val gestureStateChangedCallback: (GestureState) -> Unit,
+) : TouchpadGestureMonitor {
+ private val distanceTracker = DistanceTracker()
+
+ override fun processTouchpadEvent(event: MotionEvent) {
+ if (!isThreeFingerTouchpadSwipe(event)) return
+ val distanceState = distanceTracker.processEvent(event)
+ updateGestureStateBasedOnDistance(
+ gestureStateChangedCallback,
+ distanceState,
+ isFinished = { -it.deltaY >= gestureDistanceThresholdPx },
+ progress = { 0f },
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
index ca3880a..dd31ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
@@ -26,7 +26,7 @@
* is based on [com.android.quickstep.util.TriggerSwipeUpTouchTracker]
*/
class RecentAppsGestureMonitor(
- override val gestureDistanceThresholdPx: Int,
+ private val gestureDistanceThresholdPx: Int,
override val gestureStateChangedCallback: (GestureState) -> Unit,
private val velocityThresholdPxPerMs: Float,
private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false),
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerDistanceBasedGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerDistanceBasedGestureMonitor.kt
deleted file mode 100644
index 12bcaea..0000000
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerDistanceBasedGestureMonitor.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2024 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.touchpad.tutorial.ui.gesture
-
-import android.view.MotionEvent
-
-interface GestureDonePredicate {
- /**
- * Should return if gesture was finished. The only events this predicate receives are ACTION_UP.
- */
- fun wasGestureDone(startX: Float, startY: Float, endX: Float, endY: Float): Boolean
-}
-
-/**
- * Common implementation for three-finger gesture monitors that are only distance-based. E.g. recent
- * apps gesture is not only distance-based because it requires going over threshold distance and
- * slowing down the movement.
- */
-class ThreeFingerDistanceBasedGestureMonitor(
- override val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
- private val donePredicate: GestureDonePredicate
-) : TouchpadGestureMonitor {
-
- private var xStart = 0f
- private var yStart = 0f
-
- override fun processTouchpadEvent(event: MotionEvent) {
- val action = event.actionMasked
- when (action) {
- MotionEvent.ACTION_DOWN -> {
- if (isThreeFingerTouchpadSwipe(event)) {
- xStart = event.x
- yStart = event.y
- gestureStateChangedCallback(GestureState.InProgress())
- }
- }
- MotionEvent.ACTION_UP -> {
- if (isThreeFingerTouchpadSwipe(event)) {
- if (donePredicate.wasGestureDone(xStart, yStart, event.x, event.y)) {
- gestureStateChangedCallback(GestureState.Finished)
- } else {
- gestureStateChangedCallback(GestureState.NotStarted)
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
index 8774a92..4655c98 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
@@ -23,8 +23,6 @@
* changes. All tracked motion events should be passed to [processTouchpadEvent]
*/
interface TouchpadGestureMonitor {
-
- val gestureDistanceThresholdPx: Int
val gestureStateChangedCallback: (GestureState) -> Unit
fun processTouchpadEvent(event: MotionEvent)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
index 3fdf86a..cd8cdc8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogPluginModule.kt
@@ -17,6 +17,13 @@
package com.android.systemui.volume.dialog.dagger.module
import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.utils.VolumeTracer
+import com.android.systemui.volume.dialog.utils.VolumeTracerImpl
+import dagger.Binds
import dagger.Module
-@Module(subcomponents = [VolumeDialogComponent::class]) interface VolumeDialogPluginModule
+@Module(subcomponents = [VolumeDialogComponent::class])
+interface VolumeDialogPluginModule {
+
+ @Binds fun bindVolumeTracer(volumeTracer: VolumeTracerImpl): VolumeTracer
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/data/VolumeDialogVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/data/VolumeDialogVisibilityRepository.kt
new file mode 100644
index 0000000..2aeaa5c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/data/VolumeDialogVisibilityRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.data
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+@SysUISingleton
+class VolumeDialogVisibilityRepository @Inject constructor() {
+
+ private val mutableDialogVisibility =
+ MutableStateFlow<VolumeDialogVisibilityModel>(VolumeDialogVisibilityModel.Invisible)
+ val dialogVisibility: Flow<VolumeDialogVisibilityModel> = mutableDialogVisibility.asStateFlow()
+
+ fun updateVisibility(
+ update: (current: VolumeDialogVisibilityModel) -> VolumeDialogVisibilityModel
+ ) {
+ mutableDialogVisibility.update(update)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
index f7d6d90..2668589b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -20,8 +20,12 @@
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.data.VolumeDialogVisibilityRepository
import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
-import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel.Dismissed
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel.Visible
+import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@@ -30,13 +34,11 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.update
private val MAX_DIALOG_SHOW_TIME: Duration = 3.seconds
@@ -53,14 +55,13 @@
constructor(
@VolumeDialogPlugin coroutineScope: CoroutineScope,
callbacksInteractor: VolumeDialogCallbacksInteractor,
+ private val tracer: VolumeTracer,
+ private val repository: VolumeDialogVisibilityRepository,
) {
@SuppressLint("SharedFlowCreation")
private val mutableDismissDialogEvents = MutableSharedFlow<Unit>()
- private val mutableDialogVisibility =
- MutableStateFlow<VolumeDialogVisibilityModel>(VolumeDialogVisibilityModel.Invisible)
-
- val dialogVisibility: Flow<VolumeDialogVisibilityModel> = mutableDialogVisibility.asStateFlow()
+ val dialogVisibility: Flow<VolumeDialogVisibilityModel> = repository.dialogVisibility
init {
merge(
@@ -70,12 +71,11 @@
},
callbacksInteractor.event,
)
- .onEach { event ->
- VolumeDialogVisibilityModel.fromEvent(event)?.let { model ->
- mutableDialogVisibility.value = model
- if (model is VolumeDialogVisibilityModel.Visible) {
- resetDismissTimeout()
- }
+ .mapNotNull { it.toVisibilityModel() }
+ .onEach { model ->
+ updateVisibility { model }
+ if (model is VolumeDialogVisibilityModel.Visible) {
+ resetDismissTimeout()
}
}
.launchIn(coroutineScope)
@@ -86,9 +86,9 @@
* [dialogVisibility].
*/
fun dismissDialog(reason: Int) {
- mutableDialogVisibility.update {
- if (it is VolumeDialogVisibilityModel.Dismissed) {
- it
+ updateVisibility { visibilityModel ->
+ if (visibilityModel is VolumeDialogVisibilityModel.Dismissed) {
+ visibilityModel
} else {
VolumeDialogVisibilityModel.Dismissed(reason)
}
@@ -99,4 +99,19 @@
suspend fun resetDismissTimeout() {
mutableDismissDialogEvents.emit(Unit)
}
+
+ private fun updateVisibility(
+ update: (VolumeDialogVisibilityModel) -> VolumeDialogVisibilityModel
+ ) {
+ repository.updateVisibility { update(it).also(tracer::traceVisibilityStart) }
+ }
+
+ private fun VolumeDialogEventModel.toVisibilityModel(): VolumeDialogVisibilityModel? {
+ return when (this) {
+ is VolumeDialogEventModel.DismissRequested -> Dismissed(reason)
+ is VolumeDialogEventModel.ShowRequested ->
+ Visible(reason, keyguardLocked, lockTaskModeState)
+ else -> null
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt
index db19634..2dd0bda 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt
@@ -23,7 +23,7 @@
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
-import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogVisibilityModel.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt
rename to packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogVisibilityModel.kt
index 646445d..56a707d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogVisibilityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogVisibilityModel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.volume.dialog.domain.model
+package com.android.systemui.volume.dialog.shared.model
/** Models current Volume Dialog visibility state. */
sealed interface VolumeDialogVisibilityModel {
@@ -30,19 +30,4 @@
/** Dialog has been shown and then dismissed. */
data class Dismissed(val reason: Int) : Invisible
-
- companion object {
-
- /**
- * Creates [VolumeDialogVisibilityModel] from appropriate events and returns null otherwise.
- */
- fun fromEvent(event: VolumeDialogEventModel): VolumeDialogVisibilityModel? {
- return when (event) {
- is VolumeDialogEventModel.DismissRequested -> Dismissed(event.reason)
- is VolumeDialogEventModel.ShowRequested ->
- Visible(event.reason, event.keyguardLocked, event.lockTaskModeState)
- else -> null
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
index 9c88303..9452d8c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
@@ -33,6 +33,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+/** Binds the Volume Dialog itself. */
@VolumeDialogScope
class VolumeDialogBinder
@Inject
@@ -47,9 +48,13 @@
with(dialog) {
setupWindow(window!!)
dialog.setContentView(R.layout.volume_dialog)
+ dialog.setCanceledOnTouchOutside(true)
settingsButtonViewBinder.bind(dialog.requireViewById(R.id.settings_container))
- volumeDialogViewBinder.bind(dialog.requireViewById(R.id.volume_dialog_container))
+ volumeDialogViewBinder.bind(
+ dialog,
+ dialog.requireViewById(R.id.volume_dialog_container),
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index 600d176..23e6eac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -16,32 +16,144 @@
package com.android.systemui.volume.dialog.ui.binder
+import android.app.Dialog
+import android.view.Gravity
import android.view.View
+import com.android.internal.view.RotationPolicy
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.setSnapshotBinding
import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.SystemUIInterpolators
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
+import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogResourcesViewModel
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
+import com.android.systemui.volume.dialog.utils.VolumeTracer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+/** Binds the root view of the Volume Dialog. */
+@OptIn(ExperimentalCoroutinesApi::class)
@VolumeDialogScope
class VolumeDialogViewBinder
@Inject
-constructor(private val volumeDialogViewModelFactory: VolumeDialogViewModel.Factory) {
+constructor(
+ private val volumeResources: VolumeDialogResourcesViewModel,
+ private val gravityViewModel: VolumeDialogGravityViewModel,
+ private val viewModelFactory: VolumeDialogViewModel.Factory,
+ private val jankListenerFactory: JankListenerFactory,
+ private val tracer: VolumeTracer,
+) {
- fun bind(view: View) {
+ fun bind(dialog: Dialog, view: View) {
+ view.alpha = 0f
view.repeatWhenAttached {
view.viewModel(
traceName = "VolumeDialogViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { volumeDialogViewModelFactory.create() },
+ factory = { viewModelFactory.create() },
) { viewModel ->
- view.setSnapshotBinding {}
+ animateVisibility(view, dialog, viewModel.dialogVisibilityModel)
awaitCancellation()
}
}
}
+
+ private fun CoroutineScope.animateVisibility(
+ view: View,
+ dialog: Dialog,
+ visibilityModel: Flow<VolumeDialogVisibilityModel>,
+ ) {
+ visibilityModel
+ .mapLatest {
+ when (it) {
+ is VolumeDialogVisibilityModel.Visible -> {
+ tracer.traceVisibilityEnd(it)
+ calculateTranslationX(view)?.let(view::setTranslationX)
+ view.animateShow(volumeResources.dialogShowDurationMillis.first())
+ }
+ is VolumeDialogVisibilityModel.Dismissed -> {
+ tracer.traceVisibilityEnd(it)
+ view.animateHide(
+ duration = volumeResources.dialogHideDurationMillis.first(),
+ translationX = calculateTranslationX(view),
+ )
+ dialog.dismiss()
+ }
+ is VolumeDialogVisibilityModel.Invisible -> {
+ // do nothing
+ }
+ }
+ }
+ .launchIn(this)
+ }
+
+ private suspend fun calculateTranslationX(view: View): Float? {
+ return if (view.display.rotation == RotationPolicy.NATURAL_ROTATION) {
+ val dialogGravity = gravityViewModel.dialogGravity.first()
+ val isGravityLeft = (dialogGravity and Gravity.LEFT) == Gravity.LEFT
+ if (isGravityLeft) {
+ -1
+ } else {
+ 1
+ } * view.width / 2.0f
+ } else {
+ null
+ }
+ }
+
+ private suspend fun View.animateShow(duration: Long) {
+ animate()
+ .alpha(1f)
+ .translationX(0f)
+ .setDuration(duration)
+ .setInterpolator(SystemUIInterpolators.LogDecelerateInterpolator())
+ .suspendAnimate(jankListenerFactory.show(this, duration))
+ /* TODO(b/369993851)
+ .withEndAction(Runnable {
+ if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
+ if (mRingerIcon != null) {
+ mRingerIcon.postOnAnimationDelayed(
+ getSinglePressFor(mRingerIcon), 1500
+ )
+ }
+ }
+ })
+ */
+ }
+
+ private suspend fun View.animateHide(duration: Long, translationX: Float?) {
+ val animator =
+ animate()
+ .alpha(0f)
+ .setDuration(duration)
+ .setInterpolator(SystemUIInterpolators.LogAccelerateInterpolator())
+ /* TODO(b/369993851)
+ .withEndAction(
+ Runnable {
+ mHandler.postDelayed(
+ Runnable {
+ hideRingerDrawer()
+
+ },
+ 50
+ )
+ }
+ )
+ */
+ if (translationX != null) {
+ animator.translationX(translationX)
+ }
+ animator.suspendAnimate(jankListenerFactory.dismiss(this, duration))
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt
new file mode 100644
index 0000000..9fcd777
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/JankListenerFactory.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.ui.utils
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.view.View
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import javax.inject.Inject
+
+/** Provides [Animator.AnimatorListener] to measure Volume CUJ Jank */
+@VolumeDialogPluginScope
+class JankListenerFactory
+@Inject
+constructor(private val interactionJankMonitor: InteractionJankMonitor) {
+
+ fun show(view: View, timeout: Long) = getJunkListener(view, "show", timeout)
+
+ fun update(view: View, timeout: Long) = getJunkListener(view, "update", timeout)
+
+ fun dismiss(view: View, timeout: Long) = getJunkListener(view, "dismiss", timeout)
+
+ private fun getJunkListener(
+ view: View,
+ type: String,
+ timeout: Long,
+ ): Animator.AnimatorListener {
+ return object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ interactionJankMonitor.begin(
+ InteractionJankMonitor.Configuration.Builder.withView(
+ Cuj.CUJ_VOLUME_CONTROL,
+ view,
+ )
+ .setTag(type)
+ .setTimeout(timeout)
+ )
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ interactionJankMonitor.end(Cuj.CUJ_VOLUME_CONTROL)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ interactionJankMonitor.cancel(Cuj.CUJ_VOLUME_CONTROL)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
new file mode 100644
index 0000000..4eae3b9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.ui.utils
+
+import android.animation.Animator
+import android.view.ViewPropertyAnimator
+import kotlin.coroutines.resume
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Starts animation and suspends until it's finished. Cancels the animation if the running coroutine
+ * is cancelled.
+ *
+ * Careful! This method overrides [ViewPropertyAnimator.setListener]. Use [animationListener]
+ * instead.
+ */
+suspend fun ViewPropertyAnimator.suspendAnimate(
+ animationListener: Animator.AnimatorListener? = null
+) = suspendCancellableCoroutine { continuation ->
+ start()
+ setListener(
+ object : Animator.AnimatorListener {
+ override fun onAnimationStart(animation: Animator) {
+ animationListener?.onAnimationStart(animation)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ continuation.resume(Unit)
+ animationListener?.onAnimationEnd(animation)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ animationListener?.onAnimationCancel(animation)
+ }
+
+ override fun onAnimationRepeat(animation: Animator) {
+ animationListener?.onAnimationRepeat(animation)
+ }
+ }
+ )
+ continuation.invokeOnCancellation { this.cancel() }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
index df6523c..112afb1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogGravityViewModel.kt
@@ -39,6 +39,7 @@
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
+/** Exposes dialog [GravityInt] for use in the UI layer. */
@VolumeDialogScope
class VolumeDialogGravityViewModel
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
index 8aa0d09..f336d46 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
@@ -16,15 +16,14 @@
package com.android.systemui.volume.dialog.ui.viewmodel
-import android.app.Dialog
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.plugins.VolumeDialogController
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
-import com.android.systemui.volume.dialog.domain.model.VolumeDialogVisibilityModel
import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
@@ -32,8 +31,7 @@
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.flow.onEach
@OptIn(ExperimentalCoroutinesApi::class)
@VolumeDialogPluginScope
@@ -49,6 +47,7 @@
override suspend fun onActivated(): Nothing {
coroutineScope {
dialogVisibilityInteractor.dialogVisibility
+ .onEach { controller.notifyVisible(it is VolumeDialogVisibilityModel.Visible) }
.mapLatest { visibilityModel ->
with(visibilityModel) {
if (this is VolumeDialogVisibilityModel.Visible) {
@@ -78,15 +77,8 @@
dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN)
}
}
- launch { dialog.awaitShow() }
+ dialog.show()
Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
}
}
-
-/** Shows [Dialog] until suspend function is cancelled. */
-private suspend fun Dialog.awaitShow() =
- suspendCancellableCoroutine<Unit> {
- show()
- it.invokeOnCancellation { dismiss() }
- }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogResourcesViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogResourcesViewModel.kt
new file mode 100644
index 0000000..da9be98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogResourcesViewModel.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.ui.viewmodel
+
+import android.content.Context
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides cached resources [Flow]s that update when the configuration changes.
+ *
+ * Consume or use [kotlinx.coroutines.flow.first] to get the value.
+ */
+@VolumeDialogScope
+class VolumeDialogResourcesViewModel
+@Inject
+constructor(
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ @UiBackground private val uiBackgroundContext: CoroutineContext,
+ private val context: Context,
+ private val configurationController: ConfigurationController,
+) {
+
+ val dialogShowDurationMillis: Flow<Long> = configurationResource {
+ getInteger(R.integer.config_dialogShowAnimationDurationMs).toLong()
+ }
+
+ val dialogHideDurationMillis: Flow<Long> = configurationResource {
+ getInteger(R.integer.config_dialogHideAnimationDurationMs).toLong()
+ }
+
+ private fun <T> configurationResource(get: Resources.() -> T): Flow<T> =
+ configurationController.onConfigChanged
+ .map { context.resources.get() }
+ .onStart { emit(context.resources.get()) }
+ .flowOn(uiBackgroundContext)
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .filterNotNull()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 30c8c15..84c837c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -17,11 +17,20 @@
package com.android.systemui.volume.dialog.ui.viewmodel
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
-class VolumeDialogViewModel @AssistedInject constructor() : ExclusiveActivatable() {
+/** Provides a state for the Volume Dialog. */
+class VolumeDialogViewModel
+@AssistedInject
+constructor(dialogVisibilityInteractor: VolumeDialogVisibilityInteractor) : ExclusiveActivatable() {
+
+ val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> =
+ dialogVisibilityInteractor.dialogVisibility
override suspend fun onActivated(): Nothing {
awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/utils/VolumeTracer.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/utils/VolumeTracer.kt
new file mode 100644
index 0000000..db35ca7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/utils/VolumeTracer.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.utils
+
+import android.os.Trace
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import javax.inject.Inject
+
+/** Traces the async sections for the Volume Dialog. */
+interface VolumeTracer {
+
+ fun traceVisibilityStart(model: VolumeDialogVisibilityModel)
+
+ fun traceVisibilityEnd(model: VolumeDialogVisibilityModel)
+}
+
+@VolumeDialogPluginScope
+class VolumeTracerImpl @Inject constructor() : VolumeTracer {
+
+ override fun traceVisibilityStart(model: VolumeDialogVisibilityModel) =
+ with(model) { Trace.beginAsyncSection(methodName, tracingCookie) }
+
+ override fun traceVisibilityEnd(model: VolumeDialogVisibilityModel) =
+ with(model) { Trace.endAsyncSection(methodName, tracingCookie) }
+
+ private val VolumeDialogVisibilityModel.tracingCookie
+ get() = this.hashCode()
+
+ private val VolumeDialogVisibilityModel.methodName
+ get() =
+ when (this) {
+ is VolumeDialogVisibilityModel.Visible -> "VolumeDialog#show"
+ is VolumeDialogVisibilityModel.Dismissed -> "VolumeDialog#dismiss"
+ is VolumeDialogVisibilityModel.Invisible -> error("Invisible is unsupported")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
index f94cbda..609ba02 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
@@ -16,7 +16,10 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+import com.android.settingslib.media.PhoneMediaDevice.inputRoutingEnabledAndIsDesktop
+import android.content.Context
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
import com.android.systemui.volume.domain.model.AudioOutputDevice
@@ -46,6 +49,7 @@
class MediaOutputComponentInteractor
@Inject
constructor(
+ @Application private val context: Context,
@VolumePanelScope private val coroutineScope: CoroutineScope,
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
audioOutputInteractor: AudioOutputInteractor,
@@ -91,7 +95,8 @@
MediaOutputComponentModel.Calling(
device = currentAudioDevice,
isInAudioSharing = isInAudioSharing,
- canOpenAudioSwitcher = false,
+ /* allow open switcher when input routing is enabled in desktop */
+ canOpenAudioSwitcher = inputRoutingEnabledAndIsDesktop(context),
)
)
} else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 5e37d4c..51a7b5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -338,6 +338,25 @@
assertThat(mController.mFloatingMenu).isInstanceOf(MenuViewLayerController.class);
}
+ @Test
+ public void onUserInitializationComplete_destroysOldWidget() {
+ enableAccessibilityFloatingMenuConfig();
+ mController = setUpController();
+
+ captureKeyguardUpdateMonitorCallback();
+ mKeyguardCallback.onUserUnlocked();
+ mKeyguardCallback.onKeyguardVisibilityChanged(false);
+
+ IAccessibilityFloatingMenu floatingMenu = mController.mFloatingMenu;
+
+ mController.mUserInitializationCompleteCallback
+ .onUserInitializationComplete(mContext.getUserId());
+ mTestableLooper.processAllMessages();
+
+ assertThat(mController.mFloatingMenu).isNotNull();
+ assertThat(mController.mFloatingMenu).isNotSameInstanceAs(floatingMenu);
+ }
+
private AccessibilityFloatingMenuController setUpController() {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final ViewCaptureAwareWindowManager viewCaptureAwareWindowManager =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 6c42662..762cfa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -52,14 +52,7 @@
private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens"
private val emulationSpec =
- DeviceEmulationSpec(
- DisplaySpec(
- "phone",
- width = 320,
- height = 690,
- densityDpi = 160,
- )
- )
+ DeviceEmulationSpec(DisplaySpec("phone", width = 320, height = 690, densityDpi = 160))
}
private val kosmos = Kosmos()
@@ -68,7 +61,7 @@
TransitionAnimator(
kosmos.fakeExecutor,
ActivityTransitionAnimator.TIMINGS,
- ActivityTransitionAnimator.INTERPOLATORS
+ ActivityTransitionAnimator.INTERPOLATORS,
)
@get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
@@ -131,16 +124,17 @@
waitForIdleSync()
val controller = TestController(transitionContainer, isLaunching)
- val animator =
- transitionAnimator.createAnimator(
+ val animation =
+ transitionAnimator.createAnimation(
controller,
+ controller.createAnimatorState(),
createEndState(transitionContainer),
backgroundLayer,
- fadeWindowBackgroundLayer
- )
+ fadeWindowBackgroundLayer,
+ ) as TransitionAnimator.InterpolatedAnimation
return AnimatorSet().apply {
- duration = animator.duration
- play(animator)
+ duration = animation.animator.duration
+ play(animation.animator)
}
}
@@ -153,13 +147,13 @@
right = containerLocation[0] + emulationSpec.display.width,
bottom = containerLocation[1] + emulationSpec.display.height,
topCornerRadius = 0f,
- bottomCornerRadius = 0f
+ bottomCornerRadius = 0f,
)
}
private fun recordMotion(
backgroundLayer: GradientDrawable,
- animator: AnimatorSet
+ animator: AnimatorSet,
): RecordedMotion {
return motionRule.record(
animator,
@@ -167,7 +161,7 @@
feature(DrawableFeatureCaptures.bounds, "bounds")
feature(DrawableFeatureCaptures.cornerRadii, "corner_radii")
feature(DrawableFeatureCaptures.alpha, "alpha")
- }
+ },
)
}
}
@@ -178,7 +172,7 @@
*/
private class TestController(
override var transitionContainer: ViewGroup,
- override val isLaunching: Boolean
+ override val isLaunching: Boolean,
) : TransitionAnimator.Controller {
override fun createAnimatorState(): TransitionAnimator.State {
val containerLocation = IntArray(2)
@@ -189,7 +183,7 @@
right = containerLocation[0] + 200,
bottom = containerLocation[1] + 400,
topCornerRadius = 10f,
- bottomCornerRadius = 20f
+ bottomCornerRadius = 20f,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt
new file mode 100644
index 0000000..5d5c120
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 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.display.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DisplayScopeRepositoryImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher }
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ private val repo =
+ DisplayScopeRepositoryImpl(
+ kosmos.applicationCoroutineScope,
+ kosmos.testDispatcher,
+ fakeDisplayRepository,
+ )
+
+ @Before
+ fun setUp() {
+ repo.start()
+ }
+
+ @Test
+ fun scopeForDisplay_multipleCallsForSameDisplayId_returnsSameInstance() {
+ val scopeForDisplay = repo.scopeForDisplay(displayId = 1)
+
+ assertThat(repo.scopeForDisplay(displayId = 1)).isSameInstanceAs(scopeForDisplay)
+ }
+
+ @Test
+ fun scopeForDisplay_differentDisplayId_returnsNewInstance() {
+ val scopeForDisplay1 = repo.scopeForDisplay(displayId = 1)
+ val scopeForDisplay2 = repo.scopeForDisplay(displayId = 2)
+
+ assertThat(scopeForDisplay1).isNotSameInstanceAs(scopeForDisplay2)
+ }
+
+ @Test
+ fun scopeForDisplay_activeByDefault() =
+ testScope.runTest {
+ val scopeForDisplay = repo.scopeForDisplay(displayId = 1)
+
+ assertThat(scopeForDisplay.isActive).isTrue()
+ }
+
+ @Test
+ fun scopeForDisplay_afterDisplayRemoved_scopeIsCancelled() =
+ testScope.runTest {
+ val scopeForDisplay = repo.scopeForDisplay(displayId = 1)
+
+ fakeDisplayRepository.removeDisplay(displayId = 1)
+
+ assertThat(scopeForDisplay.isActive).isFalse()
+ }
+
+ @Test
+ fun scopeForDisplay_afterDisplayRemoved_returnsNewInstance() =
+ testScope.runTest {
+ val initialScope = repo.scopeForDisplay(displayId = 1)
+
+ fakeDisplayRepository.removeDisplay(displayId = 1)
+
+ val newScope = repo.scopeForDisplay(displayId = 1)
+ assertThat(newScope).isNotSameInstanceAs(initialScope)
+ }
+}
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 6608542..b3cccea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -26,6 +26,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
+import static com.android.systemui.Flags.FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY;
import static com.android.systemui.Flags.FLAG_SIM_PIN_BOUNCER_RESET;
import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
@@ -63,6 +64,7 @@
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
@@ -841,6 +843,32 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+ @EnableFlags(FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY)
+ public void testCancelKeyguardExitAnimationDueToSleep_withPendingLockAndRelockFlag_keyguardWillBeShowing() {
+ startMockKeyguardExitAnimation();
+
+ mViewMediator.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
+ mViewMediator.onFinishedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, false);
+
+ cancelMockKeyguardExitAnimation();
+
+ mViewMediator.maybeHandlePendingLock();
+ TestableLooper.get(this).processAllMessages();
+
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+
+ verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(true);
+
+ // Unlock animators call `exitKeyguardAndFinishSurfaceBehindRemoteAnimation` when canceled
+ mViewMediator.exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false);
+ TestableLooper.get(this).processAllMessages();
+
+ verify(mUpdateMonitor, never()).dispatchKeyguardDismissAnimationFinished();
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ @DisableFlags(FLAG_RELOCK_WITH_POWER_BUTTON_IMMEDIATELY)
public void testCancelKeyguardExitAnimationDueToSleep_withPendingLock_keyguardWillBeShowing() {
startMockKeyguardExitAnimation();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index e4329a5..0d4cb4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot.policy
import android.content.ComponentName
+import android.graphics.Bitmap
import android.graphics.Insets
import android.graphics.Rect
import android.os.UserHandle
@@ -27,10 +28,12 @@
import com.android.systemui.screenshot.ScreenshotData
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.launcherOnly
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
import com.android.systemui.screenshot.data.repository.DisplayContentRepository
import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
import com.android.systemui.screenshot.policy.TestUserIds.WORK
+import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
@@ -39,14 +42,6 @@
@RunWith(AndroidJUnit4::class)
class PolicyRequestProcessorTest {
-
- val imageCapture =
- object : ImageCapture {
- override fun captureDisplay(displayId: Int, crop: Rect?) = null
-
- override suspend fun captureTask(taskId: Int) = null
- }
-
/** Tests behavior when no policies are applied */
@Test
fun testProcess_defaultOwner_whenNoPolicyApplied() {
@@ -71,7 +66,7 @@
val requestProcessor =
PolicyRequestProcessor(
Dispatchers.Unconfined,
- imageCapture,
+ createImageCapture(),
policies = emptyList(),
defaultOwner = UserHandle.of(PERSONAL),
defaultComponent = ComponentName("default", "Component"),
@@ -91,6 +86,70 @@
assertWithMessage("Task ID").that(result.taskId).isEqualTo(TASK_ID)
}
+ @Test
+ fun testProcess_throwsWhenCaptureFails() {
+ val request = ScreenshotData.forTesting()
+
+ /* Create a policy request processor with no capture policies */
+ val requestProcessor =
+ PolicyRequestProcessor(
+ Dispatchers.Unconfined,
+ createImageCapture(display = null),
+ policies = emptyList(),
+ defaultComponent = ComponentName("default", "Component"),
+ displayTasks = DisplayContentRepository { launcherOnly() },
+ )
+
+ val result = runCatching { runBlocking { requestProcessor.process(request) } }
+
+ assertThat(result.isFailure).isTrue()
+ }
+
+ @Test
+ fun testProcess_throwsWhenTaskCaptureFails() {
+ val request = ScreenshotData.forTesting()
+ val fullScreenWork = DisplayContentRepository {
+ singleFullScreen(TaskSpec(taskId = TASK_ID, name = FILES, userId = WORK))
+ }
+
+ val captureTaskPolicy = CapturePolicy {
+ CapturePolicy.PolicyResult.Matched(
+ policy = "",
+ reason = "",
+ parameters =
+ CaptureParameters(
+ CaptureType.IsolatedTask(taskId = 0, taskBounds = null),
+ null,
+ UserHandle.CURRENT,
+ ),
+ )
+ }
+
+ /* Create a policy request processor with no capture policies */
+ val requestProcessor =
+ PolicyRequestProcessor(
+ Dispatchers.Unconfined,
+ createImageCapture(task = null),
+ policies = listOf(captureTaskPolicy),
+ defaultComponent = ComponentName("default", "Component"),
+ displayTasks = fullScreenWork,
+ )
+
+ val result = runCatching { runBlocking { requestProcessor.process(request) } }
+
+ assertThat(result.isFailure).isTrue()
+ }
+
+ private fun createImageCapture(
+ display: Bitmap? = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888),
+ task: Bitmap? = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888),
+ ) =
+ object : ImageCapture {
+ override fun captureDisplay(displayId: Int, crop: Rect?) = display
+
+ override suspend fun captureTask(taskId: Int) = task
+ }
+
companion object {
const val TASK_ID = 1001
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 4b6e313..328d310 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -50,8 +50,8 @@
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
@@ -66,6 +66,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -104,6 +105,7 @@
// to run the callback and this makes the looper place nicely with TestScope etc.
@TestableLooper.RunWithLooper
class MobileConnectionsRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
private val flags =
FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
@@ -120,13 +122,13 @@
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
- @Mock private lateinit var summaryLogger: TableLogBuffer
+ private val summaryLogger = logcatTableLogBuffer(kosmos, "summaryLogger")
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var wifiManager: WifiManager
@Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
@Mock private lateinit var wifiPickerTracker: WifiPickerTracker
- @Mock private lateinit var wifiTableLogBuffer: TableLogBuffer
+ private val wifiTableLogBuffer = logcatTableLogBuffer(kosmos, "wifiTableLog")
private val mobileMappings = FakeMobileMappingsProxy()
private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
@@ -153,7 +155,7 @@
}
whenever(logBufferFactory.getOrCreate(anyString(), anyInt())).thenAnswer { _ ->
- mock<TableLogBuffer>()
+ logcatTableLogBuffer(kosmos, "test")
}
whenever(wifiPickerTrackerFactory.create(any(), capture(wifiPickerTrackerCallback), any()))
@@ -606,10 +608,7 @@
// WHEN an appropriate intent gets sent out
val intent = serviceStateIntent(subId = -1)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- intent,
- )
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
runCurrent()
// THEN the repo's state is updated despite no listeners
@@ -636,10 +635,7 @@
// GIVEN a broadcast goes out for the appropriate subID
val intent = serviceStateIntent(subId = -1)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- intent,
- )
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
runCurrent()
// THEN the device is in ECM, because one of the service states is
@@ -666,10 +662,7 @@
// GIVEN a broadcast goes out for the appropriate subID
val intent = serviceStateIntent(subId = -1)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- intent,
- )
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
runCurrent()
// THEN the device is in ECM, because one of the service states is
@@ -820,17 +813,9 @@
// Get repos to trigger creation
underTest.getRepoForSubId(SUB_1_ID)
- verify(logBufferFactory)
- .getOrCreate(
- eq(tableBufferLogName(SUB_1_ID)),
- anyInt(),
- )
+ verify(logBufferFactory).getOrCreate(eq(tableBufferLogName(SUB_1_ID)), anyInt())
underTest.getRepoForSubId(SUB_2_ID)
- verify(logBufferFactory)
- .getOrCreate(
- eq(tableBufferLogName(SUB_2_ID)),
- anyInt(),
- )
+ verify(logBufferFactory).getOrCreate(eq(tableBufferLogName(SUB_2_ID)), anyInt())
}
@Test
@@ -1578,9 +1563,7 @@
* To properly mimic telephony manager, create a service state, and then turn it into an
* intent
*/
- private fun serviceStateIntent(
- subId: Int,
- ): Intent {
+ private fun serviceStateIntent(subId: Int): Intent {
return Intent(Intent.ACTION_SERVICE_STATE).apply {
putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index fd4b77d..44e1437 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -26,19 +26,20 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.testKosmos
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.mock
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.fakeSystemClock
import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
import com.android.wifitrackerlib.MergedCarrierEntry
@@ -67,6 +68,7 @@
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class WifiRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
// Using lazy means that the class will only be constructed once it's fetched. Because the
// repository internally sets some values on construction, we need to set up some test
@@ -84,9 +86,9 @@
)
}
- private val executor = FakeExecutor(FakeSystemClock())
+ private val executor = FakeExecutor(kosmos.fakeSystemClock)
private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock())
- private val tableLogger = mock<TableLogBuffer>()
+ private val tableLogger = logcatTableLogBuffer(kosmos, "WifiRepositoryImplTest")
private val wifiManager =
mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) }
private val wifiPickerTrackerFactory = mock<WifiPickerTrackerFactory>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
index 0e84273..e470e37 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
@@ -19,14 +19,13 @@
import com.android.systemui.brightness.data.repository.screenBrightnessRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.log.table.logcatTableLogBuffer
val Kosmos.screenBrightnessInteractor by
Kosmos.Fixture {
ScreenBrightnessInteractor(
screenBrightnessRepository,
applicationCoroutineScope,
- mock<TableLogBuffer>(),
+ logcatTableLogBuffer(this, "screenBrightness"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 6c3cf91..fcc83b3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -24,16 +24,12 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.mockito.Mockito.`when` as whenever
/** Creates a mock display. */
-fun display(
- type: Int,
- flags: Int = 0,
- id: Int = 0,
- state: Int? = null,
-): Display {
+fun display(type: Int, flags: Int = 0, id: Int = 0, state: Int? = null): Display {
return mock {
whenever(this.displayId).thenReturn(id)
whenever(this.type).thenReturn(type)
@@ -51,10 +47,21 @@
@SysUISingleton
/** Fake [DisplayRepository] implementation for testing. */
class FakeDisplayRepository @Inject constructor() : DisplayRepository {
- private val flow = MutableSharedFlow<Set<Display>>(replay = 1)
+ private val flow = MutableStateFlow<Set<Display>>(emptySet())
private val pendingDisplayFlow =
MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
- private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 1)
+ private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
+ private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0)
+
+ suspend fun addDisplay(display: Display) {
+ flow.value += display
+ displayAdditionEventFlow.emit(display)
+ }
+
+ suspend fun removeDisplay(displayId: Int) {
+ flow.value = flow.value.filter { it.displayId != displayId }.toSet()
+ displayRemovalEventFlow.emit(displayId)
+ }
/** Emits [value] as [displayAdditionEvent] flow value. */
suspend fun emit(value: Display?) = displayAdditionEventFlow.emit(value)
@@ -65,7 +72,7 @@
/** Emits [value] as [pendingDisplay] flow value. */
suspend fun emit(value: DisplayRepository.PendingDisplay?) = pendingDisplayFlow.emit(value)
- override val displays: Flow<Set<Display>>
+ override val displays: StateFlow<Set<Display>>
get() = flow
override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
@@ -78,8 +85,11 @@
override val displayAdditionEvent: Flow<Display?>
get() = displayAdditionEventFlow
+ override val displayRemovalEvent: Flow<Int> = displayRemovalEventFlow
+
private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
+
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
fun setDefaultDisplayOff(defaultDisplayOff: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 237f7e4..a25a3c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -49,6 +49,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
@@ -92,6 +93,7 @@
primaryBouncerToLockscreenTransitionViewModel,
aodBurnInViewModel = aodBurnInViewModel,
communalSceneInteractor = communalSceneInteractor,
+ headsUpNotificationInteractor = { headsUpNotificationInteractor },
unfoldTransitionInteractor = unfoldTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 99cd830..68d08e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.policy.domain.interactor
-import android.content.testableContext
+import android.content.applicationContext
import com.android.settingslib.notification.modes.zenIconLoader
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -28,7 +28,7 @@
val Kosmos.zenModeInteractor by Fixture {
ZenModeInteractor(
- context = testableContext,
+ context = applicationContext,
zenModeRepository = zenModeRepository,
notificationSettingsRepository = notificationSettingsRepository,
bgDispatcher = testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt
new file mode 100644
index 0000000..291dfc0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogVisibilityRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.dialog.data.VolumeDialogVisibilityRepository
+
+val Kosmos.volumeDialogVisibilityRepository by Kosmos.Fixture { VolumeDialogVisibilityRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
index e73539e..7376c7f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorKosmos.kt
@@ -18,8 +18,15 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
+import com.android.systemui.volume.dialog.utils.volumeTracer
val Kosmos.volumeDialogVisibilityInteractor by
Kosmos.Fixture {
- VolumeDialogVisibilityInteractor(applicationCoroutineScope, volumeDialogCallbacksInteractor)
+ VolumeDialogVisibilityInteractor(
+ applicationCoroutineScope,
+ volumeDialogCallbacksInteractor,
+ volumeTracer,
+ volumeDialogVisibilityRepository,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/utils/FakeVolumeTracer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/utils/FakeVolumeTracer.kt
new file mode 100644
index 0000000..a5074eb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/utils/FakeVolumeTracer.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.utils
+
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+
+class FakeVolumeTracer : VolumeTracer {
+
+ override fun traceVisibilityStart(model: VolumeDialogVisibilityModel) {}
+
+ override fun traceVisibilityEnd(model: VolumeDialogVisibilityModel) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/utils/VolumeTracerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/utils/VolumeTracerKosmos.kt
new file mode 100644
index 0000000..1382563
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/utils/VolumeTracerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.volume.dialog.utils
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeVolumeTracer: FakeVolumeTracer by Kosmos.Fixture { FakeVolumeTracer() }
+var Kosmos.volumeTracer: VolumeTracer by Kosmos.Fixture { fakeVolumeTracer }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
index 63a1325..db66c3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+import android.content.mockedContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.volume.domain.interactor.audioModeInteractor
@@ -27,6 +28,7 @@
val Kosmos.mediaOutputComponentInteractor by
Kosmos.Fixture {
MediaOutputComponentInteractor(
+ mockedContext,
testScope.backgroundScope,
mediaDeviceSessionInteractor,
audioOutputInteractor,
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 11b66fc..9629a87 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -154,6 +154,8 @@
"framework-annotations-lib",
"ravenwood-helper-framework-runtime",
"ravenwood-helper-libcore-runtime",
+ "hoststubgen-helper-runtime.ravenwood",
+ "mockito-ravenwood-prebuilt",
],
visibility: ["//frameworks/base"],
jarjar_rules: ":ravenwood-services-jarjar-rules",
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 644babb..908e590 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -22,10 +22,14 @@
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.ResourcesManager;
+import android.app.UiAutomation;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
@@ -40,6 +44,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.hoststubgen.hosthelper.HostTestUtils;
import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.RavenwoodRuntimeNative;
import com.android.ravenwood.common.RavenwoodCommonUtils;
@@ -52,8 +57,10 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
+import java.util.Collections;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -125,6 +132,9 @@
private static RavenwoodConfig sConfig;
private static RavenwoodSystemProperties sProps;
+ // TODO: use the real UiAutomation class instead of a mock
+ private static UiAutomation sMockUiAutomation;
+ private static Set<String> sAdoptedPermissions = Collections.emptySet();
private static boolean sInitialized = false;
/**
@@ -171,6 +181,7 @@
"androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
assertMockitoVersion();
+ sMockUiAutomation = createMockUiAutomation();
}
/**
@@ -261,7 +272,7 @@
// Prepare other fields.
config.mInstrumentation = new Instrumentation();
- config.mInstrumentation.basicInit(config.mInstContext, config.mTargetContext);
+ config.mInstrumentation.basicInit(instContext, targetContext, sMockUiAutomation);
InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY);
RavenwoodSystemServer.init(config);
@@ -300,12 +311,13 @@
config.mInstrumentation = null;
if (config.mInstContext != null) {
((RavenwoodContext) config.mInstContext).cleanUp();
+ config.mInstContext = null;
}
if (config.mTargetContext != null) {
((RavenwoodContext) config.mTargetContext).cleanUp();
+ config.mTargetContext = null;
}
- config.mInstContext = null;
- config.mTargetContext = null;
+ sMockUiAutomation.dropShellPermissionIdentity();
if (config.mProvideMainThread) {
Looper.getMainLooper().quit();
@@ -403,6 +415,31 @@
() -> Class.forName("org.mockito.Matchers"));
}
+ private static UiAutomation createMockUiAutomation() {
+ var mock = mock(UiAutomation.class, inv -> {
+ HostTestUtils.onThrowMethodCalled();
+ return null;
+ });
+ doAnswer(inv -> {
+ sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
+ return null;
+ }).when(mock).adoptShellPermissionIdentity();
+ doAnswer(inv -> {
+ if (inv.getArgument(0) == null) {
+ sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
+ } else {
+ sAdoptedPermissions = (Set) Set.of(inv.getArguments());
+ }
+ return null;
+ }).when(mock).adoptShellPermissionIdentity(any());
+ doAnswer(inv -> {
+ sAdoptedPermissions = Collections.emptySet();
+ return null;
+ }).when(mock).dropShellPermissionIdentity();
+ doAnswer(inv -> sAdoptedPermissions).when(mock).getAdoptedShellPermissions();
+ return mock;
+ }
+
@SuppressWarnings("unused") // Called from native code (ravenwood_sysprop.cpp)
private static void checkSystemPropertyAccess(String key, boolean write) {
boolean result = write ? sProps.isKeyWritable(key) : sProps.isKeyReadable(key);
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodUiAutomationTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodUiAutomationTest.java
new file mode 100644
index 0000000..eb94827
--- /dev/null
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodUiAutomationTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest;
+
+import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodUiAutomationTest {
+
+ private Instrumentation mInstrumentation;
+
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ }
+
+ @Test
+ public void testGetUiAutomation() {
+ assertNotNull(mInstrumentation.getUiAutomation());
+ }
+
+ @Test
+ public void testGetUiAutomationWithFlags() {
+ assertNotNull(mInstrumentation.getUiAutomation(UiAutomation.FLAG_DONT_USE_ACCESSIBILITY));
+ }
+
+ @Test
+ public void testShellPermissionApis() {
+ var uiAutomation = mInstrumentation.getUiAutomation();
+ assertTrue(uiAutomation.getAdoptedShellPermissions().isEmpty());
+ uiAutomation.adoptShellPermissionIdentity();
+ assertEquals(uiAutomation.getAdoptedShellPermissions(), UiAutomation.ALL_PERMISSIONS);
+ uiAutomation.adoptShellPermissionIdentity((String[]) null);
+ assertEquals(uiAutomation.getAdoptedShellPermissions(), UiAutomation.ALL_PERMISSIONS);
+ uiAutomation.adoptShellPermissionIdentity(
+ OVERRIDE_COMPAT_CHANGE_CONFIG, READ_COMPAT_CHANGE_CONFIG);
+ assertEquals(uiAutomation.getAdoptedShellPermissions(),
+ Set.of(OVERRIDE_COMPAT_CHANGE_CONFIG, READ_COMPAT_CHANGE_CONFIG));
+ uiAutomation.dropShellPermissionIdentity();
+ assertTrue(uiAutomation.getAdoptedShellPermissions().isEmpty());
+ }
+
+ @Test
+ public void testUnsupportedMethod() {
+ // Only unsupported on Ravenwood
+ assumeTrue(RavenwoodCommonUtils.isOnRavenwood());
+ assertThrows(RuntimeException.class,
+ () -> mInstrumentation.getUiAutomation().executeShellCommand("echo ok"));
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d595d02..1451dfa 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3653,6 +3653,12 @@
return;
}
+ // Magnification connection should not be requested for visible background users.
+ // (b/332222893)
+ if (mUmi.isVisibleBackgroundFullUser(userState.mUserId)) {
+ return;
+ }
+
final boolean shortcutEnabled = (userState.isShortcutMagnificationEnabledLocked()
|| userState.isMagnificationSingleFingerTripleTapEnabledLocked()
|| (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 4b97745..1df5d1a 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -138,8 +138,8 @@
DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_7),
UP_MOVE_OR_SCROLL(KeyEvent.KEYCODE_8),
DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_9),
- LEFT_MOVE(KeyEvent.KEYCODE_U),
- RIGHT_MOVE(KeyEvent.KEYCODE_O),
+ LEFT_MOVE_OR_SCROLL(KeyEvent.KEYCODE_U),
+ RIGHT_MOVE_OR_SCROLL(KeyEvent.KEYCODE_O),
DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_J),
DOWN_MOVE_OR_SCROLL(KeyEvent.KEYCODE_K),
DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_L),
@@ -267,6 +267,16 @@
);
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ private void sendVirtualMouseScrollEvent(float x, float y) {
+ waitForVirtualMouseCreation();
+ mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
+ .setXAxisMovement(x)
+ .setYAxisMovement(y)
+ .build()
+ );
+ }
+
/**
* Performs a mouse scroll action based on the provided key code.
* The scroll action will only be performed if the scroll toggle is on.
@@ -284,19 +294,31 @@
private void performMouseScrollAction(int keyCode) {
MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(
keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap);
- float y = switch (mouseKeyEvent) {
- case UP_MOVE_OR_SCROLL -> MOUSE_SCROLL_STEP;
- case DOWN_MOVE_OR_SCROLL -> -MOUSE_SCROLL_STEP;
- default -> 0.0f;
- };
- waitForVirtualMouseCreation();
- mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
- .setYAxisMovement(y)
- .build()
- );
+ float x = 0f;
+ float y = 0f;
+
+ switch (mouseKeyEvent) {
+ case UP_MOVE_OR_SCROLL -> {
+ y = MOUSE_SCROLL_STEP;
+ }
+ case DOWN_MOVE_OR_SCROLL -> {
+ y = -MOUSE_SCROLL_STEP;
+ }
+ case LEFT_MOVE_OR_SCROLL -> {
+ x = MOUSE_SCROLL_STEP;
+ }
+ case RIGHT_MOVE_OR_SCROLL -> {
+ x = -MOUSE_SCROLL_STEP;
+ }
+ default -> {
+ x = 0.0f;
+ y = 0.0f;
+ }
+ }
+ sendVirtualMouseScrollEvent(x, y);
if (DEBUG) {
Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
- + " for scroll action with axis movement (y=" + y + ")");
+ + " for scroll action with axis movement (x=" + x + ", y=" + y + ")");
}
}
@@ -344,8 +366,8 @@
* The method calculates the relative movement of the mouse pointer
* and sends the corresponding event to the virtual mouse.
*
- * The UP and DOWN pointer actions will only take place for their respective keys
- * if the scroll toggle is off.
+ * The UP, DOWN, LEFT, RIGHT pointer actions will only take place for their
+ * respective keys if the scroll toggle is off.
*
* @param keyCode The key code representing the direction or button press.
* Supported keys are:
@@ -353,8 +375,8 @@
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_LEFT_MOVE}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_RIGHT_MOVE}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE}
- * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE_OR_SCROLL}
+ * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE_OR_SCROLL}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_LEFT_MOVE}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL}
* <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_RIGHT_MOVE}
@@ -381,10 +403,10 @@
x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
}
- case LEFT_MOVE -> {
+ case LEFT_MOVE_OR_SCROLL -> {
x = -MOUSE_POINTER_MOVEMENT_STEP;
}
- case RIGHT_MOVE -> {
+ case RIGHT_MOVE_OR_SCROLL -> {
x = MOUSE_POINTER_MOVEMENT_STEP;
}
case DIAGONAL_UP_LEFT_MOVE -> {
@@ -424,7 +446,9 @@
private boolean isMouseScrollKey(int keyCode, InputDevice inputDevice) {
return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCode(inputDevice)
- || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCode(inputDevice);
+ || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCode(inputDevice)
+ || keyCode == MouseKeyEvent.LEFT_MOVE_OR_SCROLL.getKeyCode(inputDevice)
+ || keyCode == MouseKeyEvent.RIGHT_MOVE_OR_SCROLL.getKeyCode(inputDevice);
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
index 19e3e69..fe06406 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
@@ -19,6 +19,7 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION_CALLBACK;
import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
+import static android.os.UserHandle.getCallingUserId;
import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK;
import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID;
@@ -54,6 +55,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -209,6 +211,7 @@
private final Callback mCallback;
private final AccessibilityTraceManager mTrace;
private final MagnificationScaleProvider mScaleProvider;
+ private final UserManagerInternal mUserManagerInternal;
public MagnificationConnectionManager(Context context, Object lock, @NonNull Callback callback,
AccessibilityTraceManager trace, MagnificationScaleProvider scaleProvider) {
@@ -217,6 +220,7 @@
mCallback = callback;
mTrace = trace;
mScaleProvider = scaleProvider;
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
}
/**
@@ -280,12 +284,18 @@
* Requests {@link IMagnificationConnection} through
* {@link StatusBarManagerInternal#requestMagnificationConnection(boolean)} and
* destroys all window magnifications if necessary.
+ * NOTE: Currently, this is not allowed to call from visible background users.(b/332222893)
*
* @param connect {@code true} if needs connection, otherwise set the connection to null and
* destroy all window magnifications.
* @return {@code true} if {@link IMagnificationConnection} state is going to change.
*/
public boolean requestConnection(boolean connect) {
+ final int callingUserId = getCallingUserId();
+ if (mUserManagerInternal.isVisibleBackgroundFullUser(callingUserId)) {
+ throw new SecurityException("Visible background user(u" + callingUserId
+ + " is not permitted to request magnification connection.");
+ }
if (DBG) {
Slog.d(TAG, "requestConnection :" + connect);
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
new file mode 100644
index 0000000..9fc413f
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2024 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.appfunctions;
+
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID;
+import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE;
+import static android.app.appfunctions.AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID;
+
+import android.Manifest;
+import android.annotation.BinderThread;
+import android.annotation.RequiresPermission;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.util.IndentingPrintWriter;
+import android.app.appfunctions.AppFunctionRuntimeMetadata;
+import android.app.appfunctions.AppFunctionStaticMetadataHelper;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
+import android.app.appsearch.JoinSpec;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchSpec;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+public final class AppFunctionDumpHelper {
+ private static final String TAG = AppFunctionDumpHelper.class.getSimpleName();
+
+ private AppFunctionDumpHelper() {}
+
+ /** Dumps the state of all app functions for all users. */
+ @BinderThread
+ @RequiresPermission(
+ anyOf = {Manifest.permission.CREATE_USERS, Manifest.permission.MANAGE_USERS})
+ public static void dumpAppFunctionsState(@NonNull Context context, @NonNull PrintWriter w) {
+ UserManager userManager = context.getSystemService(UserManager.class);
+ if (userManager == null) {
+ w.println("Couldn't retrieve UserManager.");
+ return;
+ }
+
+ IndentingPrintWriter pw = new IndentingPrintWriter(w);
+
+ List<UserInfo> userInfos = userManager.getAliveUsers();
+ for (UserInfo userInfo : userInfos) {
+ pw.println(
+ "AppFunction state for user " + userInfo.getUserHandle().getIdentifier() + ":");
+ pw.increaseIndent();
+ dumpAppFunctionsStateForUser(
+ context.createContextAsUser(userInfo.getUserHandle(), /* flags= */ 0), pw);
+ pw.decreaseIndent();
+ }
+ }
+
+ private static void dumpAppFunctionsStateForUser(
+ @NonNull Context context, @NonNull IndentingPrintWriter pw) {
+ AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
+ if (appSearchManager == null) {
+ pw.println("Couldn't retrieve AppSearchManager.");
+ return;
+ }
+
+ try (FutureGlobalSearchSession searchSession =
+ new FutureGlobalSearchSession(appSearchManager, Runnable::run)) {
+ pw.println();
+
+ FutureSearchResults futureSearchResults =
+ searchSession.search("", buildAppFunctionMetadataSearchSpec()).get();
+ List<SearchResult> searchResultsList;
+ do {
+ searchResultsList = futureSearchResults.getNextPage().get();
+ for (SearchResult searchResult : searchResultsList) {
+ dumpAppFunctionMetadata(pw, searchResult);
+ }
+ } while (!searchResultsList.isEmpty());
+ } catch (Exception e) {
+ pw.println("Failed to dump AppFunction state: " + e);
+ }
+ }
+
+ private static SearchSpec buildAppFunctionMetadataSearchSpec() {
+ SearchSpec runtimeMetadataSearchSpec =
+ new SearchSpec.Builder()
+ .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
+ .addFilterSchemas(AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE)
+ .build();
+ JoinSpec joinSpec =
+ new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
+ .setNestedSearch(/* queryExpression= */ "", runtimeMetadataSearchSpec)
+ .build();
+
+ return new SearchSpec.Builder()
+ .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
+ .addFilterSchemas(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)
+ .setJoinSpec(joinSpec)
+ .build();
+ }
+
+ private static void dumpAppFunctionMetadata(
+ IndentingPrintWriter pw, SearchResult joinedSearchResult) {
+ pw.println(
+ "AppFunctionMetadata for: "
+ + joinedSearchResult
+ .getGenericDocument()
+ .getPropertyString(PROPERTY_FUNCTION_ID));
+ pw.increaseIndent();
+
+ pw.println("Static Metadata:");
+ pw.increaseIndent();
+ writeGenericDocumentProperties(pw, joinedSearchResult.getGenericDocument());
+ pw.decreaseIndent();
+
+ pw.println("Runtime Metadata:");
+ pw.increaseIndent();
+ if (!joinedSearchResult.getJoinedResults().isEmpty()) {
+ writeGenericDocumentProperties(
+ pw, joinedSearchResult.getJoinedResults().getFirst().getGenericDocument());
+ } else {
+ pw.println("No runtime metadata found.");
+ }
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+
+ private static void writeGenericDocumentProperties(
+ IndentingPrintWriter pw, GenericDocument genericDocument) {
+ Set<String> propertyNames = genericDocument.getPropertyNames();
+ pw.println("{");
+ pw.increaseIndent();
+ for (String propertyName : propertyNames) {
+ Object propertyValue = genericDocument.getProperty(propertyName);
+ pw.print("\"" + propertyName + "\"" + ": [");
+
+ if (propertyValue instanceof GenericDocument[]) {
+ GenericDocument[] documentValues = (GenericDocument[]) propertyValue;
+ for (int i = 0; i < documentValues.length; i++) {
+ GenericDocument documentValue = documentValues[i];
+ writeGenericDocumentProperties(pw, documentValue);
+ if (i != documentValues.length - 1) {
+ pw.print(", ");
+ }
+ pw.println();
+ }
+ } else {
+ int propertyArrLength = Array.getLength(propertyValue);
+ for (int i = 0; i < propertyArrLength; i++) {
+ Object propertyElement = Array.get(propertyValue, i);
+ if (propertyElement instanceof String) {
+ pw.print("\"" + propertyElement + "\"");
+ } else if (propertyElement instanceof byte[]) {
+ pw.print(Arrays.toString((byte[]) propertyElement));
+ } else if (propertyElement != null) {
+ pw.print(propertyElement.toString());
+ }
+ if (i != propertyArrLength - 1) {
+ pw.print(", ");
+ }
+ }
+ }
+ pw.println("]");
+ }
+ pw.decreaseIndent();
+ pw.println("}");
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index d31ced3..6d350e6 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -63,10 +63,13 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.DumpUtils;
import com.android.server.SystemService.TargetUser;
import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
@@ -122,6 +125,20 @@
}
@Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+ return;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ AppFunctionDumpHelper.dumpAppFunctionsState(mContext, pw);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public ICancellationSignal executeAppFunction(
@NonNull ExecuteAppFunctionAidlRequest requestInternal,
@NonNull IExecuteAppFunctionCallback executeAppFunctionCallback) {
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
index de2034b..b89348c 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
@@ -39,20 +39,6 @@
/** A future API wrapper of {@link AppSearchSession} APIs. */
public interface FutureAppSearchSession extends Closeable {
- /** Converts a failed app search result codes into an exception. */
- @NonNull
- static Exception failedResultToException(@NonNull AppSearchResult<?> appSearchResult) {
- return switch (appSearchResult.getResultCode()) {
- case AppSearchResult.RESULT_INVALID_ARGUMENT ->
- new IllegalArgumentException(appSearchResult.getErrorMessage());
- case AppSearchResult.RESULT_IO_ERROR ->
- new IOException(appSearchResult.getErrorMessage());
- case AppSearchResult.RESULT_SECURITY_ERROR ->
- new SecurityException(appSearchResult.getErrorMessage());
- default -> new IllegalStateException(appSearchResult.getErrorMessage());
- };
- }
-
/**
* Sets the schema that represents the organizational structure of data within the AppSearch
* database.
@@ -86,17 +72,4 @@
@Override
void close();
-
- /** A future API wrapper of {@link android.app.appsearch.SearchResults}. */
- interface FutureSearchResults {
-
- /**
- * Retrieves the next page of {@link SearchResult} objects from the {@link AppSearchSession}
- * database.
- *
- * <p>Continue calling this method to access results until it returns an empty list,
- * signifying there are no more results.
- */
- AndroidFuture<List<SearchResult>> getNextPage();
- }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
index d24bb87..87589f5 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
@@ -16,7 +16,7 @@
package com.android.server.appfunctions;
-import static com.android.server.appfunctions.FutureAppSearchSession.failedResultToException;
+import static com.android.server.appfunctions.FutureSearchResults.failedResultToException;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
@@ -192,33 +192,6 @@
});
}
- private static final class FutureSearchResultsImpl implements FutureSearchResults {
- private final SearchResults mSearchResults;
- private final Executor mExecutor;
-
- private FutureSearchResultsImpl(
- @NonNull SearchResults searchResults, @NonNull Executor executor) {
- this.mSearchResults = searchResults;
- this.mExecutor = executor;
- }
-
- @Override
- public AndroidFuture<List<SearchResult>> getNextPage() {
- AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture =
- new AndroidFuture<>();
-
- mSearchResults.getNextPage(mExecutor, nextPageFuture::complete);
- return nextPageFuture.thenApply(
- result -> {
- if (result.isSuccess()) {
- return result.getResultValue();
- } else {
- throw new RuntimeException(failedResultToException(result));
- }
- });
- }
- }
-
private static final class BatchResultCallbackAdapter<K, V>
implements BatchResultCallback<K, V> {
private final AndroidFuture<AppSearchBatchResult<K, V>> mFuture;
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java
index 874c5da..4cc0817 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java
@@ -20,6 +20,7 @@
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.GlobalSearchSession;
+import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.observer.ObserverCallback;
import android.app.appsearch.observer.ObserverSpec;
@@ -49,12 +50,23 @@
return result.getResultValue();
} else {
throw new RuntimeException(
- FutureAppSearchSession.failedResultToException(result));
+ FutureSearchResults.failedResultToException(result));
}
});
}
/**
+ * Retrieves documents from the open {@link GlobalSearchSession} that match a given query string
+ * and type of search provided.
+ */
+ public AndroidFuture<FutureSearchResults> search(
+ String queryExpression, SearchSpec searchSpec) {
+ return getSessionAsync()
+ .thenApply(session -> session.search(queryExpression, searchSpec))
+ .thenApply(result -> new FutureSearchResultsImpl(result, mExecutor));
+ }
+
+ /**
* Registers an observer callback for the given target package name.
*
* @param targetPackageName The package name of the target app.
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
new file mode 100644
index 0000000..45cbdb4
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 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.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.IOException;
+import java.util.List;
+
+/** A future API wrapper of {@link android.app.appsearch.SearchResults}. */
+public interface FutureSearchResults {
+
+ /** Converts a failed app search result codes into an exception. */
+ @NonNull
+ public static Exception failedResultToException(@NonNull AppSearchResult<?> appSearchResult) {
+ return switch (appSearchResult.getResultCode()) {
+ case AppSearchResult.RESULT_INVALID_ARGUMENT ->
+ new IllegalArgumentException(appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_IO_ERROR ->
+ new IOException(appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_SECURITY_ERROR ->
+ new SecurityException(appSearchResult.getErrorMessage());
+ default -> new IllegalStateException(appSearchResult.getErrorMessage());
+ };
+ }
+
+ /**
+ * Retrieves the next page of {@link SearchResult} objects from the {@link AppSearchSession}
+ * database.
+ *
+ * <p>Continue calling this method to access results until it returns an empty list, signifying
+ * there are no more results.
+ */
+ AndroidFuture<List<SearchResult>> getNextPage();
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
new file mode 100644
index 0000000..c3be342
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 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.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class FutureSearchResultsImpl implements FutureSearchResults {
+ private final SearchResults mSearchResults;
+ private final Executor mExecutor;
+
+ public FutureSearchResultsImpl(
+ @NonNull SearchResults searchResults, @NonNull Executor executor) {
+ this.mSearchResults = searchResults;
+ this.mExecutor = executor;
+ }
+
+ @Override
+ public AndroidFuture<List<SearchResult>> getNextPage() {
+ AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture = new AndroidFuture<>();
+
+ mSearchResults.getNextPage(mExecutor, nextPageFuture::complete);
+ return nextPageFuture
+ .thenApply(
+ result -> {
+ if (result.isSuccess()) {
+ return result.getResultValue();
+ } else {
+ throw new RuntimeException(
+ FutureSearchResults.failedResultToException(result));
+ }
+ });
+ }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index d84b205..bbf6c0b 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -45,7 +45,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
-import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 42f69e9..95281c8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -676,7 +676,7 @@
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
- null, null, null, false, null, null);
+ null, null, null, false, null, null, null);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 4fc9d55..2804945 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,8 +106,8 @@
boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
- /* callback */ null, /* resultReceiver */ null);
+ deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ /* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
}
break;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 0c54720..77b1780 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -17,10 +17,12 @@
package com.android.server.companion.association;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readByteArrayAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -36,6 +38,7 @@
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Environment;
import android.util.AtomicFile;
@@ -51,6 +54,7 @@
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -172,6 +176,7 @@
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
private static final String XML_ATTR_SYSTEM_DATA_SYNC_FLAGS = "system_data_sync_flags";
+ private static final String XML_ATTR_DEVICE_ICON = "device_icon";
private static final String LEGACY_XML_ATTR_DEVICE = "device";
@@ -393,7 +398,7 @@
return new AssociationInfo(associationId, userId, appPackage, tag,
MacAddress.fromString(deviceAddress), null, profile, null,
/* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
- timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
+ timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0, null);
}
private static Associations readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -444,10 +449,12 @@
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
final int systemDataSyncFlags = readIntAttribute(parser,
XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
+ final Icon deviceIcon = byteArrayToIcon(
+ readByteArrayAttribute(parser, XML_ATTR_DEVICE_ICON));
return new AssociationInfo(associationId, userId, appPackage, tag, macAddress, displayName,
profile, null, selfManaged, notify, revoked, pending, timeApproved,
- lastTimeConnected, systemDataSyncFlags);
+ lastTimeConnected, systemDataSyncFlags, deviceIcon);
}
private static void writeAssociations(@NonNull XmlSerializer parent,
@@ -480,6 +487,8 @@
writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
writeIntAttribute(serializer, XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, a.getSystemDataSyncFlags());
+ writeByteArrayAttribute(
+ serializer, XML_ATTR_DEVICE_ICON, iconToByteArray(a.getDeviceIcon()));
serializer.endTag(null, XML_TAG_ASSOCIATION);
}
@@ -494,4 +503,24 @@
private static @Nullable MacAddress stringToMacAddress(@Nullable String address) {
return address != null ? MacAddress.fromString(address) : null;
}
+
+ private static byte[] iconToByteArray(Icon deviceIcon)
+ throws IOException {
+ if (deviceIcon == null) {
+ return null;
+ }
+
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ deviceIcon.writeToStream(byteStream);
+ return byteStream.toByteArray();
+ }
+
+ private static Icon byteArrayToIcon(byte[] bytes) throws IOException {
+ if (bytes == null) {
+ return null;
+ }
+
+ ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
+ return Icon.createFromStream(byteStream);
+ }
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index d56f17b..aebd11a 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -48,6 +48,7 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManagerInternal;
+import android.graphics.drawable.Icon;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Bundle;
@@ -281,7 +282,7 @@
createAssociation(userId, packageName, macAddress, request.getDisplayName(),
request.getDeviceProfile(), request.getAssociatedDevice(),
request.isSelfManaged(),
- callback, resultReceiver);
+ callback, resultReceiver, request.getDeviceIcon());
});
}
@@ -292,15 +293,15 @@
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
- @Nullable ResultReceiver resultReceiver) {
+ @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon) {
final int id = mAssociationStore.getNextId();
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
/* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
- /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
-
+ /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
+ deviceIcon);
// Add role holder for association (if specified) and add new association to store.
maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
}
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index c927cd0..f37e0c9 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -86,7 +86,7 @@
@NonNull AssociationRequest request, int packageUid) {
enforcePermissionForRequestingProfile(context, request.getDeviceProfile(), packageUid);
- if (request.isSelfManaged()) {
+ if (request.isSelfManaged() || request.getDeviceIcon() != null) {
enforcePermissionForRequestingSelfManaged(context, packageUid);
}
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index d80e40c..504137a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -727,10 +727,8 @@
return;
}
// Read configuration of features, libs and priv-app permissions from apex module.
- int apexPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS;
- if (android.permission.flags.Flags.apexSignaturePermissionAllowlistEnabled()) {
- apexPermissionFlag |= ALLOW_SIGNATURE_PERMISSIONS;
- }
+ int apexPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
+ | ALLOW_SIGNATURE_PERMISSIONS;
// TODO: Use a solid way to filter apex module folders?
for (File f: FileUtils.listFilesOrEmpty(Environment.getApexDirectory())) {
if (f.isFile() || f.getPath().contains("@")) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index f9197e3c..3bfbc55 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1189,8 +1189,8 @@
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "START SERVICE WHILE RESTART PENDING: " + r);
}
final boolean wasStartRequested = r.startRequested;
- r.lastActivity = SystemClock.uptimeMillis();
- r.startRequested = true;
+ mAm.mProcessStateController.setServiceLastActivityTime(r, SystemClock.uptimeMillis());
+ mAm.mProcessStateController.setStartRequested(r, true);
r.delayedStop = false;
r.fgRequired = fgRequired;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
@@ -1623,7 +1623,7 @@
FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
mAm.mBatteryStatsService.noteServiceStopRunning(uid, packageName, serviceName);
- service.startRequested = false;
+ mAm.mProcessStateController.setStartRequested(service, false);
if (service.tracker != null) {
synchronized (mAm.mProcessStats.mLock) {
service.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -1812,7 +1812,7 @@
FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
mAm.mBatteryStatsService.noteServiceStopRunning(uid, packageName, serviceName);
- r.startRequested = false;
+ mAm.mProcessStateController.setStartRequested(r, false);
if (r.tracker != null) {
synchronized (mAm.mProcessStats.mLock) {
r.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -2618,7 +2618,7 @@
}
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
r.foregroundNoti = notification;
- r.foregroundServiceType = foregroundServiceType;
+ mAm.mProcessStateController.setForegroundServiceType(r, foregroundServiceType);
if (!r.isForeground) {
final ServiceMap smap = getServiceMapLocked(r.userId);
if (smap != null) {
@@ -2643,7 +2643,7 @@
}
active.mNumActive++;
}
- r.isForeground = true;
+ mAm.mProcessStateController.setIsForegroundService(r, true);
// The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could
// be deferred, make a copy of mAllowStartForeground and
@@ -2772,7 +2772,7 @@
}
}
- r.isForeground = false;
+ mAm.mProcessStateController.setIsForegroundService(r, false);
r.mFgsExitTime = SystemClock.uptimeMillis();
synchronized (mAm.mProcessStats.mLock) {
final ServiceState stracker = r.getTracker();
@@ -3565,7 +3565,7 @@
private void maybeUpdateShortFgsTrackingLocked(ServiceRecord sr,
boolean extendTimeout) {
if (!sr.isShortFgs()) {
- sr.clearShortFgsInfo(); // Just in case we have it.
+ mAm.mProcessStateController.clearShortFgsInfo(sr); // Just in case we have it.
unscheduleShortFgsTimeoutLocked(sr);
return;
}
@@ -3581,7 +3581,7 @@
}
}
traceInstant("short FGS start/extend: ", sr);
- sr.setShortFgsInfo(SystemClock.uptimeMillis());
+ mAm.mProcessStateController.setShortFgsInfo(sr, SystemClock.uptimeMillis());
// We'll restart the timeout.
unscheduleShortFgsTimeoutLocked(sr);
@@ -3605,7 +3605,7 @@
* Stop the timeout for a ServiceRecord, if it's of a short-FGS.
*/
private void maybeStopShortFgsTimeoutLocked(ServiceRecord sr) {
- sr.clearShortFgsInfo(); // Always clear, just in case.
+ mAm.mProcessStateController.clearShortFgsInfo(sr); // Always clear, just in case.
if (!sr.isShortFgs()) {
return;
}
@@ -3993,7 +3993,7 @@
private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
maybeStopShortFgsTimeoutLocked(service);
final ProcessServiceRecord psr = service.app.mServices;
- psr.stopService(service);
+ mAm.mProcessStateController.stopService(psr, service);
psr.updateBoundClientUids();
if (service.allowlistManager) {
updateAllowlistManagerLocked(psr);
@@ -4047,7 +4047,7 @@
}
}
if (anyClientActivities != psr.hasClientActivities()) {
- psr.setHasClientActivities(anyClientActivities);
+ mAm.mProcessStateController.setHasClientActivities(psr, anyClientActivities);
if (updateLru) {
mAm.updateLruProcessLocked(psr.mApp, anyClientActivities, null);
}
@@ -4216,7 +4216,8 @@
}
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
- s.lastActivity = SystemClock.uptimeMillis();
+ mAm.mProcessStateController.setServiceLastActivityTime(s,
+ SystemClock.uptimeMillis());
if (!s.hasAutoCreateConnections()) {
// This is the first binding, let the tracker know.
synchronized (mAm.mProcessStats.mLock) {
@@ -4253,12 +4254,12 @@
if (activity != null) {
activity.addConnection(c);
}
- clientPsr.addConnection(c);
+ mAm.mProcessStateController.addConnection(clientPsr, c);
c.startAssociationIfNeeded();
// Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from
// dropping the process' adjustment level.
if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
- clientPsr.setHasAboveClient(true);
+ mAm.mProcessStateController.setHasAboveClient(clientPsr, true);
}
if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) {
s.allowlistManager = true;
@@ -4274,7 +4275,8 @@
if (s.app != null && s.app.mState != null
&& s.app.mState.getCurProcState() <= PROCESS_STATE_TOP
&& c.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)) {
- s.lastTopAlmostPerceptibleBindRequestUptimeMs = SystemClock.uptimeMillis();
+ mAm.mProcessStateController.setLastTopAlmostPerceptibleBindRequest(s,
+ SystemClock.uptimeMillis());
}
if (s.app != null) {
@@ -4312,7 +4314,8 @@
boolean needOomAdj = false;
if (c.hasFlag(Context.BIND_AUTO_CREATE)) {
- s.lastActivity = SystemClock.uptimeMillis();
+ mAm.mProcessStateController.setServiceLastActivityTime(s,
+ SystemClock.uptimeMillis());
needOomAdj = (serviceBindingOomAdjPolicy
& SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) == 0;
if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
@@ -4328,7 +4331,7 @@
if (s.app != null) {
ProcessServiceRecord servicePsr = s.app.mServices;
if (c.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
- servicePsr.setTreatLikeActivity(true);
+ mAm.mProcessStateController.setTreatLikeActivity(servicePsr, true);
}
if (s.allowlistManager) {
servicePsr.mAllowlistManager = true;
@@ -4575,7 +4578,9 @@
}
// This could have made the service less important.
if (r.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
- psr.setTreatLikeActivity(true);
+ // TODO(b/367545398): the following line is a bug. A service unbind
+ // should potentially lower a process's importance, not elevate it.
+ mAm.mProcessStateController.setTreatLikeActivity(psr, true);
mAm.updateLruProcessLocked(app, true, null);
}
// If the bindee is more important than the binder, we may skip the OomAdjuster.
@@ -5165,8 +5170,9 @@
}
if (r.app != null) {
psr = r.app.mServices;
- psr.startExecutingService(r);
- psr.setExecServicesFg(psr.shouldExecServicesFg() || fg);
+ mAm.mProcessStateController.startExecutingService(psr, r);
+ mAm.mProcessStateController.setExecServicesFg(psr,
+ psr.shouldExecServicesFg() || fg);
if (timeoutNeeded && psr.numberOfExecutingServices() == 1) {
if (!shouldSkipTimeout) {
scheduleServiceTimeoutLocked(r.app);
@@ -5178,7 +5184,7 @@
} else if (r.app != null && fg) {
psr = r.app.mServices;
if (!psr.shouldExecServicesFg()) {
- psr.setExecServicesFg(true);
+ mAm.mProcessStateController.setExecServicesFg(psr, true);
if (timeoutNeeded) {
if (!shouldSkipTimeout) {
scheduleServiceTimeoutLocked(r.app);
@@ -6023,11 +6029,13 @@
Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid
+ ", ProcessRecord.uid = " + app.uid);
r.setProcess(app, thread, pid, uidRecord);
- r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
+ final long now = SystemClock.uptimeMillis();
+ r.restartTime = now;
+ mAm.mProcessStateController.setServiceLastActivityTime(r, now);
final boolean skipOomAdj = (serviceBindingOomAdjPolicy
& SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) != 0;
final ProcessServiceRecord psr = app.mServices;
- final boolean newService = psr.startService(r);
+ final boolean newService = mAm.mProcessStateController.startService(psr, r);
bumpServiceExecutingLocked(r, execInFg, "create",
OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */,
skipOomAdj /* skipTimeoutIfPossible */);
@@ -6086,7 +6094,7 @@
// Cleanup.
if (newService) {
- psr.stopService(r);
+ mAm.mProcessStateController.stopService(psr, r);
r.setProcess(null, null, 0, null);
}
@@ -6431,7 +6439,7 @@
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
}
- r.isForeground = false;
+ mAm.mProcessStateController.setIsForegroundService(r, false);
r.mFgsNotificationWasDeferred = false;
dropFgsNotificationStateLocked(r);
r.foregroundId = 0;
@@ -6582,9 +6590,9 @@
}
if (b.client != skipApp) {
final ProcessServiceRecord psr = b.client.mServices;
- psr.removeConnection(c);
+ mAm.mProcessStateController.removeConnection(psr, c);
if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
- psr.updateHasAboveClientLocked();
+ mAm.mProcessStateController.updateHasAboveClientLocked(psr);
}
// If this connection requested allowlist management, see if we should
// now clear that state.
@@ -6600,7 +6608,7 @@
}
// And for almost perceptible exceptions.
if (c.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)) {
- psr.updateHasTopStartedAlmostPerceptibleServices();
+ mAm.mProcessStateController.updateHasTopStartedAlmostPerceptibleServices(psr);
}
if (s.app != null) {
updateServiceClientActivitiesLocked(s.app.mServices, c, true);
@@ -6799,8 +6807,8 @@
final ProcessServiceRecord psr = r.app.mServices;
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"Nesting at 0 of " + r.shortInstanceName);
- psr.setExecServicesFg(false);
- psr.stopExecutingService(r);
+ mAm.mProcessStateController.setExecServicesFg(psr, false);
+ mAm.mProcessStateController.stopExecutingService(psr, r);
if (psr.numberOfExecutingServices() == 0) {
if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
"No more executingServices of " + r.shortInstanceName);
@@ -6809,7 +6817,7 @@
// Need to re-evaluate whether the app still needs to be in the foreground.
for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
if (psr.getExecutingServiceAt(i).executeFg) {
- psr.setExecServicesFg(true);
+ mAm.mProcessStateController.setExecServicesFg(psr, true);
break;
}
}
@@ -6822,9 +6830,9 @@
}
if (oomAdjReason != OOM_ADJ_REASON_NONE) {
if (enqueueOomAdj) {
- mAm.enqueueOomAdjTargetLocked(r.app);
+ mAm.mProcessStateController.enqueueUpdateTarget(r.app);
} else {
- mAm.updateOomAdjLocked(r.app, oomAdjReason);
+ mAm.mProcessStateController.runUpdate(r.app, oomAdjReason);
}
} else {
// Skip oom adj if it wasn't bumped during the bumpServiceExecutingLocked()
@@ -7209,8 +7217,7 @@
removeConnectionLocked(r, app, null, true);
}
updateServiceConnectionActivitiesLocked(psr);
- psr.removeAllConnections();
- psr.removeAllSdkSandboxConnections();
+ mAm.mProcessStateController.removeAllConnections(psr);
psr.mAllowlistManager = false;
@@ -7220,7 +7227,7 @@
mAm.mBatteryStatsService.noteServiceStopLaunch(sr.appInfo.uid, sr.name.getPackageName(),
sr.name.getClassName());
if (sr.app != app && sr.app != null && !sr.app.isPersistent()) {
- sr.app.mServices.stopService(sr);
+ mAm.mProcessStateController.stopService(psr, sr);
sr.app.mServices.updateBoundClientUids();
}
sr.setProcess(null, null, 0, null);
@@ -7290,7 +7297,7 @@
// Unless the process is persistent, this process record is going away,
// so make sure the service is cleaned out of it.
if (!app.isPersistent()) {
- psr.stopService(sr);
+ mAm.mProcessStateController.stopService(psr, sr);
psr.updateBoundClientUids();
}
@@ -7331,7 +7338,7 @@
// Update to stopped state because the explicit start is gone. The service is
// scheduled to restart for other reason (e.g. connections) so we don't bring
// down it.
- sr.startRequested = false;
+ mAm.mProcessStateController.setStartRequested(sr, false);
if (sr.tracker != null) {
synchronized (mAm.mProcessStats.mLock) {
sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -7345,7 +7352,7 @@
mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
if (!allowRestart) {
- psr.stopAllServices();
+ mAm.mProcessStateController.stopAllServices(psr);
psr.clearBoundClientUids();
// Make sure there are no more restarting services for this process.
@@ -7387,7 +7394,7 @@
}
}
- psr.stopAllExecutingServices();
+ mAm.mProcessStateController.stopAllExecutingServices(psr);
psr.noteScheduleServiceTimeoutPending(false);
}
@@ -9213,14 +9220,14 @@
new ForegroundServiceDelegation(options, connection);
r.mFgsDelegation = delegation;
mFgsDelegations.put(delegation, r);
- r.isForeground = true;
+ mAm.mProcessStateController.setIsForegroundService(r, true);
r.mFgsEnterTime = SystemClock.uptimeMillis();
- r.foregroundServiceType = options.mForegroundServiceTypes;
+ mAm.mProcessStateController.setForegroundServiceType(r, options.mForegroundServiceTypes);
r.updateOomAdjSeq();
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
BackgroundStartPrivileges.NONE, false /* isBindService */);
final ProcessServiceRecord psr = callerApp.mServices;
- final boolean newService = psr.startService(r);
+ final boolean newService = mAm.mProcessStateController.startService(psr, r);
// updateOomAdj.
updateServiceForegroundLocked(psr, /* oomAdj= */ true);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 74437cd..bcca20b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -626,6 +626,8 @@
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSZ");
OomAdjuster mOomAdjuster;
+ @GuardedBy("this")
+ ProcessStateController mProcessStateController;
static final String EXTRA_TITLE = "android.intent.extra.TITLE";
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
@@ -1958,7 +1960,7 @@
new HostingRecord(HostingRecord.HOSTING_TYPE_SYSTEM));
app.setPersistent(true);
app.setPid(MY_PID);
- app.mState.setMaxAdj(ProcessList.SYSTEM_ADJ);
+ mProcessStateController.setMaxAdj(app, ProcessList.SYSTEM_ADJ);
app.makeActive(new ApplicationThreadDeferred(mSystemThread.getApplicationThread()),
mProcessStats);
app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
@@ -2394,9 +2396,11 @@
mProcessList.init(this, activeUids, mPlatformCompat);
mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null);
mPhantomProcessList = new PhantomProcessList(this);
- mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
- ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread)
- : new OomAdjuster(this, mProcessList, activeUids, handlerThread);
+ mProcessStateController = new ProcessStateController.Builder(this, mProcessList, activeUids)
+ .setHandlerThread(handlerThread)
+ .useModernOomAdjuster(mConstants.ENABLE_NEW_OOMADJ)
+ .build();
+ mOomAdjuster = mProcessStateController.getOomAdjuster();
mIntentFirewall = injector.getIntentFirewall();
mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
@@ -2459,9 +2463,10 @@
mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(),
new LowMemDetector(this));
mPhantomProcessList = new PhantomProcessList(this);
- mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
- ? new OomAdjusterModernImpl(this, mProcessList, activeUids)
- : new OomAdjuster(this, mProcessList, activeUids);
+ mProcessStateController = new ProcessStateController.Builder(this, mProcessList, activeUids)
+ .useModernOomAdjuster(mConstants.ENABLE_NEW_OOMADJ)
+ .build();
+ mOomAdjuster = mProcessStateController.getOomAdjuster();
mBroadcastQueue = mInjector.getBroadcastQueue(this);
mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
@@ -4574,7 +4579,7 @@
EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
synchronized (mProcLock) {
- mOomAdjuster.setAttachingProcessStatesLSP(app);
+ mProcessStateController.setAttachingProcessStatesLSP(app);
clearProcessForegroundLocked(app);
app.setDebugging(false);
app.setKilledByAm(false);
@@ -4770,7 +4775,7 @@
app.makeActive(new ApplicationThreadDeferred(thread), mProcessStats);
checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
}
- app.setPendingFinishAttach(true);
+ mProcessStateController.setPendingFinishAttach(app, true);
updateLruProcessLocked(app, false, null);
checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked");
@@ -4854,7 +4859,7 @@
synchronized (this) {
// Mark the finish attach application phase as completed
- app.setPendingFinishAttach(false);
+ mProcessStateController.setPendingFinishAttach(app, false);
final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
final String processName = app.processName;
@@ -5009,7 +5014,7 @@
// If another follow up update is needed, it will be scheduled by OomAdjuster.
mHandler.removeMessages(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG);
synchronized (this) {
- mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
}
}
@@ -5804,10 +5809,10 @@
if (pr == null) {
return;
}
- pr.mState.setForcingToImportant(null);
+ mProcessStateController.setForcingToImportant(pr, null);
clearProcessForegroundLocked(pr);
}
- updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
+ mProcessStateController.runUpdate(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -5830,7 +5835,7 @@
oldToken.token.unlinkToDeath(oldToken, 0);
mImportantProcesses.remove(pid);
if (pr != null) {
- pr.mState.setForcingToImportant(null);
+ mProcessStateController.setForcingToImportant(pr, null);
}
changed = true;
}
@@ -5844,7 +5849,7 @@
try {
token.linkToDeath(newToken, 0);
mImportantProcesses.put(pid, newToken);
- pr.mState.setForcingToImportant(newToken);
+ mProcessStateController.setForcingToImportant(pr, newToken);
changed = true;
} catch (RemoteException e) {
// If the process died while doing this, we will later
@@ -5854,7 +5859,7 @@
}
if (changed) {
- updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
+ mProcessStateController.runUpdate(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
}
@@ -7274,7 +7279,7 @@
if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
app.setPersistent(true);
- app.mState.setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(app, ProcessList.PERSISTENT_PROC_ADJ);
}
if (app.getThread() == null && mPersistentStartingProcesses.indexOf(app) < 0) {
mPersistentStartingProcesses.add(app);
@@ -7377,7 +7382,7 @@
mServices.updateScreenStateLocked(isAwake);
reportCurWakefulnessUsageEvent();
mActivityTaskManager.onScreenAwakeChanged(isAwake);
- mOomAdjuster.onWakefulnessChanged(wakefulness);
+ mProcessStateController.setWakefulness(wakefulness);
updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
}
@@ -8346,16 +8351,10 @@
Slog.w(TAG, "setHasTopUi called on unknown pid: " + pid);
return;
}
- if (pr.mState.hasTopUi() != hasTopUi) {
- if (DEBUG_OOM_ADJ) {
- Slog.d(TAG, "Setting hasTopUi=" + hasTopUi + " for pid=" + pid);
- }
- pr.mState.setHasTopUi(hasTopUi);
- changed = true;
- }
+ changed = mProcessStateController.setHasTopUi(pr, hasTopUi);
}
if (changed) {
- updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
+ mProcessStateController.runUpdate(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
} finally {
@@ -14084,10 +14083,14 @@
proc.setInFullBackup(true);
}
r.app = proc;
+ // TODO(b/369300367): This code suggests there could be a previous backup being
+ // replaced here, but an OomAdjsuter update is not triggered on the previous app
+ // (whose state will change from being removed from mBackupTargets).
final BackupRecord backupTarget = mBackupTargets.get(targetUserId);
oldBackupUid = backupTarget != null ? backupTarget.appInfo.uid : -1;
newBackupUid = proc.isInFullBackup() ? r.appInfo.uid : -1;
mBackupTargets.put(targetUserId, r);
+ mProcessStateController.setBackupTarget(proc, targetUserId);
proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
@@ -14141,6 +14144,7 @@
}
mBackupTargets.removeAt(indexOfKey);
}
+ mProcessStateController.stopBackupTarget(userId);
}
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
@@ -14219,6 +14223,8 @@
// Not backing this app up any more; reset its OOM adjustment
final ProcessRecord proc = backupTarget.app;
+ // TODO(b/369300367): Triggering the update before the state is actually set
+ // seems wrong.
updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
proc.setInFullBackup(false);
proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
@@ -14237,6 +14243,7 @@
}
} finally {
mBackupTargets.delete(userId);
+ mProcessStateController.stopBackupTarget(userId);
}
}
@@ -15309,7 +15316,8 @@
proc.info.packageName, proc.info.uid, proc.getPid(), isForeground);
}
}
- psr.setHasForegroundServices(isForeground, fgServiceTypes, hasTypeNoneFgs);
+ mProcessStateController.setHasForegroundServices(psr, isForeground, fgServiceTypes,
+ hasTypeNoneFgs);
ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
proc.info.uid);
if (isForeground) {
@@ -15340,7 +15348,7 @@
ProcessChangeItem.CHANGE_FOREGROUND_SERVICES, fgServiceTypes);
}
if (oomAdj) {
- updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
+ mProcessStateController.runUpdate(proc, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -15390,7 +15398,7 @@
*/
@GuardedBy("this")
void enqueueOomAdjTargetLocked(ProcessRecord app) {
- mOomAdjuster.enqueueOomAdjTargetLocked(app);
+ mProcessStateController.enqueueUpdateTarget(app);
}
/**
@@ -15398,7 +15406,7 @@
*/
@GuardedBy("this")
void removeOomAdjTargetLocked(ProcessRecord app, boolean procDied) {
- mOomAdjuster.removeOomAdjTargetLocked(app, procDied);
+ mProcessStateController.removeUpdateTarget(app, procDied);
}
/**
@@ -15407,7 +15415,7 @@
*/
@GuardedBy("this")
void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
- mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
+ mProcessStateController.runPendingUpdate(oomAdjReason);
}
static final class ProcStatsRunnable implements Runnable {
@@ -15426,7 +15434,7 @@
@GuardedBy("this")
final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
- mOomAdjuster.updateOomAdjLocked(oomAdjReason);
+ mProcessStateController.runFullUpdate(oomAdjReason);
}
/**
@@ -15438,7 +15446,7 @@
*/
@GuardedBy("this")
final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) {
- return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason);
+ return mProcessStateController.runUpdate(app, oomAdjReason);
}
@Override
@@ -15703,7 +15711,7 @@
@GuardedBy({"this", "mProcLock"})
final void setUidTempAllowlistStateLSP(int uid, boolean onAllowlist) {
- mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
+ mProcessStateController.setUidTempAllowlistStateLSP(uid, onAllowlist);
}
private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
@@ -16753,12 +16761,9 @@
return;
}
}
- if (pr.mState.hasOverlayUi() == hasOverlayUi) {
- return;
+ if (mProcessStateController.setHasOverlayUi(pr, hasOverlayUi)) {
+ mProcessStateController.runUpdate(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
- pr.mState.setHasOverlayUi(hasOverlayUi);
- //Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
- updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index dda48ad..4f2d69e 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1356,6 +1356,7 @@
@GuardedBy("mService")
void setMemFactorOverrideLocked(@MemFactor int factor) {
mMemFactorOverride = factor;
+ mService.mProcessStateController.setIsLastMemoryLevelNormal(isLastMemoryLevelNormal());
}
@GuardedBy({"mService", "mProcLock"})
@@ -1423,6 +1424,7 @@
}
mLastMemoryLevel = memFactor;
+ mService.mProcessStateController.setIsLastMemoryLevelNormal(isLastMemoryLevelNormal());
mLastNumProcesses = mService.mProcessList.getLruSizeLOSP();
// Dispatch UI_HIDDEN to processes that need it
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index da40826..221938a 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -325,7 +325,8 @@
final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
boolean success = !serviceBindingOomAdjPolicy()
|| mService.mOomAdjuster.evaluateProviderConnectionAdd(r, cpr.proc)
- ? mService.updateOomAdjLocked(cpr.proc, OOM_ADJ_REASON_GET_PROVIDER)
+ ? mService.mProcessStateController.runUpdate(cpr.proc,
+ OOM_ADJ_REASON_GET_PROVIDER)
: true;
// XXX things have changed so updateOomAdjLocked doesn't actually tell us
// if the process has been successfully adjusted. So to reduce races with
@@ -534,10 +535,9 @@
if (ActivityManagerDebugConfig.DEBUG_PROVIDER) {
Slog.d(TAG, "Installing in existing process " + proc);
}
- final ProcessProviderRecord pr = proc.mProviders;
- if (!pr.hasProvider(cpi.name)) {
+ if (mService.mProcessStateController.addPublishedProvider(proc,
+ cpi.name, cpr)) {
checkTime(startTime, "getContentProviderImpl: scheduling install");
- pr.installProvider(cpi.name, cpr);
mService.mOomAdjuster.unfreezeTemporarily(proc,
CachedAppOptimizer.UNFREEZE_REASON_GET_PROVIDER);
try {
@@ -881,7 +881,8 @@
ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name);
ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
if (localCpr.hasExternalProcessHandles()) {
- if (localCpr.removeExternalProcessHandleLocked(token)) {
+ if (mService.mProcessStateController.removeExternalProviderClient(localCpr,
+ token)) {
mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
} else {
Slog.e(TAG, "Attempt to remove content provider " + localCpr
@@ -1447,7 +1448,8 @@
String callingPackage, String callingTag, boolean stable, boolean updateLru,
long startTime, ProcessList processList, @UserIdInt int expectedUserId) {
if (r == null) {
- cpr.addExternalProcessHandleLocked(externalProcessToken, callingUid, callingTag);
+ mService.mProcessStateController.addExternalProviderClient(cpr, externalProcessToken,
+ callingUid, callingTag);
return null;
}
@@ -1470,7 +1472,7 @@
if (cpr.proc != null) {
cpr.proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_PROVIDER);
}
- pr.addProviderConnection(conn);
+ mService.mProcessStateController.addProviderConnection(r, conn);
mService.startAssociationLocked(r.uid, r.processName, r.mState.getCurProcState(),
cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
if (updateLru && cpr.proc != null
@@ -1493,7 +1495,8 @@
ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable,
boolean enforceDelay, boolean updateOomAdj) {
if (conn == null) {
- cpr.removeExternalProcessHandleLocked(externalProcessToken);
+ mService.mProcessStateController.removeExternalProviderClient(cpr,
+ externalProcessToken);
return false;
}
@@ -1537,14 +1540,15 @@
if (cpr.proc != null && !hasProviderConnectionLocked(cpr.proc)) {
cpr.proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_PROVIDER);
}
- conn.client.mProviders.removeProviderConnection(conn);
+ mService.mProcessStateController.removeProviderConnection(conn.client, conn);
if (conn.client.mState.getSetProcState()
< ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
// The client is more important than last activity -- note the time this
// is happening, so we keep the old provider process around a bit as last
// activity to avoid thrashing it.
if (cpr.proc != null) {
- cpr.proc.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
+ mService.mProcessStateController.setLastProviderTime(cpr.proc,
+ SystemClock.uptimeMillis());
}
}
mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
@@ -1821,7 +1825,7 @@
}
}
if (removed && cpr.proc != null) {
- cpr.proc.mProviders.removeProvider(cpr.info.name);
+ mService.mProcessStateController.removePublishedProvider(cpr.proc, cpr.info.name);
}
}
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 2a30ad0..61079fc 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -54,8 +54,9 @@
per-file *Permission* = patb@google.com
per-file *Package* = patb@google.com
-# OOM Adjuster
+# OOM Adjuster & ProcessStateController
per-file *Oom* = file:/OOM_ADJUSTER_OWNERS
+per-file ProcessStateController.java = file:/OOM_ADJUSTER_OWNERS
# Miscellaneous
per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com, tedbauer@google.com
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 4073ab8..776a345 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -130,6 +130,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
@@ -376,6 +377,7 @@
final ActivityManagerService mService;
final Injector mInjector;
+ final GlobalState mGlobalState;
final ProcessList mProcessList;
final ActivityManagerGlobalLock mProcLock;
@@ -470,15 +472,23 @@
}
+ // TODO(b/346822474): hook up global state usage.
+ interface GlobalState {
+ /** Is device's screen on. */
+ boolean isAwake();
+
+ /** What process is running a backup for a given userId. */
+ ProcessRecord getBackupTarget(@UserIdInt int userId);
+
+ /** Is memory level normal since last evaluation. */
+ boolean isLastMemoryLevelNormal();
+ }
+
boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
ApplicationInfo app, boolean defaultValue) {
return mInjector.isChangeEnabled(cachedCompatChangeId, app, defaultValue);
}
- OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
- this(service, processList, activeUids, createAdjusterThread());
- }
-
static ServiceThread createAdjusterThread() {
// The process group is usually critical to the response time of foreground app, so the
// setter should apply it as soon as possible.
@@ -489,18 +499,9 @@
}
OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
- ServiceThread adjusterThread) {
- this(service, processList, activeUids, adjusterThread, new Injector());
- }
-
- OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
- Injector injector) {
- this(service, processList, activeUids, createAdjusterThread(), injector);
- }
-
- OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids,
- ServiceThread adjusterThread, Injector injector) {
+ ServiceThread adjusterThread, GlobalState globalState, Injector injector) {
mService = service;
+ mGlobalState = globalState;
mInjector = injector;
mProcessList = processList;
mProcLock = service.mProcLock;
@@ -1816,9 +1817,36 @@
}
}
+ private boolean isDeviceFullyAwake() {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ return mGlobalState.isAwake();
+ } else {
+ return mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE;
+ }
+ }
+
private boolean isScreenOnOrAnimatingLocked(ProcessStateRecord state) {
- return mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE
- || state.isRunningRemoteAnimation();
+ return isDeviceFullyAwake() || state.isRunningRemoteAnimation();
+ }
+
+ private boolean isBackupProcess(ProcessRecord app) {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ return app == mGlobalState.getBackupTarget(app.userId);
+ } else {
+ final BackupRecord backupTarget = mService.mBackupTargets.get(app.userId);
+ if (backupTarget == null) {
+ return false;
+ }
+ return app == backupTarget.app;
+ }
+ }
+
+ private boolean isLastMemoryLevelNormal() {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ return mGlobalState.isLastMemoryLevelNormal();
+ } else {
+ return mService.mAppProfiler.isLastMemoryLevelNormal();
+ }
}
@GuardedBy({"mService", "mProcLock"})
@@ -2259,8 +2287,7 @@
state.setHasStartedServices(false);
state.setAdjSeq(mAdjSeq);
- final BackupRecord backupTarget = mService.mBackupTargets.get(app.userId);
- if (backupTarget != null && app == backupTarget.app) {
+ if (isBackupProcess(app)) {
// If possible we want to avoid killing apps while they're being backed up
if (adj > BACKUP_APP_ADJ) {
if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
@@ -2526,8 +2553,7 @@
double cachedRestoreThreshold =
mProcessList.getCachedRestoreThresholdKb() * thresholdModifier;
- if (!mService.mAppProfiler.isLastMemoryLevelNormal()
- && lastPssOrRss >= cachedRestoreThreshold) {
+ if (!isLastMemoryLevelNormal() && lastPssOrRss >= cachedRestoreThreshold) {
state.setServiceHighRam(true);
state.setServiceB(true);
//Slog.i(TAG, "ADJ " + app + " high ram!");
@@ -2621,7 +2647,7 @@
// Put bound foreground services in a special sched group for additional
// restrictions on screen off
if (state.getCurProcState() >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
+ && !isDeviceFullyAwake()
&& !state.shouldScheduleLikeTopApp()) {
if (schedGroup > SCHED_GROUP_RESTRICTED) {
schedGroup = SCHED_GROUP_RESTRICTED;
@@ -2910,8 +2936,7 @@
clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
} else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- } else if (mService.mWakefulness.get()
- == PowerManagerInternal.WAKEFULNESS_AWAKE
+ } else if (isDeviceFullyAwake()
&& cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) {
clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else {
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index fb1c2e9..e452c45 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -756,18 +756,9 @@
new ComputeConnectionsConsumer();
OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
- ActiveUids activeUids) {
- this(service, processList, activeUids, createAdjusterThread());
- }
-
- OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
- ActiveUids activeUids, ServiceThread adjusterThread) {
- super(service, processList, activeUids, adjusterThread);
- }
-
- OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
- ActiveUids activeUids, Injector injector) {
- super(service, processList, activeUids, injector);
+ ActiveUids activeUids, ServiceThread adjusterThread, GlobalState globalState,
+ Injector injector) {
+ super(service, processList, activeUids, adjusterThread, globalState, injector);
}
private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes(
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 57922d5..2485626 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3439,12 +3439,12 @@
state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
state.setSetSchedGroup(ProcessList.SCHED_GROUP_DEFAULT);
r.setPersistent(true);
- state.setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ);
+ mService.mProcessStateController.setMaxAdj(r, ProcessList.PERSISTENT_PROC_ADJ);
}
if (isolated && isolatedUid != 0) {
// Special case for startIsolatedProcess (internal only) - assume the process
// is required by the system server to prevent it being killed.
- state.setMaxAdj(ProcessList.PERSISTENT_SERVICE_ADJ);
+ mService.mProcessStateController.setMaxAdj(r, ProcessList.PERSISTENT_SERVICE_ADJ);
}
addProcessNameLocked(r);
return r;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 3e71d00..b51db13 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -1192,7 +1193,7 @@
setWaitingToKill(null);
mState.onCleanupApplicationRecordLSP();
- mServices.onCleanupApplicationRecordLocked();
+ mService.mProcessStateController.onCleanupApplicationRecord(mServices);
mReceivers.onCleanupApplicationRecordLocked();
mService.mOomAdjuster.onProcessEndLocked(this);
@@ -1638,7 +1639,7 @@
updateProcessInfo(false /* updateServiceConnectionActivities */,
true /* activityChange */, true /* updateOomAdj */);
setPendingUiClean(true);
- mState.setHasShownUi(true);
+ mService.mProcessStateController.setHasShownUi(this, true);
mState.forceProcessStateUpTo(topProcessState);
}
}
@@ -1657,7 +1658,10 @@
return;
}
synchronized (mService) {
- mState.setRunningRemoteAnimation(runningRemoteAnimation);
+ if (mService.mProcessStateController.setRunningRemoteAnimation(this,
+ runningRemoteAnimation)) {
+ mService.mProcessStateController.runUpdate(this, OOM_ADJ_REASON_UI_VISIBILITY);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ProcessStateController.java b/services/core/java/com/android/server/am/ProcessStateController.java
new file mode 100644
index 0000000..428df23
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessStateController.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2024 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.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.PowerManagerInternal;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.ServiceThread;
+
+/**
+ * ProcessStateController is responsible for maintaining state that can affect the OomAdjuster
+ * computations of a process. Any state that can affect a process's importance must be set by
+ * only ProcessStateController.
+ */
+public class ProcessStateController {
+ public static String TAG = "ProcessStateController";
+
+ private final OomAdjuster mOomAdjuster;
+
+ private final GlobalState mGlobalState = new GlobalState();
+
+ private ProcessStateController(ActivityManagerService ams, ProcessList processList,
+ ActiveUids activeUids, ServiceThread handlerThread, OomAdjuster.Injector oomAdjInjector,
+ boolean useOomAdjusterModernImpl) {
+ mOomAdjuster = useOomAdjusterModernImpl
+ ? new OomAdjusterModernImpl(ams, processList, activeUids, handlerThread,
+ mGlobalState, oomAdjInjector)
+ : new OomAdjuster(ams, processList, activeUids, handlerThread, mGlobalState,
+ oomAdjInjector);
+ }
+
+ /**
+ * Get the instance of OomAdjuster that ProcessStateController is using.
+ * Must only be interacted with while holding the ActivityManagerService lock.
+ */
+ public OomAdjuster getOomAdjuster() {
+ return mOomAdjuster;
+ }
+
+ /**
+ * Add a process to evaluated the next time an update is run.
+ */
+ public void enqueueUpdateTarget(@NonNull ProcessRecord proc) {
+ mOomAdjuster.enqueueOomAdjTargetLocked(proc);
+ }
+
+ /**
+ * Remove a process that was added by {@link #enqueueUpdateTarget}.
+ */
+ public void removeUpdateTarget(@NonNull ProcessRecord proc, boolean procDied) {
+ mOomAdjuster.removeOomAdjTargetLocked(proc, procDied);
+ }
+
+ /**
+ * Trigger an update on a single process (and any processes that have been enqueued with
+ * {@link #enqueueUpdateTarget}).
+ */
+ public boolean runUpdate(@NonNull ProcessRecord proc,
+ @ActivityManagerInternal.OomAdjReason int oomAdjReason) {
+ return mOomAdjuster.updateOomAdjLocked(proc, oomAdjReason);
+ }
+
+ /**
+ * Trigger an update on all processes that have been enqueued with {@link #enqueueUpdateTarget}.
+ */
+ public void runPendingUpdate(@ActivityManagerInternal.OomAdjReason int oomAdjReason) {
+ mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
+ }
+
+ /**
+ * Trigger an update on all processes.
+ */
+ public void runFullUpdate(@ActivityManagerInternal.OomAdjReason int oomAdjReason) {
+ mOomAdjuster.updateOomAdjLocked(oomAdjReason);
+ }
+
+ /**
+ * Trigger an update on any processes that have been marked for follow up during a previous
+ * update.
+ */
+ public void runFollowUpUpdate() {
+ mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ }
+
+ private static class GlobalState implements OomAdjuster.GlobalState {
+ public boolean isAwake = true;
+ // TODO(b/369300367): Maintaining global state for backup processes is a bit convoluted.
+ // ideally the state gets migrated to ProcessStateRecord.
+ public final SparseArray<ProcessRecord> backupTargets = new SparseArray<>();
+ public boolean isLastMemoryLevelNormal = true;
+
+ public boolean isAwake() {
+ return isAwake;
+ }
+
+ public ProcessRecord getBackupTarget(@UserIdInt int userId) {
+ return backupTargets.get(userId);
+ }
+
+ public boolean isLastMemoryLevelNormal() {
+ return isLastMemoryLevelNormal;
+ }
+ }
+
+ /*************************** Global State Events ***************************/
+ /**
+ * Set which process state Top processes should get.
+ */
+ public void setTopProcessState(@ActivityManager.ProcessState int procState) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set whether to give Top processes the Top sched group.
+ */
+ public void setUseTopSchedGroupForTopProcess(boolean useTopSchedGroup) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set the Top process.
+ */
+ public void setTopApp(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set which process is considered the Home process, if any.
+ */
+ public void setHomeProcess(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set which process is considered the Heavy Weight process, if any.
+ */
+ public void setHeavyWeightProcess(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set which process is showing UI while the screen is off, if any.
+ */
+ public void setVisibleDozeUiProcess(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set which process is considered the Previous process, if any.
+ */
+ public void setPreviousProcess(@Nullable ProcessRecord proc) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set what wakefulness state the screen is in.
+ */
+ public void setWakefulness(int wakefulness) {
+ mGlobalState.isAwake = (wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mOomAdjuster.onWakefulnessChanged(wakefulness);
+ }
+
+ /**
+ * Set for a given user what process is currently running a backup, if any.
+ */
+ public void setBackupTarget(@NonNull ProcessRecord proc, @UserIdInt int userId) {
+ mGlobalState.backupTargets.put(userId, proc);
+ }
+
+ /**
+ * No longer consider any process running a backup for a given user.
+ */
+ public void stopBackupTarget(@UserIdInt int userId) {
+ mGlobalState.backupTargets.delete(userId);
+ }
+
+ /**
+ * Set whether the last known memory level is normal.
+ */
+ public void setIsLastMemoryLevelNormal(boolean isMemoryNormal) {
+ mGlobalState.isLastMemoryLevelNormal = isMemoryNormal;
+ }
+
+ /***************************** UID State Events ****************************/
+ /**
+ * Set a UID as temp allowlisted.
+ */
+ public void setUidTempAllowlistStateLSP(int uid, boolean allowList) {
+ mOomAdjuster.setUidTempAllowlistStateLSP(uid, allowList);
+ }
+
+ /*********************** Process Miscellaneous Events **********************/
+ /**
+ * Set the maximum adj score a process can be assigned.
+ */
+ public void setMaxAdj(@NonNull ProcessRecord proc, int adj) {
+ proc.mState.setMaxAdj(adj);
+ }
+
+ /**
+ * Initialize a process that is being attached.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ public void setAttachingProcessStatesLSP(@NonNull ProcessRecord proc) {
+ mOomAdjuster.setAttachingProcessStatesLSP(proc);
+ }
+
+ /**
+ * Note whether a process is pending attach or not.
+ */
+ public void setPendingFinishAttach(@NonNull ProcessRecord proc, boolean pendingFinishAttach) {
+ proc.setPendingFinishAttach(pendingFinishAttach);
+ }
+
+ /**
+ * Set what sched group to grant a process due to running a broadcast.
+ * {@link ProcessList.SCHED_GROUP_UNDEFINED} means the process is not running a broadcast.
+ */
+ public void setBroadcastSchedGroup(@NonNull ProcessRecord proc, int schedGroup) {
+ // TODO(b/302575389): Migrate state pulled from BroadcastQueue to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /********************* Process Visibility State Events *********************/
+ /**
+ * Note whether a process has Top UI or not.
+ *
+ * @return true if the state changed, otherwise returns false.
+ */
+ public boolean setHasTopUi(@NonNull ProcessRecord proc, boolean hasTopUi) {
+ if (proc.mState.hasTopUi() == hasTopUi) return false;
+ if (DEBUG_OOM_ADJ) {
+ Slog.d(TAG, "Setting hasTopUi=" + hasTopUi + " for pid=" + proc.getPid());
+ }
+ proc.mState.setHasTopUi(hasTopUi);
+ return true;
+ }
+
+ /**
+ * Note whether a process is displaying Overlay UI or not.
+ *
+ * @return true if the state changed, otherwise returns false.
+ */
+ public boolean setHasOverlayUi(@NonNull ProcessRecord proc, boolean hasOverlayUi) {
+ if (proc.mState.hasOverlayUi() == hasOverlayUi) return false;
+ proc.mState.setHasOverlayUi(hasOverlayUi);
+ return true;
+ }
+
+
+ /**
+ * Note whether a process is running a remote animation.
+ *
+ * @return true if the state changed, otherwise returns false.
+ */
+ public boolean setRunningRemoteAnimation(@NonNull ProcessRecord proc,
+ boolean runningRemoteAnimation) {
+ if (proc.mState.isRunningRemoteAnimation() == runningRemoteAnimation) return false;
+ if (DEBUG_OOM_ADJ) {
+ Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
+ + " for pid=" + proc.getPid());
+ }
+ proc.mState.setRunningRemoteAnimation(runningRemoteAnimation);
+ return true;
+ }
+
+ /**
+ * Note that the process is showing a toast.
+ */
+ public void setForcingToImportant(@NonNull ProcessRecord proc,
+ @Nullable Object forcingToImportant) {
+ if (proc.mState.getForcingToImportant() == forcingToImportant) return;
+ proc.mState.setForcingToImportant(forcingToImportant);
+ }
+
+ /**
+ * Note that the process has shown UI at some point in its life.
+ */
+ public void setHasShownUi(@NonNull ProcessRecord proc, boolean hasShownUi) {
+ // This arguably should be turned into an internal state of OomAdjuster.
+ if (proc.mState.hasShownUi() == hasShownUi) return;
+ proc.mState.setHasShownUi(hasShownUi);
+ }
+
+ /**
+ * Note whether the process has an activity or not.
+ */
+ public void setHasActivity(@NonNull ProcessRecord proc, boolean hasActivity) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ // Possibly not needed, maybe can use ActivityStateFlags.
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Note whether the process has a visibly activity or not.
+ */
+ public void setHasVisibleActivity(@NonNull ProcessRecord proc, boolean hasVisibleActivity) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ // maybe used ActivityStateFlags instead.
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Set the Activity State Flags for a process.
+ */
+ public void setActivityStateFlags(@NonNull ProcessRecord proc, int flags) {
+ // TODO(b/302575389): Migrate state pulled from ATMS to a pushed model
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /********************** Content Provider State Events **********************/
+ /**
+ * Note that a process is hosting a content provider.
+ */
+ public boolean addPublishedProvider(@NonNull ProcessRecord proc, String name,
+ ContentProviderRecord cpr) {
+ final ProcessProviderRecord providers = proc.mProviders;
+ if (providers.hasProvider(name)) return false;
+ providers.installProvider(name, cpr);
+ return true;
+ }
+
+ /**
+ * Remove a published content provider from a process.
+ */
+ public void removePublishedProvider(@NonNull ProcessRecord proc, String name) {
+ final ProcessProviderRecord providers = proc.mProviders;
+ providers.removeProvider(name);
+ }
+
+ /**
+ * Note that a content provider has an external client.
+ */
+ public void addExternalProviderClient(@NonNull ContentProviderRecord cpr,
+ IBinder externalProcessToken, int callingUid, String callingTag) {
+ cpr.addExternalProcessHandleLocked(externalProcessToken, callingUid, callingTag);
+ }
+
+ /**
+ * Remove an external client from a conetnt provider.
+ */
+ public boolean removeExternalProviderClient(@NonNull ContentProviderRecord cpr,
+ IBinder externalProcessToken) {
+ return cpr.removeExternalProcessHandleLocked(externalProcessToken);
+ }
+
+ /**
+ * Note the time a process is no longer hosting any content providers.
+ */
+ public void setLastProviderTime(@NonNull ProcessRecord proc, long uptimeMs) {
+ proc.mProviders.setLastProviderTime(uptimeMs);
+ }
+
+ /**
+ * Note that a process has connected to a content provider.
+ */
+ public void addProviderConnection(@NonNull ProcessRecord client,
+ ContentProviderConnection cpc) {
+ client.mProviders.addProviderConnection(cpc);
+ }
+
+ /**
+ * Note that a process is no longer connected to a content provider.
+ */
+ public void removeProviderConnection(@NonNull ProcessRecord client,
+ ContentProviderConnection cpc) {
+ client.mProviders.addProviderConnection(cpc);
+ }
+
+ /********************** Content Provider State Events **********************/
+ /*************************** Service State Events **************************/
+ /**
+ * Note that a process has started hosting a service.
+ */
+ public boolean startService(@NonNull ProcessServiceRecord psr, ServiceRecord sr) {
+ return psr.startService(sr);
+ }
+
+ /**
+ * Note that a process has stopped hosting a service.
+ */
+ public boolean stopService(@NonNull ProcessServiceRecord psr, ServiceRecord sr) {
+ return psr.stopService(sr);
+ }
+
+ /**
+ * Remove all services that the process is hosting.
+ */
+ public void stopAllServices(@NonNull ProcessServiceRecord psr) {
+ psr.stopAllServices();
+ }
+
+ /**
+ * Note that a process's service has started executing.
+ */
+ public void startExecutingService(@NonNull ProcessServiceRecord psr, ServiceRecord sr) {
+ psr.startExecutingService(sr);
+ }
+
+ /**
+ * Note that a process's service has stopped executing.
+ */
+ public void stopExecutingService(@NonNull ProcessServiceRecord psr, ServiceRecord sr) {
+ psr.stopExecutingService(sr);
+ }
+
+ /**
+ * Note all executing services a process has has stopped.
+ */
+ public void stopAllExecutingServices(@NonNull ProcessServiceRecord psr) {
+ psr.stopAllExecutingServices();
+ }
+
+ /**
+ * Note that process has bound to a service.
+ */
+ public void addConnection(@NonNull ProcessServiceRecord psr, ConnectionRecord cr) {
+ psr.addConnection(cr);
+ }
+
+ /**
+ * Note that process has unbound from a service.
+ */
+ public void removeConnection(@NonNull ProcessServiceRecord psr, ConnectionRecord cr) {
+ psr.removeConnection(cr);
+ }
+
+ /**
+ * Remove all bindings a process has to services.
+ */
+ public void removeAllConnections(@NonNull ProcessServiceRecord psr) {
+ psr.removeAllConnections();
+ psr.removeAllSdkSandboxConnections();
+ }
+
+ /**
+ * Note whether an executing service should be considered in the foreground or not.
+ */
+ public void setExecServicesFg(@NonNull ProcessServiceRecord psr, boolean execServicesFg) {
+ psr.setExecServicesFg(execServicesFg);
+ }
+
+ /**
+ * Note whether a service is in the foreground or not and what type of FGS, if so.
+ */
+ public void setHasForegroundServices(@NonNull ProcessServiceRecord psr,
+ boolean hasForegroundServices,
+ int fgServiceTypes, boolean hasTypeNoneFgs) {
+ psr.setHasForegroundServices(hasForegroundServices, fgServiceTypes, hasTypeNoneFgs);
+ }
+
+ /**
+ * Note whether a service has a client activity or not.
+ */
+ public void setHasClientActivities(@NonNull ProcessServiceRecord psr,
+ boolean hasClientActivities) {
+ psr.setHasClientActivities(hasClientActivities);
+ }
+
+ /**
+ * Note whether a service should be treated like an activity or not.
+ */
+ public void setTreatLikeActivity(@NonNull ProcessServiceRecord psr, boolean treatLikeActivity) {
+ psr.setTreatLikeActivity(treatLikeActivity);
+ }
+
+ /**
+ * Note whether a process has bound to a service with
+ * {@link android.content.Context.BIND_ABOVE_CLIENT} or not.
+ */
+ public void setHasAboveClient(@NonNull ProcessServiceRecord psr, boolean hasAboveClient) {
+ psr.setHasAboveClient(hasAboveClient);
+ }
+
+ /**
+ * Recompute whether a process has bound to a service with
+ * {@link android.content.Context.BIND_ABOVE_CLIENT} or not.
+ */
+ public void updateHasAboveClientLocked(@NonNull ProcessServiceRecord psr) {
+ psr.updateHasAboveClientLocked();
+ }
+
+ /**
+ * Cleanup a process's state.
+ */
+ public void onCleanupApplicationRecord(@NonNull ProcessServiceRecord psr) {
+ psr.onCleanupApplicationRecordLocked();
+ }
+
+ /**
+ * Set which process is hosting a service.
+ */
+ public void setHostProcess(@NonNull ServiceRecord sr, @Nullable ProcessRecord host) {
+ sr.app = host;
+ }
+
+ /**
+ * Note whether a service is a Foreground Service or not
+ */
+ public void setIsForegroundService(@NonNull ServiceRecord sr, boolean isFgs) {
+ sr.isForeground = isFgs;
+ }
+
+ /**
+ * Note the Foreground Service type of a service.
+ */
+ public void setForegroundServiceType(@NonNull ServiceRecord sr,
+ @ServiceInfo.ForegroundServiceType int fgsType) {
+ sr.foregroundServiceType = fgsType;
+ }
+
+ /**
+ * Note the start time of a short foreground service.
+ */
+ public void setShortFgsInfo(@NonNull ServiceRecord sr, long uptimeNow) {
+ sr.setShortFgsInfo(uptimeNow);
+ }
+
+ /**
+ * Note that a short foreground service has stopped.
+ */
+ public void clearShortFgsInfo(@NonNull ServiceRecord sr) {
+ sr.clearShortFgsInfo();
+ }
+
+ /**
+ * Note the last time a service was active.
+ */
+ public void setServiceLastActivityTime(@NonNull ServiceRecord sr, long lastActivityUpdateMs) {
+ sr.lastActivity = lastActivityUpdateMs;
+ }
+
+ /**
+ * Note that a service start was requested.
+ */
+ public void setStartRequested(@NonNull ServiceRecord sr, boolean startRequested) {
+ sr.startRequested = startRequested;
+ }
+
+ /**
+ * Note the last time the service was bound by a Top process with
+ * {@link android.content.Context.BIND_ALMOST_PERCEPTIBLE}
+ */
+ public void setLastTopAlmostPerceptibleBindRequest(@NonNull ServiceRecord sr,
+ long lastTopAlmostPerceptibleBindRequestUptimeMs) {
+ sr.lastTopAlmostPerceptibleBindRequestUptimeMs =
+ lastTopAlmostPerceptibleBindRequestUptimeMs;
+ }
+
+ /**
+ * Recompute whether a process has bound to a service with
+ * {@link android.content.Context.BIND_ALMOST_PERCEPTIBLE} or not.
+ */
+ public void updateHasTopStartedAlmostPerceptibleServices(@NonNull ProcessServiceRecord psr) {
+ psr.updateHasTopStartedAlmostPerceptibleServices();
+ }
+
+ /**
+ * Builder for ProcessStateController.
+ */
+ public static class Builder {
+ private final ActivityManagerService mAms;
+ private final ProcessList mProcessList;
+ private final ActiveUids mActiveUids;
+
+ private ServiceThread mHandlerThread = null;
+ private OomAdjuster.Injector mOomAdjInjector = null;
+ private boolean mUseOomAdjusterModernImpl = false;
+
+ public Builder(ActivityManagerService ams, ProcessList processList, ActiveUids activeUids) {
+ mAms = ams;
+ mProcessList = processList;
+ mActiveUids = activeUids;
+ }
+
+ /**
+ * Build the ProcessStateController object.
+ */
+ public ProcessStateController build() {
+ if (mHandlerThread == null) {
+ mHandlerThread = OomAdjuster.createAdjusterThread();
+ }
+ if (mOomAdjInjector == null) {
+ mOomAdjInjector = new OomAdjuster.Injector();
+ }
+ return new ProcessStateController(mAms, mProcessList, mActiveUids, mHandlerThread,
+ mOomAdjInjector, mUseOomAdjusterModernImpl);
+ }
+
+ /**
+ * For Testing Purposes. Set what thread OomAdjuster will offload tasks on to.
+ */
+ public Builder setHandlerThread(ServiceThread handlerThread) {
+ mHandlerThread = handlerThread;
+ return this;
+ }
+
+ /**
+ * For Testing Purposes. Set an injector for OomAdjuster.
+ */
+ public Builder setOomAdjusterInjector(OomAdjuster.Injector injector) {
+ mOomAdjInjector = injector;
+ return this;
+ }
+
+ /**
+ * Set which implementation of OomAdjuster to use.
+ */
+ public Builder useModernOomAdjuster(boolean use) {
+ mUseOomAdjusterModernImpl = use;
+ return this;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index bc990d9..b0f808b 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -19,14 +19,11 @@
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_ACTIVITY;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_STARTED_SERVICE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
-import static com.android.server.am.ProcessRecord.TAG;
import static com.android.server.wm.WindowProcessController.ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED;
import static com.android.server.wm.WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING;
import static com.android.server.wm.WindowProcessController.ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING;
@@ -38,7 +35,6 @@
import android.content.ComponentName;
import android.os.SystemClock;
import android.os.Trace;
-import android.util.Slog;
import android.util.TimeUtils;
import com.android.internal.annotations.CompositeRWLock;
@@ -790,15 +786,7 @@
@GuardedBy("mService")
void setRunningRemoteAnimation(boolean runningRemoteAnimation) {
- if (mRunningRemoteAnimation == runningRemoteAnimation) {
- return;
- }
mRunningRemoteAnimation = runningRemoteAnimation;
- if (DEBUG_OOM_ADJ) {
- Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
- + " for pid=" + mApp.getPid());
- }
- mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY);
}
@GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index b9cdf27..92d33c9 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1248,7 +1248,7 @@
app.mServices.updateBoundClientUids();
app.mServices.updateHostingComonentTypeForBindingsLocked();
}
- app = proc;
+ ams.mProcessStateController.setHostProcess(this, proc);
updateProcessStateOnRequest();
if (pendingConnectionGroup > 0 && proc != null) {
final ProcessServiceRecord psr = proc.mServices;
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 7873d34..adf0e64 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -218,3 +218,10 @@
description: "Set reset_on_fork flag."
bug: "370988407"
}
+
+flag {
+ name: "push_global_state_to_oomadjuster"
+ namespace: "backstage_power"
+ description: "Migrate OomAdjuster pulled device state to a push model"
+ bug: "302575389"
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e0cf96f..e97629b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -72,6 +72,10 @@
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
import static android.permission.flags.Flags.deviceAwareAppOpNewSchemaEnabled;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
import android.Manifest;
@@ -160,6 +164,7 @@
import com.android.internal.pm.pkg.component.ParsedAttribution;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -2829,12 +2834,26 @@
@Override
public int checkOperation(int code, int uid, String packageName) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
Context.DEVICE_ID_DEFAULT, false /*raw*/);
}
@Override
public int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
virtualDeviceId, false /*raw*/);
}
@@ -3015,6 +3034,13 @@
public SyncNotedAppOp noteProxyOperationWithState(int code,
AttributionSourceState attributionSourceState, boolean shouldCollectAsyncNotedOp,
String message, boolean shouldCollectMessage, boolean skipProxyOperation) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, attributionSourceState.uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_PROXY_OPERATION,
+ attributionSourceState.attributionTag != null);
+ }
AttributionSource attributionSource = new AttributionSource(attributionSourceState);
return mCheckOpsDelegateDispatcher.noteProxyOperation(code, attributionSource,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation);
@@ -3096,6 +3122,13 @@
public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION,
+ attributionTag != null);
+ }
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
shouldCollectMessage);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1563a62..d206b20 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2748,6 +2748,11 @@
}
}
+ @Override
+ protected void onUnhandledException(int code, int flags, Exception e) {
+ Slog.wtf(TAG, "Uncaught exception in AudioService: " + code + ", " + flags, e);
+ }
+
@Override // Binder call
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
index 01f770b..9fa5da4 100644
--- a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
+++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
@@ -133,6 +133,9 @@
private static final EventLogger sLogger = new EventLogger(
AudioService.LOG_NB_EVENTS_LOUDNESS_CODEC, "Loudness updates");
+ private final Object mDispatcherLock = new Object();
+
+ @GuardedBy("mDispatcherLock")
private final LoudnessRemoteCallbackList mLoudnessUpdateDispatchers =
new LoudnessRemoteCallbackList(this);
@@ -339,12 +342,16 @@
}
void registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher) {
- mLoudnessUpdateDispatchers.register(dispatcher, Binder.getCallingPid());
+ synchronized (mDispatcherLock) {
+ mLoudnessUpdateDispatchers.register(dispatcher, Binder.getCallingPid());
+ }
}
void unregisterLoudnessCodecUpdatesDispatcher(
ILoudnessCodecUpdatesDispatcher dispatcher) {
- mLoudnessUpdateDispatchers.unregister(dispatcher);
+ synchronized (mDispatcherLock) {
+ mLoudnessUpdateDispatchers.unregister(dispatcher);
+ }
}
void startLoudnessCodecUpdates(int sessionId) {
@@ -640,17 +647,20 @@
Log.d(TAG,
"dispatchNewLoudnessParameters: sessionId " + sessionId + " bundle: " + bundle);
}
- final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast();
- for (int i = 0; i < nbDispatchers; ++i) {
- try {
- mLoudnessUpdateDispatchers.getBroadcastItem(i)
- .dispatchLoudnessCodecParameterChange(sessionId, bundle);
- } catch (RemoteException e) {
- Log.e(TAG, "Error dispatching for sessionId " + sessionId + " bundle: " + bundle,
- e);
+ synchronized (mDispatcherLock) {
+ final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast();
+ for (int i = 0; i < nbDispatchers; ++i) {
+ try {
+ mLoudnessUpdateDispatchers.getBroadcastItem(i)
+ .dispatchLoudnessCodecParameterChange(sessionId, bundle);
+ } catch (RemoteException e) {
+ Log.e(TAG,
+ "Error dispatching for sessionId " + sessionId + " bundle: " + bundle,
+ e);
+ }
}
+ mLoudnessUpdateDispatchers.finishBroadcast();
}
- mLoudnessUpdateDispatchers.finishBroadcast();
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 1094bee..6216a58 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -785,8 +785,8 @@
+ " IDeviceStateManagerCallback.");
}
- ProcessRecord record = new ProcessRecord(callback, pid, this::handleProcessDied,
- mHandler);
+ final ProcessRecord record =
+ new ProcessRecord(callback, pid, this::handleProcessDied, mHandler);
try {
callback.asBinder().linkToDeath(record, 0);
} catch (RemoteException ex) {
@@ -797,8 +797,8 @@
// Callback clients should not be notified of invalid device states, so calls to
// #getDeviceStateInfoLocked should be gated on checks if a committed state is present
// before getting the device state info.
- DeviceStateInfo currentInfo = mCommittedState.isPresent()
- ? getDeviceStateInfoLocked() : null;
+ final DeviceStateInfo currentInfo =
+ mCommittedState.isPresent() ? getDeviceStateInfoLocked() : null;
if (currentInfo != null) {
// If there is not a committed state we'll wait to notify the process of the initial
// value.
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 3f4a9bb..ed69f7a 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -444,8 +444,17 @@
mSupportedContextHubPerms = hubInfo.second;
mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
- mTransactionManager = new ContextHubTransactionManager(
- mContextHubWrapper, mClientManager, mNanoAppStateManager);
+
+ if (Flags.reduceLockingContextHubTransactionManager()) {
+ mTransactionManager =
+ new ContextHubTransactionManager(
+ mContextHubWrapper, mClientManager, mNanoAppStateManager);
+ } else {
+ mTransactionManager =
+ new ContextHubTransactionManagerOld(
+ mContextHubWrapper, mClientManager, mNanoAppStateManager);
+ }
+
mSensorPrivacyManagerInternal =
LocalServices.getService(SensorPrivacyManagerInternal.class);
return true;
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 2a0b1af..da31bf2 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -26,6 +26,8 @@
import android.os.SystemClock;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.Collections;
@@ -43,8 +45,8 @@
/**
* Manages transactions at the Context Hub Service.
- * <p>
- * This class maintains a queue of transaction requests made to the ContextHubService by clients,
+ *
+ * <p>This class maintains a queue of transaction requests made to the ContextHubService by clients,
* and executes them through the Context Hub. At any point in time, either the transaction queue is
* empty, or there is a pending transaction that is waiting for an asynchronous response from the
* hub. This class also handles synchronous errors and timeouts of each transaction.
@@ -52,66 +54,80 @@
* @hide
*/
/* package */ class ContextHubTransactionManager {
- private static final String TAG = "ContextHubTransactionManager";
+ protected static final String TAG = "ContextHubTransactionManager";
public static final Duration RELIABLE_MESSAGE_TIMEOUT = Duration.ofSeconds(1);
public static final Duration RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT =
RELIABLE_MESSAGE_TIMEOUT.multipliedBy(3);
- private static final int MAX_PENDING_REQUESTS = 10000;
+ // TODO: b/362299144: When cleaning up the flag
+ // reduce_locking_context_hub_transaction_manager, change these to private
+ protected static final int MAX_PENDING_REQUESTS = 10000;
- private static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3;
+ protected static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3;
- private static final Duration RELIABLE_MESSAGE_RETRY_WAIT_TIME = Duration.ofMillis(250);
+ protected static final Duration RELIABLE_MESSAGE_RETRY_WAIT_TIME = Duration.ofMillis(250);
- private static final Duration RELIABLE_MESSAGE_MIN_WAIT_TIME = Duration.ofNanos(1000);
+ protected static final Duration RELIABLE_MESSAGE_MIN_WAIT_TIME = Duration.ofNanos(1000);
- private final IContextHubWrapper mContextHubProxy;
+ protected final IContextHubWrapper mContextHubProxy;
- private final ContextHubClientManager mClientManager;
+ protected final ContextHubClientManager mClientManager;
- private final NanoAppStateManager mNanoAppStateManager;
+ protected final NanoAppStateManager mNanoAppStateManager;
- private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
+ @GuardedBy("mTransactionLock")
+ protected final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
- private final Map<Integer, ContextHubServiceTransaction> mReliableMessageTransactionMap =
+ @GuardedBy("mReliableMessageLock")
+ protected final Map<Integer, ContextHubServiceTransaction> mReliableMessageTransactionMap =
new HashMap<>();
/** A set of host endpoint IDs that have an active pending transaction. */
- private final Set<Short> mReliableMessageHostEndpointIdActiveSet = new HashSet<>();
+ @GuardedBy("mReliableMessageLock")
+ protected final Set<Short> mReliableMessageHostEndpointIdActiveSet = new HashSet<>();
- private final AtomicInteger mNextAvailableId = new AtomicInteger();
+ protected final AtomicInteger mNextAvailableId = new AtomicInteger();
/**
- * The next available message sequence number. We choose a random
- * number to start with to avoid collisions and limit the bound to
- * half of the max value to avoid overflow.
+ * The next available message sequence number. We choose a random number to start with to avoid
+ * collisions and limit the bound to half of the max value to avoid overflow.
*/
- private final AtomicInteger mNextAvailableMessageSequenceNumber =
+ protected final AtomicInteger mNextAvailableMessageSequenceNumber =
new AtomicInteger(new Random().nextInt(Integer.MAX_VALUE / 2));
/*
- * An executor and the future object for scheduling timeout timers and
+ * An executor and the future objects for scheduling timeout timers and
* for scheduling the processing of reliable message transactions.
*/
- private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1);
- private ScheduledFuture<?> mTimeoutFuture = null;
- private ScheduledFuture<?> mReliableMessageTransactionFuture = null;
+ protected final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(2);
+
+ @GuardedBy("mTransactionLock")
+ protected ScheduledFuture<?> mTimeoutFuture = null;
+
+ @GuardedBy("mReliableMessageLock")
+ protected ScheduledFuture<?> mReliableMessageTransactionFuture = null;
/*
* The list of previous transaction records.
*/
- private static final int NUM_TRANSACTION_RECORDS = 20;
- private final ConcurrentLinkedEvictingDeque<TransactionRecord> mTransactionRecordDeque =
+ protected static final int NUM_TRANSACTION_RECORDS = 20;
+ protected final ConcurrentLinkedEvictingDeque<TransactionRecord> mTransactionRecordDeque =
new ConcurrentLinkedEvictingDeque<>(NUM_TRANSACTION_RECORDS);
- /**
- * A container class to store a record of transactions.
+ /*
+ * Locks for synchronization of normal transactions separately from reliable message
+ * transactions.
*/
- private class TransactionRecord {
- private final String mTransaction;
- private final long mTimestamp;
+ protected final Object mTransactionLock = new Object();
+ protected final Object mReliableMessageLock = new Object();
+ protected final Object mTransactionRecordLock = new Object();
+
+ /** A container class to store a record of transactions. */
+ protected static class TransactionRecord {
+ protected final String mTransaction;
+ protected final long mTimestamp;
TransactionRecord(String transaction) {
mTransaction = transaction;
@@ -126,8 +142,18 @@
}
}
+ /** Used when finishing a transaction. */
+ interface TransactionAcceptConditions {
+ /**
+ * Returns whether to accept the found transaction when receiving a response from the
+ * Context Hub.
+ */
+ boolean acceptTransaction(ContextHubServiceTransaction transaction);
+ }
+
/* package */ ContextHubTransactionManager(
- IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager,
+ IContextHubWrapper contextHubProxy,
+ ContextHubClientManager clientManager,
NanoAppStateManager nanoAppStateManager) {
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
@@ -409,34 +435,47 @@
/**
* Adds a new transaction to the queue.
- * <p>
- * If there was no pending transaction at the time, the transaction that was added will be
+ *
+ * <p>If there was no pending transaction at the time, the transaction that was added will be
* started in this method. If there were too many transactions in the queue, an exception will
* be thrown.
*
* @param transaction the transaction to add
- * @throws IllegalStateException if the queue is full
*/
/* package */
- synchronized void addTransaction(
- ContextHubServiceTransaction transaction) throws IllegalStateException {
+ void addTransaction(ContextHubServiceTransaction transaction) {
if (transaction == null) {
return;
}
- if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS
- || mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) {
- throw new IllegalStateException("Transaction queue is full (capacity = "
- + MAX_PENDING_REQUESTS + ")");
+ synchronized (mTransactionRecordLock) {
+ mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
}
- mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
if (Flags.reliableMessageRetrySupportService()
&& transaction.getTransactionType()
== ContextHubTransaction.TYPE_RELIABLE_MESSAGE) {
- mReliableMessageTransactionMap.put(transaction.getMessageSequenceNumber(), transaction);
+ synchronized (mReliableMessageLock) {
+ if (mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) {
+ throw new IllegalStateException(
+ "Reliable message transaction queue is full "
+ + "(capacity = "
+ + MAX_PENDING_REQUESTS
+ + ")");
+ }
+ mReliableMessageTransactionMap.put(
+ transaction.getMessageSequenceNumber(), transaction);
+ }
mExecutor.execute(() -> processMessageTransactions());
- } else {
+ return;
+ }
+
+ synchronized (mTransactionLock) {
+ if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS) {
+ throw new IllegalStateException(
+ "Transaction queue is full (capacity = " + MAX_PENDING_REQUESTS + ")");
+ }
+
mTransactionQueue.add(transaction);
if (mTransactionQueue.size() == 1) {
startNextTransaction();
@@ -448,62 +487,85 @@
* Handles a transaction response from a Context Hub.
*
* @param transactionId the transaction ID of the response
- * @param success true if the transaction succeeded
+ * @param success true if the transaction succeeded
*/
/* package */
- synchronized void onTransactionResponse(int transactionId, boolean success) {
- ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ void onTransactionResponse(int transactionId, boolean success) {
+ TransactionAcceptConditions conditions =
+ transaction -> transaction.getTransactionId() == transactionId;
+ ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
if (transaction == null) {
- Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
- return;
- }
- if (transaction.getTransactionId() != transactionId) {
Log.w(TAG, "Received unexpected transaction response (expected ID = "
- + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
+ + transactionId
+ + ", received ID = "
+ + transaction.getTransactionId()
+ + ")");
return;
}
- transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS :
- ContextHubTransaction.RESULT_FAILED_AT_HUB);
- removeTransactionAndStartNext();
+ synchronized (transaction) {
+ transaction.onTransactionComplete(
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ transaction.setComplete();
+ }
}
+ /**
+ * Handles a message delivery response from a Context Hub.
+ *
+ * @param messageSequenceNumber the message sequence number of the response
+ * @param success true if the message was delivered successfully
+ */
/* package */
- synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) {
+ void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) {
if (!Flags.reliableMessageRetrySupportService()) {
- ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ TransactionAcceptConditions conditions =
+ transaction -> transaction.getTransactionType()
+ == ContextHubTransaction.TYPE_RELIABLE_MESSAGE
+ && transaction.getMessageSequenceNumber()
+ == messageSequenceNumber;
+ ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
if (transaction == null) {
- Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ Log.w(TAG, "Received unexpected message delivery response (expected"
+ + " message sequence number = "
+ + messageSequenceNumber
+ + ", received messageSequenceNumber = "
+ + messageSequenceNumber
+ + ")");
return;
}
- int transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
- if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE
- || transactionMessageSequenceNumber != messageSequenceNumber) {
- Log.w(TAG, "Received unexpected message transaction response (expected message "
- + "sequence number = "
- + transaction.getMessageSequenceNumber()
- + ", received messageSequenceNumber = " + messageSequenceNumber + ")");
+ synchronized (transaction) {
+ transaction.onTransactionComplete(
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ transaction.setComplete();
+ }
+ return;
+ }
+
+ ContextHubServiceTransaction transaction = null;
+ synchronized (mReliableMessageLock) {
+ transaction = mReliableMessageTransactionMap.get(messageSequenceNumber);
+ if (transaction == null) {
+ Log.w(
+ TAG,
+ "Could not find reliable message transaction with "
+ + "message sequence number = "
+ + messageSequenceNumber);
return;
}
- transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS :
- ContextHubTransaction.RESULT_FAILED_AT_HUB);
- removeTransactionAndStartNext();
- return;
+ removeMessageTransaction(transaction);
}
- ContextHubServiceTransaction transaction =
- mReliableMessageTransactionMap.get(messageSequenceNumber);
- if (transaction == null) {
- Log.w(TAG, "Could not find reliable message transaction with "
- + "message sequence number = "
- + messageSequenceNumber);
- return;
- }
-
- completeMessageTransaction(transaction,
- success ? ContextHubTransaction.RESULT_SUCCESS
+ completeMessageTransaction(
+ transaction,
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
: ContextHubTransaction.RESULT_FAILED_AT_HUB);
mExecutor.execute(() -> processMessageTransactions());
}
@@ -514,77 +576,116 @@
* @param nanoAppStateList the list of nanoapps included in the response
*/
/* package */
- synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
- ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ void onQueryResponse(List<NanoAppState> nanoAppStateList) {
+ TransactionAcceptConditions conditions = transaction ->
+ transaction.getTransactionType() == ContextHubTransaction.TYPE_QUERY_NANOAPPS;
+ ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
if (transaction == null) {
- Log.w(TAG, "Received unexpected query response (no transaction pending)");
- return;
- }
- if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
return;
}
- transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
- removeTransactionAndStartNext();
+ synchronized (transaction) {
+ transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
+ transaction.setComplete();
+ }
}
- /**
- * Handles a hub reset event by stopping a pending transaction and starting the next.
- */
+ /** Handles a hub reset event by stopping a pending transaction and starting the next. */
/* package */
- synchronized void onHubReset() {
+ void onHubReset() {
if (Flags.reliableMessageRetrySupportService()) {
- Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
- mReliableMessageTransactionMap.entrySet().iterator();
- while (iter.hasNext()) {
- completeMessageTransaction(iter.next().getValue(),
- ContextHubTransaction.RESULT_FAILED_AT_HUB, iter);
+ synchronized (mReliableMessageLock) {
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ removeAndCompleteMessageTransaction(
+ iter.next().getValue(),
+ ContextHubTransaction.RESULT_FAILED_AT_HUB,
+ iter);
+ }
}
}
- ContextHubServiceTransaction transaction = mTransactionQueue.peek();
- if (transaction == null) {
- return;
- }
+ synchronized (mTransactionLock) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ return;
+ }
- removeTransactionAndStartNext();
+ removeTransactionAndStartNext();
+ }
+ }
+
+ /**
+ * This function also starts the next transaction and removes the active transaction from the
+ * queue. The caller should complete the transaction.
+ *
+ * <p>Returns the active transaction if the transaction queue is not empty, the transaction is
+ * not null, and the transaction matches the conditions.
+ */
+ private ContextHubServiceTransaction getTransactionAndHandleNext(
+ TransactionAcceptConditions conditions) {
+ ContextHubServiceTransaction transaction = null;
+ synchronized (mTransactionLock) {
+ transaction = mTransactionQueue.peek();
+ if (transaction == null
+ || (conditions != null && !conditions.acceptTransaction(transaction))) {
+ return null;
+ }
+
+ cancelTimeoutFuture();
+ mTransactionQueue.remove();
+ if (!mTransactionQueue.isEmpty()) {
+ startNextTransaction();
+ }
+ }
+ return transaction;
}
/**
* Pops the front transaction from the queue and starts the next pending transaction request.
- * <p>
- * Removing elements from the transaction queue must only be done through this method. When a
+ *
+ * <p>Removing elements from the transaction queue must only be done through this method. When a
* pending transaction is removed, the timeout timer is cancelled and the transaction is marked
* complete.
- * <p>
- * It is assumed that the transaction queue is non-empty when this method is invoked, and that
- * the caller has obtained a lock on this ContextHubTransactionManager object.
+ *
+ * <p>It is assumed that the transaction queue is non-empty when this method is invoked, and
+ * that the caller has obtained mTransactionLock.
*/
+ @GuardedBy("mTransactionLock")
private void removeTransactionAndStartNext() {
- if (mTimeoutFuture != null) {
- mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false);
- mTimeoutFuture = null;
- }
+ cancelTimeoutFuture();
ContextHubServiceTransaction transaction = mTransactionQueue.remove();
- transaction.setComplete();
+ synchronized (transaction) {
+ transaction.setComplete();
+ }
if (!mTransactionQueue.isEmpty()) {
startNextTransaction();
}
}
+ /** Cancels the timeout future. */
+ @GuardedBy("mTransactionLock")
+ private void cancelTimeoutFuture() {
+ if (mTimeoutFuture != null) {
+ mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mTimeoutFuture = null;
+ }
+ }
+
/**
* Starts the next pending transaction request.
- * <p>
- * Starting new transactions must only be done through this method. This method continues to
+ *
+ * <p>Starting new transactions must only be done through this method. This method continues to
* process the transaction queue as long as there are pending requests, and no transaction is
* pending.
- * <p>
- * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
- * object.
+ *
+ * <p>It is assumed that the caller has obtained a lock on mTransactionLock.
*/
+ @GuardedBy("mTransactionLock")
private void startNextTransaction() {
int result = ContextHubTransaction.RESULT_FAILED_UNKNOWN;
while (result != ContextHubTransaction.RESULT_SUCCESS && !mTransactionQueue.isEmpty()) {
@@ -592,28 +693,36 @@
result = transaction.onTransact();
if (result == ContextHubTransaction.RESULT_SUCCESS) {
- Runnable onTimeoutFunc = () -> {
- synchronized (this) {
- if (!transaction.isComplete()) {
- Log.d(TAG, transaction + " timed out");
- transaction.onTransactionComplete(
- ContextHubTransaction.RESULT_FAILED_TIMEOUT);
+ Runnable onTimeoutFunc =
+ () -> {
+ synchronized (transaction) {
+ if (!transaction.isComplete()) {
+ Log.d(TAG, transaction + " timed out");
+ transaction.onTransactionComplete(
+ ContextHubTransaction.RESULT_FAILED_TIMEOUT);
+ transaction.setComplete();
+ }
+ }
- removeTransactionAndStartNext();
- }
- }
- };
+ synchronized (mTransactionLock) {
+ removeTransactionAndStartNext();
+ }
+ };
long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS);
try {
- mTimeoutFuture = mExecutor.schedule(
- onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS);
+ mTimeoutFuture =
+ mExecutor.schedule(onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS);
} catch (Exception e) {
Log.e(TAG, "Error when schedule a timer", e);
}
} else {
- transaction.onTransactionComplete(
- ContextHubServiceUtil.toTransactionResult(result));
+ synchronized (transaction) {
+ transaction.onTransactionComplete(
+ ContextHubServiceUtil.toTransactionResult(result));
+ transaction.setComplete();
+ }
+
mTransactionQueue.remove();
}
}
@@ -621,81 +730,97 @@
/**
* Processes message transactions, starting and completing them as needed.
- * <p>
- * This function is called when adding a message transaction or when a timer
- * expires for an existing message transaction's retry or timeout. The
- * internal processing loop will iterate at most twice as if one iteration
- * completes a transaction, the next iteration can only start new transactions.
- * If the first iteration does not complete any transaction, the loop will
- * only iterate once.
+ *
+ * <p>This function is called when adding a message transaction or when a timer expires for an
+ * existing message transaction's retry or timeout. The internal processing loop will iterate at
+ * most twice as if one iteration completes a transaction, the next iteration can only start new
+ * transactions. If the first iteration does not complete any transaction, the loop will only
+ * iterate once.
+ *
* <p>
*/
- private synchronized void processMessageTransactions() {
- if (!Flags.reliableMessageRetrySupportService()) {
- return;
- }
+ private void processMessageTransactions() {
+ synchronized (mReliableMessageLock) {
+ if (!Flags.reliableMessageRetrySupportService()) {
+ return;
+ }
- if (mReliableMessageTransactionFuture != null) {
- mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false);
- mReliableMessageTransactionFuture = null;
- }
+ if (mReliableMessageTransactionFuture != null) {
+ mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mReliableMessageTransactionFuture = null;
+ }
- long now = SystemClock.elapsedRealtimeNanos();
- long nextExecutionTime = Long.MAX_VALUE;
- boolean continueProcessing;
- do {
- continueProcessing = false;
- Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
- mReliableMessageTransactionMap.entrySet().iterator();
- while (iter.hasNext()) {
- ContextHubServiceTransaction transaction = iter.next().getValue();
- short hostEndpointId = transaction.getHostEndpointId();
- int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
- if (numCompletedStartCalls == 0
- && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) {
- continue;
- }
-
- long nextRetryTime = transaction.getNextRetryTime();
- long timeoutTime = transaction.getTimeoutTime();
- boolean transactionTimedOut = timeoutTime <= now;
- boolean transactionHitMaxRetries = nextRetryTime <= now
- && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY;
- if (transactionTimedOut || transactionHitMaxRetries) {
- completeMessageTransaction(transaction,
- ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter);
- continueProcessing = true;
- } else {
- if (nextRetryTime <= now || numCompletedStartCalls <= 0) {
- startMessageTransaction(transaction, now);
+ long now = SystemClock.elapsedRealtimeNanos();
+ long nextExecutionTime = Long.MAX_VALUE;
+ boolean continueProcessing;
+ do {
+ continueProcessing = false;
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ ContextHubServiceTransaction transaction = iter.next().getValue();
+ short hostEndpointId = transaction.getHostEndpointId();
+ int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
+ if (numCompletedStartCalls == 0
+ && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) {
+ continue;
}
- nextExecutionTime = Math.min(nextExecutionTime,
- transaction.getNextRetryTime());
- nextExecutionTime = Math.min(nextExecutionTime,
- transaction.getTimeoutTime());
- }
- }
- } while (continueProcessing);
+ long nextRetryTime = transaction.getNextRetryTime();
+ long timeoutTime = transaction.getTimeoutTime();
+ boolean transactionTimedOut = timeoutTime <= now;
+ boolean transactionHitMaxRetries =
+ nextRetryTime <= now
+ && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY;
+ if (transactionTimedOut || transactionHitMaxRetries) {
+ removeAndCompleteMessageTransaction(
+ transaction, ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter);
+ continueProcessing = true;
+ } else {
+ if (nextRetryTime <= now || numCompletedStartCalls <= 0) {
+ startMessageTransaction(transaction, now);
+ }
- if (nextExecutionTime < Long.MAX_VALUE) {
- mReliableMessageTransactionFuture = mExecutor.schedule(
- () -> processMessageTransactions(),
- Math.max(nextExecutionTime - SystemClock.elapsedRealtimeNanos(),
- RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()),
- TimeUnit.NANOSECONDS);
+ nextExecutionTime =
+ Math.min(nextExecutionTime, transaction.getNextRetryTime());
+ nextExecutionTime =
+ Math.min(nextExecutionTime, transaction.getTimeoutTime());
+ }
+ }
+ } while (continueProcessing);
+
+ if (nextExecutionTime < Long.MAX_VALUE) {
+ mReliableMessageTransactionFuture =
+ mExecutor.schedule(
+ () -> processMessageTransactions(),
+ Math.max(
+ nextExecutionTime - SystemClock.elapsedRealtimeNanos(),
+ RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()),
+ TimeUnit.NANOSECONDS);
+ }
}
}
/**
- * Completes a message transaction and removes it from the reliable message map.
+ * Completes a message transaction.
*
* @param transaction The transaction to complete.
* @param result The result code.
*/
- private void completeMessageTransaction(ContextHubServiceTransaction transaction,
- @ContextHubTransaction.Result int result) {
- completeMessageTransaction(transaction, result, /* iter= */ null);
+ private void completeMessageTransaction(
+ ContextHubServiceTransaction transaction, @ContextHubTransaction.Result int result) {
+ synchronized (transaction) {
+ transaction.onTransactionComplete(result);
+ transaction.setComplete();
+ }
+
+ Log.d(
+ TAG,
+ "Successfully completed reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + " and result = "
+ + result);
}
/**
@@ -705,25 +830,41 @@
* @param result The result code.
* @param iter The iterator for the reliable message map - used to remove the message directly.
*/
- private void completeMessageTransaction(ContextHubServiceTransaction transaction,
+ @GuardedBy("mReliableMessageLock")
+ private void removeAndCompleteMessageTransaction(
+ ContextHubServiceTransaction transaction,
@ContextHubTransaction.Result int result,
Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) {
- transaction.onTransactionComplete(result);
+ removeMessageTransaction(transaction, iter);
+ completeMessageTransaction(transaction, result);
+ }
+ /**
+ * Removes a message transaction from the reliable message map.
+ *
+ * @param transaction The transaction to remove.
+ */
+ @GuardedBy("mReliableMessageLock")
+ private void removeMessageTransaction(ContextHubServiceTransaction transaction) {
+ removeMessageTransaction(transaction, /* iter= */ null);
+ }
+
+ /**
+ * Removes a message transaction from the reliable message map.
+ *
+ * @param transaction The transaction to remove.
+ * @param iter The iterator for the reliable message map - used to remove the message directly.
+ */
+ @GuardedBy("mReliableMessageLock")
+ private void removeMessageTransaction(
+ ContextHubServiceTransaction transaction,
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) {
if (iter == null) {
mReliableMessageTransactionMap.remove(transaction.getMessageSequenceNumber());
} else {
iter.remove();
}
mReliableMessageHostEndpointIdActiveSet.remove(transaction.getHostEndpointId());
-
- Log.d(
- TAG,
- "Successfully completed reliable message transaction with "
- + "message sequence number = "
- + transaction.getMessageSequenceNumber()
- + " and result = "
- + result);
}
/**
@@ -732,24 +873,25 @@
* @param transaction The transaction to start.
* @param now The now time.
*/
+ @GuardedBy("mReliableMessageLock")
private void startMessageTransaction(ContextHubServiceTransaction transaction, long now) {
int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
@ContextHubTransaction.Result int result = transaction.onTransact();
if (result == ContextHubTransaction.RESULT_SUCCESS) {
- Log.d(
- TAG,
- "Successfully "
- + (numCompletedStartCalls == 0 ? "started" : "retried")
- + " reliable message transaction with message sequence number = "
- + transaction.getMessageSequenceNumber());
+ Log.d(
+ TAG,
+ "Successfully "
+ + (numCompletedStartCalls == 0 ? "started" : "retried")
+ + " reliable message transaction with message sequence number = "
+ + transaction.getMessageSequenceNumber());
} else {
- Log.w(
- TAG,
- "Could not start reliable message transaction with "
- + "message sequence number = "
- + transaction.getMessageSequenceNumber()
- + ", result = "
- + result);
+ Log.w(
+ TAG,
+ "Could not start reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + ", result = "
+ + result);
}
transaction.setNextRetryTime(now + RELIABLE_MESSAGE_RETRY_WAIT_TIME.toNanos());
@@ -788,17 +930,19 @@
public String toString() {
StringBuilder sb = new StringBuilder();
int i = 0;
- synchronized (this) {
- for (ContextHubServiceTransaction transaction: mTransactionQueue) {
+ synchronized (mTransactionLock) {
+ for (ContextHubServiceTransaction transaction : mTransactionQueue) {
sb.append(i);
sb.append(": ");
sb.append(transaction.toString());
sb.append("\n");
++i;
}
+ }
- if (Flags.reliableMessageRetrySupportService()) {
- for (ContextHubServiceTransaction transaction:
+ if (Flags.reliableMessageRetrySupportService()) {
+ synchronized (mReliableMessageLock) {
+ for (ContextHubServiceTransaction transaction :
mReliableMessageTransactionMap.values()) {
sb.append(i);
sb.append(": ");
@@ -807,7 +951,9 @@
++i;
}
}
+ }
+ synchronized (mTransactionRecordLock) {
sb.append("Transaction History:\n");
Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator();
while (iterator.hasNext()) {
@@ -815,6 +961,7 @@
sb.append("\n");
}
}
+
return sb.toString();
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java
new file mode 100644
index 0000000..a67fa30
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManagerOld.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2024 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.location.contexthub;
+
+import android.chre.flags.Flags;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubTransactionCallback;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.time.Duration;
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages transactions at the Context Hub Service.
+ *
+ * <p>This class maintains a queue of transaction requests made to the ContextHubService by clients,
+ * and executes them through the Context Hub. At any point in time, either the transaction queue is
+ * empty, or there is a pending transaction that is waiting for an asynchronous response from the
+ * hub. This class also handles synchronous errors and timeouts of each transaction.
+ *
+ * <p>This is the old version of ContextHubTransactionManager that uses global synchronization
+ * instead of individual locks. This will be deleted when the
+ * reduce_locking_context_hub_transaction_manager flag is cleaned up.
+ *
+ * @hide
+ */
+/* package */ class ContextHubTransactionManagerOld extends ContextHubTransactionManager {
+ /* package */ ContextHubTransactionManagerOld(
+ IContextHubWrapper contextHubProxy,
+ ContextHubClientManager clientManager,
+ NanoAppStateManager nanoAppStateManager) {
+ super(contextHubProxy, clientManager, nanoAppStateManager);
+ }
+
+ /**
+ * Adds a new transaction to the queue.
+ *
+ * <p>If there was no pending transaction at the time, the transaction that was added will be
+ * started in this method. If there were too many transactions in the queue, an exception will
+ * be thrown.
+ *
+ * @param transaction the transaction to add
+ * @throws IllegalStateException if the queue is full
+ */
+ /* package */
+ @Override
+ synchronized void addTransaction(ContextHubServiceTransaction transaction)
+ throws IllegalStateException {
+ if (transaction == null) {
+ return;
+ }
+
+ if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS
+ || mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) {
+ throw new IllegalStateException(
+ "Transaction queue is full (capacity = " + MAX_PENDING_REQUESTS + ")");
+ }
+
+ mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
+ if (Flags.reliableMessageRetrySupportService()
+ && transaction.getTransactionType()
+ == ContextHubTransaction.TYPE_RELIABLE_MESSAGE) {
+ mReliableMessageTransactionMap.put(transaction.getMessageSequenceNumber(), transaction);
+ mExecutor.execute(() -> processMessageTransactions());
+ } else {
+ mTransactionQueue.add(transaction);
+ if (mTransactionQueue.size() == 1) {
+ startNextTransaction();
+ }
+ }
+ }
+
+ /**
+ * Handles a transaction response from a Context Hub.
+ *
+ * @param transactionId the transaction ID of the response
+ * @param success true if the transaction succeeded
+ */
+ /* package */
+ @Override
+ synchronized void onTransactionResponse(int transactionId, boolean success) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ return;
+ }
+ if (transaction.getTransactionId() != transactionId) {
+ Log.w(
+ TAG,
+ "Received unexpected transaction response (expected ID = "
+ + transaction.getTransactionId()
+ + ", received ID = "
+ + transactionId
+ + ")");
+ return;
+ }
+
+ transaction.onTransactionComplete(
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ removeTransactionAndStartNext();
+ }
+
+ /* package */
+ @Override
+ synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) {
+ if (!Flags.reliableMessageRetrySupportService()) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ return;
+ }
+
+ int transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
+ if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE
+ || transactionMessageSequenceNumber != messageSequenceNumber) {
+ Log.w(
+ TAG,
+ "Received unexpected message transaction response (expected message "
+ + "sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + ", received messageSequenceNumber = "
+ + messageSequenceNumber
+ + ")");
+ return;
+ }
+
+ transaction.onTransactionComplete(
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ removeTransactionAndStartNext();
+ return;
+ }
+
+ ContextHubServiceTransaction transaction =
+ mReliableMessageTransactionMap.get(messageSequenceNumber);
+ if (transaction == null) {
+ Log.w(
+ TAG,
+ "Could not find reliable message transaction with "
+ + "message sequence number = "
+ + messageSequenceNumber);
+ return;
+ }
+
+ completeMessageTransaction(
+ transaction,
+ success
+ ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ mExecutor.execute(() -> processMessageTransactions());
+ }
+
+ /**
+ * Handles a query response from a Context Hub.
+ *
+ * @param nanoAppStateList the list of nanoapps included in the response
+ */
+ /* package */
+ @Override
+ synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected query response (no transaction pending)");
+ return;
+ }
+ if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+ Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
+ return;
+ }
+
+ transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
+ removeTransactionAndStartNext();
+ }
+
+ /** Handles a hub reset event by stopping a pending transaction and starting the next. */
+ /* package */
+ @Override
+ synchronized void onHubReset() {
+ if (Flags.reliableMessageRetrySupportService()) {
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ completeMessageTransaction(
+ iter.next().getValue(), ContextHubTransaction.RESULT_FAILED_AT_HUB, iter);
+ }
+ }
+
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ return;
+ }
+
+ removeTransactionAndStartNext();
+ }
+
+ /**
+ * Pops the front transaction from the queue and starts the next pending transaction request.
+ *
+ * <p>Removing elements from the transaction queue must only be done through this method. When a
+ * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
+ * complete.
+ *
+ * <p>It is assumed that the transaction queue is non-empty when this method is invoked, and
+ * that the caller has obtained a lock on this ContextHubTransactionManager object.
+ */
+ private void removeTransactionAndStartNext() {
+ if (mTimeoutFuture != null) {
+ mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mTimeoutFuture = null;
+ }
+
+ ContextHubServiceTransaction transaction = mTransactionQueue.remove();
+ transaction.setComplete();
+
+ if (!mTransactionQueue.isEmpty()) {
+ startNextTransaction();
+ }
+ }
+
+ /**
+ * Starts the next pending transaction request.
+ *
+ * <p>Starting new transactions must only be done through this method. This method continues to
+ * process the transaction queue as long as there are pending requests, and no transaction is
+ * pending.
+ *
+ * <p>It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
+ * object.
+ */
+ private void startNextTransaction() {
+ int result = ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ while (result != ContextHubTransaction.RESULT_SUCCESS && !mTransactionQueue.isEmpty()) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ result = transaction.onTransact();
+
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
+ Runnable onTimeoutFunc =
+ () -> {
+ synchronized (this) {
+ if (!transaction.isComplete()) {
+ Log.d(TAG, transaction + " timed out");
+ transaction.onTransactionComplete(
+ ContextHubTransaction.RESULT_FAILED_TIMEOUT);
+
+ removeTransactionAndStartNext();
+ }
+ }
+ };
+
+ long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS);
+ try {
+ mTimeoutFuture =
+ mExecutor.schedule(onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Log.e(TAG, "Error when schedule a timer", e);
+ }
+ } else {
+ transaction.onTransactionComplete(
+ ContextHubServiceUtil.toTransactionResult(result));
+ mTransactionQueue.remove();
+ }
+ }
+ }
+
+ /**
+ * Processes message transactions, starting and completing them as needed.
+ *
+ * <p>This function is called when adding a message transaction or when a timer expires for an
+ * existing message transaction's retry or timeout. The internal processing loop will iterate at
+ * most twice as if one iteration completes a transaction, the next iteration can only start new
+ * transactions. If the first iteration does not complete any transaction, the loop will only
+ * iterate once.
+ *
+ * <p>
+ */
+ private synchronized void processMessageTransactions() {
+ if (!Flags.reliableMessageRetrySupportService()) {
+ return;
+ }
+
+ if (mReliableMessageTransactionFuture != null) {
+ mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mReliableMessageTransactionFuture = null;
+ }
+
+ long now = SystemClock.elapsedRealtimeNanos();
+ long nextExecutionTime = Long.MAX_VALUE;
+ boolean continueProcessing;
+ do {
+ continueProcessing = false;
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ ContextHubServiceTransaction transaction = iter.next().getValue();
+ short hostEndpointId = transaction.getHostEndpointId();
+ int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
+ if (numCompletedStartCalls == 0
+ && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) {
+ continue;
+ }
+
+ long nextRetryTime = transaction.getNextRetryTime();
+ long timeoutTime = transaction.getTimeoutTime();
+ boolean transactionTimedOut = timeoutTime <= now;
+ boolean transactionHitMaxRetries =
+ nextRetryTime <= now
+ && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY;
+ if (transactionTimedOut || transactionHitMaxRetries) {
+ completeMessageTransaction(
+ transaction, ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter);
+ continueProcessing = true;
+ } else {
+ if (nextRetryTime <= now || numCompletedStartCalls <= 0) {
+ startMessageTransaction(transaction, now);
+ }
+
+ nextExecutionTime = Math.min(nextExecutionTime, transaction.getNextRetryTime());
+ nextExecutionTime = Math.min(nextExecutionTime, transaction.getTimeoutTime());
+ }
+ }
+ } while (continueProcessing);
+
+ if (nextExecutionTime < Long.MAX_VALUE) {
+ mReliableMessageTransactionFuture =
+ mExecutor.schedule(
+ () -> processMessageTransactions(),
+ Math.max(
+ nextExecutionTime - SystemClock.elapsedRealtimeNanos(),
+ RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()),
+ TimeUnit.NANOSECONDS);
+ }
+ }
+
+ /**
+ * Completes a message transaction and removes it from the reliable message map.
+ *
+ * @param transaction The transaction to complete.
+ * @param result The result code.
+ */
+ private void completeMessageTransaction(
+ ContextHubServiceTransaction transaction, @ContextHubTransaction.Result int result) {
+ completeMessageTransaction(transaction, result, /* iter= */ null);
+ }
+
+ /**
+ * Completes a message transaction and removes it from the reliable message map using iter.
+ *
+ * @param transaction The transaction to complete.
+ * @param result The result code.
+ * @param iter The iterator for the reliable message map - used to remove the message directly.
+ */
+ private void completeMessageTransaction(
+ ContextHubServiceTransaction transaction,
+ @ContextHubTransaction.Result int result,
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) {
+ transaction.onTransactionComplete(result);
+
+ if (iter == null) {
+ mReliableMessageTransactionMap.remove(transaction.getMessageSequenceNumber());
+ } else {
+ iter.remove();
+ }
+ mReliableMessageHostEndpointIdActiveSet.remove(transaction.getHostEndpointId());
+
+ Log.d(
+ TAG,
+ "Successfully completed reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + " and result = "
+ + result);
+ }
+
+ /**
+ * Starts a message transaction.
+ *
+ * @param transaction The transaction to start.
+ * @param now The now time.
+ */
+ private void startMessageTransaction(ContextHubServiceTransaction transaction, long now) {
+ int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
+ @ContextHubTransaction.Result int result = transaction.onTransact();
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
+ Log.d(
+ TAG,
+ "Successfully "
+ + (numCompletedStartCalls == 0 ? "started" : "retried")
+ + " reliable message transaction with message sequence number = "
+ + transaction.getMessageSequenceNumber());
+ } else {
+ Log.w(
+ TAG,
+ "Could not start reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + ", result = "
+ + result);
+ }
+
+ transaction.setNextRetryTime(now + RELIABLE_MESSAGE_RETRY_WAIT_TIME.toNanos());
+ if (transaction.getTimeoutTime() == Long.MAX_VALUE) { // first time starting transaction
+ transaction.setTimeoutTime(now + RELIABLE_MESSAGE_TIMEOUT.toNanos());
+ }
+ transaction.setNumCompletedStartCalls(numCompletedStartCalls + 1);
+ mReliableMessageHostEndpointIdActiveSet.add(transaction.getHostEndpointId());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
+ synchronized (this) {
+ for (ContextHubServiceTransaction transaction : mTransactionQueue) {
+ sb.append(i);
+ sb.append(": ");
+ sb.append(transaction.toString());
+ sb.append("\n");
+ ++i;
+ }
+
+ if (Flags.reliableMessageRetrySupportService()) {
+ for (ContextHubServiceTransaction transaction :
+ mReliableMessageTransactionMap.values()) {
+ sb.append(i);
+ sb.append(": ");
+ sb.append(transaction.toString());
+ sb.append("\n");
+ ++i;
+ }
+ }
+
+ sb.append("Transaction History:\n");
+ Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator();
+ while (iterator.hasNext()) {
+ sb.append(iterator.next());
+ sb.append("\n");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6c2d4f7..88334eb 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -12577,18 +12577,31 @@
// Checks if this is a request to notify system UI about a notification that
// has been lifetime extended.
- // (We only need to check old for the flag, because in both cancellation and
- // update cases, old should have the flag, whereas in update cases the
- // new will NOT have the flag.)
- // If it is such a request, and this is system UI, we send the post request
- // only to System UI, and break as we don't need to continue checking other
- // Managed Services.
- if (info.isSystemUi() && old != null && old.getNotification() != null
+ // We check both old and new for the flag, to avoid catching updates
+ // (where new will not have the flag).
+ // If it is such a request, and this is the system UI listener, we send
+ // the post request. If it's any other listener, we skip it.
+ if (old != null && old.getNotification() != null
&& (old.getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0
+ && sbn != null && sbn.getNotification() != null
+ && (sbn.getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
- final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
- listenerCalls.add(() -> notifyPosted(info, sbnToPost, update));
- break;
+ if (info.isSystemUi()) {
+ final NotificationRankingUpdate update =
+ makeRankingUpdateLocked(info);
+ listenerCalls.add(() -> notifyPosted(info, sbnToPost, update));
+ break;
+ } else {
+ // Skipping because this is the direct-reply "update" and we only
+ // need to send it to sysui, so we immediately continue, before it
+ // can get sent to other listeners below.
+ if (DBG) {
+ Slog.d(TAG, "prepareNotifyPostedLocked: direct reply update, "
+ + "skipping post to " + info.toString());
+ }
+ continue;
+ }
}
}
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 3bcaf58..f23d782 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -96,12 +96,15 @@
mHandler = thread.getThreadHandler();
mWatcher = new TombstoneWatcher();
- mWatcher.startWatching();
}
void onSystemReady() {
registerForUserRemoval();
registerForPackageRemoval();
+ // TombstoneWatcher depends on DropboxManagerService.
+ // DropboxManagerService started before systemReady.
+ // So it is good to call startWatching here.
+ mWatcher.startWatching();
BootReceiver.initDropboxRateLimiter();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 897ee431..eb7c243 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -109,6 +109,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
import android.content.pm.dex.DexMetadataHelper;
@@ -2880,19 +2881,38 @@
// since installation is in progress.
activate();
}
+ try {
+ List<PackageInstallerSession> children = getChildSessions();
+ if (isMultiPackage()) {
+ for (PackageInstallerSession child : children) {
+ child.prepareInheritedFiles();
+ child.parseApk();
+ }
+ } else {
+ prepareInheritedFiles();
+ parseApk();
+ }
+ } catch (PackageManagerException e) {
+ final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+ final String errorMsg = PackageManager.installStatusToString(e.error, completeMsg);
+ setSessionFailed(e.error, errorMsg);
+ onSessionVerificationFailure(e.error, errorMsg);
+ }
if (Flags.verificationService()) {
final Supplier<Computer> snapshotSupplier = mPm::snapshotComputer;
if (mVerifierController.isVerifierInstalled(snapshotSupplier, userId)) {
- // TODO: extract shared library declarations
final SigningInfo signingInfo;
+ final List<SharedLibraryInfo> declaredLibraries;
synchronized (mLock) {
signingInfo = new SigningInfo(mSigningDetails);
+ declaredLibraries =
+ mPackageLite == null ? null : mPackageLite.getDeclaredLibraries();
}
// Send the request to the verifier and wait for its response before the rest of
// the installation can proceed.
if (!mVerifierController.startVerificationSession(snapshotSupplier, userId,
- sessionId, params.appPackageName, Uri.fromFile(stageDir), signingInfo,
- /* declaredLibraries= */null, /* extensionParams= */ null,
+ sessionId, getPackageName(), Uri.fromFile(stageDir), signingInfo,
+ declaredLibraries, /* extensionParams= */ null,
new VerifierCallback(), /* retry= */ false)) {
// A verifier is installed but cannot be connected. Installation disallowed.
onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
@@ -2927,12 +2947,10 @@
List<PackageInstallerSession> children = getChildSessions();
if (isMultiPackage()) {
for (PackageInstallerSession child : children) {
- child.prepareInheritedFiles();
- child.parseApkAndExtractNativeLibraries();
+ child.extractNativeLibraries();
}
} else {
- prepareInheritedFiles();
- parseApkAndExtractNativeLibraries();
+ extractNativeLibraries();
}
verifyNonStaged();
} catch (PackageManagerException e) {
@@ -3109,7 +3127,7 @@
mStageDirInUse = true;
}
- private void parseApkAndExtractNativeLibraries() throws PackageManagerException {
+ private void parseApk() throws PackageManagerException {
synchronized (mLock) {
if (mStageDirInUse) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
@@ -3142,12 +3160,16 @@
// stage dir here.
// Besides, PackageLite may be null for staged sessions that don't complete
// pre-reboot verification.
- result = getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
+ mPackageLite = getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
} else {
- result = getOrParsePackageLiteLocked(mResolvedBaseFile, /* flags */ 0);
+ mPackageLite = getOrParsePackageLiteLocked(mResolvedBaseFile, /* flags */ 0);
}
- if (result != null) {
- mPackageLite = result;
+ }
+ }
+
+ private void extractNativeLibraries() throws PackageManagerException {
+ synchronized (mLock) {
+ if (mPackageLite != null) {
if (!isApexSession()) {
synchronized (mProgressLock) {
mInternalProgress = 0.5f;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e47b4c2..ad5c840 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -731,7 +731,10 @@
KeyEvent.KEYCODE_ASSIST,
KeyEvent.KEYCODE_VOICE_ASSIST,
KeyEvent.KEYCODE_MUTE,
- KeyEvent.KEYCODE_VOLUME_MUTE
+ KeyEvent.KEYCODE_VOLUME_MUTE,
+ KeyEvent.KEYCODE_RECENT_APPS,
+ KeyEvent.KEYCODE_APP_SWITCH,
+ KeyEvent.KEYCODE_NOTIFICATION
));
private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
@@ -2082,12 +2085,21 @@
}
switch (mDoubleTapOnHomeBehavior) {
case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI:
+ if (!isKeyEventForCurrentUser(
+ event.getDisplayId(), event.getKeyCode(), "toggleRecentApps")) {
+ break;
+ }
notifyKeyGestureCompleted(event,
KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH);
mHomeConsumed = true;
toggleRecentApps();
break;
case DOUBLE_TAP_HOME_PIP_MENU:
+ if (!isKeyEventForCurrentUser(
+ event.getDisplayId(), event.getKeyCode(),
+ "showPictureInPictureMenu")) {
+ break;
+ }
mHomeConsumed = true;
showPictureInPictureMenuInternal();
break;
@@ -2116,12 +2128,20 @@
}
break;
case LONG_PRESS_HOME_ASSIST:
+ if (!isKeyEventForCurrentUser(
+ event.getDisplayId(), event.getKeyCode(), "launchAssistAction")) {
+ break;
+ }
notifyKeyGestureCompleted(event,
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT);
launchAssistAction(null, event.getDeviceId(), event.getEventTime(),
AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
break;
case LONG_PRESS_HOME_NOTIFICATION_PANEL:
+ if (!isKeyEventForCurrentUser(
+ event.getDisplayId(), event.getKeyCode(), "toggleNotificationPanel")) {
+ break;
+ }
notifyKeyGestureCompleted(event,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
toggleNotificationPanel();
@@ -3497,7 +3517,11 @@
if (isUserSetupComplete() && !keyguardOn) {
if (mModifierShortcutManager.interceptKey(event)) {
- dismissKeyboardShortcutsMenu();
+ if (isKeyEventForCurrentUser(
+ event.getDisplayId(), event.getKeyCode(),
+ "dismissKeyboardShortcutsMenu")) {
+ dismissKeyboardShortcutsMenu();
+ }
mPendingMetaAction = false;
mPendingCapsLockToggle = false;
return true;
@@ -4820,7 +4844,10 @@
}
// no keyguard stuff to worry about, just launch home!
- if (mRecentsVisible) {
+ // If Recents is visible and the action is not from visible background users,
+ // hide Recents and notify it to launch Home.
+ if (mRecentsVisible
+ && (!mVisibleBackgroundUsersEnabled || displayId == DEFAULT_DISPLAY)) {
try {
ActivityManager.getService().stopAppSwitches();
} catch (RemoteException e) {}
@@ -5570,6 +5597,9 @@
* Notify the StatusBar that a system key was pressed.
*/
private void sendSystemKeyToStatusBar(KeyEvent key) {
+ if (!isKeyEventForCurrentUser(key.getDisplayId(), key.getKeyCode(), "handleSystemKey")) {
+ return;
+ }
IStatusBarService statusBar = getStatusBarService();
if (statusBar != null) {
try {
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java
index 9b39fa1..a49a9fd 100644
--- a/services/core/java/com/android/server/search/SearchManagerService.java
+++ b/services/core/java/com/android/server/search/SearchManagerService.java
@@ -46,6 +46,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.FileDescriptor;
@@ -89,6 +90,8 @@
@GuardedBy("mSearchables")
private final SparseArray<Searchables> mSearchables = new SparseArray<>();
+ private final UserManagerInternal mUserManagerInternal;
+
/**
* Initializes the Search Manager service in the provided system context.
* Only one instance of this object should be created!
@@ -101,6 +104,7 @@
mMyPackageMonitor.register(context, null, UserHandle.ALL, true);
new GlobalSearchProviderObserver(context.getContentResolver());
mHandler = BackgroundThread.getHandler();
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
}
private Searchables getSearchables(int userId) {
@@ -336,6 +340,14 @@
@Override
public void launchAssist(int userHandle, Bundle args) {
+ // Currently, visible background users are not allowed to launch assist.(b/332222893)
+ // TODO(b/368715893): Consider indirect calls from system service when checking the
+ // calling user.
+ final int callingUserId = UserHandle.getCallingUserId();
+ if (mUserManagerInternal.isVisibleBackgroundFullUser(callingUserId)) {
+ throw new SecurityException("Visible background user(u" + callingUserId
+ + ") is not permitted to launch assist.");
+ }
StatusBarManagerInternal statusBarManager =
LocalServices.getService(StatusBarManagerInternal.class);
if (statusBarManager != null) {
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 9662a87..e7735d8 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1275,49 +1275,39 @@
case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
TRANSPORT_WIFI);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
- new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
+ new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
break;
}
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
TRANSPORT_WIFI);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
- new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/true));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/true));
break;
}
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: {
final NetworkStats stats =
getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
- new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
+ new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
break;
}
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats =
getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
- new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true));
break;
}
case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
- new int[]{TRANSPORT_BLUETOOTH},
- /*slicedByFgbg=*/true, /*slicedByTag=*/false,
- /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/true));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[]{TRANSPORT_BLUETOOTH},
+ /*slicedByFgbg=*/true, /*slicedByTag=*/false,
+ /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/true));
break;
}
case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: {
@@ -1326,14 +1316,12 @@
final NetworkStats cellularStats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_MOBILE)
.setMeteredness(METERED_YES).build(), /*includeTags=*/true);
- if (wifiStats != null && cellularStats != null) {
- final NetworkStats stats = wifiStats.add(cellularStats);
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
- new int[]{TRANSPORT_WIFI, TRANSPORT_CELLULAR},
- /*slicedByFgbg=*/false, /*slicedByTag=*/true,
- /*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/false));
- }
+ final NetworkStats stats = wifiStats.add(cellularStats);
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
+ new int[]{TRANSPORT_WIFI, TRANSPORT_CELLULAR},
+ /*slicedByFgbg=*/false, /*slicedByTag=*/true,
+ /*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/false));
break;
}
case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER: {
@@ -1519,12 +1507,10 @@
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
template, false);
final Integer transport = ruleAndTransport.second;
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
- new int[]{transport}, /*slicedByFgbg=*/true, /*slicedByTag=*/false,
- /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- /*subInfo=*/null, oemManaged, /*isTypeProxy=*/false));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[]{transport}, /*slicedByFgbg=*/true, /*slicedByTag=*/false,
+ /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ /*subInfo=*/null, oemManaged, /*isTypeProxy=*/false));
}
}
@@ -1535,7 +1521,7 @@
* Create a snapshot of NetworkStats for a given transport.
*/
@GuardedBy("mDataBytesTransferLock")
- @Nullable
+ @NonNull
private NetworkStats getUidNetworkStatsSnapshotForTransportLocked(int transport) {
NetworkTemplate template = null;
switch (transport) {
@@ -1574,7 +1560,7 @@
* some traffic before boot.
*/
@GuardedBy("mDataBytesTransferLock")
- @Nullable
+ @NonNull
private NetworkStats getUidNetworkStatsSnapshotForTemplateLocked(
@NonNull NetworkTemplate template, boolean includeTags) {
final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
@@ -1613,7 +1599,7 @@
}
@GuardedBy("mDataBytesTransferLock")
- @Nullable
+ @NonNull
private NetworkStats getUidNetworkStatsSnapshotForTemplateLocked(
@NonNull NetworkTemplate template, boolean includeTags, long startTime, long endTime) {
final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
@@ -1660,12 +1646,10 @@
.setMeteredness(METERED_YES).build();
final NetworkStats stats =
getUidNetworkStatsSnapshotForTemplateLocked(template, /*includeTags=*/false);
- if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
- new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
- /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo,
- OEM_MANAGED_ALL, /*isTypeProxy=*/false));
- }
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
+ new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
+ /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo,
+ OEM_MANAGED_ALL, /*isTypeProxy=*/false));
}
return ret;
}
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
index cc63968..e798bc4 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
@@ -17,7 +17,6 @@
package com.android.server.stats.pull.netstats;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
@@ -55,7 +54,7 @@
* This method method may call {@code queryFunction} more than once, which includes maintaining
* an internal cumulative stats snapshot and querying stats after the snapshot.
*/
- @Nullable
+ @NonNull
public NetworkStats queryStats(long currentTimeMillis,
@NonNull StatsQueryFunction queryFunction) {
maybeExpandSnapshot(currentTimeMillis, queryFunction);
@@ -80,23 +79,21 @@
if (newEndTimeMillis - mSnapshotEndTimeMillis > mBucketDurationMillis) {
NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags,
mSnapshotEndTimeMillis, newEndTimeMillis);
- if (extraStats != null) {
- mSnapshot = mSnapshot.add(extraStats);
- mSnapshotEndTimeMillis = newEndTimeMillis;
- }
+ mSnapshot = mSnapshot.add(extraStats);
+ mSnapshotEndTimeMillis = newEndTimeMillis;
}
}
/**
* Adds up stats in the internal cumulative snapshot and the stats that follow after it.
*/
- @Nullable
+ @NonNull
private NetworkStats snapshotPlusFollowingStats(long currentTimeMillis,
@NonNull StatsQueryFunction queryFunction) {
// Set end time in the future to include all stats in the active bucket.
NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags,
mSnapshotEndTimeMillis, currentTimeMillis + mBucketDurationMillis);
- return extraStats != null ? mSnapshot.add(extraStats) : null;
+ return mSnapshot.add(extraStats);
}
@FunctionalInterface
@@ -104,7 +101,7 @@
/**
* Returns network stats during the given time period.
*/
- @Nullable
+ @NonNull
NetworkStats queryNetworkStats(@NonNull NetworkTemplate template, boolean includeTags,
long startTime, long endTime);
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index fb5c115..3f814f9 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -244,7 +244,7 @@
status = ":completed-same-process:";
} else {
if (endInfo.mTransitionType == TYPE_TRANSITION_HOT_LAUNCH) {
- status = ":completed-hot:";
+ status = !endInfo.mRelaunched ? ":completed-hot:" : ":completed-relaunch:";
} else if (endInfo.mTransitionType == TYPE_TRANSITION_WARM_LAUNCH) {
status = ":completed-warm:";
} else {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 31f4d08..a44eb48 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -33,7 +33,6 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.WaitResult.INVALID_DELAY;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
@@ -42,7 +41,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -3198,6 +3196,9 @@
if (!compatEnabled && !mWmService.mConstants.mIgnoreActivityOrientationRequest) {
return false;
}
+ if (mWmService.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(packageName)) {
+ return false;
+ }
// If the user preference respects aspect ratio, then it becomes non-resizable.
return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
.shouldApplyUserMinAspectRatioOverride();
@@ -5877,6 +5878,7 @@
return;
}
+ final State prevState = mState;
mState = state;
if (getTaskFragment() != null) {
@@ -5917,6 +5919,14 @@
mAtmService.updateBatteryStats(this, false);
mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_PAUSED);
break;
+ case STOPPING:
+ // It is possible that an Activity is scheduled to be STOPPED directly from RESUMED
+ // state. Updating the PAUSED usage state in that case, since the Activity will be
+ // STOPPED while cycled through the PAUSED state.
+ if (prevState == RESUMED) {
+ mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_PAUSED);
+ }
+ break;
case STOPPED:
mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
if (mDisplayContent != null) {
@@ -8382,15 +8392,12 @@
// and back which can cause visible issues (see b/184078928).
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
- final boolean isInCameraCompatFreeform = parentWindowingMode == WINDOWING_MODE_FREEFORM
- && mAppCompatController.getAppCompatCameraOverrides().getFreeformCameraCompatMode()
- != CAMERA_COMPAT_FREEFORM_NONE;
// Bubble activities should always fill their parent and should not be letterboxed.
final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble()
&& (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
|| parentWindowingMode == WINDOWING_MODE_FULLSCREEN
- || isInCameraCompatFreeform
+ || AppCompatCameraPolicy.shouldCameraCompatControlOrientation(this)
// When starting to switch between PiP and fullscreen, the task is pinned
// and the activity is fullscreen. But only allow to apply letterbox if the
// activity is exiting PiP because an entered PiP should fill the task.
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index dcf0319..a4fe064 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
@@ -28,9 +29,12 @@
import android.gui.StalledTransactionInfo;
import android.os.Debug;
import android.os.IBinder;
+import android.os.Trace;
import android.util.Slog;
+import android.util.SparseIntArray;
import android.view.Display;
import android.view.InputApplicationHandle;
+import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -64,6 +68,12 @@
// which point the ActivityManager will enable dispatching.
private boolean mInputDispatchEnabled;
+ /**
+ * The last input devices info which may affect display configuration. This is a quick lookup
+ * to detect interested changes without entering WM lock.
+ */
+ private SparseIntArray mLastInputConfigurationSources;
+
public InputManagerCallback(WindowManagerService service) {
mService = service;
}
@@ -117,8 +127,16 @@
/** Notifies that the input device configuration has changed. */
@Override
public void notifyConfigurationChanged() {
- synchronized (mService.mGlobalLock) {
- mService.mRoot.forAllDisplays(DisplayContent::sendNewConfiguration);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "notifyConfigurationChanged");
+ final boolean changed = !com.android.window.flags.Flags.filterIrrelevantInputDeviceChange()
+ || updateLastInputConfigurationSources();
+
+ if (changed) {
+ synchronized (mService.mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "inputDeviceConfigChanged");
+ mService.mRoot.forAllDisplays(DisplayContent::sendNewConfiguration);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
}
synchronized (mInputDevicesReadyMonitor) {
@@ -127,6 +145,40 @@
mInputDevicesReadyMonitor.notifyAll();
}
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ /** Returns {@code true} if the change of input devices may affect display configuration. */
+ private boolean updateLastInputConfigurationSources() {
+ final InputDevice[] devices = mService.mInputManager.getInputDevices();
+ final SparseIntArray newSources = new SparseIntArray(8);
+ final SparseIntArray lastSources = mLastInputConfigurationSources;
+ boolean changed = lastSources == null;
+ for (InputDevice device : devices) {
+ final String descriptor = device.getDescriptor();
+ if (descriptor == null || device.isVirtual()) {
+ continue;
+ }
+ final int key = descriptor.hashCode();
+ // The interested attributes from DisplayContent#computeScreenConfiguration.
+ int newSourceHash = device.getSources();
+ newSourceHash = newSourceHash * 31 + device.getKeyboardType();
+ newSourceHash = newSourceHash * 31 + device.getAssociatedDisplayId();
+ newSourceHash = newSourceHash * 31 + (device.isExternal() ? 1 : 0);
+ newSourceHash = newSourceHash * 31 + (device.isEnabled() ? 1 : 0);
+ newSources.put(key, newSourceHash);
+ if (lastSources != null && !changed) {
+ final int lastSource = lastSources.get(key, 0 /* valueIfKeyNotFound */);
+ if (lastSource != newSourceHash) {
+ changed = true;
+ }
+ }
+ }
+ if (lastSources != null && lastSources.size() != newSources.size()) {
+ changed = true;
+ }
+ mLastInputConfigurationSources = newSources;
+ return changed;
}
/** Notifies that the pointer location configuration has changed. */
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 266fdbb..41a5804 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1407,7 +1407,7 @@
displayId = rootTask != null ? rootTask.getDisplayId() : DEFAULT_DISPLAY;
}
- final DisplayContent display = getDisplayContent(displayId);
+ final DisplayContent display = getDisplayContentOrCreate(displayId);
return display.reduceOnAllTaskDisplayAreas((taskDisplayArea, result) ->
result | startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,
allowInstrumenting, fromHomeKey),
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1659f7b..585537b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -547,13 +547,12 @@
if (!ar.isVisible() || !ar.isVisibleRequested()) return;
if (mConfigAtEndActivities == null) {
mConfigAtEndActivities = new ArrayList<>();
- }
- if (mConfigAtEndActivities.contains(ar)) {
+ } else if (mConfigAtEndActivities.contains(ar)) {
return;
}
mConfigAtEndActivities.add(ar);
ar.pauseConfigurationDispatch();
- snapshotStartState(ar);
+ collect(ar);
mChanges.get(ar).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
});
}
@@ -1705,54 +1704,6 @@
change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
}
- void prepareConfigAtEnd(SurfaceControl.Transaction transact, ArrayList<ChangeInfo> targets) {
- if (mConfigAtEndActivities == null) return;
- for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
- final ActivityRecord ar = mConfigAtEndActivities.get(i);
- if (!ar.isVisibleRequested()) continue;
- final SurfaceControl sc = ar.getSurfaceControl();
- if (sc == null) continue;
- final Task task = ar.getTask();
- if (task == null) continue;
- // If task isn't animating, then it means shell is animating activity directly (within
- // task), so don't do any setup.
- if (!containsChangeFor(task, targets)) continue;
- final ChangeInfo change = mChanges.get(ar);
- final Rect startBounds = change.mAbsoluteBounds;
- Rect hintRect = null;
- if (ar.getWindowingMode() == WINDOWING_MODE_PINNED && ar.pictureInPictureArgs != null
- && ar.pictureInPictureArgs.getSourceRectHint() != null) {
- hintRect = ar.pictureInPictureArgs.getSourceRectHint();
- }
- if (hintRect == null) {
- hintRect = new Rect(startBounds);
- hintRect.offsetTo(0, 0);
- }
- final Rect endBounds = ar.getBounds();
- final Rect taskEndBounds = task.getBounds();
- // FA = final activity bounds (absolute)
- // FT = final task bounds (absolute)
- // SA = start activity bounds (absolute)
- // H = source hint (relative to start activity bounds)
- // We want to transform the activity so that when the task is at FT, H overlaps with FA
-
- // This scales the activity such that the hint rect has the same dimensions
- // as the final activity bounds.
- float hintToEndScaleX = ((float) endBounds.width()) / ((float) hintRect.width());
- float hintToEndScaleY = ((float) endBounds.height()) / ((float) hintRect.height());
- // top-left needs to be (FA.tl - FT.tl) - H.tl * hintToEnd . H is relative to the
- // activity; so, for example, if shrinking H to FA (hintToEnd < 1), then the tl of the
- // shrunk SA is closer to H than expected, so we need to reduce how much we offset SA
- // to get H.tl to match.
- float startActPosInTaskEndX =
- (endBounds.left - taskEndBounds.left) - hintRect.left * hintToEndScaleX;
- float startActPosInTaskEndY =
- (endBounds.top - taskEndBounds.top) - hintRect.top * hintToEndScaleY;
- transact.setScale(sc, hintToEndScaleX, hintToEndScaleY);
- transact.setPosition(sc, startActPosInTaskEndX, startActPosInTaskEndY);
- }
- }
-
static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) {
for (int i = list.size() - 1; i >= 0; --i) {
if (list.get(i).mContainer == wc) return true;
@@ -1833,7 +1784,6 @@
// Resolve the animating targets from the participants.
mTargets = calculateTargets(mParticipants, mChanges);
- prepareConfigAtEnd(transaction, mTargets);
// Check whether the participants were animated from back navigation.
mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets,
@@ -2669,6 +2619,11 @@
if (reportIfNotTop(target)) {
ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" keep as target %s", target);
+ } else if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) {
+ // config-at-end activities do not match the end-state, so they should be treated
+ // as independent.
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ " keep as cfg-at-end target %s", target);
} else {
ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
" remove from targets %s", target);
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index e0f24d8..31ca24c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -22,10 +22,12 @@
import android.provider.AndroidDeviceConfig;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
+import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -38,6 +40,10 @@
private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST =
"ignore_activity_orientation_request";
+ /** The packages that ignore {@link #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST}. */
+ private static final String KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST =
+ "opt_out_ignore_activity_orientation_request_list";
+
/**
* The minimum duration between gesture exclusion logging for a given window in
* milliseconds.
@@ -65,6 +71,9 @@
/** @see #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST */
boolean mIgnoreActivityOrientationRequest;
+ /** @see #KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST */
+ private ArraySet<String> mOptOutIgnoreActivityOrientationRequestPackages;
+
private final WindowManagerGlobalLock mGlobalLock;
private final Runnable mUpdateSystemGestureExclusionCallback;
private final DeviceConfigInterface mDeviceConfig;
@@ -97,6 +106,7 @@
updateSystemGestureExclusionLimitDp();
updateSystemGestureExcludedByPreQStickyImmersive();
updateIgnoreActivityOrientationRequest();
+ updateOptOutIgnoreActivityOrientationRequestList();
}
private void onAndroidPropertiesChanged(DeviceConfig.Properties properties) {
@@ -138,6 +148,9 @@
case KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST:
updateIgnoreActivityOrientationRequest();
break;
+ case KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST:
+ updateOptOutIgnoreActivityOrientationRequestList();
+ break;
default:
break;
}
@@ -169,6 +182,25 @@
KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST, false);
}
+ private void updateOptOutIgnoreActivityOrientationRequestList() {
+ final String packageList = mDeviceConfig.getString(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST, "");
+ if (packageList.isEmpty()) {
+ mOptOutIgnoreActivityOrientationRequestPackages = null;
+ return;
+ }
+ mOptOutIgnoreActivityOrientationRequestPackages = new ArraySet<>();
+ mOptOutIgnoreActivityOrientationRequestPackages.addAll(
+ Arrays.asList(packageList.split(",")));
+ }
+
+ boolean isPackageOptOutIgnoreActivityOrientationRequest(String packageName) {
+ return mIgnoreActivityOrientationRequest
+ && mOptOutIgnoreActivityOrientationRequestPackages != null
+ && mOptOutIgnoreActivityOrientationRequestPackages.contains(packageName);
+ }
+
void dump(PrintWriter pw) {
pw.println("WINDOW MANAGER CONSTANTS (dumpsys window constants):");
@@ -180,6 +212,10 @@
pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
pw.print(" "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST);
pw.print("="); pw.println(mIgnoreActivityOrientationRequest);
+ if (mOptOutIgnoreActivityOrientationRequestPackages != null) {
+ pw.print(" "); pw.print(KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST);
+ pw.print("="); pw.println(mOptOutIgnoreActivityOrientationRequestPackages);
+ }
pw.println();
}
}
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index bcd0b94..903d892 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -43,6 +43,8 @@
static jmethodID sMethodIdOnComplete;
static jclass sFrequencyProfileLegacyClass;
static jmethodID sFrequencyProfileLegacyCtor;
+static jclass sFrequencyProfileClass;
+static jmethodID sFrequencyProfileCtor;
static struct {
jmethodID setCapabilities;
jmethodID setSupportedEffects;
@@ -54,6 +56,7 @@
jmethodID setCompositionSizeMax;
jmethodID setQFactor;
jmethodID setFrequencyProfileLegacy;
+ jmethodID setFrequencyProfile;
jmethodID setMaxEnvelopeEffectSize;
jmethodID setMinEnvelopeEffectControlPointDurationMillis;
jmethodID setMaxEnvelopeEffectControlPointDurationMillis;
@@ -524,6 +527,40 @@
sVibratorInfoBuilderClassInfo.setFrequencyProfileLegacy,
frequencyProfileLegacy);
+ if (info.frequencyToOutputAccelerationMap.isOk()) {
+ size_t mapSize = info.frequencyToOutputAccelerationMap.value().size();
+
+ jfloatArray frequenciesHz = env->NewFloatArray(mapSize);
+ jfloatArray outputAccelerationsGs = env->NewFloatArray(mapSize);
+
+ jfloat* frequenciesHzPtr = env->GetFloatArrayElements(frequenciesHz, nullptr);
+ jfloat* outputAccelerationsGsPtr =
+ env->GetFloatArrayElements(outputAccelerationsGs, nullptr);
+
+ size_t i = 0;
+ for (auto const& dataEntry : info.frequencyToOutputAccelerationMap.value()) {
+ frequenciesHzPtr[i] = static_cast<jfloat>(dataEntry.frequencyHz);
+ outputAccelerationsGsPtr[i] = static_cast<jfloat>(dataEntry.maxOutputAccelerationGs);
+ i++;
+ }
+
+ // Release the float pointers
+ env->ReleaseFloatArrayElements(frequenciesHz, frequenciesHzPtr, 0);
+ env->ReleaseFloatArrayElements(outputAccelerationsGs, outputAccelerationsGsPtr, 0);
+
+ jobject frequencyProfile =
+ env->NewObject(sFrequencyProfileClass, sFrequencyProfileCtor, resonantFrequency,
+ frequenciesHz, outputAccelerationsGs);
+
+ env->CallObjectMethod(vibratorInfoBuilder,
+ sVibratorInfoBuilderClassInfo.setFrequencyProfile, frequencyProfile);
+
+ // Delete local references to avoid memory leaks
+ env->DeleteLocalRef(frequenciesHz);
+ env->DeleteLocalRef(outputAccelerationsGs);
+ env->DeleteLocalRef(frequencyProfile);
+ }
+
return info.shouldRetry() ? JNI_FALSE : JNI_TRUE;
}
@@ -574,6 +611,10 @@
sFrequencyProfileLegacyCtor =
GetMethodIDOrDie(env, sFrequencyProfileLegacyClass, "<init>", "(FFF[F)V");
+ jclass frequencyProfileClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfile");
+ sFrequencyProfileClass = static_cast<jclass>(env->NewGlobalRef(frequencyProfileClass));
+ sFrequencyProfileCtor = GetMethodIDOrDie(env, sFrequencyProfileClass, "<init>", "(F[F[F)V");
+
jclass vibratorInfoBuilderClass = FindClassOrDie(env, "android/os/VibratorInfo$Builder");
sVibratorInfoBuilderClassInfo.setCapabilities =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setCapabilities",
@@ -606,6 +647,10 @@
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfileLegacy",
"(Landroid/os/VibratorInfo$FrequencyProfileLegacy;)"
"Landroid/os/VibratorInfo$Builder;");
+ sVibratorInfoBuilderClassInfo.setFrequencyProfile =
+ GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfile",
+ "(Landroid/os/VibratorInfo$FrequencyProfile;)"
+ "Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setMaxEnvelopeEffectSize =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setMaxEnvelopeEffectSize",
"(I)Landroid/os/VibratorInfo$Builder;");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2be999f..7e450dd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11874,75 +11874,51 @@
throw new IllegalArgumentException("Invalid package name: " + validationResult);
}
- if (Flags.setApplicationRestrictionsCoexistence()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
- caller.getPackageName(),
- caller.getUserId()
- );
-
+ final boolean isRoleHolder;
+ if (who != null) {
+ // DO or PO
+ Preconditions.checkCallAuthorization(
+ (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
+ Preconditions.checkCallAuthorization(!parent,
+ "DO or PO cannot call this on parent");
+ // Caller has opted to be treated as DPC (by passing a non-null who), so don't
+ // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
+ isRoleHolder = false;
+ } else {
+ // Delegates, or the DMRH. Only DMRH can call this on COPE parent
+ isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
+ if (parent) {
+ Preconditions.checkCallAuthorization(isRoleHolder);
+ Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
+ "Role Holder can only operate parent app restriction on COPE devices");
+ } else {
+ Preconditions.checkCallAuthorization(isRoleHolder
+ || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ }
+ }
+ // DMRH caller uses policy engine, others still use legacy code path
+ if (isRoleHolder) {
+ EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
+ caller.getPackageName());
+ int affectedUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
if (restrictions == null || restrictions.isEmpty()) {
mDevicePolicyEngine.removeLocalPolicy(
PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
enforcingAdmin,
- caller.getUserId());
+ affectedUserId);
} else {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
enforcingAdmin,
new BundlePolicyValue(restrictions),
- caller.getUserId());
+ affectedUserId);
}
- setBackwardsCompatibleAppRestrictions(
- caller, packageName, restrictions, caller.getUserHandle());
} else {
- final boolean isRoleHolder;
- if (who != null) {
- // DO or PO
- Preconditions.checkCallAuthorization(
- (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
- Preconditions.checkCallAuthorization(!parent,
- "DO or PO cannot call this on parent");
- // Caller has opted to be treated as DPC (by passing a non-null who), so don't
- // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
- isRoleHolder = false;
- } else {
- // Delegates, or the DMRH. Only DMRH can call this on COPE parent
- isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
- if (parent) {
- Preconditions.checkCallAuthorization(isRoleHolder);
- Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
- "Role Holder can only operate parent app restriction on COPE devices");
- } else {
- Preconditions.checkCallAuthorization(isRoleHolder
- || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
- }
- }
- // DMRH caller uses policy engine, others still use legacy code path
- if (isRoleHolder) {
- EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
- caller.getPackageName());
- int affectedUserId = parent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- if (restrictions == null || restrictions.isEmpty()) {
- mDevicePolicyEngine.removeLocalPolicy(
- PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
- enforcingAdmin,
- affectedUserId);
- } else {
- mDevicePolicyEngine.setLocalPolicy(
- PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
- enforcingAdmin,
- new BundlePolicyValue(restrictions),
- affectedUserId);
- }
- } else {
- mInjector.binderWithCleanCallingIdentity(() -> {
- mUserManager.setApplicationRestrictions(packageName, restrictions,
- caller.getUserHandle());
- });
- }
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ mUserManager.setApplicationRestrictions(packageName, restrictions,
+ caller.getUserHandle());
+ });
}
DevicePolicyEventLogger
@@ -11953,31 +11929,6 @@
.write();
}
- /**
- * Set app restrictions in user manager for DPC callers only to keep backwards compatibility
- * for the old getApplicationRestrictions API.
- */
- private void setBackwardsCompatibleAppRestrictions(
- CallerIdentity caller, String packageName, Bundle restrictions, UserHandle userHandle) {
- if ((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS))) {
- Bundle restrictionsToApply = restrictions == null || restrictions.isEmpty()
- ? getAppRestrictionsSetByAnyAdmin(packageName, userHandle)
- : restrictions;
- mInjector.binderWithCleanCallingIdentity(() -> {
- mUserManager.setApplicationRestrictions(packageName, restrictionsToApply,
- userHandle);
- });
- } else {
- // Notify package of changes via an intent - only sent to explicitly registered
- // receivers. Sending here because For DPCs, this is being sent in UMS.
- final Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
- changeIntent.setPackage(packageName);
- changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(changeIntent, userHandle);
- }
- }
-
private Bundle getAppRestrictionsSetByAnyAdmin(String packageName, UserHandle userHandle) {
LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
@@ -13257,68 +13208,47 @@
String packageName, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- // IMPORTANT: The code behind the if branch is OUTDATED and requires additional work before
- // enabling the feature flag below.
- // TODO(b/369141952): Update DPM.getApplicationRestrictions coexistence code
- if (Flags.setApplicationRestrictionsCoexistence()) {
- EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
- caller.getPackageName(),
- caller.getUserId()
- );
-
+ final boolean isRoleHolder;
+ if (who != null) {
+ // Caller is DO or PO. They cannot call this on parent
+ Preconditions.checkCallAuthorization(!parent
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
+ // Caller has opted to be treated as DPC (by passing a non-null who), so don't
+ // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
+ isRoleHolder = false;
+ } else {
+ // Caller is delegates or the DMRH. Only DMRH can call this on parent
+ isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
+ if (parent) {
+ Preconditions.checkCallAuthorization(isRoleHolder);
+ Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
+ "Role Holder can only operate parent app restriction on COPE devices");
+ } else {
+ Preconditions.checkCallAuthorization(isRoleHolder
+ || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ }
+ }
+ if (isRoleHolder) {
+ EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
+ caller.getPackageName());
+ int affectedUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
- caller.getUserId());
- if (policies.isEmpty() || !policies.containsKey(enforcingAdmin)) {
+ affectedUserId);
+ if (!policies.containsKey(enforcingAdmin)) {
return Bundle.EMPTY;
}
return policies.get(enforcingAdmin).getValue();
} else {
- final boolean isRoleHolder;
- if (who != null) {
- // Caller is DO or PO. They cannot call this on parent
- Preconditions.checkCallAuthorization(!parent
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
- // Caller has opted to be treated as DPC (by passing a non-null who), so don't
- // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
- isRoleHolder = false;
- } else {
- // Caller is delegates or the DMRH. Only DMRH can call this on parent
- isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
- if (parent) {
- Preconditions.checkCallAuthorization(isRoleHolder);
- Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
- "Role Holder can only operate parent app restriction on COPE devices");
- } else {
- Preconditions.checkCallAuthorization(isRoleHolder
- || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
- }
- }
- if (isRoleHolder) {
- EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
- caller.getPackageName());
- int affectedUserId = parent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
- mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
- PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
- affectedUserId);
- if (!policies.containsKey(enforcingAdmin)) {
- return Bundle.EMPTY;
- }
- return policies.get(enforcingAdmin).getValue();
- } else {
- return mInjector.binderWithCleanCallingIdentity(() -> {
- Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
- caller.getUserHandle());
- // if no restrictions were saved, mUserManager.getApplicationRestrictions
- // returns null, but DPM method should return an empty Bundle as per JavaDoc
- return bundle != null ? bundle : Bundle.EMPTY;
- });
- }
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
+ caller.getUserHandle());
+ // if no restrictions were saved, mUserManager.getApplicationRestrictions
+ // returns null, but DPM method should return an empty Bundle as per JavaDoc
+ return bundle != null ? bundle : Bundle.EMPTY;
+ });
}
}
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index bc64e15..5758da8 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -34,7 +34,6 @@
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.infra.AndroidFuture
-import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.atomic.AtomicBoolean
import org.junit.Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 2107406..412599d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -171,6 +171,8 @@
private static int sUiTierSize = 5;
private Context mContext;
+ private ProcessStateController mProcessStateController;
+ private ActiveUids mActiveUids;
private PackageManagerInternal mPackageManagerInternal;
private ActivityManagerService mService;
private OomAdjusterInjector mInjector = new OomAdjusterInjector();
@@ -229,15 +231,19 @@
doCallRealMethod().when(mService).enqueueOomAdjTargetLocked(any(ProcessRecord.class));
doCallRealMethod().when(mService).updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_ACTIVITY);
setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
+
doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
anyInt());
doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
anyBoolean());
- mService.mOomAdjuster = mService.mConstants.ENABLE_NEW_OOMADJ
- ? new OomAdjusterModernImpl(mService, mService.mProcessList,
- new ActiveUids(mService, false), mInjector)
- : new OomAdjuster(mService, mService.mProcessList, new ActiveUids(mService, false),
- mInjector);
+ mActiveUids = new ActiveUids(mService, false);
+ mProcessStateController = new ProcessStateController.Builder(mService,
+ mService.mProcessList, mActiveUids)
+ .useModernOomAdjuster(mService.mConstants.ENABLE_NEW_OOMADJ)
+ .setOomAdjusterInjector(mInjector)
+ .build();
+ mService.mProcessStateController = mProcessStateController;
+ mService.mOomAdjuster = mService.mProcessStateController.getOomAdjuster();
mService.mOomAdjuster.mAdjSeq = 10000;
mService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
mSetFlagsRule.enableFlags(Flags.FLAG_NEW_FGS_RESTRICTION_LOGIC);
@@ -246,8 +252,8 @@
@SuppressWarnings("GuardedBy")
@After
public void tearDown() {
- mService.mOomAdjuster.resetInternal();
- mService.mOomAdjuster.mActiveUids.clear();
+ mProcessStateController.getOomAdjuster().resetInternal();
+ mActiveUids.clear();
LocalServices.removeServiceForTest(PackageManagerInternal.class);
mInjector.reset();
}
@@ -293,7 +299,7 @@
private void updateOomAdj(ProcessRecord... apps) {
if (apps.length == 0) {
updateProcessRecordNodes(mService.mProcessList.getLruProcessesLOSP());
- mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
} else {
updateProcessRecordNodes(Arrays.asList(apps));
if (apps.length == 1) {
@@ -301,10 +307,10 @@
if (!mService.mProcessList.getLruProcessesLOSP().contains(app)) {
mService.mProcessList.getLruProcessesLOSP().add(app);
}
- mService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
+ mProcessStateController.runUpdate(apps[0], OOM_ADJ_REASON_NONE);
} else {
setProcessesToLru(apps);
- mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
mService.mProcessList.getLruProcessesLOSP().clear();
}
}
@@ -318,9 +324,9 @@
private void updateOomAdjPending(ProcessRecord... apps) {
setProcessesToLru(apps);
for (ProcessRecord app : apps) {
- mService.mOomAdjuster.enqueueOomAdjTargetLocked(app);
+ mProcessStateController.enqueueUpdateTarget(app);
}
- mService.mOomAdjuster.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_NONE);
+ mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE);
mService.mProcessList.getLruProcessesLOSP().clear();
}
@@ -341,11 +347,10 @@
public void testUpdateOomAdj_DoOne_Persistent_TopUi_Sleeping() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- app.mState.setHasTopUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ mProcessStateController.setMaxAdj(app, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasTopUi(app, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_ASLEEP);
updateOomAdj(app);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERSISTENT_PROC_ADJ,
SCHED_GROUP_RESTRICTED);
@@ -357,9 +362,9 @@
public void testUpdateOomAdj_DoOne_Persistent_TopUi_Awake() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- app.mState.setHasTopUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(app, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasTopUi(app, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
@@ -371,9 +376,9 @@
public void testUpdateOomAdj_DoOne_Persistent_TopApp() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(app, PERSISTENT_PROC_ADJ);
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(null).when(mService).getTopApp();
@@ -388,7 +393,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(null).when(mService).getTopApp();
@@ -401,8 +406,8 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP_SLEEPING).when(mService.mAtmInternal).getTopProcessState();
- app.mState.setRunningRemoteAnimation(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setRunningRemoteAnimation(app, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
@@ -415,7 +420,7 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(mock(ActiveInstrumentation.class)).when(app).getActiveInstrumentation();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doCallRealMethod().when(app).getActiveInstrumentation();
@@ -431,7 +436,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(true).when(mService).isReceivingBroadcastLocked(any(ProcessRecord.class),
any(int[].class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(false).when(mService).isReceivingBroadcastLocked(any(ProcessRecord.class),
any(int[].class));
@@ -466,8 +471,8 @@
public void testUpdateOomAdj_DoOne_ExecutingService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.startExecutingService(mock(ServiceRecord.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.startExecutingService(app.mServices, mock(ServiceRecord.class));
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, FOREGROUND_APP_ADJ, SCHED_GROUP_BACKGROUND);
@@ -480,11 +485,11 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP_SLEEPING).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_ASLEEP);
updateOomAdj(app);
doReturn(null).when(mService).getTopApp();
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
assertProcStates(app, PROCESS_STATE_TOP_SLEEPING, FOREGROUND_APP_ADJ,
SCHED_GROUP_BACKGROUND);
@@ -498,7 +503,7 @@
app.mState.setCurRawAdj(CACHED_APP_MIN_ADJ);
app.mState.setCurAdj(CACHED_APP_MIN_ADJ);
doReturn(null).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
@@ -516,7 +521,7 @@
doReturn(true).when(wpc).hasActivities();
doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE)
.when(wpc).getActivityStateFlags();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -555,7 +560,7 @@
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).hasRecentTasks();
app.mState.setLastTopTime(SystemClock.uptimeMillis());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doCallRealMethod().when(wpc).hasRecentTasks();
@@ -567,9 +572,9 @@
public void testUpdateOomAdj_DoOne_FgServiceLocation() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION,
- /* hasNoneType=*/false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION, /* hasNoneType=*/false);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -582,8 +587,9 @@
public void testUpdateOomAdj_DoOne_FgService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app.mServices, true, 0, /* hasNoneType=*/
+ true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -599,20 +605,20 @@
ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
+ mProcessStateController.setStartRequested(s, true);
s.isForeground = true;
s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis());
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis());
// SHORT_SERVICE FGS will get IMP_FG and a slightly different recent-adjustment.
{
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.startService(s);
- app.mServices.setHasForegroundServices(true,
+ mProcessStateController.startService(app.mServices, s);
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
app.mState.setLastTopTime(SystemClock.uptimeMillis());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -625,9 +631,9 @@
{
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true,
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
- app.mServices.startService(s);
+ mProcessStateController.startService(app.mServices, s);
app.mState.setLastTopTime(SystemClock.uptimeMillis()
- mService.mConstants.TOP_TO_FGS_GRACE_DURATION);
mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -642,21 +648,21 @@
// SHORT_SERVICE, timed out already.
s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
+ mProcessStateController.setStartRequested(s, true);
s.isForeground = true;
s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis()
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis()
- mService.mConstants.mShortFgsTimeoutDuration
- mService.mConstants.mShortFgsProcStateExtraWaitDuration);
{
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true,
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
- app.mServices.startService(s);
+ mProcessStateController.startService(app.mServices, s);
app.mState.setLastTopTime(SystemClock.uptimeMillis()
- mService.mConstants.TOP_TO_FGS_GRACE_DURATION);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -671,8 +677,8 @@
public void testUpdateOomAdj_DoOne_OverlayUi() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setHasOverlayUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasOverlayUi(app, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
@@ -684,9 +690,10 @@
public void testUpdateOomAdj_DoOne_PerceptibleRecent_FgService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app.mServices, true, 0, /* hasNoneType=*/
+ true);
app.mState.setLastTopTime(SystemClock.uptimeMillis());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE,
@@ -699,7 +706,7 @@
verify(mService.mHandler).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT, "fg-service");
@@ -722,9 +729,9 @@
// Simulate the system starting and binding to a service in the app.
ServiceRecord s = bindService(app, system,
null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
- s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime;
+ mProcessStateController.setLastTopAlmostPerceptibleBindRequest(s, nowUptime);
s.getConnections().clear();
- app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+ mProcessStateController.updateHasTopStartedAlmostPerceptibleServices(app.mServices);
mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -736,7 +743,7 @@
verify(mService.mHandler).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
? sFirstUiCachedAdj : sFirstCachedAdj;
@@ -758,15 +765,15 @@
// Simulate the system starting and binding to a service in the app.
ServiceRecord s = bindService(app, system,
null, null, Context.BIND_ALMOST_PERCEPTIBLE + 2, mock(IBinder.class));
- s.lastTopAlmostPerceptibleBindRequestUptimeMs =
- nowUptime - 2 * mService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
- app.mServices.updateHasTopStartedAlmostPerceptibleServices();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setLastTopAlmostPerceptibleBindRequest(s,
+ nowUptime - 2 * mService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs);
+ mProcessStateController.updateHasTopStartedAlmostPerceptibleServices(app.mServices);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
// Out of grace period and no valid binding so no adjustment.
@@ -780,16 +787,16 @@
// Simulate the system starting and binding to a service in the app.
ServiceRecord s = bindService(app, system,
null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
- s.lastTopAlmostPerceptibleBindRequestUptimeMs =
- nowUptime - 2 * mService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
+ mProcessStateController.setLastTopAlmostPerceptibleBindRequest(s,
+ nowUptime - 2 * mService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs);
s.getConnections().clear();
- app.mServices.updateHasTopStartedAlmostPerceptibleServices();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.updateHasTopStartedAlmostPerceptibleServices(app.mServices);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
}
@@ -800,12 +807,12 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
- system.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- system.mState.setHasTopUi(true);
+ mProcessStateController.setMaxAdj(system, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasTopUi(system, true);
// Simulate the system starting and binding to a service in the app.
ServiceRecord s = bindService(app, system,
null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(system, app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
@@ -817,8 +824,8 @@
public void testUpdateOomAdj_DoOne_Toast() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mState.setForcingToImportant(new Object());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setForcingToImportant(app, new Object());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -832,7 +839,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHeavyWeightProcess();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(false).when(wpc).isHeavyWeightProcess();
@@ -847,7 +854,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_HOME, HOME_APP_ADJ, SCHED_GROUP_BACKGROUND);
@@ -861,7 +868,7 @@
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isPreviousProcess();
doReturn(true).when(wpc).hasActivities();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
@@ -873,7 +880,7 @@
verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
? sFirstUiCachedAdj : CACHED_APP_MIN_ADJ;
@@ -896,9 +903,9 @@
doReturn(true).when(wpc).isPreviousProcess();
doReturn(true).when(wpc).hasActivities();
}
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
setProcessesToLru(apps);
- mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
for (int i = 0; i < numberOfApps; i++) {
assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
@@ -914,7 +921,7 @@
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
}
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
for (int i = 0; i < numberOfApps; i++) {
final int mruIndex = numberOfApps - i - 1;
@@ -938,10 +945,8 @@
public void testUpdateOomAdj_DoOne_Backup() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
- backupTarget.app = app;
- doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setBackupTarget(app);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
doReturn(null).when(mService.mBackupTargets).get(anyInt());
@@ -954,8 +959,8 @@
public void testUpdateOomAdj_DoOne_ClientActivities() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setHasClientActivities(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasClientActivities(app.mServices, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY_CLIENT, app.mState.getSetProcState());
@@ -966,8 +971,8 @@
public void testUpdateOomAdj_DoOne_TreatLikeActivity() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- app.mServices.setTreatLikeActivity(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setTreatLikeActivity(app.mServices, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
@@ -981,10 +986,10 @@
app.mState.setServiceB(true);
ServiceRecord s = mock(ServiceRecord.class);
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- app.mServices.startService(s);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(app.mServices, s);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_B_ADJ, SCHED_GROUP_BACKGROUND);
@@ -995,8 +1000,8 @@
public void testUpdateOomAdj_DoOne_MaxAdj() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.mState.setMaxAdj(PERCEPTIBLE_LOW_APP_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(app, PERCEPTIBLE_LOW_APP_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, PERCEPTIBLE_LOW_APP_ADJ,
@@ -1011,7 +1016,7 @@
app.mState.setCurRawAdj(SERVICE_ADJ);
app.mState.setCurAdj(SERVICE_ADJ);
doReturn(null).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertTrue(ProcessList.CACHED_APP_MIN_ADJ <= app.mState.getSetAdj());
@@ -1025,10 +1030,10 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ServiceRecord s = mock(ServiceRecord.class);
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- app.mServices.startService(s);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(app.mServices, s);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
@@ -1043,8 +1048,8 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
ServiceRecord s = bindService(app, client, null, null, Context.BIND_WAIVE_PRIORITY,
mock(IBinder.class));
- s.startRequested = true;
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client).when(mService).getTopApp();
updateOomAdj(client, app);
@@ -1062,10 +1067,10 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- client.mServices.setTreatLikeActivity(true);
+ mProcessStateController.setTreatLikeActivity(client.mServices, true);
bindService(app, client, null, null, Context.BIND_WAIVE_PRIORITY
| Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
@@ -1086,7 +1091,7 @@
mock(ActivityServiceConnectionsHolder.class));
doReturn(client).when(mService).getTopApp();
doReturn(true).when(cr.activity).isActivityVisible();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -1099,7 +1104,7 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
bindService(app, app, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
@@ -1114,9 +1119,9 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- client.mServices.setTreatLikeActivity(true);
+ mProcessStateController.setTreatLikeActivity(client.mServices, true);
bindService(app, client, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
@@ -1137,7 +1142,7 @@
mock(IBinder.class));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(null).when(mService).getTopApp();
@@ -1152,9 +1157,9 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- client.mState.setHasTopUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasTopUi(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
@@ -1170,8 +1175,8 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_IMPORTANT, mock(IBinder.class));
- client.mServices.startExecutingService(mock(ServiceRecord.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.startExecutingService(client.mServices, mock(ServiceRecord.class));
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -1188,7 +1193,7 @@
bindService(app, client, null, null, 0, mock(IBinder.class));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(null).when(mService).getTopApp();
@@ -1203,8 +1208,8 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.mState.getSetProcState());
@@ -1222,9 +1227,9 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_ASLEEP);
updateOomAdj(client, app);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_RESTRICTED);
@@ -1240,8 +1245,8 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
@@ -1256,8 +1261,9 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, 0, mock(IBinder.class));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true,
+ 0, /* hasNoneType=*/true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, client.mState.getSetProcState());
@@ -1279,16 +1285,16 @@
// In order to trick OomAdjuster to think it has a short-service, we need this logic.
ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
- s.isForeground = true;
- s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis());
- client.mServices.startService(s);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setIsForegroundService(s, true);
+ mProcessStateController.setForegroundServiceType(s, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(client.mServices, s);
client.mState.setLastTopTime(SystemClock.uptimeMillis());
- client.mServices.setHasForegroundServices(true, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
- /* hasNoneType=*/false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
// Client only has a SHORT_FGS, so it doesn't have BFSL, and that's propagated.
@@ -1310,16 +1316,16 @@
// In order to trick OomAdjuster to think it has a short-service, we need this logic.
ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
- s.isForeground = true;
- s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis());
- app2.mServices.startService(s);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setIsForegroundService(s, true);
+ mProcessStateController.setForegroundServiceType(s, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(app2.mServices, s);
app2.mState.setLastTopTime(SystemClock.uptimeMillis());
- app2.mServices.setHasForegroundServices(true, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
- /* hasNoneType=*/false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app2.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, /* hasNoneType=*/false);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app2);
// Client only has a SHORT_FGS, so it doesn't have BFSL, and that's propagated.
@@ -1331,7 +1337,7 @@
// Persistent process
ProcessRecord pers = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- pers.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(pers, PERSISTENT_PROC_ADJ);
// app1, which is bound by pers (which makes it BFGS)
ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
@@ -1358,18 +1364,16 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
- BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
- backupTarget.app = client;
- doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setBackupTarget(client);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
- doReturn(null).when(mService.mBackupTargets).get(anyInt());
+ mProcessStateController.stopBackupTarget(UserHandle.getUserId(MOCKAPP2_UID));
assertEquals(BACKUP_APP_ADJ, app.mState.getSetAdj());
assertNoBfsl(app);
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
updateOomAdj(client, app);
assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
@@ -1384,8 +1388,8 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
- client.mState.setRunningRemoteAnimation(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setRunningRemoteAnimation(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
@@ -1399,8 +1403,8 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_NOT_VISIBLE, mock(IBinder.class));
- client.mState.setRunningRemoteAnimation(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setRunningRemoteAnimation(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
@@ -1414,8 +1418,8 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, 0, mock(IBinder.class));
- client.mState.setHasOverlayUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasOverlayUi(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
@@ -1432,13 +1436,13 @@
bindService(app, client, null, null,
Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND,
mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
{
@@ -1451,14 +1455,14 @@
bindService(app, client, null, null,
Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND,
mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
{
@@ -1469,13 +1473,13 @@
bindService(app, client, null, null,
Context.BIND_ALMOST_PERCEPTIBLE,
mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
{
@@ -1488,14 +1492,14 @@
bindService(app, client, null, null,
Context.BIND_ALMOST_PERCEPTIBLE,
mock(IBinder.class));
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
- mService.mOomAdjuster.resetInternal();
+ mProcessStateController.getOomAdjuster().resetInternal();
}
}
@@ -1507,8 +1511,8 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, 0, mock(IBinder.class));
- client.mState.setRunningRemoteAnimation(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setRunningRemoteAnimation(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(VISIBLE_APP_ADJ, app.mState.getSetAdj());
@@ -1523,8 +1527,8 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindService(app, client, null, null, Context.BIND_IMPORTANT_BACKGROUND,
mock(IBinder.class));
- client.mState.setHasOverlayUi(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasOverlayUi(client, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.mState.getSetProcState());
@@ -1552,8 +1556,8 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindProvider(app, client, null, null, false);
- client.mServices.setTreatLikeActivity(true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setTreatLikeActivity(client.mServices, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client);
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
@@ -1571,7 +1575,7 @@
bindProvider(app, client, null, null, false);
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
doReturn(null).when(mService).getTopApp();
@@ -1585,9 +1589,9 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client.mServices, true, 0, true);
bindProvider(app, client, null, null, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1607,17 +1611,17 @@
// In order to trick OomAdjuster to think it has a short-service, we need this logic.
ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
s.appInfo = new ApplicationInfo();
- s.startRequested = true;
+ mProcessStateController.setStartRequested(s, true);
s.isForeground = true;
s.foregroundServiceType = FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- s.setShortFgsInfo(SystemClock.uptimeMillis());
- client.mServices.startService(s);
+ mProcessStateController.setShortFgsInfo(s, SystemClock.uptimeMillis());
+ mProcessStateController.startService(client.mServices, s);
client.mState.setLastTopTime(SystemClock.uptimeMillis());
- client.mServices.setHasForegroundServices(true, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
- /* hasNoneType=*/false);
+ mProcessStateController.setHasForegroundServices(client.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, false);
bindProvider(app, client, null, null, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
// Client only has a SHORT_FGS, so it doesn't have BFSL, and that's propagated.
@@ -1639,7 +1643,7 @@
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindProvider(app, client, null, null, true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, FOREGROUND_APP_ADJ,
@@ -1652,7 +1656,7 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
app.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
@@ -1664,7 +1668,7 @@
verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
? sFirstNonUiCachedAdj : sFirstCachedAdj;
@@ -1688,7 +1692,7 @@
bindService(client, client2, null, null, 0, mock(IBinder.class));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client2).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
doReturn(null).when(mService).getTopApp();
@@ -1707,8 +1711,8 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1727,8 +1731,8 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1747,13 +1751,13 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
bindService(client2, app, null, null, 0, mock(IBinder.class));
// Note: We add processes to LRU but still call updateOomAdjLocked() with a specific
// processes.
setProcessesToLru(app, client, client2);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1766,8 +1770,8 @@
assertBfsl(client);
assertBfsl(client2);
- client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client2.mServices, false, 0, false);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client2);
assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
@@ -1790,8 +1794,8 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client2, client, null, null, 0, mock(IBinder.class));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1817,8 +1821,8 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, null, 0, mock(IBinder.class));
bindService(client2, client, null, null, 0, mock(IBinder.class));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1851,8 +1855,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
bindService(client3, client4, null, null, 0, mock(IBinder.class));
bindService(client4, client3, null, null, 0, mock(IBinder.class));
- client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1883,13 +1887,13 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
bindService(client2, app, null, null, 0, mock(IBinder.class));
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client3, new Object());
bindService(app, client3, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1913,9 +1917,9 @@
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client3, new Object());
bindService(app, client3, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -1940,9 +1944,9 @@
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- client4.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client4, new Object());
bindService(app, client4, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -1965,13 +1969,13 @@
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client3, new Object());
bindService(app, client3, null, null, 0, mock(IBinder.class));
ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- client4.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client4.mServices, true, 0, true);
bindService(app, client4, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1992,12 +1996,12 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setForcingToImportant(new Object());
+ mProcessStateController.setForcingToImportant(client3, new Object());
bindService(app, client3, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, client3, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2016,8 +2020,8 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindProvider(client, client2, null, null, false);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2036,9 +2040,9 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindProvider(client, client2, null, null, false);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
bindService(client2, app, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2057,8 +2061,8 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindProvider(client, client2, null, null, false);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2077,9 +2081,9 @@
ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindProvider(client, client2, null, null, false);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
bindProvider(client2, app, null, null, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2102,10 +2106,10 @@
mock(IBinder.class));
bindService(app2, client2, null, null, Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
mock(IBinder.class));
- client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setMaxAdj(client1, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasForegroundServices(client2.mServices, true, 0, true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
@@ -2126,7 +2130,7 @@
assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_ASLEEP);
updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_IMPORTANT_FOREGROUND, VISIBLE_APP_ADJ,
SCHED_GROUP_TOP_APP);
@@ -2136,7 +2140,7 @@
bindService(client2, app1, null, null, 0, mock(IBinder.class));
bindService(app1, client2, null, null, 0, mock(IBinder.class));
- client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
+ mProcessStateController.setHasForegroundServices(client2.mServices, false, 0, false);
updateOomAdj(app1, client1, client2);
assertProcStates(app1, PROCESS_STATE_IMPORTANT_FOREGROUND, VISIBLE_APP_ADJ,
SCHED_GROUP_TOP_APP);
@@ -2153,8 +2157,8 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
final ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- client2.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client1, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client2, PERSISTENT_PROC_ADJ);
final ServiceRecord s1 = bindService(app1, client1, null, null,
Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class));
@@ -2178,10 +2182,10 @@
s2.getConnections().clear();
client1.mServices.removeAllConnections();
client2.mServices.removeAllConnections();
- client1.mState.setMaxAdj(UNKNOWN_ADJ);
- client2.mState.setMaxAdj(UNKNOWN_ADJ);
- client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- client2.mState.setHasOverlayUi(true);
+ mProcessStateController.setMaxAdj(client1, UNKNOWN_ADJ);
+ mProcessStateController.setMaxAdj(client2, UNKNOWN_ADJ);
+ mProcessStateController.setHasForegroundServices(client1.mServices, true, 0, true);
+ mProcessStateController.setHasOverlayUi(client2, true);
bindService(app1, client1, null, s1, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
mock(IBinder.class));
@@ -2196,10 +2200,10 @@
assertProcStates(app2, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
- client2.mState.setHasOverlayUi(false);
+ mProcessStateController.setHasOverlayUi(client2, false);
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(client2).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(client2, app2);
assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
@@ -2213,10 +2217,10 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
- client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client1, PERSISTENT_PROC_ADJ);
- app1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app1.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
bindService(app1, client1, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
@@ -2234,10 +2238,10 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
- client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client1, PERSISTENT_PROC_ADJ);
- app1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setHasForegroundServices(app1.mServices, true, 0, true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
bindService(app1, client1, null, null, Context.BIND_ALMOST_PERCEPTIBLE,
mock(IBinder.class));
@@ -2254,10 +2258,10 @@
public void testUpdateOomAdj_DoOne_PendingFinishAttach() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.setPendingFinishAttach(true);
+ mProcessStateController.setPendingFinishAttach(app, true);
app.mState.setHasForegroundActivities(false);
- mService.mOomAdjuster.setAttachingProcessStatesLSP(app);
+ mProcessStateController.setAttachingProcessStatesLSP(app);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FOREGROUND_APP_ADJ,
@@ -2269,11 +2273,11 @@
public void testUpdateOomAdj_DoOne_TopApp_PendingFinishAttach() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.setPendingFinishAttach(true);
+ mProcessStateController.setPendingFinishAttach(app, true);
app.mState.setHasForegroundActivities(true);
doReturn(app).when(mService).getTopApp();
- mService.mOomAdjuster.setAttachingProcessStatesLSP(app);
+ mProcessStateController.setAttachingProcessStatesLSP(app);
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ,
@@ -2303,40 +2307,40 @@
client1.setUidRecord(clientUidRecord);
client2.setUidRecord(clientUidRecord);
- client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- client2.mState.setForcingToImportant(new Object());
+ mProcessStateController.setHasForegroundServices(client1.mServices, true, 0, true);
+ mProcessStateController.setForcingToImportant(client2, new Object());
setProcessesToLru(app1, app2, app3, client1, client2);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
final ComponentName cn1 = ComponentName.unflattenFromString(
MOCKAPP_PACKAGENAME + "/.TestService");
final ServiceRecord s1 = bindService(app1, client1, null, null, 0, mock(IBinder.class));
setFieldValue(ServiceRecord.class, s1, "name", cn1);
- s1.startRequested = true;
+ mProcessStateController.setStartRequested(s1, true);
final ComponentName cn2 = ComponentName.unflattenFromString(
MOCKAPP2_PACKAGENAME + "/.TestService");
final ServiceRecord s2 = bindService(app2, client2, null, null, 0, mock(IBinder.class));
setFieldValue(ServiceRecord.class, s2, "name", cn2);
- s2.startRequested = true;
+ mProcessStateController.setStartRequested(s2, true);
final ComponentName cn3 = ComponentName.unflattenFromString(
MOCKAPP5_PACKAGENAME + "/.TestService");
final ServiceRecord s3 = bindService(app3, client1, null, null, 0, mock(IBinder.class));
setFieldValue(ServiceRecord.class, s3, "name", cn3);
- s3.startRequested = true;
+ mProcessStateController.setStartRequested(s3, true);
final ComponentName cn4 = ComponentName.unflattenFromString(
MOCKAPP3_PACKAGENAME + "/.TestService");
final ServiceRecord c2s = makeServiceRecord(client2);
setFieldValue(ServiceRecord.class, c2s, "name", cn4);
- c2s.startRequested = true;
+ mProcessStateController.setStartRequested(c2s, true);
try {
- mService.mOomAdjuster.mActiveUids.put(MOCKAPP_UID, app1UidRecord);
- mService.mOomAdjuster.mActiveUids.put(MOCKAPP2_UID, app2UidRecord);
- mService.mOomAdjuster.mActiveUids.put(MOCKAPP5_UID, app3UidRecord);
- mService.mOomAdjuster.mActiveUids.put(MOCKAPP3_UID, clientUidRecord);
+ mActiveUids.put(MOCKAPP_UID, app1UidRecord);
+ mActiveUids.put(MOCKAPP2_UID, app2UidRecord);
+ mActiveUids.put(MOCKAPP5_UID, app3UidRecord);
+ mActiveUids.put(MOCKAPP3_UID, clientUidRecord);
setServiceMap(s1, MOCKAPP_UID, cn1);
setServiceMap(s2, MOCKAPP2_UID, cn2);
@@ -2354,8 +2358,8 @@
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app2.mState.getSetProcState());
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, client2.mState.getSetProcState());
- client1.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
- client2.mState.setForcingToImportant(null);
+ mProcessStateController.setHasForegroundServices(client1.mServices, false, 0, false);
+ mProcessStateController.setForcingToImportant(client2, null);
app1UidRecord.reset();
app2UidRecord.reset();
app3UidRecord.reset();
@@ -2379,7 +2383,7 @@
.getAppStartModeLOSP(anyInt(), any(String.class), anyInt(),
anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
mService.mServices.mServiceMap.clear();
- mService.mOomAdjuster.mActiveUids.clear();
+ mActiveUids.clear();
}
}
@@ -2388,12 +2392,13 @@
public void testUpdateOomAdj_DoAll_Unbound() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.mState.setForcingToImportant(new Object());
ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setForcingToImportant(app, new Object());
+ mProcessStateController.setHasForegroundServices(app2.mServices, true, 0, /* hasNoneType=*/
+ true);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -2408,12 +2413,14 @@
public void testUpdateOomAdj_DoAll_BoundFgService() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.mState.setForcingToImportant(new Object());
ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+
+ mProcessStateController.setForcingToImportant(app, new Object());
+ mProcessStateController.setHasForegroundServices(app2.mServices, true, 0, /* hasNoneType=*/
+ true);
bindService(app, app2, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2435,9 +2442,9 @@
ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(app2, app3, null, null, 0, mock(IBinder.class));
- app3.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app3.mServices, true, 0, true);
bindService(app3, app, null, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2, app3);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2476,13 +2483,13 @@
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.mState.setHasOverlayUi(true);
+ mProcessStateController.setHasOverlayUi(app4, true);
bindService(app, app4, null, s, 0, mock(IBinder.class));
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app5.mServices, true, 0, true);
bindService(app, app5, null, s, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2, app3, app4, app5);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2518,13 +2525,13 @@
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.mState.setHasOverlayUi(true);
+ mProcessStateController.setHasOverlayUi(app4, true);
bindService(app, app4, null, s, 0, mock(IBinder.class));
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app5.mServices, true, 0, true);
bindService(app, app5, null, s, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app5, app4, app3, app2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2560,13 +2567,13 @@
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.mState.setHasOverlayUi(true);
+ mProcessStateController.setHasOverlayUi(app4, true);
bindService(app, app4, null, s, 0, mock(IBinder.class));
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app5.mServices, true, 0, true);
bindService(app, app5, null, s, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app3, app4, app2, app, app5);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2603,10 +2610,10 @@
mock(IBinder.class));
ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ mProcessStateController.setMaxAdj(client3, PERSISTENT_PROC_ADJ);
bindService(app, client3, null, null, Context.BIND_INCLUDE_CAPABILITIES,
mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client, client2, client3);
final int expected = PROCESS_CAPABILITY_ALL & ~PROCESS_CAPABILITY_BFSL;
@@ -2631,13 +2638,13 @@
doReturn(true).when(wpc).isHomeProcess();
ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
- app4.mState.setHasOverlayUi(true);
+ mProcessStateController.setHasOverlayUi(app4, true);
bindProvider(app, app4, cr, null, false);
ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
- app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
+ mProcessStateController.setHasForegroundServices(app5.mServices, true, 0, true);
bindProvider(app, app5, cr, null, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, app2, app3, app4, app5);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -2666,22 +2673,22 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
long now = SystemClock.uptimeMillis();
ServiceRecord s = bindService(app, app2, null, null, 0, mock(IBinder.class));
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
s = bindService(app2, app, null, null, 0, mock(IBinder.class));
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
s = mock(ServiceRecord.class);
- s.app = app3;
+ mProcessStateController.setHostProcess(s, app3);
setFieldValue(ServiceRecord.class, s, "connections",
new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
- app3.mServices.startService(s);
+ mProcessStateController.startService(app3.mServices, s);
doCallRealMethod().when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = now;
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
mService.mOomAdjuster.mNumServiceProcs = 3;
updateOomAdj(app3, app2, app);
@@ -2702,8 +2709,8 @@
// cachedAdj1 and cachedAdj2 will be read if USE_TIERED_CACHED_ADJ is disabled. Otherwise,
// sFirstUiCachedAdj and sFirstNonUiCachedAdj are used instead.
- final int cachedAdj1 = CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
- final int cachedAdj2 = cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+ final int cachedAdj1 = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
+ final int cachedAdj2 = cachedAdj1 + CACHED_APP_IMPORTANCE_LEVELS * 2;
doReturn(userOwner).when(mService.mUserController).getCurrentUserId();
final ArrayList<ProcessRecord> lru = mService.mProcessList.getLruProcessesLOSP();
@@ -2723,11 +2730,11 @@
ServiceRecord s = spy(new ServiceRecord(mService, cn, cn, null, 0, null,
si, false, null));
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
- app.mServices.startService(s);
- app.mState.setHasShownUi(true);
+ mProcessStateController.startService(app.mServices, s);
+ mProcessStateController.setHasShownUi(app, true);
final ServiceInfo si2 = mock(ServiceInfo.class);
si2.applicationInfo = mock(ApplicationInfo.class);
@@ -2735,13 +2742,14 @@
ServiceRecord s2 = spy(new ServiceRecord(mService, cn2, cn2, null, 0, null,
si2, false, null));
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s2).getConnections();
- s2.startRequested = true;
- s2.lastActivity = now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1;
+ mProcessStateController.setStartRequested(s2, true);
+ mProcessStateController.setServiceLastActivityTime(s2,
+ now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1);
- app2.mServices.startService(s2);
- app2.mState.setHasShownUi(false);
+ mProcessStateController.startService(app2.mServices, s2);
+ mProcessStateController.setHasShownUi(app2, false);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE,
@@ -2754,7 +2762,7 @@
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
- app.mState.setHasShownUi(false);
+ mProcessStateController.setHasShownUi(app, false);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND,
@@ -2763,27 +2771,28 @@
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
- s.lastActivity = now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1;
+ mProcessStateController.setServiceLastActivityTime(s,
+ now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE,
mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj1,
SCHED_GROUP_BACKGROUND, "cch-started-services", true);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
- app.mState.setHasShownUi(true);
+ mProcessStateController.setHasShownUi(app, true);
mService.mConstants.KEEP_WARMING_SERVICES.add(cn);
mService.mConstants.KEEP_WARMING_SERVICES.add(cn2);
s = spy(new ServiceRecord(mService, cn, cn, null, 0, null,
si, false, null));
doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
- app.mServices.startService(s);
+ mProcessStateController.startService(app.mServices, s);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND,
@@ -2795,8 +2804,9 @@
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
- app.mState.setHasShownUi(false);
- s.lastActivity = now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1;
+ mProcessStateController.setHasShownUi(app, false);
+ mProcessStateController.setServiceLastActivityTime(s,
+ now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND,
@@ -2825,7 +2835,7 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -2847,7 +2857,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -2873,14 +2883,14 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
long now = SystemClock.uptimeMillis();
ServiceRecord s = bindService(app, app2, null, null, 0, mock(IBinder.class));
- s.startRequested = true;
- s.lastActivity = now;
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, now);
s = bindService(app2, app3, null, null, 0, mock(IBinder.class));
- s.lastActivity = now;
+ mProcessStateController.setServiceLastActivityTime(s, now);
s = bindService(app3, app2, null, null, 0, mock(IBinder.class));
- s.lastActivity = now;
+ mProcessStateController.setServiceLastActivityTime(s, now);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
mService.mOomAdjuster.mNumServiceProcs = 3;
updateOomAdj(app, app2, app3);
@@ -2898,14 +2908,14 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(PROCESS_STATE_TOP).when(mService.mAtmInternal).getTopProcessState();
doReturn(app).when(mService).getTopApp();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
// Start binding to a service that isn't running yet.
ServiceRecord sr = makeServiceRecord(app);
- sr.app = null;
+ mProcessStateController.setHostProcess(sr, null);
bindService(null, app, null, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
// Since sr.app is null, this service cannot be in the same process as the
@@ -2924,13 +2934,13 @@
setProcessesToLru(app);
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
updateOomAdj();
// isolated process should be killed immediately after service stop.
verify(app).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
@@ -2944,13 +2954,13 @@
MOCKAPP_ISOLATED_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdjPending(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
updateOomAdjPending(app);
// isolated process should be killed immediately after service stop.
verify(app).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
@@ -2966,13 +2976,13 @@
setProcessesToLru(app);
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
updateOomAdj();
// isolated process with entry point should not be killed
verify(app, never()).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
@@ -2987,13 +2997,13 @@
app.setIsolatedEntryPoint("test");
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdjPending(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
- app.mServices.stopService(s);
+ mProcessStateController.stopService(app.mServices, s);
updateOomAdjPending(app);
// isolated process with entry point should not be killed
verify(app, never()).killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
@@ -3014,10 +3024,10 @@
setProcessesToLru(sandboxService, client, attributedClient);
- client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- attributedClient.mServices.setHasForegroundServices(true, 0, true);
+ mProcessStateController.setMaxAdj(client, PERSISTENT_PROC_ADJ);
+ mProcessStateController.setHasForegroundServices(attributedClient.mServices, true, 0, true);
bindService(sandboxService, client, attributedClient, null, 0, mock(IBinder.class));
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(client, PROCESS_STATE_PERSISTENT, PERSISTENT_PROC_ADJ,
SCHED_GROUP_DEFAULT);
@@ -3038,13 +3048,13 @@
// App1 binds to app2 and gets temp allowlisted.
bindService(app2, app, null, null, 0, mock(IBinder.class));
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
assertEquals(true, app.getUidRecord().isSetAllowListed());
assertEquals(true, app.mOptRecord.shouldNotFreeze());
assertEquals(true, app2.mOptRecord.shouldNotFreeze());
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
assertEquals(false, app.mOptRecord.shouldNotFreeze());
assertEquals(false, app2.mOptRecord.shouldNotFreeze());
@@ -3064,8 +3074,8 @@
// App1 and app2 both bind to app3 and get temp allowlisted.
bindService(app3, app, null, null, 0, mock(IBinder.class));
bindService(app3, app2, null, null, 0, mock(IBinder.class));
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP2_UID, true);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, true);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP2_UID, true);
assertEquals(true, app.getUidRecord().isSetAllowListed());
assertEquals(true, app2.getUidRecord().isSetAllowListed());
@@ -3074,7 +3084,7 @@
assertEquals(true, app3.mOptRecord.shouldNotFreeze());
// Remove app1 from allowlist.
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
assertEquals(true, app2.getUidRecord().isSetAllowListed());
assertEquals(false, app.mOptRecord.shouldNotFreeze());
@@ -3082,7 +3092,7 @@
assertEquals(true, app3.mOptRecord.shouldNotFreeze());
// Now remove app2 from allowlist.
- mService.mOomAdjuster.setUidTempAllowlistStateLSP(MOCKAPP2_UID, false);
+ mProcessStateController.setUidTempAllowlistStateLSP(MOCKAPP2_UID, false);
assertEquals(false, app.getUidRecord().isSetAllowListed());
assertEquals(false, app2.getUidRecord().isSetAllowListed());
assertEquals(false, app.mOptRecord.shouldNotFreeze());
@@ -3098,9 +3108,9 @@
setProcessesToLru(app);
ServiceRecord s = makeServiceRecord(app);
- s.startRequested = true;
- s.lastActivity = SystemClock.uptimeMillis();
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mProcessStateController.setStartRequested(s, true);
+ mProcessStateController.setServiceLastActivityTime(s, SystemClock.uptimeMillis());
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj();
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND,
"started-services");
@@ -3111,7 +3121,7 @@
verify(mService.mHandler).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
? sFirstNonUiCachedAdj : sFirstCachedAdj;
@@ -3131,9 +3141,9 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
app1.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
app2.mProviders.setLastProviderTime(SystemClock.uptimeMillis() + 2000);
- mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
setProcessesToLru(app1, app2);
- mService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND, "recent-provider");
@@ -3146,7 +3156,7 @@
verify(mService.mHandler, atLeastOnce()).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
? sFirstNonUiCachedAdj : sFirstCachedAdj;
@@ -3156,7 +3166,7 @@
verify(mService.mHandler, atLeastOnce()).sendEmptyMessageAtTime(
eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
- mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
+ mProcessStateController.runFollowUpUpdate();
assertProcStates(app2, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND,
"cch-empty");
}
@@ -3169,12 +3179,12 @@
private ServiceRecord makeServiceRecord(ProcessRecord app) {
final ServiceRecord record = mock(ServiceRecord.class);
- record.app = app;
+ mProcessStateController.setHostProcess(record, app);
setFieldValue(ServiceRecord.class, record, "connections",
new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
doCallRealMethod().when(record).getConnections();
setFieldValue(ServiceRecord.class, record, "packageName", app.info.packageName);
- app.mServices.startService(record);
+ mProcessStateController.startService(app.mServices, record);
record.appInfo = app.info;
setFieldValue(ServiceRecord.class, record, "bindings", new ArrayMap<>());
setFieldValue(ServiceRecord.class, record, "pendingStarts", new ArrayList<>());
@@ -3213,17 +3223,36 @@
doCallRealMethod().when(record).addConnection(any(IBinder.class),
any(ConnectionRecord.class));
record.addConnection(binder, cr);
- client.mServices.addConnection(cr);
+ mProcessStateController.addConnection(client.mServices, cr);
binding.connections.add(cr);
doNothing().when(cr).trackProcState(anyInt(), anyInt());
return record;
}
+ private void setWakefulness(int state) {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ mProcessStateController.setWakefulness(state);
+ } else {
+ mService.mWakefulness.set(state);
+ }
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private void setBackupTarget(ProcessRecord app) {
+ if (Flags.pushGlobalStateToOomadjuster()) {
+ mProcessStateController.setBackupTarget(app, app.userId);
+ } else {
+ BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
+ backupTarget.app = app;
+ doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt());
+ }
+ }
+
private ContentProviderRecord bindProvider(ProcessRecord publisher, ProcessRecord client,
ContentProviderRecord record, String name, boolean hasExternalProviders) {
if (record == null) {
record = mock(ContentProviderRecord.class);
- publisher.mProviders.installProvider(name, record);
+ mProcessStateController.addPublishedProvider(publisher, name, record);
record.proc = publisher;
setFieldValue(ContentProviderRecord.class, record, "connections",
new ArrayList<ContentProviderConnection>());
@@ -3232,7 +3261,7 @@
ContentProviderConnection conn = spy(new ContentProviderConnection(record, client,
client.info.packageName, UserHandle.getUserId(client.uid)));
record.connections.add(conn);
- client.mProviders.addProviderConnection(conn);
+ mProcessStateController.addProviderConnection(client, conn);
return record;
}
@@ -3405,10 +3434,10 @@
}
providers.setLastProviderTime(mLastProviderTime);
- UidRecord uidRec = mService.mOomAdjuster.mActiveUids.get(mUid);
+ UidRecord uidRec = mActiveUids.get(mUid);
if (uidRec == null) {
uidRec = new UidRecord(mUid, mService);
- mService.mOomAdjuster.mActiveUids.put(mUid, uidRec);
+ mActiveUids.put(mUid, uidRec);
}
uidRec.addProcess(app);
app.setUidRecord(uidRec);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 1ff4a27..59302ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -50,7 +50,6 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
@@ -169,6 +168,7 @@
realAtm.initialize(null, null, mContext.getMainLooper());
realAms.mActivityTaskManager = spy(realAtm);
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mProcessStateController = spy(realAms.mProcessStateController);
realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
realAms.mPackageManagerInt = mPackageManagerInt;
@@ -242,14 +242,14 @@
USER_SYSTEM // userId
));
- verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt());
- clearInvocations(mAms.mOomAdjuster);
+ verify(mAms.mProcessStateController, bindMode).runPendingUpdate(anyInt());
+ clearInvocations(mAms.mProcessStateController);
// Unbind the service.
mAms.unbindService(serviceConnection);
- verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt());
- clearInvocations(mAms.mOomAdjuster);
+ verify(mAms.mProcessStateController, unbindMode).runPendingUpdate(anyInt());
+ clearInvocations(mAms.mProcessStateController);
removeProcessRecord(app);
}
@@ -496,8 +496,8 @@
USER_SYSTEM // userId
));
- verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt());
- clearInvocations(mAms.mOomAdjuster);
+ verify(mAms.mProcessStateController, bindMode).runPendingUpdate(anyInt());
+ clearInvocations(mAms.mProcessStateController);
if (clientApp.isFreezable()) {
verify(mAms.mOomAdjuster.mCachedAppOptimizer,
@@ -509,8 +509,8 @@
// Unbind the service.
mAms.unbindService(serviceConnection);
- verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt());
- clearInvocations(mAms.mOomAdjuster);
+ verify(mAms.mProcessStateController, unbindMode).runPendingUpdate(anyInt());
+ clearInvocations(mAms.mProcessStateController);
removeProcessRecord(clientApp);
removeProcessRecord(serviceApp);
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index fab2031..d9e071f 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -71,15 +71,16 @@
"androidx.test.uiautomator_uiautomator",
"modules-utils-binary-xml",
"flag-junit",
- "statsdprotolite",
- "StatsdTestUtils",
- "platformprotoslite",
],
srcs: [
"src/com/android/server/power/stats/*.java",
"src/com/android/server/power/stats/format/*.java",
"src/com/android/server/power/stats/processor/*.java",
],
+ // TODO(b/372292543): Enable this test.
+ exclude_srcs: [
+ "src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java",
+ ],
java_resources: [
"res/xml/power_profile*.xml",
],
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index c645c08..2724149 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -113,7 +113,6 @@
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
- <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
index c76392b..5134737 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -236,6 +236,27 @@
}
@Test
+ fun whenScrollToggleOn_ScrollRightKeyIsPressed_scrollEventIsSent() {
+ // There should be some delay between the downTime of the key event and calling onKeyEvent
+ val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
+ val keyCodeScrollToggle = MouseKeysInterceptor.MouseKeyEvent.SCROLL_TOGGLE.keyCodeValue
+ val keyCodeScroll = MouseKeysInterceptor.MouseKeyEvent.RIGHT_MOVE_OR_SCROLL.keyCodeValue
+
+ val scrollToggleDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeScrollToggle, 0, 0, DEVICE_ID, 0)
+ val scrollDownEvent = KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeScroll, 0, 0, DEVICE_ID, 0)
+
+ mouseKeysInterceptor.onKeyEvent(scrollToggleDownEvent, 0)
+ mouseKeysInterceptor.onKeyEvent(scrollDownEvent, 0)
+ testLooper.dispatchAll()
+
+ // Verify the sendScrollEvent method is called once and capture the arguments
+ verifyScrollEvents(arrayOf<Float>(-MouseKeysInterceptor.MOUSE_SCROLL_STEP),
+ arrayOf<Float>(0f))
+ }
+
+ @Test
fun whenScrollToggleOff_DirectionalUpKeyIsPressed_RelativeEventIsSent() {
// There should be some delay between the downTime of the key event and calling onKeyEvent
val downTime = clock.now() - KEYBOARD_POST_EVENT_DELAY_MILLIS
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
index 6aa8a32..06ebe6e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -62,6 +62,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import org.junit.Before;
@@ -92,12 +93,16 @@
private MagnificationConnectionManager.Callback mMockCallback;
private MockContentResolver mResolver;
private MagnificationConnectionManager mMagnificationConnectionManager;
+ @Mock
+ private UserManagerInternal mMockUserManagerInternal;
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal);
+ LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
mResolver = new MockContentResolver();
mMockConnection = new MockMagnificationConnection();
mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, new Object(),
@@ -110,6 +115,8 @@
Settings.Secure.putFloatForUser(mResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.5f,
CURRENT_USER_ID);
+
+ when(mMockUserManagerInternal.isVisibleBackgroundFullUser(anyInt())).thenReturn(false);
}
private void stubSetConnection(boolean needDelay) {
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index 31bf5f0..4981ceb 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -61,6 +61,7 @@
private static final long USAGE_STATS_INTERACTION = 10 * 60 * 1000L;
private static final long SERVICE_USAGE_INTERACTION = 60 * 1000;
+ @SuppressWarnings("GuardedBy")
@BeforeClass
public static void setUpOnce() {
sContext = getInstrumentation().getTargetContext();
@@ -92,8 +93,11 @@
return true;
}
};
- sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, null,
- injector);
+ sService.mProcessStateController = new ProcessStateController.Builder(sService,
+ sService.mProcessList, null)
+ .setOomAdjusterInjector(injector)
+ .build();
+ sService.mOomAdjuster = sService.mProcessStateController.getOomAdjuster();
LocalServices.addService(UsageStatsManagerInternal.class,
mock(UsageStatsManagerInternal.class));
sService.mUsageStatsService = LocalServices.getService(UsageStatsManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index 840e5c5..c970a3e 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -65,6 +65,7 @@
VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.CREATE_VIRTUAL_DEVICE,
Manifest.permission.GET_APP_OPS_STATS
);
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
index e3eca6d..7f2327aa 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
@@ -58,6 +58,7 @@
VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.CREATE_VIRTUAL_DEVICE,
Manifest.permission.GET_APP_OPS_STATS);
private static final String ATTRIBUTION_TAG_1 = "attributionTag1";
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index b0846f6..1abd4eb 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -22,14 +22,16 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpNotedListener;
import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
-import android.virtualdevice.cts.common.VirtualDeviceRule;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -40,6 +42,8 @@
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Tests watching noted ops.
*/
@@ -47,7 +51,7 @@
@RunWith(AndroidJUnit4.class)
public class AppOpsNotedWatcherTest {
@Rule
- public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -115,12 +119,19 @@
public void testWatchNotedOpsForExternalDevice() {
final AppOpsManager.OnOpNotedListener listener = mock(
AppOpsManager.OnOpNotedListener.class);
- final VirtualDeviceManager.VirtualDevice virtualDevice =
- mVirtualDeviceRule.createManagedVirtualDevice();
- final int virtualDeviceId = virtualDevice.getDeviceId();
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
getContext().getOpPackageName(), getContext().getAttributionTag(),
- virtualDeviceId);
+ virtualDeviceId.get());
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
appOpsManager.startWatchingNoted(new int[]{AppOpsManager.OP_FINE_LOCATION,
@@ -131,7 +142,7 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getOpPackageName()),
- eq(getContext().getAttributionTag()), eq(virtualDeviceId),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index d46fb90..8a6ba4d 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -16,6 +16,8 @@
package com.android.server.appop;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -26,10 +28,11 @@
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpStartedListener;
import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
-import android.virtualdevice.cts.common.VirtualDeviceRule;
+import android.virtualdevice.cts.common.FakeAssociationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -40,13 +43,15 @@
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+import java.util.concurrent.atomic.AtomicInteger;
+
/** Tests watching started ops. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsStartedWatcherTest {
@Rule
- public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -119,13 +124,20 @@
@Test
public void testWatchStartedOpsForExternalDevice() {
- final VirtualDeviceManager.VirtualDevice virtualDevice =
- mVirtualDeviceRule.createManagedVirtualDevice();
- final int virtualDeviceId = virtualDevice.getDeviceId();
+ final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
+ VirtualDeviceManager.class);
+ AtomicInteger virtualDeviceId = new AtomicInteger();
+ runWithShellPermissionIdentity(() -> {
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().setName("virtual_device").build());
+ virtualDeviceId.set(virtualDevice.getDeviceId());
+ });
final OnOpStartedListener listener = mock(OnOpStartedListener.class);
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
getContext().getOpPackageName(), getContext().getAttributionTag(),
- virtualDeviceId);
+ virtualDeviceId.get());
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
appOpsManager.startWatchingStarted(new int[]{AppOpsManager.OP_FINE_LOCATION,
@@ -138,7 +150,7 @@
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getOpPackageName()),
- eq(getContext().getAttributionTag()), eq(virtualDeviceId),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
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 98b1191..62f5edc 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
@@ -50,6 +50,7 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.Manifest;
import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
@@ -112,11 +113,10 @@
import android.view.DisplayInfo;
import android.view.KeyEvent;
import android.view.WindowManager;
-import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
@@ -224,7 +224,9 @@
public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
- public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ Manifest.permission.CREATE_VIRTUAL_DEVICE);
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
@@ -1067,65 +1069,64 @@
@Test
public void createVirtualDpad_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER)));
+ try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
+ }
}
@Test
public void createVirtualKeyboard_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER)));
+ try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
+ }
}
@Test
public void createVirtualMouse_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER)));
+ try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
+ }
}
@Test
public void createVirtualTouchscreen_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER)));
+ try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
+ }
}
@Test
public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualNavigationTouchpad(
- NAVIGATION_TOUCHPAD_CONFIG,
- BINDER)));
+ try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
+ BINDER));
+ }
}
@Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.onAudioSessionStarting(
- DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback)));
+ try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.onAudioSessionStarting(
+ DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback));
+ }
}
@Test
public void onAudioSessionEnded_noPermission_failsSecurityException() {
- // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
- SystemUtil.runWithShellPermissionIdentity(() ->
- assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded()));
+ try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+ assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
+ }
}
@Test
@@ -1998,6 +1999,21 @@
/* tag= */ null, MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
/* associatedDevice= */ null, /* selfManaged= */ true,
/* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
- /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
+ /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0,
+ /* systemDataSyncFlags= */ -1, /* deviceIcon= */ null);
+ }
+
+ /** Helper class to drop permissions temporarily and restore them at the end of a test. */
+ static final class DropShellPermissionsTemporarily implements AutoCloseable {
+ DropShellPermissionsTemporarily() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Override
+ public void close() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity();
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 733f056..b565f4b 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -24,13 +24,9 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
-import android.annotation.NonNull;
+import android.content.Context;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateRequest;
@@ -40,9 +36,10 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import androidx.test.InstrumentationRegistry;
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -63,16 +60,25 @@
/**
* Unit tests for {@link DeviceStateManagerService}.
- * <p/>
- * Run with <code>atest DeviceStateManagerServiceTest</code>.
+ *
+ * <p> Build/Install/Run:
+ * atest FrameworksServicesTests:DeviceStateManagerServiceTest
*/
@Presubmit
@RunWith(AndroidJUnit4.class)
public final class DeviceStateManagerServiceTest {
private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(
new DeviceState.Configuration.Builder(0, "DEFAULT").build());
+ private static final int DEFAULT_DEVICE_STATE_IDENTIFIER = DEFAULT_DEVICE_STATE.getIdentifier();
+ private static final String DEFAULT_DEVICE_STATE_TRACE_STRING =
+ DEFAULT_DEVICE_STATE_IDENTIFIER + ":" + DEFAULT_DEVICE_STATE.getName();
+
private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(
new DeviceState.Configuration.Builder(1, "DEFAULT").build());
+ private static final int OTHER_DEVICE_STATE_IDENTIFIER = OTHER_DEVICE_STATE.getIdentifier();
+ private static final String OTHER_DEVICE_STATE_TRACE_STRING =
+ OTHER_DEVICE_STATE_IDENTIFIER + ":" + OTHER_DEVICE_STATE.getName();
+
private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
new DeviceState(new DeviceState.Configuration.Builder(2,
"DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP")
@@ -93,24 +99,29 @@
private static final int TIMEOUT = 2000;
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ @NonNull
private TestDeviceStatePolicy mPolicy;
+ @NonNull
private TestDeviceStateProvider mProvider;
+ @NonNull
private DeviceStateManagerService mService;
+ @NonNull
private TestSystemPropertySetter mSysPropSetter;
+ @NonNull
private WindowProcessController mWindowProcessController;
@Before
public void setup() {
mProvider = new TestDeviceStateProvider();
- mPolicy = new TestDeviceStatePolicy(mProvider);
+ mPolicy = new TestDeviceStatePolicy(mContext, mProvider);
mSysPropSetter = new TestSystemPropertySetter();
setupDeviceStateManagerService();
flushHandler(); // Flush the handler to ensure the initial values are committed.
}
private void setupDeviceStateManagerService() {
- mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy,
- mSysPropSetter);
+ mService = new DeviceStateManagerService(mContext, mPolicy, mSysPropSetter);
// Necessary to allow us to check for top app process id in tests
mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
@@ -136,56 +147,53 @@
@Test
public void baseStateChanged() {
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
}
@Test
public void baseStateChanged_withStatePendingPolicyCallback() {
mPolicy.blockConfigure();
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
- mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
+ mProvider.setState(DEFAULT_DEVICE_STATE_IDENTIFIER);
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
mPolicy.resumeConfigure();
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
@@ -194,13 +202,12 @@
mProvider.setState(UNSUPPORTED_DEVICE_STATE.getIdentifier());
});
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
@@ -208,25 +215,23 @@
assertThrows(IllegalArgumentException.class,
() -> mProvider.setState(INVALID_DEVICE_STATE_IDENTIFIER));
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
public void supportedStatesChanged() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
@@ -235,30 +240,29 @@
// The current committed and requests states do not change because the current state remains
// supported.
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().supportedStates, List.of(DEFAULT_DEVICE_STATE));
+ assertThat(callback.getLastNotifiedInfo().supportedStates)
+ .containsExactly(DEFAULT_DEVICE_STATE);
}
@Test
public void supportedStatesChanged_statesRemainSame() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
// An initial callback will be triggered on registration, so we clear it here.
flushHandler();
callback.clearLastNotifiedInfo();
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
@@ -268,26 +272,26 @@
// The current committed and requests states do not change because the current state remains
// supported.
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
// The callback wasn't notified about a change in supported states as the states have not
// changed.
- assertNull(callback.getLastNotifiedInfo());
+ assertThat(callback.getLastNotifiedInfo()).isNull();
}
@Test
public void getDeviceStateInfo() throws RemoteException {
- DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
- assertNotNull(info);
- assertEquals(info.supportedStates, SUPPORTED_DEVICE_STATES);
- assertEquals(info.baseState, DEFAULT_DEVICE_STATE);
- assertEquals(info.currentState, DEFAULT_DEVICE_STATE);
+ final DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
+ assertThat(info).isNotNull();
+ assertThat(info.supportedStates)
+ .containsExactlyElementsIn(SUPPORTED_DEVICE_STATES).inOrder();
+ assertThat(info.baseState).isEqualTo(DEFAULT_DEVICE_STATE);
+ assertThat(info.currentState).isEqualTo(DEFAULT_DEVICE_STATE);
}
@FlakyTest(bugId = 297949293)
@@ -295,105 +299,109 @@
public void getDeviceStateInfo_baseStateAndCommittedStateNotSet() throws RemoteException {
// Create a provider and a service without an initial base state.
mProvider = new TestDeviceStateProvider(null /* initialState */);
- mPolicy = new TestDeviceStatePolicy(mProvider);
+ mPolicy = new TestDeviceStatePolicy(mContext, mProvider);
setupDeviceStateManagerService();
flushHandler(); // Flush the handler to ensure the initial values are committed.
- DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
+ final DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
- assertEquals(info.supportedStates, SUPPORTED_DEVICE_STATES);
- assertEquals(info.baseState.getIdentifier(), INVALID_DEVICE_STATE_IDENTIFIER);
- assertEquals(info.currentState.getIdentifier(), INVALID_DEVICE_STATE_IDENTIFIER);
+ assertThat(info.supportedStates)
+ .containsExactlyElementsIn(SUPPORTED_DEVICE_STATES).inOrder();
+ assertThat(info.baseState.getIdentifier()).isEqualTo(INVALID_DEVICE_STATE_IDENTIFIER);
+ assertThat(info.currentState.getIdentifier()).isEqualTo(INVALID_DEVICE_STATE_IDENTIFIER);
}
@Test
public void registerCallback() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == OTHER_DEVICE_STATE.getIdentifier());
+ == OTHER_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
- == OTHER_DEVICE_STATE.getIdentifier());
+ == OTHER_DEVICE_STATE_IDENTIFIER);
- mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
+ mProvider.setState(DEFAULT_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
-
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
mPolicy.blockConfigure();
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
// The callback should not have been notified of the state change as the policy is still
// pending callback.
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
mPolicy.resumeConfigure();
// Now that the policy is finished processing the callback should be notified of the state
// change.
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == OTHER_DEVICE_STATE.getIdentifier());
+ == OTHER_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
- == OTHER_DEVICE_STATE.getIdentifier());
+ == OTHER_DEVICE_STATE_IDENTIFIER);
}
@Test
- public void registerCallback_emitsInitialValue() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ public void registerCallback_initialValueAvailable_emitsDeviceState() throws RemoteException {
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+
mService.getBinderService().registerCallback(callback);
flushHandler();
- assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
+ final DeviceStateInfo stateInfo = callback.getLastNotifiedInfo();
+
+ assertThat(stateInfo).isNotNull();
+ assertThat(stateInfo.baseState).isEqualTo(DEFAULT_DEVICE_STATE);
+ assertThat(stateInfo.currentState).isEqualTo(DEFAULT_DEVICE_STATE);
}
@Test
- public void registerCallback_initialValueUnavailable() throws RemoteException {
+ public void registerCallback_initialValueUnavailable_nullDeviceState() throws RemoteException {
// Create a provider and a service without an initial base state.
mProvider = new TestDeviceStateProvider(null /* initialState */);
- mPolicy = new TestDeviceStatePolicy(mProvider);
+ mPolicy = new TestDeviceStatePolicy(mContext, mProvider);
setupDeviceStateManagerService();
flushHandler(); // Flush the handler to ensure the initial values are committed.
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
flushHandler();
+ final DeviceStateInfo stateInfo = callback.getLastNotifiedInfo();
+
// The callback should never be called when the base state is not set yet.
- assertNull(callback.getLastNotifiedInfo());
+ assertThat(stateInfo).isNull();
}
@Test
public void requestState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, OTHER_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
waitAndAssert(() -> callback.getLastNotifiedStatus(token)
== TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
- assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo()).isNotNull();
+ assertThat(callback.getLastNotifiedInfo().baseState).isEqualTo(DEFAULT_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(OTHER_DEVICE_STATE);
mService.getBinderService().cancelStateRequest();
@@ -401,21 +409,20 @@
== TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state once the override is cleared.
waitAndAssert(() -> mService.getCommittedState().equals(Optional.of(DEFAULT_DEVICE_STATE)));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
- assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo().baseState).isEqualTo(DEFAULT_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(DEFAULT_DEVICE_STATE);
}
@FlakyTest(bugId = 200332057)
@Test
public void requestState_pendingStateAtRequest() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
flushHandler();
@@ -423,134 +430,129 @@
final IBinder firstRequestToken = new Binder();
final IBinder secondRequestToken = new Binder();
- assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(firstRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(secondRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(firstRequestToken,
- OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+ mService.getBinderService()
+ .requestState(firstRequestToken, OTHER_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
// Flush the handler twice. The first flush ensures the request is added and the policy is
// notified, while the second flush ensures the callback is notified once the change is
// committed.
flushHandler(2 /* count */);
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
- mService.getBinderService().requestState(secondRequestToken,
- DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+ mService.getBinderService()
+ .requestState(secondRequestToken, DEFAULT_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
mPolicy.resumeConfigureOnce();
flushHandler();
// First request status is now canceled as there is another pending request.
- assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(firstRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
// Second request status still unknown because the service is still awaiting policy
// callback.
- assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(callback.getLastNotifiedStatus(secondRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getPendingState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
mPolicy.resumeConfigure();
flushHandler();
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
// Now cancel the second request to make the first request active.
mService.getBinderService().cancelStateRequest();
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
- assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(firstRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(secondRequestToken))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getPendingState(), Optional.empty());
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getPendingState()).isEmpty();
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
public void requestState_sameAsBaseState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(),
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, DEFAULT_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
}
@Test
public void requestState_flagCancelWhenBaseChanges() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
+ mService.getBinderService().requestState(token, OTHER_DEVICE_STATE_IDENTIFIER,
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES);
// Flush the handler twice. The first flush ensures the request is added and the policy is
// notified, while the second flush ensures the callback is notified once the change is
// committed.
flushHandler(2 /* count */);
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getOverrideState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
- mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ mProvider.setState(OTHER_DEVICE_STATE_IDENTIFIER);
flushHandler();
// Request is canceled because the base state changed.
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state once the override is cleared.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
}
@Test
@@ -581,8 +583,8 @@
requestState_flagCancelWhenRequesterNotOnTop_common(
// When the app is foreground, the state should not change
() -> {
- int pid = Binder.getCallingPid();
- int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
try {
mService.mProcessObserver.onForegroundActivitiesChanged(pid, uid,
true /* foregroundActivities */);
@@ -594,8 +596,8 @@
() -> {
when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
try {
- int pid = Binder.getCallingPid();
- int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
mService.mProcessObserver.onForegroundActivitiesChanged(pid, uid,
false /* foregroundActivities */);
@@ -609,68 +611,66 @@
@FlakyTest(bugId = 200332057)
@Test
public void requestState_becomesUnsupported() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
- mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, OTHER_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
mProvider.notifySupportedDeviceStates(
new DeviceState[]{DEFAULT_DEVICE_STATE});
flushHandler();
// Request is canceled because the state is no longer supported.
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state as the override state is no longer
// supported.
- assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
}
@Test
public void requestState_unsupportedState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
final IBinder token = new Binder();
- mService.getBinderService().requestState(token,
- UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */);
});
}
@Test
public void requestState_invalidState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
final IBinder token = new Binder();
- mService.getBinderService().requestState(token, INVALID_DEVICE_STATE_IDENTIFIER,
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, INVALID_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
});
}
@@ -678,40 +678,39 @@
public void requestState_beforeRegisteringCallback() {
assertThrows(IllegalStateException.class, () -> {
final IBinder token = new Binder();
- mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(),
- 0 /* flags */);
+ mService.getBinderService()
+ .requestState(token, DEFAULT_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
});
}
@Test
public void requestBaseStateOverride() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
mService.getBinderService().requestBaseStateOverride(token,
- OTHER_DEVICE_STATE.getIdentifier(),
+ OTHER_DEVICE_STATE_IDENTIFIER,
0 /* flags */);
waitAndAssert(() -> callback.getLastNotifiedStatus(token)
== TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getOverrideBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
- assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo()).isNotNull();
+ assertThat(callback.getLastNotifiedInfo().baseState).isEqualTo(OTHER_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(OTHER_DEVICE_STATE);
mService.getBinderService().cancelBaseStateOverride();
@@ -719,52 +718,50 @@
== TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state once the override is cleared.
waitAndAssert(() -> mService.getCommittedState().equals(Optional.of(DEFAULT_DEVICE_STATE)));
- assertEquals(mSysPropSetter.getValue(),
- DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertFalse(mService.getOverrideBaseState().isPresent());
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertThat(mSysPropSetter.getValue()).isEqualTo(DEFAULT_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getOverrideBaseState()).isEmpty();
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(DEFAULT_DEVICE_STATE_IDENTIFIER);
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
- == DEFAULT_DEVICE_STATE.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
+ == DEFAULT_DEVICE_STATE_IDENTIFIER);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(DEFAULT_DEVICE_STATE);
}
@Test
public void requestBaseStateOverride_cancelledByBaseStateUpdate() throws RemoteException {
- final DeviceState testDeviceState = new DeviceState(new DeviceState.Configuration.Builder(2,
- "TEST").build());
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final DeviceState testDeviceState = new DeviceState(
+ new DeviceState.Configuration.Builder(2, "TEST").build());
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, testDeviceState});
flushHandler();
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
mService.getBinderService().requestBaseStateOverride(token,
- OTHER_DEVICE_STATE.getIdentifier(),
+ OTHER_DEVICE_STATE_IDENTIFIER,
0 /* flags */);
waitAndAssert(() -> callback.getLastNotifiedStatus(token)
== TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mSysPropSetter.getValue(),
- OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
- assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
- assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mSysPropSetter.getValue()).isEqualTo(OTHER_DEVICE_STATE_TRACE_STRING);
+ assertThat(mService.getBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getOverrideBaseState()).hasValue(OTHER_DEVICE_STATE);
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(OTHER_DEVICE_STATE_IDENTIFIER);
- assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE);
- assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo()).isNotNull();
+ assertThat(callback.getLastNotifiedInfo().baseState).isEqualTo(OTHER_DEVICE_STATE);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(OTHER_DEVICE_STATE);
mProvider.setState(testDeviceState.getIdentifier());
@@ -772,22 +769,22 @@
== TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set to the new base state once the override is cleared.
waitAndAssert(() -> mService.getCommittedState().equals(Optional.of(testDeviceState)));
- assertEquals(mSysPropSetter.getValue(),
- testDeviceState.getIdentifier() + ":" + testDeviceState.getName());
- assertEquals(mService.getBaseState(), Optional.of(testDeviceState));
- assertFalse(mService.getOverrideBaseState().isPresent());
- assertFalse(mService.getOverrideState().isPresent());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- testDeviceState.getIdentifier());
+ assertThat(mSysPropSetter.getValue())
+ .isEqualTo(testDeviceState.getIdentifier() + ":" + testDeviceState.getName());
+ assertThat(mService.getBaseState()).hasValue(testDeviceState);
+ assertThat(mService.getOverrideBaseState()).isEmpty();
+ assertThat(mService.getOverrideState()).isEmpty();
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(testDeviceState.getIdentifier());
waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
== testDeviceState.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState, testDeviceState);
+ assertThat(callback.getLastNotifiedInfo().currentState).isEqualTo(testDeviceState);
}
@Test
public void requestBaseState_unsupportedState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
@@ -799,7 +796,7 @@
@Test
public void requestBaseState_invalidState() throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
@@ -814,7 +811,7 @@
assertThrows(IllegalStateException.class, () -> {
final IBinder token = new Binder();
mService.getBinderService().requestBaseStateOverride(token,
- DEFAULT_DEVICE_STATE.getIdentifier(),
+ DEFAULT_DEVICE_STATE_IDENTIFIER,
0 /* flags */);
});
}
@@ -834,21 +831,21 @@
Runnable noChangeEvent,
Runnable autoCancelEvent
) throws RemoteException {
- TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_UNKNOWN);
mService.getBinderService().requestState(token,
DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier(),
0 /* flags */);
flushHandler(2 /* count */);
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
assertDeviceStateConditions(
@@ -858,8 +855,8 @@
noChangeEvent.run();
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_ACTIVE);
assertDeviceStateConditions(
DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
DEFAULT_DEVICE_STATE, /* base state */
@@ -867,8 +864,8 @@
autoCancelEvent.run();
flushHandler();
- assertEquals(callback.getLastNotifiedStatus(token),
- TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertThat(callback.getLastNotifiedStatus(token))
+ .isEqualTo(TestDeviceStateManagerCallback.STATUS_CANCELED);
assertDeviceStateConditions(DEFAULT_DEVICE_STATE, DEFAULT_DEVICE_STATE,
false /* isOverrideState */);
}
@@ -881,20 +878,20 @@
* @param isOverrideState whether a state override is active.
*/
private void assertDeviceStateConditions(
- DeviceState state, DeviceState baseState,
+ @NonNull DeviceState state, @NonNull DeviceState baseState,
boolean isOverrideState) {
- assertEquals(mService.getCommittedState(), Optional.of(state));
- assertEquals(mService.getBaseState(), Optional.of(baseState));
- assertEquals(mSysPropSetter.getValue(),
- state.getIdentifier() + ":" + state.getName());
- assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- state.getIdentifier());
+ assertThat(mService.getCommittedState()).hasValue(state);
+ assertThat(mService.getBaseState()).hasValue(baseState);
+ assertThat(mSysPropSetter.getValue())
+ .isEqualTo(state.getIdentifier() + ":" + state.getName());
+ assertThat(mPolicy.getMostRecentRequestedStateToConfigure())
+ .isEqualTo(state.getIdentifier());
if (isOverrideState) {
// When a state override is active, the committed state should batch the override state.
- assertEquals(mService.getOverrideState().get(), state);
+ assertThat(mService.getOverrideState()).hasValue(state);
} else {
// When there is no state override, the override state should be empty.
- assertFalse(mService.getOverrideState().isPresent());
+ assertThat(mService.getOverrideState()).isEmpty();
}
}
@@ -902,10 +899,11 @@
private final DeviceStateProvider mProvider;
private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE_IDENTIFIER;
private boolean mConfigureBlocked = false;
+ @Nullable
private Runnable mPendingConfigureCompleteRunnable;
- TestDeviceStatePolicy(DeviceStateProvider provider) {
- super(InstrumentationRegistry.getContext());
+ TestDeviceStatePolicy(@NonNull Context context, @NonNull DeviceStateProvider provider) {
+ super(context);
mProvider = provider;
}
@@ -921,7 +919,7 @@
public void resumeConfigure() {
mConfigureBlocked = false;
if (mPendingConfigureCompleteRunnable != null) {
- Runnable onComplete = mPendingConfigureCompleteRunnable;
+ final Runnable onComplete = mPendingConfigureCompleteRunnable;
mPendingConfigureCompleteRunnable = null;
onComplete.run();
}
@@ -929,7 +927,7 @@
public void resumeConfigureOnce() {
if (mPendingConfigureCompleteRunnable != null) {
- Runnable onComplete = mPendingConfigureCompleteRunnable;
+ final Runnable onComplete = mPendingConfigureCompleteRunnable;
mPendingConfigureCompleteRunnable = null;
onComplete.run();
}
@@ -940,7 +938,7 @@
}
@Override
- public void configureDeviceForState(int state, Runnable onComplete) {
+ public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
if (mPendingConfigureCompleteRunnable != null) {
throw new IllegalStateException("configureDeviceForState() called while configure"
+ " is pending");
@@ -966,7 +964,9 @@
OTHER_DEVICE_STATE,
DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
- @Nullable private final DeviceState mInitialState;
+ @Nullable
+ private final DeviceState mInitialState;
+ @Nullable
private Listener mListener;
private TestDeviceStateProvider() {
@@ -991,7 +991,7 @@
}
}
- public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
+ public void notifySupportedDeviceStates(@NonNull DeviceState[] supportedDeviceStates) {
mSupportedDeviceStates = supportedDeviceStates;
mListener.onSupportedDeviceStatesChanged(supportedDeviceStates,
SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
@@ -1018,17 +1018,17 @@
private final HashMap<IBinder, Integer> mLastNotifiedStatus = new HashMap<>();
@Override
- public void onDeviceStateInfoChanged(DeviceStateInfo info) {
+ public void onDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
mLastNotifiedInfo = info;
}
@Override
- public void onRequestActive(IBinder token) {
+ public void onRequestActive(@NonNull IBinder token) {
mLastNotifiedStatus.put(token, STATUS_ACTIVE);
}
@Override
- public void onRequestCanceled(IBinder token) {
+ public void onRequestCanceled(@NonNull IBinder token) {
mLastNotifiedStatus.put(token, STATUS_CANCELED);
}
@@ -1041,20 +1041,22 @@
mLastNotifiedInfo = null;
}
- int getLastNotifiedStatus(IBinder requestToken) {
+ int getLastNotifiedStatus(@NonNull IBinder requestToken) {
return mLastNotifiedStatus.getOrDefault(requestToken, STATUS_UNKNOWN);
}
}
private static final class TestSystemPropertySetter implements
DeviceStateManagerService.SystemPropertySetter {
+ @NonNull
private String mValue;
@Override
- public void setDebugTracingDeviceStateProperty(String value) {
+ public void setDebugTracingDeviceStateProperty(@NonNull String value) {
mValue = value;
}
+ @NonNull
public String getValue() {
return mValue;
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 7e22d74..425bb15 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -1256,8 +1256,7 @@
Manifest.permission.BYPASS_ROLE_QUALIFICATION);
roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(role, packageName,
- /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+ roleManager.addRoleHolderAsUser(role, packageName, /* flags = */ 0, user,
mContext.getMainExecutor(), success -> {
if (success) {
latch.countDown();
@@ -1272,9 +1271,9 @@
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
- roleManager.removeRoleHolderAsUser(role, packageName,
- /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
- mContext.getMainExecutor(), (aBool) -> {});
+ roleManager.removeRoleHolderAsUser(role, packageName, 0, user,
+ mContext.getMainExecutor(), (aBool) -> {
+ });
roleManager.setBypassingRoleQualification(false);
instrumentation.getUiAutomation()
.dropShellPermissionIdentity();
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsAccumulatorTest.kt b/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsAccumulatorTest.kt
index 7280c69..8cf0e82 100644
--- a/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsAccumulatorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsAccumulatorTest.kt
@@ -60,7 +60,7 @@
// Accumulator has data until 1000 (= 0), and its end-point is still in the history period.
// Current time is less than one bucket away from snapshot end-point: 1050 - 1000 < 200
- val stats = snapshot.queryStats(1050, FakeStats(500, 1050, 1))!!
+ val stats = snapshot.queryStats(1050, FakeStats(500, 1050, 1))
// After the query at 1050, accumulator should have 1 * (1050 - 1000) = 50 bytes.
assertNetworkStatsEquals(stats, networkStatsWithBytes(50))
@@ -72,7 +72,7 @@
// Accumulator has data until 1000 (= 0), and its end-point is still in the history period.
// Current time is one bucket away from snapshot end-point: 1250 - 1000 > 200
- val stats = snapshot.queryStats(1250, FakeStats(550, 1250, 2))!!
+ val stats = snapshot.queryStats(1250, FakeStats(550, 1250, 2))
// After the query at 1250, accumulator should have 2 * (1250 - 1000) = 500 bytes.
assertNetworkStatsEquals(stats, networkStatsWithBytes(500))
@@ -84,7 +84,7 @@
// Accumulator has data until 1000 (= 0), and its end-point is in the history period.
// Current time is two buckets away from snapshot end-point: 1450 - 1000 > 2*200
- val stats = snapshot.queryStats(1450, FakeStats(600, 1450, 3))!!
+ val stats = snapshot.queryStats(1450, FakeStats(600, 1450, 3))
// After the query at 1450, accumulator should have 3 * (1450 - 1000) = 1350 bytes.
assertNetworkStatsEquals(stats, networkStatsWithBytes(1350))
@@ -96,7 +96,7 @@
// Accumulator has data until 1000 (= 0), and its end-point is still in the history period.
// Current time is many buckets away from snapshot end-point
- val stats = snapshot.queryStats(6100, FakeStats(900, 6100, 1))!!
+ val stats = snapshot.queryStats(6100, FakeStats(900, 6100, 1))
// After the query at 6100, accumulator should have 1 * (6100 - 1000) = 5100 bytes.
assertNetworkStatsEquals(stats, networkStatsWithBytes(5100))
@@ -108,9 +108,9 @@
// Accumulator is queried within the history period, whose starting point stays the same.
// After each query, accumulator should contain bytes from the initial end-point until now.
- val stats1 = snapshot.queryStats(5100, FakeStats(900, 5100, 1))!!
- val stats2 = snapshot.queryStats(10100, FakeStats(900, 10100, 1))!!
- val stats3 = snapshot.queryStats(15100, FakeStats(900, 15100, 1))!!
+ val stats1 = snapshot.queryStats(5100, FakeStats(900, 5100, 1))
+ val stats2 = snapshot.queryStats(10100, FakeStats(900, 10100, 1))
+ val stats3 = snapshot.queryStats(15100, FakeStats(900, 15100, 1))
assertNetworkStatsEquals(stats1, networkStatsWithBytes(4100))
assertNetworkStatsEquals(stats2, networkStatsWithBytes(9100))
@@ -123,9 +123,9 @@
// Accumulator is queried within the history period, whose starting point is moving.
// After each query, accumulator should contain bytes from the initial end-point until now.
- val stats1 = snapshot.queryStats(5100, FakeStats(900, 5100, 1))!!
- val stats2 = snapshot.queryStats(10100, FakeStats(4000, 10100, 1))!!
- val stats3 = snapshot.queryStats(15100, FakeStats(7000, 15100, 1))!!
+ val stats1 = snapshot.queryStats(5100, FakeStats(900, 5100, 1))
+ val stats2 = snapshot.queryStats(10100, FakeStats(4000, 10100, 1))
+ val stats3 = snapshot.queryStats(15100, FakeStats(7000, 15100, 1))
assertNetworkStatsEquals(stats1, networkStatsWithBytes(4100))
assertNetworkStatsEquals(stats2, networkStatsWithBytes(9100))
@@ -138,7 +138,7 @@
// Accumulator has data until 1000 (= 0), but its end-point is not in the history period.
// After the query, accumulator should add only those bytes that are covered by the history.
- val stats = snapshot.queryStats(2700, FakeStats(2200, 2700, 1))!!
+ val stats = snapshot.queryStats(2700, FakeStats(2200, 2700, 1))
assertNetworkStatsEquals(stats, networkStatsWithBytes(500))
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 983e694..b34b1fb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -835,7 +835,7 @@
}
@Test
- public void testListenerPost_UpdateLifetimeExtended() throws Exception {
+ public void testListenerPostLifetimeExtended_UpdatesOnlySysui() throws Exception {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
// Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY.
@@ -856,31 +856,51 @@
Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId())
.setContentTitle("new title")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false);
+ .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true);
StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
nb2.build(), userHandle, null, 0);
NotificationRecord toPost = new NotificationRecord(mContext, sbn2, channel);
// Create system ui-like service.
- ManagedServices.ManagedServiceInfo info = mListeners.new ManagedServiceInfo(
+ ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo(
null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33);
- info.isSystemUi = true;
- INotificationListener l1 = mock(INotificationListener.class);
- info.service = l1;
- List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(info);
+ sysuiInfo.isSystemUi = true;
+ INotificationListener sysuiListener = mock(INotificationListener.class);
+ sysuiInfo.service = sysuiListener;
+
+ // Create two non-system ui-like services.
+ ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo1.isSystemUi = false;
+ INotificationListener otherListener1 = mock(INotificationListener.class);
+ otherInfo1.service = otherListener1;
+
+ ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo2.isSystemUi = false;
+ INotificationListener otherListener2 = mock(INotificationListener.class);
+ otherInfo2.service = otherListener2;
+
+ List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo,
+ otherInfo2);
when(mListeners.getServices()).thenReturn(services);
FieldSetter.setField(mNm,
NotificationManagerService.class.getDeclaredField("mHandler"),
mock(NotificationManagerService.WorkerHandler.class));
doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
- doReturn(mock(NotificationRankingUpdate.class)).when(mNm).makeRankingUpdateLocked(info);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(sysuiInfo);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo1);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo2);
doReturn(false).when(mNm).isInLockDownMode(anyInt());
doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt());
doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2);
doReturn(sbn2).when(mListeners).redactStatusBarNotification(any());
- // The notification change is posted to the service listener.
+ // Post notification change to the service listeners.
mListeners.notifyPostedLocked(toPost, old);
// Verify that the post occcurs with the updated notification value.
@@ -889,11 +909,190 @@
runnableCaptor.getValue().run();
ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
- verify(l1, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
+ verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
StatusBarNotification sbnResult = sbnCaptor.getValue().get();
assertThat(sbnResult.getNotification()
.extras.getCharSequence(Notification.EXTRA_TITLE).toString())
.isEqualTo("new title");
+
+ verify(otherListener1, never()).onNotificationPosted(any(), any());
+ verify(otherListener2, never()).onNotificationPosted(any(), any());
+ }
+
+ @Test
+ public void testListenerPostLifeimteExtension_postsToAppropriateListeners() throws Exception {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
+
+ // Create original notification, with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY.
+ String pkg = "pkg";
+ int uid = 9;
+ UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true);
+ StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
+ nb.build(), userHandle, null, 0);
+ NotificationRecord leRecord = new NotificationRecord(mContext, sbn, channel);
+
+ // Creates updated notification (without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY)
+ Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("new title")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setFlag(Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false);
+ StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
+ nb2.build(), userHandle, null, 0);
+ NotificationRecord nonLeRecord = new NotificationRecord(mContext, sbn2, channel);
+
+ // Create system ui-like service.
+ ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33);
+ sysuiInfo.isSystemUi = true;
+ INotificationListener sysuiListener = mock(INotificationListener.class);
+ sysuiInfo.service = sysuiListener;
+
+ // Create two non-system ui-like services.
+ ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo1.isSystemUi = false;
+ INotificationListener otherListener1 = mock(INotificationListener.class);
+ otherInfo1.service = otherListener1;
+
+ ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo2.isSystemUi = false;
+ INotificationListener otherListener2 = mock(INotificationListener.class);
+ otherInfo2.service = otherListener2;
+
+ List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo,
+ otherInfo2);
+ when(mListeners.getServices()).thenReturn(services);
+
+ FieldSetter.setField(mNm,
+ NotificationManagerService.class.getDeclaredField("mHandler"),
+ mock(NotificationManagerService.WorkerHandler.class));
+ doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(sysuiInfo);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo1);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo2);
+ doReturn(false).when(mNm).isInLockDownMode(anyInt());
+ doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt());
+ doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2);
+ doReturn(sbn2).when(mListeners).redactStatusBarNotification(any());
+
+ // The notification change is posted to the service listener.
+ // NonLE to LE should never happen, as LE can't be set in an update by the app.
+ // So we just want to test LE to NonLE.
+ mListeners.notifyPostedLocked(nonLeRecord /*=toPost*/, leRecord /*=old*/);
+
+ // Verify that the post occcurs with the updated notification value.
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mNm.mHandler, times(3)).post(runnableCaptor.capture());
+ List<Runnable> capturedRunnable = runnableCaptor.getAllValues();
+ for (Runnable r : capturedRunnable) {
+ r.run();
+ }
+
+ ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
+ ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
+ verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
+ StatusBarNotification sbnResult = sbnCaptor.getValue().get();
+ assertThat(sbnResult.getNotification()
+ .extras.getCharSequence(Notification.EXTRA_TITLE).toString())
+ .isEqualTo("new title");
+
+ verify(otherListener1, times(1)).onNotificationPosted(any(), any());
+ verify(otherListener2, times(1)).onNotificationPosted(any(), any());
+ }
+
+ @Test
+ public void testNotifyPostedLocked_postsToAppropriateListeners() throws Exception {
+ // Create original notification
+ String pkg = "pkg";
+ int uid = 9;
+ UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
+ nb.build(), userHandle, null, 0);
+ NotificationRecord oldRecord = new NotificationRecord(mContext, sbn, channel);
+
+ // Creates updated notification
+ Notification.Builder nb2 = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("new title")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn2 = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
+ nb2.build(), userHandle, null, 0);
+ NotificationRecord newRecord = new NotificationRecord(mContext, sbn2, channel);
+
+ // Create system ui-like service.
+ ManagedServices.ManagedServiceInfo sysuiInfo = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("a", "a"), sbn2.getUserId(), false, null, 33, 33);
+ sysuiInfo.isSystemUi = true;
+ INotificationListener sysuiListener = mock(INotificationListener.class);
+ sysuiInfo.service = sysuiListener;
+
+ // Create two non-system ui-like services.
+ ManagedServices.ManagedServiceInfo otherInfo1 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("b", "b"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo1.isSystemUi = false;
+ INotificationListener otherListener1 = mock(INotificationListener.class);
+ otherInfo1.service = otherListener1;
+
+ ManagedServices.ManagedServiceInfo otherInfo2 = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("c", "c"), sbn2.getUserId(), false, null, 33, 33);
+ otherInfo2.isSystemUi = false;
+ INotificationListener otherListener2 = mock(INotificationListener.class);
+ otherInfo2.service = otherListener2;
+
+ List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(otherInfo1, sysuiInfo,
+ otherInfo2);
+ when(mListeners.getServices()).thenReturn(services);
+
+ FieldSetter.setField(mNm,
+ NotificationManagerService.class.getDeclaredField("mHandler"),
+ mock(NotificationManagerService.WorkerHandler.class));
+ doReturn(true).when(mNm).isVisibleToListener(any(), anyInt(), any());
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(sysuiInfo);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo1);
+ doReturn(mock(NotificationRankingUpdate.class)).when(mNm)
+ .makeRankingUpdateLocked(otherInfo2);
+ doReturn(false).when(mNm).isInLockDownMode(anyInt());
+ doNothing().when(mNm).updateUriPermissions(any(), any(), any(), anyInt());
+ doReturn(sbn2).when(mListeners).redactStatusBarNotification(sbn2);
+ doReturn(sbn2).when(mListeners).redactStatusBarNotification(any());
+
+ // The notification change is posted to the service listeners.
+ mListeners.notifyPostedLocked(newRecord, oldRecord);
+
+ // Verify that the post occcurs with the updated notification value.
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mNm.mHandler, times(3)).post(runnableCaptor.capture());
+ List<Runnable> capturedRunnable = runnableCaptor.getAllValues();
+ for (Runnable r : capturedRunnable) {
+ r.run();
+ }
+
+ ArgumentCaptor<IStatusBarNotificationHolder> sbnCaptor =
+ ArgumentCaptor.forClass(IStatusBarNotificationHolder.class);
+ verify(sysuiListener, times(1)).onNotificationPosted(sbnCaptor.capture(), any());
+ StatusBarNotification sbnResult = sbnCaptor.getValue().get();
+ assertThat(sbnResult.getNotification()
+ .extras.getCharSequence(Notification.EXTRA_TITLE).toString())
+ .isEqualTo("new title");
+
+ verify(otherListener1, times(1)).onNotificationPosted(any(), any());
+ verify(otherListener2, times(1)).onNotificationPosted(any(), any());
}
/**
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index 1493253..d7ae046 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -35,7 +35,9 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import androidx.test.core.app.ApplicationProvider;
@@ -63,6 +65,8 @@
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private PackageManagerInternal mPackageManagerInternalMock;
@@ -186,6 +190,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testStepAndRampSegments_withValidFreqMapping_returnsClippedValuesOnlyInRamps() {
VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
// Individual step without frequency control, will not use PWLE composition
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
index 8103682..96f0fda2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -26,8 +26,11 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -49,6 +52,9 @@
private RampToStepAdapter mAdapter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mAdapter = new RampToStepAdapter(TEST_STEP_DURATION);
@@ -87,6 +93,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testRampSegments_withoutPwleCapability_convertsRampsToSteps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
index f2c3726..53e49e0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
@@ -26,8 +26,11 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -52,6 +55,9 @@
private SplitSegmentsAdapter mAdapter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mAdapter = new SplitSegmentsAdapter();
@@ -97,6 +103,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testRampSegments_withPwleDurationLimit_splitsLongRampsAndPreserveOtherSegments() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
index d501dba..fae634d 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -26,8 +26,11 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
@@ -48,6 +51,9 @@
private StepToRampAdapter mAdapter;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() throws Exception {
mAdapter = new StepToRampAdapter();
@@ -134,6 +140,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void testStepSegments_withPwleCapabilityAndFrequency_convertsStepsToRamps() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 100, /* duration= */ 10),
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 7536f5f..58a1e84 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -63,9 +63,11 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.SparseArray;
@@ -113,6 +115,8 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -780,6 +784,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
@@ -870,6 +875,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_singleVibratorPwle_runsComposePwle() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
@@ -1724,6 +1730,7 @@
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index f96177d..6dc1b10 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -76,6 +76,9 @@
private float mFrequencyResolution = Float.NaN;
private float mQFactor = Float.NaN;
private float[] mMaxAmplitudes;
+
+ private float[] mFrequenciesHz;
+ private float[] mOutputAccelerationsGs;
private long mVendorEffectDuration = EFFECT_DURATION;
void recordEffectSegment(long vibrationId, VibrationEffectSegment segment) {
@@ -220,6 +223,9 @@
infoBuilder.setQFactor(mQFactor);
infoBuilder.setFrequencyProfileLegacy(new VibratorInfo.FrequencyProfileLegacy(
mResonantFrequency, mMinFrequency, mFrequencyResolution, mMaxAmplitudes));
+ infoBuilder.setFrequencyProfile(
+ new VibratorInfo.FrequencyProfile(mResonantFrequency, mFrequenciesHz,
+ mOutputAccelerationsGs));
infoBuilder.setMaxEnvelopeEffectSize(mMaxEnvelopeEffectSize);
infoBuilder.setMinEnvelopeEffectControlPointDurationMillis(
mMinEnvelopeEffectControlPointDurationMillis);
@@ -360,6 +366,16 @@
mMaxAmplitudes = maxAmplitudes;
}
+ /** Set the list of available frequencies. */
+ public void setFrequenciesHz(float[] frequenciesHz) {
+ mFrequenciesHz = frequenciesHz;
+ }
+
+ /** Set the max output acceleration achievable by the supported frequencies. */
+ public void setOutputAccelerationsGs(float[] outputAccelerationsGs) {
+ mOutputAccelerationsGs = outputAccelerationsGs;
+ }
+
/** Set the duration of vendor effects in fake vibrator hardware. */
public void setVendorEffectDuration(long durationMs) {
mVendorEffectDuration = durationMs;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 5b3fd53..7196acc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -2933,6 +2933,11 @@
controller.requestStartTransition(transit, task, null, null);
player.start();
+ // always include config-at-end activity since it is considered "independent" due to
+ // changing at a different time.
+ assertTrue(player.mLastReady.getChanges().stream()
+ .anyMatch((change -> change.getActivityComponent() != null
+ && (change.getFlags() & TransitionInfo.FLAG_CONFIG_AT_END) != 0)));
assertTrue(activity.isConfigurationDispatchPaused());
player.finish();
assertFalse(activity.isConfigurationDispatchPaused());
@@ -2962,6 +2967,11 @@
controller.requestStartTransition(transit, task, null, null);
player.start();
+ // always include config-at-end activity since it is considered "independent" due to
+ // changing at a different time.
+ assertTrue(player.mLastReady.getChanges().stream()
+ .anyMatch((change -> change.getActivityComponent() != null
+ && (change.getFlags() & TransitionInfo.FLAG_CONFIG_AT_END) != 0)));
assertTrue(activity.isConfigurationDispatchPaused());
player.finish();
assertFalse(activity.isConfigurationDispatchPaused());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 6111a65..8bbba1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -94,6 +94,7 @@
import android.view.ContentRecordingSession;
import android.view.IWindow;
import android.view.InputChannel;
+import android.view.InputDevice;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.Surface;
@@ -1275,6 +1276,48 @@
}
@Test
+ public void testInputDeviceNotifyConfigurationChanged() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_FILTER_IRRELEVANT_INPUT_DEVICE_CHANGE);
+ spyOn(mDisplayContent);
+ doReturn(false).when(mDisplayContent).sendNewConfiguration();
+ final InputDevice deviceA = mock(InputDevice.class);
+ final InputDevice deviceB = mock(InputDevice.class);
+ doReturn("deviceA").when(deviceA).getDescriptor();
+ doReturn("deviceB").when(deviceB).getDescriptor();
+ final InputDevice[] devices1 = { deviceA };
+ final InputDevice[] devices2 = { deviceB, deviceA };
+ final Runnable verifySendNewConfiguration = () -> {
+ clearInvocations(mDisplayContent);
+ mWm.mInputManagerCallback.notifyConfigurationChanged();
+ verify(mDisplayContent).sendNewConfiguration();
+ };
+ doReturn(devices1).when(mWm.mInputManager).getInputDevices();
+ verifySendNewConfiguration.run();
+
+ doReturn(devices2).when(mWm.mInputManager).getInputDevices();
+ verifySendNewConfiguration.run();
+
+ doReturn(true).when(deviceB).isEnabled();
+ verifySendNewConfiguration.run();
+
+ doReturn(true).when(deviceA).isExternal();
+ verifySendNewConfiguration.run();
+
+ doReturn(1).when(deviceA).getSources();
+ verifySendNewConfiguration.run();
+
+ doReturn(1).when(deviceA).getAssociatedDisplayId();
+ verifySendNewConfiguration.run();
+
+ doReturn(1).when(deviceA).getKeyboardType();
+ verifySendNewConfiguration.run();
+
+ clearInvocations(mDisplayContent);
+ mWm.mInputManagerCallback.notifyConfigurationChanged();
+ verify(mDisplayContent, never()).sendNewConfiguration();
+ }
+
+ @Test
public void testReportSystemGestureExclusionChanged_invalidWindow() {
final Session session = mock(Session.class);
final IWindow window = mock(IWindow.class);
diff --git a/tests/testables/Android.bp b/tests/testables/Android.bp
index f211185..17cc0b2 100644
--- a/tests/testables/Android.bp
+++ b/tests/testables/Android.bp
@@ -35,4 +35,8 @@
"androidx.test.rules",
"mockito-target-inline-minus-junit4",
],
+ static_libs: [
+ "PlatformMotionTesting",
+ "kotlinx_coroutines_test",
+ ],
}
diff --git a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
new file mode 100644
index 0000000..b27b826
--- /dev/null
+++ b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 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.animation
+
+import android.animation.AnimatorTestRuleToolkit.Companion.TAG
+import android.util.Log
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import platform.test.motion.MotionTestRule
+import platform.test.motion.RecordedMotion
+import platform.test.motion.RecordedMotion.Companion.create
+import platform.test.motion.golden.DataPoint
+import platform.test.motion.golden.Feature
+import platform.test.motion.golden.FrameId
+import platform.test.motion.golden.TimeSeries
+import platform.test.motion.golden.TimeSeriesCaptureScope
+import platform.test.motion.golden.TimestampFrameId
+
+class AnimatorTestRuleToolkit(val animatorTestRule: AnimatorTestRule, val testScope: TestScope) {
+ internal companion object {
+ const val TAG = "AnimatorRuleToolkit"
+ }
+}
+
+/**
+ * Controls the timing of the motion recording.
+ *
+ * The time series is recorded while the [recording] function is running.
+ */
+class MotionControl(val recording: MotionControlFn)
+
+typealias MotionControlFn = suspend MotionControlScope.() -> Unit
+
+interface MotionControlScope {
+ /** Waits until [check] returns true. Invoked on each frame. */
+ suspend fun awaitCondition(check: () -> Boolean)
+
+ /** Waits for [count] frames to be processed. */
+ suspend fun awaitFrames(count: Int = 1)
+}
+
+/** Defines the sampling of features during a test run. */
+data class AnimatorRuleRecordingSpec<T>(
+ /** The root `observing` object, available in [timeSeriesCapture]'s [TimeSeriesCaptureScope]. */
+ val captureRoot: T,
+
+ /** The timing for the recording. */
+ val motionControl: MotionControl,
+
+ /** Time interval between frame captures, in milliseconds. */
+ val frameDurationMs: Long = 16L,
+
+ /** Produces the time-series, invoked on each animation frame. */
+ val timeSeriesCapture: TimeSeriesCaptureScope<T>.() -> Unit,
+)
+
+/** Records the time-series of the features specified in [recordingSpec]. */
+fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion(
+ recordingSpec: AnimatorRuleRecordingSpec<T>,
+): RecordedMotion {
+ with(toolkit.animatorTestRule) {
+ val frameIdCollector = mutableListOf<FrameId>()
+ val propertyCollector = mutableMapOf<String, MutableList<DataPoint<*>>>()
+
+ fun recordFrame(frameId: FrameId) {
+ Log.i(TAG, "recordFrame($frameId)")
+ frameIdCollector.add(frameId)
+ recordingSpec.timeSeriesCapture.invoke(
+ TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
+ )
+ }
+
+ val motionControl =
+ MotionControlImpl(
+ toolkit.animatorTestRule,
+ toolkit.testScope,
+ recordingSpec.frameDurationMs,
+ recordingSpec.motionControl,
+ )
+
+ Log.i(TAG, "recordMotion() begin recording")
+
+ val startFrameTime = currentTime
+ while (!motionControl.recordingEnded) {
+ recordFrame(TimestampFrameId(currentTime - startFrameTime))
+ motionControl.nextFrame()
+ }
+
+ Log.i(TAG, "recordMotion() end recording")
+
+ val timeSeries =
+ TimeSeries(
+ frameIdCollector.toList(),
+ propertyCollector.entries.map { entry -> Feature(entry.key, entry.value) },
+ )
+
+ return create(timeSeries, null)
+ }
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+private class MotionControlImpl(
+ val animatorTestRule: AnimatorTestRule,
+ val testScope: TestScope,
+ val frameMs: Long,
+ motionControl: MotionControl,
+) : MotionControlScope {
+ private val recordingJob = motionControl.recording.launch()
+
+ private val frameEmitter = MutableStateFlow<Long>(0)
+ private val onFrame = frameEmitter.asStateFlow()
+
+ var recordingEnded: Boolean = false
+
+ fun nextFrame() {
+ animatorTestRule.advanceTimeBy(frameMs)
+
+ frameEmitter.tryEmit(animatorTestRule.currentTime)
+ testScope.runCurrent()
+
+ if (recordingJob.isCompleted) {
+ recordingEnded = true
+ }
+ }
+
+ override suspend fun awaitCondition(check: () -> Boolean) {
+ onFrame.takeWhile { !check() }.collect {}
+ }
+
+ override suspend fun awaitFrames(count: Int) {
+ onFrame.take(count).collect {}
+ }
+
+ private fun MotionControlFn.launch(): Job {
+ val function = this
+ return testScope.launch { function() }
+ }
+}
diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp
index c23f41a..7110564 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -29,13 +29,17 @@
"src/**/*.kt",
"src/**/I*.aidl",
],
+ asset_dirs: ["goldens"],
resource_dirs: ["res"],
static_libs: [
+ "PlatformMotionTesting",
"androidx.core_core-animation",
"androidx.core_core-ktx",
+ "androidx.test.ext.junit",
"androidx.test.rules",
"androidx.test.ext.junit",
"hamcrest-library",
+ "kotlinx_coroutines_test",
"mockito-target-inline-minus-junit4",
"testables",
"truth",
diff --git a/tests/testables/tests/goldens/recordMotion_withAnimator.json b/tests/testables/tests/goldens/recordMotion_withAnimator.json
new file mode 100644
index 0000000..87fece5
--- /dev/null
+++ b/tests/testables/tests/goldens/recordMotion_withAnimator.json
@@ -0,0 +1,64 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "value",
+ "type": "float",
+ "data_points": [
+ 1,
+ 0.9960574,
+ 0.98429155,
+ 0.9648882,
+ 0.9381534,
+ 0.9045085,
+ 0.8644843,
+ 0.818712,
+ 0.76791346,
+ 0.7128896,
+ 0.65450853,
+ 0.5936906,
+ 0.5313952,
+ 0.46860474,
+ 0.40630943,
+ 0.34549147,
+ 0.2871104,
+ 0.23208654,
+ 0.181288,
+ 0.13551569,
+ 0.09549153,
+ 0.061846733,
+ 0.035111785,
+ 0.015708387,
+ 0.003942609,
+ 0
+ ]
+ }
+ ]
+}
diff --git a/tests/testables/tests/goldens/recordMotion_withSpring.json b/tests/testables/tests/goldens/recordMotion_withSpring.json
new file mode 100644
index 0000000..e9fb5b4
--- /dev/null
+++ b/tests/testables/tests/goldens/recordMotion_withSpring.json
@@ -0,0 +1,48 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272
+ ],
+ "features": [
+ {
+ "name": "value",
+ "type": "float",
+ "data_points": [
+ 1,
+ 0.9488604,
+ 0.83574325,
+ 0.7016156,
+ 0.5691678,
+ 0.4497436,
+ 0.34789434,
+ 0.26431116,
+ 0.19766562,
+ 0.14572789,
+ 0.10601636,
+ 0.076149896,
+ 0.05401709,
+ 0.037837274,
+ 0.026161024,
+ 0.017839976,
+ 0.011983856,
+ 0.007914998
+ ]
+ }
+ ]
+}
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
new file mode 100644
index 0000000..fbef489
--- /dev/null
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 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.animation
+
+import android.util.FloatProperty
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.dynamicanimation.animation.SpringAnimation
+import com.android.internal.dynamicanimation.animation.SpringForce
+import kotlinx.coroutines.test.TestScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.motion.MotionTestRule
+import platform.test.motion.RecordedMotion
+import platform.test.motion.golden.FeatureCapture
+import platform.test.motion.golden.asDataPoint
+import platform.test.motion.testing.createGoldenPathManager
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AnimatorTestRuleToolkitTest {
+ companion object {
+ private val GOLDEN_PATH_MANAGER =
+ createGoldenPathManager("frameworks/base/tests/testables/tests/goldens")
+
+ private val TEST_PROPERTY =
+ object : FloatProperty<TestState>("value") {
+ override fun get(state: TestState): Float {
+ return state.animatedValue
+ }
+
+ override fun setValue(state: TestState, value: Float) {
+ state.animatedValue = value
+ }
+ }
+ }
+
+ @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 1)
+ val motionRule =
+ MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, TestScope()), GOLDEN_PATH_MANAGER)
+
+ @Test
+ fun recordMotion_withAnimator() {
+ val state = TestState()
+ AnimatorSet().apply {
+ duration = 500
+ play(
+ ValueAnimator.ofFloat(state.animatedValue, 0f).apply {
+ addUpdateListener { state.animatedValue = it.animatedValue as Float }
+ }
+ )
+ getInstrumentation().runOnMainSync { start() }
+ }
+
+ val recordedMotion =
+ record(state, MotionControl { awaitFrames(count = 26) }, sampleIntervalMs = 20L)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withAnimator")
+ }
+
+ @Test
+ fun recordMotion_withSpring() {
+ val state = TestState()
+ var isDone = false
+ SpringAnimation(state, TEST_PROPERTY).apply {
+ spring =
+ SpringForce(0f).apply {
+ stiffness = 500f
+ dampingRatio = 0.95f
+ }
+
+ setStartValue(1f)
+ setMinValue(0f)
+ setMaxValue(1f)
+ minimumVisibleChange = 0.01f
+
+ addEndListener { _, _, _, _ -> isDone = true }
+ getInstrumentation().runOnMainSync { start() }
+ }
+
+ val recordedMotion =
+ record(state, MotionControl { awaitCondition { isDone } }, sampleIntervalMs = 16L)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withSpring")
+ }
+
+ private fun record(
+ state: TestState,
+ motionControl: MotionControl,
+ sampleIntervalMs: Long,
+ ): RecordedMotion {
+ var recordedMotion: RecordedMotion? = null
+ getInstrumentation().runOnMainSync {
+ recordedMotion =
+ motionRule.recordMotion(
+ AnimatorRuleRecordingSpec(
+ state,
+ motionControl,
+ sampleIntervalMs,
+ ) {
+ feature(
+ FeatureCapture("value") { state -> state.animatedValue.asDataPoint() },
+ "value",
+ )
+ }
+ )
+ }
+ return recordedMotion!!
+ }
+
+ data class TestState(var animatedValue: Float = 1f)
+}
diff --git a/wifi/java/src/android/net/wifi/WifiMigration.java b/wifi/java/src/android/net/wifi/WifiMigration.java
index f1850dd..28e9c45 100644
--- a/wifi/java/src/android/net/wifi/WifiMigration.java
+++ b/wifi/java/src/android/net/wifi/WifiMigration.java
@@ -38,6 +38,8 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.os.BackgroundThread;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -48,6 +50,8 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
/**
* Class used to provide one time hooks for existing OEM devices to migrate their config store
@@ -605,13 +609,35 @@
/**
* Migrate any certificates in Legacy Keystore to the newer WifiBlobstore database.
*
- * If there are no certificates to migrate, this method will return immediately.
+ * Operation will be handled on the BackgroundThread, and the result will be posted
+ * to the provided Executor.
+ *
+ * @param executor The executor on which callback will be invoked
+ * @param resultsCallback Callback to receive the status code
*
* @hide
*/
@FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY)
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static @KeystoreMigrationStatus int migrateLegacyKeystoreToWifiBlobstore() {
+ public static void migrateLegacyKeystoreToWifiBlobstore(
+ @NonNull Executor executor, @NonNull IntConsumer resultsCallback) {
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(resultsCallback, "resultsCallback cannot be null");
+ BackgroundThread.getHandler().post(() -> {
+ int status = migrateLegacyKeystoreToWifiBlobstoreInternal();
+ executor.execute(() -> {
+ resultsCallback.accept(status);
+ });
+ });
+ }
+
+ /**
+ * Synchronously perform the Keystore migration described in
+ * {@link #migrateLegacyKeystoreToWifiBlobstore(Executor, IntConsumer)}
+ *
+ * @hide
+ */
+ public static @KeystoreMigrationStatus int migrateLegacyKeystoreToWifiBlobstoreInternal() {
if (!WifiBlobStore.supplicantCanAccessBlobstore()) {
// Supplicant cannot access WifiBlobstore, so keep the certs in Legacy Keystore
Log.i(TAG, "Avoiding migration since supplicant cannot access WifiBlobstore");
diff --git a/wifi/tests/src/android/net/wifi/WifiMigrationTest.java b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
index 0aa299f..ce5b60a 100644
--- a/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
@@ -81,7 +81,7 @@
public void testKeystoreMigrationAvoidedOnLegacyVendorPartition() {
when(WifiBlobStore.supplicantCanAccessBlobstore()).thenReturn(false);
assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED,
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstoreInternal());
verifyNoMoreInteractions(mLegacyKeystore, mWifiBlobStore);
}
@@ -93,7 +93,7 @@
public void testKeystoreMigrationNoLegacyAliases() throws Exception {
when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(new String[0]);
assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED,
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstoreInternal());
verify(mLegacyKeystore).list(anyString(), anyInt());
verifyNoMoreInteractions(mLegacyKeystore, mWifiBlobStore);
}
@@ -110,7 +110,7 @@
when(mWifiBlobStore.list(anyString())).thenReturn(blobstoreAliases);
assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE,
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstoreInternal());
verify(mWifiBlobStore, times(legacyAliases.length)).put(anyString(), any(byte[].class));
}
@@ -129,7 +129,7 @@
// Expect that only the unique legacy alias is migrated to the blobstore
assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE,
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstoreInternal());
verify(mWifiBlobStore).list(anyString());
verify(mWifiBlobStore).put(eq(uniqueLegacyAlias), any(byte[].class));
verifyNoMoreInteractions(mWifiBlobStore);
@@ -146,7 +146,7 @@
when(mLegacyKeystore.list(anyString(), anyInt())).thenThrow(
new ServiceSpecificException(ILegacyKeystore.ERROR_SYSTEM_ERROR));
assertEquals(WifiMigration.KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED,
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstoreInternal());
}
/**
@@ -157,6 +157,6 @@
public void testKeystoreMigrationFailsIfExceptionEncountered() throws Exception {
when(mLegacyKeystore.list(anyString(), anyInt())).thenThrow(new RemoteException());
assertEquals(WifiMigration.KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION,
- WifiMigration.migrateLegacyKeystoreToWifiBlobstore());
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstoreInternal());
}
}