Merge "Set elapsedRealtime if HAL doesn't set it"
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
index adcd571..b995b06 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java
@@ -158,11 +158,10 @@
}
@Test
- public void timeClonedSimpleDateFormat() {
- SimpleDateFormat sdf = new SimpleDateFormat();
+ public void timeNewSimpleDateFormat() {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- sdf.clone();
+ new SimpleDateFormat();
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index b6d29aa..5b61f9a 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -676,6 +676,9 @@
private static final String KEY_TIME_TICK_ALLOWED_WHILE_IDLE =
"time_tick_allowed_while_idle";
+ private static final String KEY_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF =
+ "delay_nonwakeup_alarms_while_screen_off";
+
@VisibleForTesting
static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota";
@@ -743,6 +746,8 @@
private static final int DEFAULT_TEMPORARY_QUOTA_BUMP = 0;
+ private static final boolean DEFAULT_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF = true;
+
// Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -835,6 +840,9 @@
*/
public int TEMPORARY_QUOTA_BUMP = DEFAULT_TEMPORARY_QUOTA_BUMP;
+ public boolean DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF =
+ DEFAULT_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF;
+
private long mLastAllowWhileIdleWhitelistDuration = -1;
private int mVersion = 0;
@@ -1011,6 +1019,11 @@
TEMPORARY_QUOTA_BUMP = properties.getInt(KEY_TEMPORARY_QUOTA_BUMP,
DEFAULT_TEMPORARY_QUOTA_BUMP);
break;
+ case KEY_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF:
+ DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF = properties.getBoolean(
+ KEY_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF,
+ DEFAULT_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF);
+ break;
default:
if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
// The quotas need to be updated in order, so we can't just rely
@@ -1249,6 +1262,10 @@
pw.print(KEY_TEMPORARY_QUOTA_BUMP, TEMPORARY_QUOTA_BUMP);
pw.println();
+ pw.print(KEY_DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF,
+ DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF);
+ pw.println();
+
pw.decreaseIndent();
}
@@ -4360,7 +4377,11 @@
}
}
+ @GuardedBy("mLock")
boolean checkAllowNonWakeupDelayLocked(long nowELAPSED) {
+ if (!mConstants.DELAY_NONWAKEUP_ALARMS_WHILE_SCREEN_OFF) {
+ return false;
+ }
if (mInteractive) {
return false;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index f1e13df..9c16772 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -644,7 +644,7 @@
static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms";
static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
- FC_CONFIG_PREFIX + "min_alarm_time_flexibility_ms";
+ FC_CONFIG_PREFIX + "min_time_between_flexibility_alarms_ms";
static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints";
static final String KEY_MAX_RESCHEDULED_DEADLINE_MS =
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index 58aff42..03e714a 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -21,42 +21,51 @@
* 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* overlay_entry*
- * string_pool
- * data_header := target_entry_count target_inline_entry_count overlay_entry_count
+ * data := data_header target_entry* target_inline_entry*
+ target_inline_entry_value* config* overlay_entry* 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 Res_value::size padding(1) Res_value::type
+ * target_inline_entry := target_id 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
*
- * debug_info := string
- * enforce_overlayable := <uint32_t>
- * fulfilled_policies := <uint32_t>
- * magic := <uint32_t>
- * overlay_crc := <uint32_t>
- * overlay_entry_count := <uint32_t>
- * overlay_id := <uint32_t>
- * overlay_package_id := <uint8_t>
- * overlay_name := string
- * overlay_path := string
- * padding(n) := <uint8_t>[n]
- * Res_value::size := <uint16_t>
- * Res_value::type := <uint8_t>
- * Res_value::value := <uint32_t>
- * string := <uint32_t> <uint8_t>+ padding(n)
- * string_pool := string
- * string_pool_index := <uint32_t>
- * string_pool_length := <uint32_t>
- * target_crc := <uint32_t>
- * target_entry_count := <uint32_t>
- * target_inline_entry_count := <uint32_t>
- * target_id := <uint32_t>
- * target_package_id := <uint8_t>
- * target_path := string
- * value_type := <uint8_t>
- * value_data := <uint32_t>
- * version := <uint32_t>
+ * debug_info := string
+ * enforce_overlayable := <uint32_t>
+ * fulfilled_policies := <uint32_t>
+ * magic := <uint32_t>
+ * overlay_crc := <uint32_t>
+ * overlay_entry_count := <uint32_t>
+ * overlay_id := <uint32_t>
+ * overlay_package_id := <uint8_t>
+ * overlay_name := string
+ * overlay_path := string
+ * padding(n) := <uint8_t>[n]
+ * Res_value::size := <uint16_t>
+ * Res_value::type := <uint8_t>
+ * Res_value::value := <uint32_t>
+ * string := <uint32_t> <uint8_t>+ padding(n)
+ * string_pool := string
+ * string_pool_index := <uint32_t>
+ * string_pool_length := <uint32_t>
+ * target_crc := <uint32_t>
+ * target_entry_count := <uint32_t>
+ * target_inline_entry_count := <uint32_t>
+ * target_inline_entry_value_count := <uint32_t>
+ * config_count := <uint32_t>
+ * config_index := <uint32_t>
+ * start_value_index := <uint32_t>
+ * value_count := <uint32_t>
+ * target_id := <uint32_t>
+ * target_package_id := <uint8_t>
+ * target_path := string
+ * value_type := <uint8_t>
+ * value_data := <uint32_t>
+ * version := <uint32_t>
*/
#ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
@@ -70,6 +79,7 @@
#include "android-base/macros.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/StringPiece.h"
+#include "androidfw/ConfigDescription.h"
#include "idmap2/ResourceContainer.h"
#include "idmap2/ResourceMapping.h"
@@ -165,19 +175,27 @@
public:
static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream);
- inline uint32_t GetTargetEntryCount() const {
+ [[nodiscard]] inline uint32_t GetTargetEntryCount() const {
return target_entry_count;
}
- inline uint32_t GetTargetInlineEntryCount() const {
+ [[nodiscard]] inline uint32_t GetTargetInlineEntryCount() const {
return target_entry_inline_count;
}
- inline uint32_t GetOverlayEntryCount() const {
+ [[nodiscard]] inline uint32_t GetTargetInlineEntryValueCount() const {
+ return target_entry_inline_value_count;
+ }
+
+ [[nodiscard]] inline uint32_t GetConfigCount() const {
+ return config_count;
+ }
+
+ [[nodiscard]] inline uint32_t GetOverlayEntryCount() const {
return overlay_entry_count;
}
- inline uint32_t GetStringPoolIndexOffset() const {
+ [[nodiscard]] inline uint32_t GetStringPoolIndexOffset() const {
return string_pool_index_offset;
}
@@ -186,6 +204,8 @@
private:
uint32_t target_entry_count;
uint32_t target_entry_inline_count;
+ uint32_t target_entry_inline_value_count;
+ uint32_t config_count;
uint32_t overlay_entry_count;
uint32_t string_pool_index_offset;
Header() = default;
@@ -202,7 +222,7 @@
struct TargetInlineEntry {
ResourceId target_id;
- TargetValue value;
+ std::map<ConfigDescription, TargetValue> values;
};
struct OverlayEntry {
@@ -227,11 +247,11 @@
return target_inline_entries_;
}
- const std::vector<OverlayEntry>& GetOverlayEntries() const {
+ [[nodiscard]] const std::vector<OverlayEntry>& GetOverlayEntries() const {
return overlay_entries_;
}
- const std::string& GetStringPoolData() const {
+ [[nodiscard]] const std::string& GetStringPoolData() const {
return string_pool_data_;
}
diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h
index 21862a36..4bad2fa 100644
--- a/cmds/idmap2/include/idmap2/ResourceMapping.h
+++ b/cmds/idmap2/include/idmap2/ResourceMapping.h
@@ -33,7 +33,8 @@
using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
namespace android::idmap2 {
-using TargetResourceMap = std::map<ResourceId, std::variant<ResourceId, TargetValue>>;
+using ConfigMap = std::unordered_map<std::string, TargetValue>;
+using TargetResourceMap = std::map<ResourceId, std::variant<ResourceId, ConfigMap>>;
using OverlayResourceMap = std::map<ResourceId, ResourceId>;
class ResourceMapping {
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index 8ec7496..af4dd89 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -46,6 +46,10 @@
struct TargetValueWithConfig {
TargetValue value;
std::string config;
+
+ [[nodiscard]] std::pair<std::string, TargetValue> to_pair() const {
+ return std::make_pair(config, value);
+ }
};
namespace utils {
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index 3bbe9d9..4b271a1 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -70,12 +70,35 @@
}
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());
+ }
+
+ 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);
+ }
Write16(kValueSize);
Write8(0U); // padding
- Write8(target_entry.value.data_type);
- Write32(target_entry.value.data_value);
+ 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));
}
for (const auto& overlay_entry : data.GetOverlayEntries()) {
@@ -89,6 +112,8 @@
void BinaryStreamVisitor::visit(const IdmapData::Header& header) {
Write32(header.GetTargetEntryCount());
Write32(header.GetTargetInlineEntryCount());
+ Write32(header.GetTargetInlineEntryValueCount());
+ Write32(header.GetConfigCount());
Write32(header.GetOverlayEntryCount());
Write32(header.GetStringPoolIndexOffset());
}
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 06650f6..4efaeea 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -186,6 +186,8 @@
std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header());
if (!Read32(stream, &idmap_data_header->target_entry_count) ||
!Read32(stream, &idmap_data_header->target_entry_inline_count) ||
+ !Read32(stream, &idmap_data_header->target_entry_inline_value_count) ||
+ !Read32(stream, &idmap_data_header->config_count) ||
!Read32(stream, &idmap_data_header->overlay_entry_count) ||
!Read32(stream, &idmap_data_header->string_pool_index_offset)) {
return nullptr;
@@ -207,20 +209,59 @@
if (!Read32(stream, &target_entry.target_id) || !Read32(stream, &target_entry.overlay_id)) {
return nullptr;
}
- data->target_entries_.push_back(target_entry);
+ data->target_entries_.emplace_back(target_entry);
}
// Read the mapping of target resource id to inline overlay values.
- uint8_t unused1;
- uint16_t unused2;
+ std::vector<std::tuple<TargetInlineEntry, uint32_t, uint32_t>> target_inline_entries;
for (size_t i = 0; i < data->header_->GetTargetInlineEntryCount(); i++) {
TargetInlineEntry target_entry{};
- if (!Read32(stream, &target_entry.target_id) || !Read16(stream, &unused2) ||
- !Read8(stream, &unused1) || !Read8(stream, &target_entry.value.data_type) ||
- !Read32(stream, &target_entry.value.data_value)) {
+ uint32_t entry_offset;
+ uint32_t entry_count;
+ if (!Read32(stream, &target_entry.target_id) || !Read32(stream, &entry_offset)
+ || !Read32(stream, &entry_count)) {
return nullptr;
}
- data->target_inline_entries_.push_back(target_entry);
+ target_inline_entries.emplace_back(std::make_tuple(target_entry, entry_offset, entry_count));
+ }
+
+ // 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++) {
+ uint32_t config_index;
+ if (!Read32(stream, &config_index)) {
+ return nullptr;
+ }
+ TargetValue value;
+ if (!Read16(stream, &unused2)
+ || !Read8(stream, &unused1)
+ || !Read8(stream, &value.data_type)
+ || !Read32(stream, &value.data_value)) {
+ return nullptr;
+ }
+ target_values.emplace_back(std::make_pair(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))) {
+ 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_.emplace_back(target_entry);
}
// Read the mapping of overlay resource id to target resource id.
@@ -278,12 +319,21 @@
std::unique_ptr<IdmapData> data(new IdmapData());
data->string_pool_data_ = resource_mapping.GetStringPoolData().to_string();
+ uint32_t inline_value_count = 0;
+ std::set<std::string> 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});
} else {
- data->target_inline_entries_.push_back(
- {mapping.first, std::get<TargetValue>(mapping.second)});
+ std::map<ConfigDescription, TargetValue> values;
+ for (const auto& [config, value] : std::get<ConfigMap>(mapping.second)) {
+ config_set.insert(config);
+ ConfigDescription cd;
+ ConfigDescription::Parse(config, &cd);
+ values[cd] = value;
+ inline_value_count++;
+ }
+ data->target_inline_entries_.push_back({mapping.first, values});
}
}
@@ -295,6 +345,8 @@
data_header->target_entry_count = static_cast<uint32_t>(data->target_entries_.size());
data_header->target_entry_inline_count =
static_cast<uint32_t>(data->target_inline_entries_.size());
+ data_header->target_entry_inline_value_count = inline_value_count;
+ data_header->config_count = config_set.size();
data_header->overlay_entry_count = static_cast<uint32_t>(data->overlay_entries_.size());
data_header->string_pool_index_offset = resource_mapping.GetStringPoolOffset();
data->header_ = std::move(data_header);
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index d10a278..a44fa75 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -94,14 +94,17 @@
}
for (auto& target_entry : data.GetTargetInlineEntries()) {
- stream_ << TAB << base::StringPrintf("0x%08x -> ", target_entry.target_id)
- << utils::DataTypeToString(target_entry.value.data_type);
+ for(auto iter = target_entry.values.begin(); iter != target_entry.values.end(); ++iter) {
+ auto value = iter->second;
+ stream_ << TAB << base::StringPrintf("0x%08x -> ", target_entry.target_id)
+ << utils::DataTypeToString(value.data_type);
- if (target_entry.value.data_type == Res_value::TYPE_STRING) {
- auto str = string_pool.stringAt(target_entry.value.data_value - string_pool_offset);
- stream_ << " \"" << str.value_or(StringPiece16(u"")) << "\"";
- } else {
- stream_ << " " << base::StringPrintf("0x%08x", target_entry.value.data_value);
+ if (value.data_type == Res_value::TYPE_STRING) {
+ auto str = string_pool.stringAt(value.data_value - string_pool_offset);
+ stream_ << " \"" << str.value_or(StringPiece16(u"")) << "\"";
+ } else {
+ stream_ << " " << base::StringPrintf("0x%08x", value.data_value);
+ }
}
std::string target_name = kUnknownResourceName;
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
index 779538c..3531cd7 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -89,22 +89,30 @@
print(target_entry.target_id, "target id");
}
+
pad(sizeof(Res_value::size) + sizeof(Res_value::res0));
- print(target_entry.value.data_type, "type: %s",
- utils::DataTypeToString(target_entry.value.data_type).data());
+ for (auto& target_entry_value : target_entry.values) {
+ auto value = target_entry_value.second;
- Result<std::string> overlay_name(Error(""));
- if (overlay_ != nullptr &&
- (target_entry.value.data_value == Res_value::TYPE_REFERENCE ||
- target_entry.value.data_value == Res_value::TYPE_DYNAMIC_REFERENCE)) {
- overlay_name = overlay_->GetResourceName(target_entry.value.data_value);
- }
+ print(target_entry_value.first.to_string(), false, "config: %s",
+ target_entry_value.first.toString().string());
- if (overlay_name) {
- print(target_entry.value.data_value, "data: %s", overlay_name->c_str());
- } else {
- print(target_entry.value.data_value, "data");
+ print(value.data_type, "type: %s",
+ utils::DataTypeToString(value.data_type).data());
+
+ Result<std::string> overlay_name(Error(""));
+ if (overlay_ != nullptr &&
+ (value.data_value == Res_value::TYPE_REFERENCE ||
+ value.data_value == Res_value::TYPE_DYNAMIC_REFERENCE)) {
+ overlay_name = overlay_->GetResourceName(value.data_value);
+ }
+
+ if (overlay_name) {
+ print(value.data_value, "data: %s", overlay_name->c_str());
+ } else {
+ print(value.data_value, "data");
+ }
}
}
@@ -138,6 +146,8 @@
void RawPrintVisitor::visit(const IdmapData::Header& header) {
print(header.GetTargetEntryCount(), "target entry count");
print(header.GetTargetInlineEntryCount(), "target inline entry count");
+ print(header.GetTargetInlineEntryValueCount(), "target inline entry value count");
+ print(header.GetConfigCount(), "config count");
print(header.GetOverlayEntryCount(), "overlay entry count");
print(header.GetStringPoolIndexOffset(), "string pool index offset");
}
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 8ebe5aa4..bb31c11 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -161,14 +161,13 @@
Result<Unit> ResourceMapping::AddMapping(
ResourceId target_resource,
const std::variant<OverlayData::ResourceIdValue, TargetValueWithConfig>& value) {
- if (target_map_.find(target_resource) != target_map_.end()) {
- return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
- }
-
// TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
// runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
if (auto overlay_resource = std::get_if<OverlayData::ResourceIdValue>(&value)) {
+ if (target_map_.find(target_resource) != target_map_.end()) {
+ return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
+ }
target_map_.insert(std::make_pair(target_resource, overlay_resource->overlay_id));
if (overlay_resource->rewrite_id) {
// An overlay resource can override multiple target resources at once. Rewrite the overlay
@@ -176,8 +175,18 @@
overlay_map_.insert(std::make_pair(overlay_resource->overlay_id, target_resource));
}
} else {
- auto overlay_value = std::get<TargetValueWithConfig>(value);
- target_map_.insert(std::make_pair(target_resource, overlay_value.value));
+ auto[iter, inserted] = target_map_.try_emplace(target_resource, ConfigMap());
+ auto& resource_value = iter->second;
+ if (!inserted && std::holds_alternative<ResourceId>(resource_value)) {
+ return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
+ }
+ auto& config_map = std::get<ConfigMap>(resource_value);
+ const auto& config_value = std::get<TargetValueWithConfig>(value);
+ if (config_map.find(config_value.config) != config_map.end()) {
+ return Error(R"(target resource id "0x%08x" mapped to multiple values with the same config)",
+ target_resource);
+ }
+ config_map.insert(config_value.to_pair());
}
return Unit{};
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
index bf63327..f1eeab9 100644
--- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -84,8 +84,10 @@
const auto& target_inline_entries2 = data2->GetTargetInlineEntries();
ASSERT_EQ(target_inline_entries1.size(), target_inline_entries2.size());
ASSERT_EQ(target_inline_entries1[0].target_id, target_inline_entries2[0].target_id);
- ASSERT_EQ(target_inline_entries1[0].value.data_type, target_inline_entries2[0].value.data_type);
- ASSERT_EQ(target_inline_entries1[0].value.data_value, target_inline_entries2[0].value.data_value);
+ ASSERT_EQ(target_inline_entries1[0].values.begin()->second.data_type,
+ target_inline_entries2[0].values.begin()->second.data_type);
+ ASSERT_EQ(target_inline_entries1[0].values.begin()->second.data_value,
+ target_inline_entries2[0].values.begin()->second.data_value);
const auto& overlay_entries1 = data1->GetOverlayEntries();
const auto& overlay_entries2 = data2->GetOverlayEntries();
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index ee9a424..7b7dc17 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -47,10 +47,11 @@
ASSERT_EQ((entry).target_id, (target_resid)); \
ASSERT_EQ((entry).overlay_id, (overlay_resid))
-#define ASSERT_TARGET_INLINE_ENTRY(entry, target_resid, expected_type, expected_value) \
- ASSERT_EQ((entry).target_id, target_resid); \
- ASSERT_EQ((entry).value.data_type, (expected_type)); \
- ASSERT_EQ((entry).value.data_value, (expected_value))
+#define ASSERT_TARGET_INLINE_ENTRY(entry, target_resid, ex_config, expected_type, expected_value) \
+ ASSERT_EQ((entry).target_id, target_resid); \
+ ASSERT_EQ((entry).values.begin()->first.to_string(), (ex_config)); \
+ ASSERT_EQ((entry).values.begin()->second.data_type, (expected_type)); \
+ ASSERT_EQ((entry).values.begin()->second.data_value, (expected_value))
#define ASSERT_OVERLAY_ENTRY(entry, overlay_resid, target_resid) \
ASSERT_EQ((entry).overlay_id, (overlay_resid)); \
@@ -67,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(), 0x08U);
+ ASSERT_EQ(header->GetVersion(), 0x09U);
ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(header->GetFulfilledPolicies(), 0x11);
@@ -122,8 +123,8 @@
const auto& target_inline_entries = data->GetTargetInlineEntries();
ASSERT_EQ(target_inline_entries.size(), 1U);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], 0x7f040000, Res_value::TYPE_INT_HEX,
- 0x12345678);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], 0x7f040000, "land-xxhdpi-v7",
+ Res_value::TYPE_INT_HEX, 0x12345678);
const auto& overlay_entries = data->GetOverlayEntries();
ASSERT_EQ(target_entries.size(), 3U);
@@ -142,7 +143,7 @@
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
- ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x09U);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies);
@@ -165,8 +166,8 @@
const auto& target_inline_entries = data->GetTargetInlineEntries();
ASSERT_EQ(target_inline_entries.size(), 1U);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], 0x7f040000, Res_value::TYPE_INT_HEX,
- 0x12345678);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], 0x7f040000, "land-xxhdpi-v7",
+ Res_value::TYPE_INT_HEX, 0x12345678);
const auto& overlay_entries = data->GetOverlayEntries();
ASSERT_EQ(target_entries.size(), 3U);
@@ -203,7 +204,7 @@
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
- ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x09U);
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);
@@ -261,9 +262,9 @@
auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
.SetOverlayable("TestResources")
- .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
- .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
- .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
+ .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7")
+ .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land")
+ .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7")
.Build();
ASSERT_TRUE(frro);
@@ -295,11 +296,11 @@
const auto& target_inline_entries = data->GetTargetInlineEntries();
ASSERT_EQ(target_inline_entries.size(), 3U);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, "land-xxhdpi-v7",
Res_value::TYPE_INT_DEC, 2U);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1,
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, "land",
Res_value::TYPE_REFERENCE, 0x7f010000);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2,
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2, "xxhdpi-v7",
Res_value::TYPE_STRING,
(uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1));
}
@@ -442,9 +443,9 @@
constexpr size_t overlay_string_pool_size = 10U;
const auto& target_inline_entries = data->GetTargetInlineEntries();
ASSERT_EQ(target_inline_entries.size(), 2U);
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1, std::string(),
Res_value::TYPE_INT_DEC, 73U); // -> 73
- ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1,
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1, std::string(),
Res_value::TYPE_STRING,
overlay_string_pool_size + 0U); // -> "Hello World"
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index a6371cb..7112eeb 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 "00000008 version\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000009 version\n", stream.str());
ASSERT_CONTAINS_REGEX(
StringPrintf(ADDRESS "%s target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING),
stream.str());
@@ -75,6 +75,8 @@
ASSERT_CONTAINS_REGEX(ADDRESS "00000001 enforce overlayable\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 target entry count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000000 target inline entry count", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000000 target inline entry value count", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000000 config count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "0000000a string pool index offset", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1", stream.str());
@@ -111,7 +113,7 @@
(*idmap)->accept(&visitor);
ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "00000008 version\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000009 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());
@@ -124,17 +126,25 @@
ASSERT_CONTAINS_REGEX(ADDRESS "........ overlay name: OverlayName\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000003 target entry count\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000001 target inline entry count\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000001 target inline entry value count", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000001 config count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000003 overlay entry count\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000000 string pool index offset\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 overlay id\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030000 target id\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030000 overlay id\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030002 target id\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f030001 overlay id\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f040000 target id\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "0000000e config: land-xxhdpi-v7 size\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "........ config: land-xxhdpi-v7\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS " 11 type: integer\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "12345678 data\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 overlay id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f030002 target id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 string pool size\n", stream.str());
- ASSERT_CONTAINS_REGEX("000000a4: ........ string pool\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "........ string pool\n", stream.str());
}
} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index ca9a444..016d427 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -111,19 +111,20 @@
return Error("Failed to find mapping for target resource");
}
- auto actual_overlay_value = std::get_if<TargetValue>(&entry_map->second);
- if (actual_overlay_value == nullptr) {
+ auto config_map = std::get_if<ConfigMap>(&entry_map->second);
+ if (config_map == nullptr || config_map->empty()) {
return Error("Target resource is not mapped to an inline value");
}
+ auto actual_overlay_value = config_map->begin()->second;
- if (actual_overlay_value->data_type != type) {
+ if (actual_overlay_value.data_type != type) {
return Error(R"(Expected type: "0x%02x" Actual type: "0x%02x")", type,
- actual_overlay_value->data_type);
+ actual_overlay_value.data_type);
}
- if (actual_overlay_value->data_value != value) {
+ if (actual_overlay_value.data_value != value) {
return Error(R"(Expected value: "0x%08x" Actual value: "0x%08x")", type,
- actual_overlay_value->data_value);
+ actual_overlay_value.data_value);
}
return Result<Unit>({});
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index 6b5f3a8..45740e2 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -30,7 +30,7 @@
0x49, 0x44, 0x4d, 0x50,
// 0x4: version
- 0x08, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00,
// 0x8: target crc
0x34, 0x12, 0x00, 0x00,
@@ -76,66 +76,123 @@
// 0x58: target_inline_entry_count
0x01, 0x00, 0x00, 0x00,
- // 0x5c: overlay_entry_count
+ // 0x5c: target_inline_entry_value_count
+ 0x01, 0x00, 0x00, 0x00,
+
+ // 0x60: config_count
+ 0x01, 0x00, 0x00, 0x00,
+
+ // 0x64: overlay_entry_count
0x03, 0x00, 0x00, 0x00,
- // 0x60: string_pool_offset
+ // 0x68: string_pool_offset
0x00, 0x00, 0x00, 0x00,
// TARGET ENTRIES
- // 0x64: target id (0x7f020000)
+ // 0x6c: target id (0x7f020000)
0x00, 0x00, 0x02, 0x7f,
- // 0x68: overlay_id (0x7f020000)
+ // 0x70: overlay_id (0x7f020000)
0x00, 0x00, 0x02, 0x7f,
- // 0x6c: target id (0x7f030000)
+ // 0x74: target id (0x7f030000)
0x00, 0x00, 0x03, 0x7f,
- // 0x70: overlay_id (0x7f030000)
+ // 0x78: overlay_id (0x7f030000)
0x00, 0x00, 0x03, 0x7f,
- // 0x74: target id (0x7f030002)
+ // 0x7c: target id (0x7f030002)
0x02, 0x00, 0x03, 0x7f,
- // 0x78: overlay_id (0x7f030001)
+ // 0x80: overlay_id (0x7f030001)
0x01, 0x00, 0x03, 0x7f,
// INLINE TARGET ENTRIES
- // 0x7c: target_id
+ // 0x84: target_id
0x00, 0x00, 0x04, 0x7f,
- // 0x80: Res_value::size (value ignored by idmap)
+ // 0x88: start value index
+ 0x00, 0x00, 0x00, 0x00,
+
+ // 0x8c: value count
+ 0x01, 0x00, 0x00, 0x00,
+
+ // INLINE TARGET ENTRY VALUES
+
+ // 0x90: config index
+ 0x00, 0x00, 0x00, 0x00,
+
+ // 0x94: Res_value::size (value ignored by idmap)
0x08, 0x00,
- // 0x82: Res_value::res0 (value ignored by idmap)
+ // 0x98: Res_value::res0 (value ignored by idmap)
0x00,
- // 0x83: Res_value::dataType (TYPE_INT_HEX)
+ // 0x9c: Res_value::dataType (TYPE_INT_HEX)
0x11,
- // 0x84: Res_value::data
+ // 0xa0: Res_value::data
0x78, 0x56, 0x34, 0x12,
+ // CONFIGURATIONS
+
+ // 0xa4: ConfigDescription
+ // size
+ 0x40, 0x00, 0x00, 0x00,
+ // 0xa8: imsi
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xac: locale
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xb0: screenType
+ 0x02, 0x00, 0xe0, 0x01,
+ // 0xb4: input
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xb8: screenSize
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xbc: version
+ 0x07, 0x00, 0x00, 0x00,
+ // 0xc0: screenConfig
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xc4: screenSizeDp
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xc8: localeScript
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xcc: localVariant(1)
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xd0: localVariant(2)
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xd4: screenConfig2
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xd8: localeScriptWasComputed
+ 0x00,
+ // 0xd9: localeNumberingSystem(1)
+ 0x00, 0x00, 0x00, 0x00,
+ // 0xdd: localeNumberingSystem(2)
+ 0x00, 0x00, 0x00, 0x00,
+
+ // 0xe1: padding
+ 0x00, 0x00, 0x00,
+
+
// OVERLAY ENTRIES
- // 0x88: 0x7f020000 -> 0x7f020000
+ // 0xe4: 0x7f020000 -> 0x7f020000
0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
- // 0x90: 0x7f030000 -> 0x7f030000
+ // 0xec: 0x7f030000 -> 0x7f030000
0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
- // 0x98: 0x7f030001 -> 0x7f030002
+ // 0xf4: 0x7f030001 -> 0x7f030002
0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f,
- // 0xa0: string pool
+ // 0xfc: string pool
// string length,
0x04, 0x00, 0x00, 0x00,
- // 0xa4 string contents "test"
+ // 0x100 string contents "test"
0x74, 0x65, 0x73, 0x74};
-const unsigned int kIdmapRawDataLen = 0xa8;
+const unsigned int kIdmapRawDataLen = 0x104;
const unsigned int kIdmapRawDataOffset = 0x54;
const unsigned int kIdmapRawDataTargetCrc = 0x1234;
const unsigned int kIdmapRawOverlayCrc = 0x5678;
diff --git a/core/api/current.txt b/core/api/current.txt
index fb0cff5..1bf0d0e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -48771,6 +48771,10 @@
field public static final int KEYCODE_STEM_2 = 266; // 0x10a
field public static final int KEYCODE_STEM_3 = 267; // 0x10b
field public static final int KEYCODE_STEM_PRIMARY = 264; // 0x108
+ field public static final int KEYCODE_STYLUS_BUTTON_PRIMARY = 308; // 0x134
+ field public static final int KEYCODE_STYLUS_BUTTON_SECONDARY = 309; // 0x135
+ field public static final int KEYCODE_STYLUS_BUTTON_TAIL = 311; // 0x137
+ field public static final int KEYCODE_STYLUS_BUTTON_TERTIARY = 310; // 0x136
field public static final int KEYCODE_SWITCH_CHARSET = 95; // 0x5f
field public static final int KEYCODE_SYM = 63; // 0x3f
field public static final int KEYCODE_SYSRQ = 120; // 0x78
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 619e9fd..449900d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6666,6 +6666,15 @@
}
+package android.media.projection {
+
+ public class MediaProjectionGlobal {
+ method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface);
+ method @NonNull public static android.media.projection.MediaProjectionGlobal getInstance();
+ }
+
+}
+
package android.media.session {
public final class MediaSessionManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f780e6f..085bd55 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2854,7 +2854,7 @@
method public static String actionToString(int);
method public final void setDisplayId(int);
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
- field public static final int LAST_KEYCODE = 307; // 0x133
+ field public static final int LAST_KEYCODE = 311; // 0x137
}
public final class KeyboardShortcutGroup implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d899dab..dfef279 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4216,13 +4216,23 @@
}
}*/
+ /** @hide
+ * Determines whether the given UID can access unexported components
+ * @param uid the calling UID
+ * @return true if the calling UID is ROOT or SYSTEM
+ */
+ public static boolean canAccessUnexportedComponents(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ return (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID);
+ }
+
/** @hide */
@UnsupportedAppUsage
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root, system server get to do everything.
final int appId = UserHandle.getAppId(uid);
- if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
+ if (canAccessUnexportedComponents(uid)) {
return PackageManager.PERMISSION_GRANTED;
}
// Isolated processes don't get any permissions.
@@ -4350,20 +4360,28 @@
}
/**
- * Starts the given user in background and associate the user with the given display.
+ * Starts the given user in background and assign the user to the given display.
*
* <p>This method will allow the user to launch activities on that display, and it's typically
* used only on automotive builds when the vehicle has multiple displays (you can verify if it's
- * supported by calling {@link UserManager#isBackgroundUsersOnSecondaryDisplaysSupported()}).
+ * supported by calling {@link UserManager#isUsersOnSecondaryDisplaysSupported()}).
*
- * @return whether the user was started.
+ * <p><b>NOTE:</b> differently from {@link #switchUser(int)}, which stops the current foreground
+ * user before starting a new one, this method does not stop the previous user running in
+ * background in the display, and it will return {@code false} in this case. It's up to the
+ * caller to call {@link #stopUser(int, boolean)} before starting a new user.
+ *
+ * @param userId user to be started in the display. It will return {@code false} if the user is
+ * a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
+ * does not exist.
+ *
+ * @param displayId id of the display, it must exist.
+ *
+ * @return whether the operation succeeded. Notice that if the user was already started in such
+ * display before, it will return {@code false}.
*
* @throws UnsupportedOperationException if the device does not support background users on
* secondary displays.
- * @throws IllegalArgumentException if the display does not exist.
- * @throws IllegalStateException if the user cannot be started on that display (for example, if
- * there's already a user using that display or if the user is already associated with other
- * display).
*
* @hide
*/
@@ -4372,6 +4390,10 @@
android.Manifest.permission.CREATE_USERS})
public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId,
int displayId) {
+ if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+ throw new UnsupportedOperationException(
+ "device does not support users on secondary displays");
+ }
try {
return getService().startUserInBackgroundOnSecondaryDisplay(userId, displayId);
} catch (RemoteException e) {
@@ -4532,6 +4554,10 @@
/**
* Stops the given {@code userId}.
*
+ * <p><b>NOTE:</b> on systems that support
+ * {@link UserManager#isUsersOnSecondaryDisplaysSupported() background users on secondary
+ * displays}, this method will also unassign the user from the display it was started on.
+ *
* @hide
*/
@TestApi
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 9c8d010..980b79b 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -766,6 +766,8 @@
*
* <p>Typically used only by automotive builds when the vehicle has multiple displays.
*/
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional = true)")
boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);
}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index ca3e580..fa3c450 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -184,4 +184,9 @@
// Query for DISPLAY_DECORATION support.
DisplayDecorationSupport getDisplayDecorationSupport(int displayId);
+
+ // This method is to support behavior that was calling hidden APIs. The caller was requesting
+ // to set the layerStack after the display was created, which is not something we support in
+ // DMS. This should be deleted in V release.
+ void setDisplayIdToMirror(in IBinder token, int displayId);
}
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 71cbd20..02ab8be 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -68,6 +68,13 @@
}
/**
+ * @hide
+ */
+ public IVirtualDisplayCallback getToken() {
+ return mToken;
+ }
+
+ /**
* Sets the surface that backs the virtual display.
* <p>
* Detaching the surface that backs a virtual display has a similar effect to
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index f9bb880..57ba7e9 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -653,7 +653,7 @@
break;
case MotionEvent.ACTION_MOVE:
- if ((mCurrentDownEvent == null) || mInLongPress || mInContextClick) {
+ if (mInLongPress || mInContextClick) {
break;
}
@@ -736,9 +736,6 @@
break;
case MotionEvent.ACTION_UP:
- if (mCurrentDownEvent == null) {
- break;
- }
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 229de31..b2a26fa 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -114,6 +114,7 @@
@UnsupportedAppUsage
int getInitialDisplayDensity(int displayId);
int getBaseDisplayDensity(int displayId);
+ int getDisplayIdByUniqueId(String uniqueId);
void setForcedDisplayDensityForUser(int displayId, int density, int userId);
void clearForcedDisplayDensityForUser(int displayId, int userId);
void setForcedDisplayScalingMode(int displayId, int mode); // 0 = auto, 1 = disable
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 6c238e5..9789b56 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -872,13 +872,33 @@
public static final int KEYCODE_KEYBOARD_BACKLIGHT_UP = 306;
/** Key code constant: Keyboard backlight toggle */
public static final int KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE = 307;
+ /**
+ * Key code constant: The primary button on the barrel of a stylus.
+ * This is usually the button closest to the tip of the stylus.
+ */
+ public static final int KEYCODE_STYLUS_BUTTON_PRIMARY = 308;
+ /**
+ * Key code constant: The secondary button on the barrel of a stylus.
+ * This is usually the second button from the tip of the stylus.
+ */
+ public static final int KEYCODE_STYLUS_BUTTON_SECONDARY = 309;
+ /**
+ * Key code constant: The tertiary button on the barrel of a stylus.
+ * This is usually the third button from the tip of the stylus.
+ */
+ public static final int KEYCODE_STYLUS_BUTTON_TERTIARY = 310;
+ /**
+ * Key code constant: A button on the tail end of a stylus.
+ * The use of this button does not usually correspond to the function of an eraser.
+ */
+ public static final int KEYCODE_STYLUS_BUTTON_TAIL = 311;
/**
* Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
* @hide
*/
@TestApi
- public static final int LAST_KEYCODE = KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE;
+ public static final int LAST_KEYCODE = KEYCODE_STYLUS_BUTTON_TAIL;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index f650cd2..fb0cac3 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,6 +22,7 @@
import static android.graphics.Matrix.MSKEW_Y;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.SurfaceControlProto.HASH_CODE;
import static android.view.SurfaceControlProto.LAYER_ID;
import static android.view.SurfaceControlProto.NAME;
@@ -35,6 +36,7 @@
import android.annotation.Size;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
import android.graphics.ColorSpace;
import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
@@ -47,15 +49,23 @@
import android.hardware.HardwareBuffer;
import android.hardware.SyncFence;
import android.hardware.display.DeviceProductInfo;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.display.IDisplayManager;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplay;
import android.hardware.graphics.common.DisplayDecorationSupport;
+import android.media.projection.MediaProjectionGlobal;
import android.opengl.EGLDisplay;
import android.opengl.EGLSync;
import android.os.Build;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseIntArray;
@@ -166,8 +176,6 @@
private static native long[] nativeGetPhysicalDisplayIds();
private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId);
- private static native IBinder nativeCreateDisplay(String name, boolean secure);
- private static native void nativeDestroyDisplay(IBinder displayToken);
private static native void nativeSetDisplaySurface(long transactionObj,
IBinder displayToken, long nativeSurfaceObject);
private static native void nativeSetDisplayLayerStack(long transactionObj,
@@ -515,7 +523,7 @@
* be copied. In particular, screenshots and secondary, non-secure displays will render black
* content instead of the surface content.
*
- * @see #createDisplay(String, boolean)
+ * @see com.android.server.display.DisplayControl#createDisplay(String, boolean)
* @hide
*/
public static final int SECURE = 0x00000080;
@@ -1943,6 +1951,92 @@
}
/**
+ * Because this API is now going through {@link DisplayManager}, orientation and displayRect
+ * will automatically be computed based on configuration changes. Because of this, the params
+ * orientation and displayRect are ignored
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+ publicAlternatives = "Use {@code VirtualDisplay#resize(int, int, int)} instead.",
+ trackingBug = 247078497)
+ public static void setDisplayProjection(IBinder displayToken, int orientation,
+ Rect layerStackRect, Rect displayRect) {
+ DisplayManagerGlobal.getInstance().resizeVirtualDisplay(
+ IVirtualDisplayCallback.Stub.asInterface(displayToken), layerStackRect.width(),
+ layerStackRect.height(), 1);
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+ publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} with flag "
+ + " {@code VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} for mirroring instead.",
+ trackingBug = 247078497)
+ public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
+ if (b == null) {
+ throw new UnsupportedOperationException();
+ }
+
+ IDisplayManager dm = IDisplayManager.Stub.asInterface(b);
+ try {
+ dm.setDisplayIdToMirror(displayToken, layerStack);
+ } catch (RemoteException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+ publicAlternatives = "Use {@code VirtualDisplay#setSurface(Surface)} instead.",
+ trackingBug = 247078497)
+ public static void setDisplaySurface(IBinder displayToken, Surface surface) {
+ IVirtualDisplayCallback virtualDisplayCallback =
+ IVirtualDisplayCallback.Stub.asInterface(displayToken);
+ DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+ dm.setVirtualDisplaySurface(virtualDisplayCallback, surface);
+ }
+
+ /**
+ * Secure is no longer supported because this is only called from outside system which cannot
+ * create secure displays.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+ publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} or "
+ + "{@code DisplayManager#createVirtualDisplay()} instead.",
+ trackingBug = 247078497)
+ public static IBinder createDisplay(String name, boolean secure) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+
+ // We don't have a size yet so pass in 1 for width and height since 0 is invalid
+ VirtualDisplay vd = MediaProjectionGlobal.getInstance().createVirtualDisplay(name,
+ 1 /* width */, 1 /* height */, INVALID_DISPLAY, null /* Surface */);
+ return vd == null ? null : vd.getToken().asBinder();
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+ publicAlternatives = "Use {@code VirtualDisplay#release()} instead.",
+ trackingBug = 247078497)
+ public static void destroyDisplay(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+
+ DisplayManagerGlobal.getInstance().releaseVirtualDisplay(
+ IVirtualDisplayCallback.Stub.asInterface(displayToken));
+ }
+
+ /**
* Overrides HDR modes for a display device.
*
* If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security
@@ -1957,28 +2051,6 @@
/**
* @hide
*/
- @UnsupportedAppUsage
- public static IBinder createDisplay(String name, boolean secure) {
- if (name == null) {
- throw new IllegalArgumentException("name must not be null");
- }
- return nativeCreateDisplay(name, secure);
- }
-
- /**
- * @hide
- */
- @UnsupportedAppUsage
- public static void destroyDisplay(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeDestroyDisplay(displayToken);
- }
-
- /**
- * @hide
- */
public static long[] getPhysicalDisplayIds() {
return nativeGetPhysicalDisplayIds();
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 7acf319..9075de1 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -107,6 +107,14 @@
* and scaling a SurfaceView on screen will not cause rendering artifacts. Such
* artifacts may occur on previous versions of the platform when its window is
* positioned asynchronously.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Starting in platform version
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, SurfaceView will support arbitrary
+ * alpha blending. Prior platform versions ignored alpha values on the SurfaceView if they were
+ * between 0 and 1. If the SurfaceView is configured with Z-above, then the alpha is applied
+ * directly to the Surface. If the SurfaceView is configured with Z-below, then the alpha is
+ * applied to the hole punch directly. Note that when using Z-below, overlapping SurfaceViews
+ * may not blend properly as a consequence of not applying alpha to the surface content directly.
*/
public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCallback {
private static final String TAG = "SurfaceView";
@@ -146,6 +154,7 @@
Paint mRoundedViewportPaint;
int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+ int mRequestedSubLayer = APPLICATION_MEDIA_SUBLAYER;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
boolean mIsCreating = false;
@@ -177,8 +186,7 @@
@UnsupportedAppUsage
int mRequestedFormat = PixelFormat.RGB_565;
- boolean mUseAlpha = false;
- float mSurfaceAlpha = 1f;
+ float mAlpha = 1f;
boolean mClipSurfaceToBounds;
int mBackgroundColor = Color.BLACK;
@@ -335,58 +343,25 @@
* @hide
*/
public void setUseAlpha() {
- if (!mUseAlpha) {
- mUseAlpha = true;
- updateSurfaceAlpha();
- }
+ // TODO(b/241474646): Remove me
+ return;
}
@Override
public void setAlpha(float alpha) {
- // Sets the opacity of the view to a value, where 0 means the view is completely transparent
- // and 1 means the view is completely opaque.
- //
- // Note: Alpha value of this view is ignored by default. To enable alpha blending, you need
- // to call setUseAlpha() as well.
- // This view doesn't support translucent opacity if the view is located z-below, since the
- // logic to punch a hole in the view hierarchy cannot handle such case. See also
- // #clearSurfaceViewPort(Canvas)
if (DEBUG) {
Log.d(TAG, System.identityHashCode(this)
- + " setAlpha: mUseAlpha = " + mUseAlpha + " alpha=" + alpha);
+ + " setAlpha: alpha=" + alpha);
}
super.setAlpha(alpha);
- updateSurfaceAlpha();
}
- private float getFixedAlpha() {
- // Compute alpha value to be set on the underlying surface.
- final float alpha = getAlpha();
- return mUseAlpha && (mSubLayer > 0 || alpha == 0f) ? alpha : 1f;
- }
-
- private void updateSurfaceAlpha() {
- if (!mUseAlpha || !mHaveFrame || mSurfaceControl == null) {
- return;
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ if (Math.round(mAlpha * 255) != alpha) {
+ updateSurface();
}
- final float viewAlpha = getAlpha();
- if (mSubLayer < 0 && 0f < viewAlpha && viewAlpha < 1f) {
- Log.w(TAG, System.identityHashCode(this)
- + " updateSurfaceAlpha:"
- + " translucent color is not supported for a surface placed z-below.");
- }
- final ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot == null) {
- return;
- }
- final float alpha = getFixedAlpha();
- if (alpha != mSurfaceAlpha) {
- final Transaction transaction = new Transaction();
- transaction.setAlpha(mSurfaceControl, alpha);
- viewRoot.applyTransactionOnDraw(transaction);
- damageInParent();
- mSurfaceAlpha = alpha;
- }
+ return true;
}
private void performDrawFinished() {
@@ -534,7 +509,15 @@
invalidate();
}
+ @Override
+ public boolean hasOverlappingRendering() {
+ // SurfaceViews only alpha composite by modulating the Surface alpha for Z-above, or
+ // applying alpha on the hole punch for Z-below - no deferral to a layer is necessary.
+ return false;
+ }
+
private void clearSurfaceViewPort(Canvas canvas) {
+ final float alpha = getAlpha();
if (mCornerRadius > 0f) {
canvas.getClipBounds(mTmpRect);
if (mClipSurfaceToBounds && mClipBounds != null) {
@@ -546,10 +529,11 @@
mTmpRect.right,
mTmpRect.bottom,
mCornerRadius,
- mCornerRadius
+ mCornerRadius,
+ alpha
);
} else {
- canvas.punchHole(0f, 0f, getWidth(), getHeight(), 0f, 0f);
+ canvas.punchHole(0f, 0f, getWidth(), getHeight(), 0f, 0f, alpha);
}
}
@@ -626,7 +610,7 @@
* @hide
*/
public boolean isZOrderedOnTop() {
- return mSubLayer > 0;
+ return mRequestedSubLayer > 0;
}
/**
@@ -652,10 +636,10 @@
} else {
subLayer = APPLICATION_MEDIA_SUBLAYER;
}
- if (mSubLayer == subLayer) {
+ if (mRequestedSubLayer == subLayer) {
return false;
}
- mSubLayer = subLayer;
+ mRequestedSubLayer = subLayer;
if (!allowDynamicChange) {
return false;
@@ -667,9 +651,8 @@
if (viewRoot == null) {
return true;
}
- final Transaction transaction = new SurfaceControl.Transaction();
- updateRelativeZ(transaction);
- viewRoot.applyTransactionOnDraw(transaction);
+
+ updateSurface();
invalidate();
return true;
}
@@ -722,8 +705,7 @@
}
private void releaseSurfaces(boolean releaseSurfacePackage) {
- mSurfaceAlpha = 1f;
-
+ mAlpha = 1f;
synchronized (mSurfaceControlLock) {
mSurface.destroy();
if (mBlastBufferQueue != null) {
@@ -770,7 +752,7 @@
}
private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
- boolean creating, boolean sizeChanged, boolean hintChanged,
+ boolean creating, boolean sizeChanged, boolean hintChanged, boolean relativeZChanged,
Transaction surfaceUpdateTransaction) {
boolean realSizeChanged = false;
@@ -800,14 +782,20 @@
surfaceUpdateTransaction.hide(mSurfaceControl);
}
-
-
updateBackgroundVisibility(surfaceUpdateTransaction);
updateBackgroundColor(surfaceUpdateTransaction);
- if (mUseAlpha) {
- float alpha = getFixedAlpha();
+ if (isAboveParent()) {
+ float alpha = getAlpha();
surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
- mSurfaceAlpha = alpha;
+ }
+
+ if (relativeZChanged) {
+ if (!isAboveParent()) {
+ // If we're moving from z-above to z-below, then restore the surface alpha back to 1
+ // and let the holepunch drive visibility and blending.
+ surfaceUpdateTransaction.setAlpha(mSurfaceControl, 1.f);
+ }
+ updateRelativeZ(surfaceUpdateTransaction);
}
surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
@@ -873,6 +861,7 @@
} finally {
mSurfaceLock.unlock();
}
+
return realSizeChanged;
}
@@ -906,10 +895,10 @@
int myHeight = mRequestedHeight;
if (myHeight <= 0) myHeight = getHeight();
- final float alpha = getFixedAlpha();
+ final float alpha = getAlpha();
final boolean formatChanged = mFormat != mRequestedFormat;
final boolean visibleChanged = mVisible != mRequestedVisible;
- final boolean alphaChanged = mSurfaceAlpha != alpha;
+ final boolean alphaChanged = mAlpha != alpha;
final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
&& mRequestedVisible;
final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
@@ -921,17 +910,17 @@
|| getHeight() != mScreenRect.height();
final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint)
&& mRequestedVisible;
+ final boolean relativeZChanged = mSubLayer != mRequestedSubLayer;
- if (creating || formatChanged || sizeChanged || visibleChanged ||
- (mUseAlpha && alphaChanged) || windowVisibleChanged ||
- positionChanged || layoutSizeChanged || hintChanged) {
+ if (creating || formatChanged || sizeChanged || visibleChanged
+ || alphaChanged || windowVisibleChanged || positionChanged
+ || layoutSizeChanged || hintChanged || relativeZChanged) {
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged
+ " visible=" + visibleChanged + " alpha=" + alphaChanged
+ " hint=" + hintChanged
- + " mUseAlpha=" + mUseAlpha
+ " visible=" + visibleChanged
+ " left=" + (mWindowSpaceLeft != mLocation[0])
+ " top=" + (mWindowSpaceTop != mLocation[1]));
@@ -943,8 +932,10 @@
mSurfaceWidth = myWidth;
mSurfaceHeight = myHeight;
mFormat = mRequestedFormat;
+ mAlpha = alpha;
mLastWindowVisibility = mWindowVisibility;
mTransformHint = viewRoot.getBufferTransformHint();
+ mSubLayer = mRequestedSubLayer;
mScreenRect.left = mWindowSpaceLeft;
mScreenRect.top = mWindowSpaceTop;
@@ -968,7 +959,7 @@
}
final boolean redrawNeeded = sizeChanged || creating || hintChanged
- || (mVisible && !mDrawFinished);
+ || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
boolean shouldSyncBuffer =
redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
SyncBufferTransactionCallback syncBufferTransactionCallback = null;
@@ -979,8 +970,9 @@
syncBufferTransactionCallback::onTransactionReady);
}
- final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
- translator, creating, sizeChanged, hintChanged, surfaceUpdateTransaction);
+ final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
+ creating, sizeChanged, hintChanged, relativeZChanged,
+ surfaceUpdateTransaction);
try {
SurfaceHolder.Callback[] callbacks = null;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c4b03a8..4b16788 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8225,6 +8225,7 @@
mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
mTempInsets, mTempControls, mRelayoutBundle);
mRelayoutRequested = true;
+
final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
if (maybeSyncSeqId > 0) {
mSyncSeqId = maybeSyncSeqId;
@@ -8265,6 +8266,12 @@
}
}
+ if (mSurfaceControl.isValid() && !HardwareRenderer.isDrawingEnabled()) {
+ // When drawing is disabled the window layer won't have a valid buffer.
+ // Set a window crop so input can get delivered to the window.
+ mTransaction.setWindowCrop(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y).apply();
+ }
+
mLastTransformHint = transformHint;
mSurfaceControl.setTransformHint(transformHint);
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e89f836..7528e2a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -585,6 +585,18 @@
/**
* Returns if the accessibility in the system is enabled.
+ * <p>
+ * <b>Note:</b> This query is used for sending {@link AccessibilityEvent}s, since events are
+ * only needed if accessibility is on. Avoid changing UI or app behavior based on the state of
+ * accessibility. While well-intentioned, doing this creates brittle, less
+ * well-maintained code that works for some users but not others. Shared code leads to more
+ * equitable experiences and less technical debt.
+ *
+ *<p>
+ * For example, if you want to expose a unique interaction with your app, use
+ * ViewCompat#addAccessibilityAction in AndroidX to make this interaction - ideally
+ * with the same code path used for non-accessibility users - available to accessibility
+ * services. Services can then expose this action in the way best fit for their users.
*
* @return True if accessibility is enabled, false otherwise.
*/
@@ -597,6 +609,13 @@
/**
* Returns if the touch exploration in the system is enabled.
+ * <p>
+ * <b>Note:</b> This query is used for dispatching hover events, such as
+ * {@link android.view.MotionEvent#ACTION_HOVER_ENTER}, to accessibility services to manage
+ * touch exploration. Avoid changing UI or app behavior based on the state of accessibility.
+ * While well-intentioned, doing this creates brittle, less well-maintained code that works for
+ * som users but not others. Shared code leads to more equitable experiences and less technical
+ * debt.
*
* @return True if touch exploration is enabled, false otherwise.
*/
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index cebaa76..a2a198a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4583,7 +4583,6 @@
*/
private final class CursorAnchorInfoNotifier implements TextViewPositionListener {
final CursorAnchorInfo.Builder mSelectionInfoBuilder = new CursorAnchorInfo.Builder();
- final int[] mTmpIntOffset = new int[2];
final Matrix mViewToScreenMatrix = new Matrix();
@Override
@@ -4635,9 +4634,8 @@
builder.setSelectionRange(selectionStart, mTextView.getSelectionEnd());
// Construct transformation matrix from view local coordinates to screen coordinates.
- mViewToScreenMatrix.set(mTextView.getMatrix());
- mTextView.getLocationOnScreen(mTmpIntOffset);
- mViewToScreenMatrix.postTranslate(mTmpIntOffset[0], mTmpIntOffset[1]);
+ mViewToScreenMatrix.reset();
+ mTextView.transformMatrixToGlobal(mViewToScreenMatrix);
builder.setMatrix(mViewToScreenMatrix);
if (includeEditorBounds) {
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8ca763e..33ea2e4 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -110,11 +110,11 @@
/** The container is an input-method window. */
public static final int FLAG_IS_INPUT_METHOD = 1 << 8;
- /** The container is ActivityEmbedding embedded. */
- public static final int FLAG_IS_EMBEDDED = 1 << 9;
+ /** The container is in a Task with embedded activity. */
+ public static final int FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY = 1 << 9;
- /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 10;
+ /** The container fills its parent Task before and after the transition. */
+ public static final int FLAG_FILLS_TASK = 1 << 10;
/** The container is going to show IME on its task after the transition. */
public static final int FLAG_WILL_IME_SHOWN = 1 << 11;
@@ -125,6 +125,9 @@
/** The container attaches work profile thumbnail for cross profile animation. */
public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13;
+ /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
+ public static final int FLAG_FIRST_CUSTOM = 1 << 14;
+
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
@@ -137,9 +140,12 @@
FLAG_OCCLUDES_KEYGUARD,
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_IS_INPUT_METHOD,
- FLAG_IS_EMBEDDED,
- FLAG_FIRST_CUSTOM,
- FLAG_WILL_IME_SHOWN
+ FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY,
+ FLAG_FILLS_TASK,
+ FLAG_WILL_IME_SHOWN,
+ FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
+ FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
+ FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -322,28 +328,31 @@
sb.append("IS_INPUT_METHOD");
}
if ((flags & FLAG_TRANSLUCENT) != 0) {
- sb.append((sb.length() == 0 ? "" : "|") + "TRANSLUCENT");
+ sb.append(sb.length() == 0 ? "" : "|").append("TRANSLUCENT");
}
if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- sb.append((sb.length() == 0 ? "" : "|") + "STARTING_WINDOW_TRANSFER");
+ sb.append(sb.length() == 0 ? "" : "|").append("STARTING_WINDOW_TRANSFER");
}
if ((flags & FLAG_IS_VOICE_INTERACTION) != 0) {
- sb.append((sb.length() == 0 ? "" : "|") + "IS_VOICE_INTERACTION");
+ sb.append(sb.length() == 0 ? "" : "|").append("IS_VOICE_INTERACTION");
}
if ((flags & FLAG_IS_DISPLAY) != 0) {
- sb.append((sb.length() == 0 ? "" : "|") + "IS_DISPLAY");
+ sb.append(sb.length() == 0 ? "" : "|").append("IS_DISPLAY");
}
if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) {
- sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD");
+ sb.append(sb.length() == 0 ? "" : "|").append("OCCLUDES_KEYGUARD");
}
if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
- sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS");
+ sb.append(sb.length() == 0 ? "" : "|").append("DISPLAY_HAS_ALERT_WINDOWS");
}
- if ((flags & FLAG_IS_EMBEDDED) != 0) {
- sb.append((sb.length() == 0 ? "" : "|") + "IS_EMBEDDED");
+ if ((flags & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("IN_TASK_WITH_EMBEDDED_ACTIVITY");
+ }
+ if ((flags & FLAG_FILLS_TASK) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK");
}
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
- sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM");
+ sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
return sb.toString();
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index f097bf7..5523344 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -247,6 +247,13 @@
SystemProperties.set("debug.tracing." + name, Integer.toString(value));
}
}
+
+ /**
+ * Records an instant event (one with no duration).
+ */
+ public void traceInstantEvent(@NonNull String track, @NonNull String name) {
+ Trace.instantForTrack(Trace.TRACE_TAG_POWER, track, name);
+ }
}
private TraceDelegate mTracer;
@@ -1165,6 +1172,25 @@
}
/**
+ * Writes event details into Atrace.
+ */
+ private void recordTraceEvents(int code, HistoryTag tag) {
+ if (code == HistoryItem.EVENT_NONE) return;
+ if (!mTracer.tracingEnabled()) return;
+
+ final int idx = code & HistoryItem.EVENT_TYPE_MASK;
+ final String prefix = (code & HistoryItem.EVENT_FLAG_START) != 0 ? "+" :
+ (code & HistoryItem.EVENT_FLAG_FINISH) != 0 ? "-" : "";
+
+ final String[] names = BatteryStats.HISTORY_EVENT_NAMES;
+ if (idx < 0 || idx >= names.length) return;
+
+ final String track = "battery_stats." + names[idx];
+ final String name = prefix + names[idx] + "=" + tag.uid + ":\"" + tag.string + "\"";
+ mTracer.traceInstantEvent(track, name);
+ }
+
+ /**
* Writes changes to a HistoryItem state bitmap to Atrace.
*/
private void recordTraceCounters(int oldval, int newval, BitDescription[] descriptions) {
@@ -1229,6 +1255,7 @@
+ Integer.toHexString(lastDiffStates2));
}
+ recordTraceEvents(cur.eventCode, cur.eventTag);
recordTraceCounters(mHistoryLastWritten.states,
cur.states & mActiveHistoryStates, BatteryStats.HISTORY_STATE_DESCRIPTIONS);
recordTraceCounters(mHistoryLastWritten.states2,
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 80d50ff..c08f264 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -18,6 +18,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
@@ -32,6 +33,7 @@
import android.os.SystemProperties;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.ISystemGestureExclusionListener;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -45,8 +47,6 @@
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
-import java.util.ArrayList;
-
public class PointerLocationView extends View implements InputDeviceListener,
PointerEventListener {
private static final String TAG = "Pointer";
@@ -61,6 +61,9 @@
*/
private static final String GESTURE_EXCLUSION_PROP = "debug.pointerlocation.showexclusion";
+ // In case when it's in first time or no active pointer found, draw the empty state.
+ private static final PointerState EMPTY_POINTER_STATE = new PointerState();
+
public static class PointerState {
// Trace of previous points.
private float[] mTraceX = new float[32];
@@ -149,7 +152,7 @@
private int mMaxNumPointers;
private int mActivePointerId;
@UnsupportedAppUsage
- private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>();
+ private final SparseArray<PointerState> mPointers = new SparseArray<PointerState>();
private final PointerCoords mTempCoords = new PointerCoords();
private final Region mSystemGestureExclusion = new Region();
@@ -175,8 +178,6 @@
mVC = ViewConfiguration.get(c);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
- mTextPaint.setTextSize(10
- * getResources().getDisplayMetrics().density);
mTextPaint.setARGB(255, 0, 0, 0);
mTextBackgroundPaint = new Paint();
mTextBackgroundPaint.setAntiAlias(false);
@@ -188,20 +189,19 @@
mPaint.setAntiAlias(true);
mPaint.setARGB(255, 255, 255, 255);
mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(2);
mCurrentPointPaint = new Paint();
mCurrentPointPaint.setAntiAlias(true);
mCurrentPointPaint.setARGB(255, 255, 0, 0);
mCurrentPointPaint.setStyle(Paint.Style.STROKE);
- mCurrentPointPaint.setStrokeWidth(2);
mTargetPaint = new Paint();
mTargetPaint.setAntiAlias(false);
mTargetPaint.setARGB(255, 0, 0, 192);
mPathPaint = new Paint();
mPathPaint.setAntiAlias(false);
mPathPaint.setARGB(255, 0, 96, 255);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(1);
+ mPathPaint.setStyle(Paint.Style.STROKE);
+
+ configureDensityDependentFactors();
mSystemGestureExclusionPaint = new Paint();
mSystemGestureExclusionPaint.setARGB(25, 255, 0, 0);
@@ -211,8 +211,6 @@
mSystemGestureExclusionRejectedPaint.setARGB(25, 0, 0, 255);
mSystemGestureExclusionRejectedPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- PointerState ps = new PointerState();
- mPointers.add(ps);
mActivePointerId = 0;
mVelocity = VelocityTracker.obtain();
@@ -308,7 +306,7 @@
// Pointer trace.
for (int p = 0; p < NP; p++) {
- final PointerState ps = mPointers.get(p);
+ final PointerState ps = mPointers.valueAt(p);
// Draw path.
final int N = ps.mTraceCount;
@@ -319,7 +317,7 @@
for (int i=0; i < N; i++) {
float x = ps.mTraceX[i];
float y = ps.mTraceY[i];
- if (Float.isNaN(x)) {
+ if (Float.isNaN(x) || Float.isNaN(y)) {
haveLast = false;
continue;
}
@@ -418,10 +416,6 @@
}
private void drawLabels(Canvas canvas) {
- if (mActivePointerId < 0 || mActivePointerId >= mPointers.size()) {
- return;
- }
-
final int w = getWidth() - mWaterfallInsets.left - mWaterfallInsets.right;
final int itemW = w / 7;
final int base = mHeaderPaddingTop - mTextMetrics.ascent + 1;
@@ -429,7 +423,7 @@
canvas.save();
canvas.translate(mWaterfallInsets.left, 0);
- final PointerState ps = mPointers.get(mActivePointerId);
+ final PointerState ps = mPointers.get(mActivePointerId, EMPTY_POINTER_STATE);
canvas.drawRect(0, mHeaderPaddingTop, itemW - 1, bottom, mTextBackgroundPaint);
canvas.drawText(mText.clear()
@@ -600,18 +594,13 @@
@Override
public void onPointerEvent(MotionEvent event) {
final int action = event.getAction();
- int NP = mPointers.size();
if (action == MotionEvent.ACTION_DOWN
|| (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down
if (action == MotionEvent.ACTION_DOWN) {
- for (int p=0; p<NP; p++) {
- final PointerState ps = mPointers.get(p);
- ps.clearTrace();
- ps.mCurDown = false;
- }
+ mPointers.clear();
mCurDown = true;
mCurNumPointers = 0;
mMaxNumPointers = 0;
@@ -627,18 +616,17 @@
}
final int id = event.getPointerId(index);
- while (NP <= id) {
- PointerState ps = new PointerState();
- mPointers.add(ps);
- NP++;
+ PointerState ps = mPointers.get(id);
+ if (ps == null) {
+ ps = new PointerState();
+ mPointers.put(id, ps);
}
- if (mActivePointerId < 0 || mActivePointerId >= NP
+ if (!mPointers.contains(mActivePointerId)
|| !mPointers.get(mActivePointerId).mCurDown) {
mActivePointerId = id;
}
- final PointerState ps = mPointers.get(id);
ps.mCurDown = true;
InputDevice device = InputDevice.getDevice(event.getDeviceId());
ps.mHasBoundingBox = device != null &&
@@ -707,13 +695,13 @@
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
final int id = event.getPointerId(index);
- if (id >= NP) {
- Slog.wtf(TAG, "Got pointer ID out of bounds: id=" + id + " arraysize="
- + NP + " pointerindex=" + index
+ final PointerState ps = mPointers.get(id);
+ if (ps == null) {
+ Slog.wtf(TAG, "Could not find pointer id=" + id + " in mPointers map,"
+ + " size=" + mPointers.size() + " pointerindex=" + index
+ " action=0x" + Integer.toHexString(action));
return;
}
- final PointerState ps = mPointers.get(id);
ps.mCurDown = false;
if (action == MotionEvent.ACTION_UP
@@ -1016,4 +1004,19 @@
}
}
};
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ configureDensityDependentFactors();
+ }
+
+ // Compute size by display density.
+ private void configureDensityDependentFactors() {
+ final float density = getResources().getDisplayMetrics().density;
+ mTextPaint.setTextSize(10 * density);
+ mPaint.setStrokeWidth(1 * density);
+ mCurrentPointPaint.setStrokeWidth(1 * density);
+ mPathPaint.setStrokeWidth(1 * density);
+ }
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 11410c7..aefec6c 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -974,20 +974,6 @@
histogramComponent3);
}
-static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj,
- jboolean secure) {
- ScopedUtfChars name(env, nameObj);
- sp<IBinder> token(SurfaceComposerClient::createDisplay(
- String8(name.c_str()), bool(secure)));
- return javaObjectForIBinder(env, token);
-}
-
-static void nativeDestroyDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) {
- sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
- if (token == NULL) return;
- SurfaceComposerClient::destroyDisplay(token);
-}
-
static void nativeSetDisplaySurface(JNIEnv* env, jclass clazz,
jlong transactionObj,
jobject tokenObj, jlong nativeSurfaceObject) {
@@ -2001,10 +1987,6 @@
(void*)nativeGetPhysicalDisplayIds },
{"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;",
(void*)nativeGetPhysicalDisplayToken },
- {"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;",
- (void*)nativeCreateDisplay },
- {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
- (void*)nativeDestroyDisplay },
{"nativeSetDisplaySurface", "(JLandroid/os/IBinder;J)V",
(void*)nativeSetDisplaySurface },
{"nativeSetDisplayLayerStack", "(JLandroid/os/IBinder;I)V",
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index a186aca..af96c74 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -302,6 +302,11 @@
key 318 BUTTON_THUMBR
+key 329 STYLUS_BUTTON_TERTIARY
+key 331 STYLUS_BUTTON_PRIMARY
+key 332 STYLUS_BUTTON_SECONDARY
+
+
# key 352 "KEY_OK"
key 353 DPAD_CENTER
# key 354 "KEY_GOTO"
@@ -424,6 +429,8 @@
key usage 0x0c0173 MEDIA_AUDIO_TRACK
key usage 0x0c019C PROFILE_SWITCH
key usage 0x0c01A2 ALL_APPS
+key usage 0x0d0044 STYLUS_BUTTON_PRIMARY
+key usage 0x0d005a STYLUS_BUTTON_SECONDARY
# Joystick and game controller axes.
# Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index a8ab6d9..54d6428 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -670,8 +670,9 @@
/**
* @hide
*/
- public void punchHole(float left, float top, float right, float bottom, float rx, float ry) {
- nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry);
+ public void punchHole(float left, float top, float right, float bottom, float rx, float ry,
+ float alpha) {
+ nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, alpha);
}
/**
@@ -823,5 +824,5 @@
float hOffset, float vOffset, int flags, long nativePaint);
private static native void nPunchHole(long renderer, float left, float top, float right,
- float bottom, float rx, float ry);
+ float bottom, float rx, float ry, float alpha);
}
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index d06f665..1ba79b8 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -610,8 +610,9 @@
* @hide
*/
@Override
- public void punchHole(float left, float top, float right, float bottom, float rx, float ry) {
- nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry);
+ public void punchHole(float left, float top, float right, float bottom, float rx, float ry,
+ float alpha) {
+ nPunchHole(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, alpha);
}
@FastNative
@@ -742,5 +743,5 @@
@FastNative
private static native void nPunchHole(long renderer, float left, float top, float right,
- float bottom, float rx, float ry);
+ float bottom, float rx, float ry, float alpha);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index e0004fc..521a65c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -16,7 +16,8 @@
package com.android.wm.shell.activityembedding;
-import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
+import static android.window.TransitionInfo.FLAG_FILLS_TASK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static java.util.Objects.requireNonNull;
@@ -84,12 +85,23 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- // TODO(b/207070762) Handle AE animation as a part of other transitions.
- // Only handle the transition if all containers are embedded.
+ boolean containsEmbeddingSplit = false;
for (TransitionInfo.Change change : info.getChanges()) {
- if (!isEmbedded(change)) {
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ // Only animate the transition if all changes are in a Task with ActivityEmbedding.
return false;
}
+ if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) {
+ // Whether the Task contains any ActivityEmbedding split before or after the
+ // transition.
+ containsEmbeddingSplit = true;
+ }
+ }
+ if (!containsEmbeddingSplit) {
+ // Let the system to play the default animation if there is no ActivityEmbedding split
+ // window. This allows to play the app customized animation when there is no embedding,
+ // such as the device is in a folded state.
+ return false;
}
// Start ActivityEmbedding animation.
@@ -119,8 +131,4 @@
}
callback.onTransitionFinished(null /* wct */, null /* wctCB */);
}
-
- private static boolean isEmbedded(@NonNull TransitionInfo.Change change) {
- return (change.getFlags() & FLAG_IS_EMBEDDED) != 0;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 1d26172..2d9c2a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1270,7 +1270,7 @@
}
final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
- && mExpandedBubble != null;
+ && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show manage edu: " + shouldShow);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
index 063dac3..ab194df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
@@ -24,8 +24,8 @@
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
-import android.view.WindowManager
import android.view.WindowInsets
+import android.view.WindowManager
import android.widget.FrameLayout
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
@@ -41,6 +41,7 @@
var circle = DismissCircleView(context)
var isShowing = false
+ var targetSizeResId: Int
private val animator = PhysicsAnimator.getInstance(circle)
private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
@@ -70,7 +71,8 @@
setVisibility(View.INVISIBLE)
setBackgroundDrawable(gradientDrawable)
- val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
+ targetSizeResId = R.dimen.dismiss_circle_size
+ val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId)
addView(circle, LayoutParams(targetSize, targetSize,
Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL))
// start with circle offscreen so it's animated up
@@ -126,7 +128,7 @@
layoutParams.height = resources.getDimensionPixelSize(
R.dimen.floating_dismiss_gradient_height)
- val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
+ val targetSize = resources.getDimensionPixelSize(targetSizeResId)
circle.layoutParams.width = targetSize
circle.layoutParams.height = targetSize
circle.requestLayout()
@@ -153,4 +155,4 @@
setPadding(0, 0, 0, navInset.bottom +
resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index b0080b2..e7ec15e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -45,11 +45,6 @@
iconProvider);
}
- @Override
- void dismiss(WindowContainerTransaction wct, boolean toTop) {
- deactivate(wct, toTop);
- }
-
boolean isActive() {
return mIsActive;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index 86efbe0..8639b36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -42,11 +42,6 @@
iconProvider);
}
- @Override
- void dismiss(WindowContainerTransaction wct, boolean toTop) {
- removeAllTasks(wct, toTop);
- }
-
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
if (mChildrenTaskInfo.size() == 0) return false;
wct.reparentTasks(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 946dfde..db35b48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -123,6 +123,7 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
+import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
@@ -203,6 +204,8 @@
@StageType
private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ private DefaultMixedHandler mMixedHandler;
+
private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
new SplitWindowManager.ParentContainerCallbacks() {
@Override
@@ -326,6 +329,10 @@
transitions.addHandler(this);
}
+ public void setMixedHandler(DefaultMixedHandler mixedHandler) {
+ mMixedHandler = mixedHandler;
+ }
+
@VisibleForTesting
SplitScreenTransitions getSplitTransitions() {
return mSplitTransitions;
@@ -927,13 +934,10 @@
// Expand to top side split as full screen for fading out decor animation and dismiss
// another side split(Moving its children to bottom).
mIsExiting = true;
- final StageTaskListener tempFullStage = childrenToTop;
- final StageTaskListener dismissStage = mMainStage == childrenToTop
- ? mSideStage : mMainStage;
- tempFullStage.resetBounds(wct);
- wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token,
+ childrenToTop.resetBounds(wct);
+ wct.reorder(childrenToTop.mRootTaskInfo.token, true);
+ wct.setSmallestScreenWidthDp(childrenToTop.mRootTaskInfo.token,
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
- dismissStage.dismiss(wct, false /* toTop */);
}
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -950,7 +954,8 @@
childrenToTop.fadeOutDecor(() -> {
WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
mIsExiting = false;
- childrenToTop.dismiss(finishedWCT, true /* toTop */);
+ mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
+ mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
@@ -1879,8 +1884,25 @@
// Use normal animations.
return false;
+ } else if (mMixedHandler != null && hasDisplayChange(info)) {
+ // A display-change has been un-expectedly inserted into the transition. Redirect
+ // handling to the mixed-handler to deal with splitting it up.
+ if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
+ startTransaction, finishTransaction, finishCallback)) {
+ return true;
+ }
}
+ return startPendingAnimation(transition, info, startTransaction, finishTransaction,
+ finishCallback);
+ }
+
+ /** Starts the pending transition animation. */
+ public boolean startPendingAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
boolean shouldAnimate = true;
if (mSplitTransitions.isPendingEnter(transition)) {
shouldAnimate = startPendingEnterAnimation(
@@ -1899,6 +1921,15 @@
return true;
}
+ private boolean hasDisplayChange(TransitionInfo info) {
+ boolean has = false;
+ for (int iC = 0; iC < info.getChanges().size() && !has; ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ has = change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0;
+ }
+ return has;
+ }
+
/** Called to clean-up state and do house-keeping after the animation is done. */
public void onTransitionAnimationComplete() {
// If still playing, let it finish.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 1af9415..6b90eab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -106,11 +106,6 @@
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
- /**
- * General function for dismiss this stage.
- */
- void dismiss(WindowContainerTransaction wct, boolean toTop) {}
-
int getChildCount() {
return mChildrenTaskInfo.size();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index bcf4fbd..3cba929 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -57,6 +58,9 @@
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
+ /** Both the display and split-state (enter/exit) is changing */
+ static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -69,6 +73,7 @@
Transitions.TransitionFinishCallback mFinishCallback = null;
Transitions.TransitionHandler mLeftoversHandler = null;
+ WindowContainerTransaction mFinishWCT = null;
/**
* Mixed transitions are made up of multiple "parts". This keeps track of how many
@@ -95,6 +100,9 @@
mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler();
mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler();
mPlayer.addHandler(this);
+ if (mSplitHandler != null) {
+ mSplitHandler.setMixedHandler(this);
+ }
}, this);
}
}
@@ -122,10 +130,12 @@
}
private TransitionInfo subCopy(@NonNull TransitionInfo info,
- @WindowManager.TransitionType int newType) {
- final TransitionInfo out = new TransitionInfo(newType, info.getFlags());
- for (int i = 0; i < info.getChanges().size(); ++i) {
- out.getChanges().add(info.getChanges().get(i));
+ @WindowManager.TransitionType int newType, boolean withChanges) {
+ final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
+ if (withChanges) {
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ out.getChanges().add(info.getChanges().get(i));
+ }
}
out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
out.setAnimationOptions(info.getAnimationOptions());
@@ -157,6 +167,8 @@
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
+ return false;
} else {
mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -173,7 +185,7 @@
+ "entering PIP while Split-Screen is active.");
TransitionInfo.Change pipChange = null;
TransitionInfo.Change wallpaper = null;
- final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK);
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
boolean homeIsOpening = false;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
TransitionInfo.Change change = info.getChanges().get(i);
@@ -254,6 +266,87 @@
return true;
}
+ private void unlinkMissingParents(TransitionInfo from) {
+ for (int i = 0; i < from.getChanges().size(); ++i) {
+ final TransitionInfo.Change chg = from.getChanges().get(i);
+ if (chg.getParent() == null) continue;
+ if (from.getChange(chg.getParent()) == null) {
+ from.getChanges().get(i).setParent(null);
+ }
+ }
+ }
+
+ private boolean isWithinTask(TransitionInfo info, TransitionInfo.Change chg) {
+ TransitionInfo.Change curr = chg;
+ while (curr != null) {
+ if (curr.getTaskInfo() != null) return true;
+ if (curr.getParent() == null) break;
+ curr = info.getChange(curr.getParent());
+ }
+ return false;
+ }
+
+ /**
+ * This is intended to be called by SplitCoordinator as a helper to mix an already-pending
+ * split transition with a display-change. The use-case for this is when a display
+ * change/rotation gets collected into a split-screen enter/exit transition which has already
+ * been claimed by StageCoordinator.handleRequest . This happens during launcher tests.
+ */
+ public boolean animatePendingSplitWithDisplayChange(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ final TransitionInfo everythingElse = subCopy(info, info.getType(), true /* withChanges */);
+ final TransitionInfo displayPart = subCopy(info, TRANSIT_CHANGE, false /* withChanges */);
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (isWithinTask(info, change)) continue;
+ displayPart.addChange(change);
+ everythingElse.getChanges().remove(i);
+ }
+ if (displayPart.getChanges().isEmpty()) return false;
+ unlinkMissingParents(everythingElse);
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
+ mixed.mFinishCallback = finishCallback;
+ mActiveTransitions.add(mixed);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
+ + "and split change.");
+ // We need to split the transition into 2 parts: the split part and the display part.
+ mixed.mInFlightSubAnimations = 2;
+
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ --mixed.mInFlightSubAnimations;
+ if (wctCB != null) {
+ throw new IllegalArgumentException("Can't mix transitions that require finish"
+ + " sync callback");
+ }
+ if (wct != null) {
+ if (mixed.mFinishWCT == null) {
+ mixed.mFinishWCT = wct;
+ } else {
+ mixed.mFinishWCT.merge(wct, true /* transfer */);
+ }
+ }
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ mixed.mFinishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */);
+ };
+
+ // Dispatch the display change. This will most-likely be taken by the default handler.
+ // Do this first since the first handler used will apply the startT; the display change
+ // needs to take a screenshot before that happens so we need it to be the first handler.
+ mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, displayPart,
+ startT, finishT, finishCB, mSplitHandler);
+
+ // Note: at this point, startT has probably already been applied, so we are basically
+ // giving splitHandler an empty startT. This is currently OK because display-change will
+ // grab a screenshot and paste it on top anyways.
+ mSplitHandler.startPendingAnimation(
+ transition, everythingElse, startT, finishT, finishCB);
+ return true;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -279,6 +372,8 @@
} else {
mPipHandler.end();
}
+ } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
+ // queue
} else {
throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index b2e45a6..a7234c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -17,7 +17,7 @@
package com.android.wm.shell.activityembedding;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -57,7 +57,7 @@
public void testStartAnimation() {
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
final TransitionInfo.Change embeddingChange = createChange();
- embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+ embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
info.addChange(embeddingChange);
doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index 84befdd..3792e83 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -16,6 +16,9 @@
package com.android.wm.shell.activityembedding;
+import static android.window.TransitionInfo.FLAG_FILLS_TASK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertNotNull;
@@ -24,6 +27,8 @@
import android.animation.Animator;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -80,4 +85,23 @@
return new TransitionInfo.Change(mock(WindowContainerToken.class),
mock(SurfaceControl.class));
}
+
+ /**
+ * Creates a mock {@link TransitionInfo.Change} with
+ * {@link TransitionInfo#FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY} flag.
+ */
+ static TransitionInfo.Change createEmbeddedChange(@NonNull Rect startBounds,
+ @NonNull Rect endBounds, @NonNull Rect taskBounds) {
+ final TransitionInfo.Change change = createChange();
+ change.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+ change.setStartAbsBounds(startBounds);
+ change.setEndAbsBounds(endBounds);
+ if (taskBounds.width() == startBounds.width()
+ && taskBounds.height() == startBounds.height()
+ && taskBounds.width() == endBounds.width()
+ && taskBounds.height() == endBounds.height()) {
+ change.setFlags(FLAG_FILLS_TASK);
+ }
+ return change;
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index cf43b00..baecf6f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -17,7 +17,6 @@
package com.android.wm.shell.activityembedding;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -29,6 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.graphics.Rect;
import android.window.TransitionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -48,6 +48,10 @@
@RunWith(AndroidJUnit4.class)
public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase {
+ private static final Rect TASK_BOUNDS = new Rect(0, 0, 1000, 500);
+ private static final Rect EMBEDDED_LEFT_BOUNDS = new Rect(0, 0, 500, 500);
+ private static final Rect EMBEDDED_RIGHT_BOUNDS = new Rect(500, 0, 1000, 500);
+
@Before
public void setup() {
super.setUp();
@@ -77,13 +81,13 @@
@Test
public void testStartAnimation_containsNonActivityEmbeddingChange() {
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createChange();
- embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+ final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS,
+ EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
final TransitionInfo.Change nonEmbeddingChange = createChange();
info.addChange(embeddingChange);
info.addChange(nonEmbeddingChange);
- // No-op
+ // No-op because it contains non-embedded change.
assertFalse(mController.startAnimation(mTransition, info, mStartTransaction,
mFinishTransaction, mFinishCallback));
verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any());
@@ -93,13 +97,65 @@
}
@Test
- public void testStartAnimation_onlyActivityEmbeddingChange() {
+ public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() {
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createChange();
- embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+ final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, TASK_BOUNDS,
+ TASK_BOUNDS);
info.addChange(embeddingChange);
- // No-op
+ // No-op because it only contains embedded change that fills the Task. We will let the
+ // default handler to animate such transition.
+ assertFalse(mController.startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction, mFinishCallback));
+ verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any());
+ verifyNoMoreInteractions(mStartTransaction);
+ verifyNoMoreInteractions(mFinishTransaction);
+ verifyNoMoreInteractions(mFinishCallback);
+ }
+
+ @Test
+ public void testStartAnimation_containsActivityEmbeddingSplitChange() {
+ // Change that occupies only part of the Task.
+ final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+ final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS,
+ EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
+ info.addChange(embeddingChange);
+
+ // ActivityEmbeddingController will handle such transition.
+ assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction, mFinishCallback));
+ verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction);
+ verify(mStartTransaction).apply();
+ verifyNoMoreInteractions(mFinishTransaction);
+ }
+
+ @Test
+ public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() {
+ // Change that is entering ActivityEmbedding split.
+ final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+ final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS,
+ EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
+ info.addChange(embeddingChange);
+
+ // ActivityEmbeddingController will handle such transition.
+ assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction, mFinishCallback));
+ verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction);
+ verify(mStartTransaction).apply();
+ verifyNoMoreInteractions(mFinishTransaction);
+ }
+
+ @Test
+ public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() {
+ // Change that is exiting ActivityEmbedding split.
+ final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+ final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS,
+ TASK_BOUNDS, TASK_BOUNDS);
+ info.addChange(embeddingChange);
+
+ // ActivityEmbeddingController will handle such transition.
assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
mFinishTransaction, mFinishCallback));
verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction,
@@ -115,8 +171,8 @@
() -> mController.onAnimationFinished(mTransition));
final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
- final TransitionInfo.Change embeddingChange = createChange();
- embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+ final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS,
+ EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
info.addChange(embeddingChange);
mController.startAnimation(mTransition, info, mStartTransaction,
mFinishTransaction, mFinishCallback);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index ea9390e..9240abf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -225,7 +225,6 @@
mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME);
verify(mMainStage).reorderChild(eq(testTaskId), eq(true),
any(WindowContainerTransaction.class));
- verify(mSideStage).dismiss(any(WindowContainerTransaction.class), eq(false));
verify(mMainStage).resetBounds(any(WindowContainerTransaction.class));
}
@@ -239,7 +238,6 @@
verify(mSideStage).reorderChild(eq(testTaskId), eq(true),
any(WindowContainerTransaction.class));
verify(mSideStage).resetBounds(any(WindowContainerTransaction.class));
- verify(mMainStage).dismiss(any(WindowContainerTransaction.class), eq(false));
}
@Test
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 39c7d19..235700b 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -601,10 +601,6 @@
return base::unexpected(result.error());
}
- if (type_idx == 0x1c) {
- LOG(ERROR) << base::StringPrintf("foobar first result %s", result->package_name->c_str());
- }
-
bool overlaid = false;
if (!stop_at_first_match && !ignore_configuration && !apk_assets_[result->cookie]->IsLoader()) {
for (const auto& id_map : package_group.overlays_) {
@@ -615,7 +611,21 @@
}
if (overlay_entry.IsInlineValue()) {
// The target resource is overlaid by an inline value not represented by a resource.
- result->entry = overlay_entry.GetInlineValue();
+ ConfigDescription best_frro_config;
+ Res_value best_frro_value;
+ bool frro_found = false;
+ for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+ if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+ && config.match(*desired_config)) {
+ frro_found = true;
+ best_frro_config = config;
+ best_frro_value = value;
+ }
+ }
+ if (!frro_found) {
+ continue;
+ }
+ result->entry = best_frro_value;
result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
result->cookie = id_map.cookie;
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index efd1f6a..e122d48 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -56,6 +56,8 @@
struct Idmap_data_header {
uint32_t target_entry_count;
uint32_t target_inline_entry_count;
+ uint32_t target_inline_entry_value_count;
+ uint32_t configuration_count;
uint32_t overlay_entry_count;
uint32_t string_pool_index_offset;
@@ -68,6 +70,12 @@
struct Idmap_target_entry_inline {
uint32_t target_id;
+ uint32_t start_value_index;
+ uint32_t value_count;
+};
+
+struct Idmap_target_entry_inline_value {
+ uint32_t config_index;
Res_value value;
};
@@ -138,11 +146,15 @@
IdmapResMap::IdmapResMap(const Idmap_data_header* data_header,
const Idmap_target_entry* entries,
const Idmap_target_entry_inline* inline_entries,
+ const Idmap_target_entry_inline_value* inline_entry_values,
+ const ConfigDescription* configs,
uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table)
: data_header_(data_header),
entries_(entries),
inline_entries_(inline_entries),
+ inline_entry_values_(inline_entry_values),
+ configurations_(configs),
target_assigned_package_id_(target_assigned_package_id),
overlay_ref_table_(overlay_ref_table) { }
@@ -183,7 +195,13 @@
if (inline_entry != end_inline_entry &&
(0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) {
- return Result(inline_entry->value);
+ 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];
+ values_map[config] = value.value;
+ }
+ return Result(values_map);
}
return {};
}
@@ -237,6 +255,8 @@
const Idmap_data_header* data_header,
const Idmap_target_entry* target_entries,
const Idmap_target_entry_inline* target_inline_entries,
+ const Idmap_target_entry_inline_value* inline_entry_values,
+ const ConfigDescription* configs,
const Idmap_overlay_entry* overlay_entries,
std::unique_ptr<ResStringPool>&& string_pool,
std::string_view overlay_apk_path,
@@ -245,6 +265,8 @@
data_header_(data_header),
target_entries_(target_entries),
target_inline_entries_(target_inline_entries),
+ inline_entry_values_(inline_entry_values),
+ configurations_(configs),
overlay_entries_(overlay_entries),
string_pool_(std::move(string_pool)),
idmap_path_(std::move(idmap_path)),
@@ -303,6 +325,21 @@
if (target_inline_entries == nullptr) {
return {};
}
+
+ auto target_inline_entry_values = ReadType<Idmap_target_entry_inline_value>(
+ &data_ptr, &data_size, "target inline values",
+ dtohl(data_header->target_inline_entry_value_count));
+ if (target_inline_entry_values == nullptr) {
+ return {};
+ }
+
+ auto configurations = ReadType<ConfigDescription>(
+ &data_ptr, &data_size, "configurations",
+ dtohl(data_header->configuration_count));
+ if (configurations == nullptr) {
+ return {};
+ }
+
auto overlay_entries = ReadType<Idmap_overlay_entry>(&data_ptr, &data_size, "target inline",
dtohl(data_header->overlay_entry_count));
if (overlay_entries == nullptr) {
@@ -329,8 +366,8 @@
// Can't use make_unique because LoadedIdmap constructor is private.
return std::unique_ptr<LoadedIdmap>(
new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries,
- target_inline_entries, overlay_entries, std::move(idmap_string_pool),
- *target_path, *overlay_path));
+ target_inline_entries, target_inline_entry_values, configurations,
+ overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path));
}
bool LoadedIdmap::IsUpToDate() const {
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 6804472..a1cbbbf 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -23,6 +23,7 @@
#include <variant>
#include "android-base/macros.h"
+#include "androidfw/ConfigDescription.h"
#include "androidfw/StringPiece.h"
#include "androidfw/ResourceTypes.h"
#include "utils/ByteOrder.h"
@@ -35,6 +36,7 @@
struct Idmap_data_header;
struct Idmap_target_entry;
struct Idmap_target_entry_inline;
+struct Idmap_target_entry_inline_value;
struct Idmap_overlay_entry;
// A string pool for overlay apk assets. The string pool holds the strings of the overlay resources
@@ -91,7 +93,8 @@
public:
Result() = default;
explicit Result(uint32_t value) : data_(value) {};
- explicit Result(const Res_value& value) : data_(value) { };
+ explicit Result(const std::map<ConfigDescription, Res_value> &value)
+ : data_(value) { };
// Returns `true` if the resource is overlaid.
explicit operator bool() const {
@@ -107,15 +110,16 @@
}
bool IsInlineValue() const {
- return std::get_if<Res_value>(&data_) != nullptr;
+ return std::get_if<2>(&data_) != nullptr;
}
- const Res_value& GetInlineValue() const {
- return std::get<Res_value>(data_);
+ const std::map<ConfigDescription, Res_value>& GetInlineValue() const {
+ return std::get<2>(data_);
}
private:
- std::variant<std::monostate, uint32_t, Res_value> data_;
+ std::variant<std::monostate, uint32_t,
+ std::map<ConfigDescription, Res_value> > data_;
};
// Looks up the value that overlays the target resource id.
@@ -129,12 +133,16 @@
explicit IdmapResMap(const Idmap_data_header* data_header,
const Idmap_target_entry* entries,
const Idmap_target_entry_inline* inline_entries,
+ const Idmap_target_entry_inline_value* inline_entry_values,
+ 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_;
+ const Idmap_target_entry_inline_value* inline_entry_values_;
+ const ConfigDescription* configurations_;
const uint8_t target_assigned_package_id_;
const OverlayDynamicRefTable* overlay_ref_table_;
@@ -170,8 +178,8 @@
// Returns a mapping from target resource ids to overlay values.
const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
const OverlayDynamicRefTable* overlay_ref_table) const {
- return IdmapResMap(data_header_, target_entries_, target_inline_entries_,
- target_assigned_package_id, overlay_ref_table);
+ return IdmapResMap(data_header_, target_entries_, target_inline_entries_, inline_entry_values_,
+ configurations_, target_assigned_package_id, overlay_ref_table);
}
// Returns a dynamic reference table for a loaded overlay package.
@@ -191,6 +199,8 @@
const Idmap_data_header* data_header_;
const Idmap_target_entry* target_entries_;
const Idmap_target_entry_inline* target_inline_entries_;
+ const Idmap_target_entry_inline_value* inline_entry_values_;
+ const ConfigDescription* configurations_;
const Idmap_overlay_entry* overlay_entries_;
const std::unique_ptr<ResStringPool> string_pool_;
@@ -207,6 +217,8 @@
const Idmap_data_header* data_header,
const Idmap_target_entry* target_entries,
const Idmap_target_entry_inline* target_inline_entries,
+ const Idmap_target_entry_inline_value* inline_entry_values_,
+ const ConfigDescription* configs,
const Idmap_overlay_entry* overlay_entries,
std::unique_ptr<ResStringPool>&& string_pool,
std::string_view overlay_apk_path,
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 8c614bc..9309091 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -45,7 +45,7 @@
namespace android {
constexpr const uint32_t kIdmapMagic = 0x504D4449u;
-constexpr const uint32_t kIdmapCurrentVersion = 0x00000008u;
+constexpr const uint32_t kIdmapCurrentVersion = 0x00000009u;
// This must never change.
constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian)
@@ -1098,7 +1098,7 @@
SDKVERSION_ANY = 0
};
- enum {
+ enum {
MINORVERSION_ANY = 0
};
diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap
index 88eadcc..8e847e8 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 397975d..473afbd 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -18,6 +18,7 @@
#include "CanvasProperty.h"
#include "NinePatchUtils.h"
+#include "SkBlendMode.h"
#include "VectorDrawable.h"
#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
@@ -251,10 +252,11 @@
return (rec && rec->saveCount == currentSaveCount) ? rec : nullptr;
}
-void SkiaCanvas::punchHole(const SkRRect& rect) {
+void SkiaCanvas::punchHole(const SkRRect& rect, float alpha) {
SkPaint paint = SkPaint();
- paint.setColor(0);
- paint.setBlendMode(SkBlendMode::kClear);
+ paint.setColor(SkColors::kBlack);
+ paint.setAlphaf(alpha);
+ paint.setBlendMode(SkBlendMode::kDstOut);
mCanvas->drawRRect(rect, paint);
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index c6313f6..51007c5 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -65,7 +65,7 @@
LOG_ALWAYS_FATAL("SkiaCanvas does not support enableZ");
}
- virtual void punchHole(const SkRRect& rect) override;
+ virtual void punchHole(const SkRRect& rect, float alpha) override;
virtual void setBitmap(const SkBitmap& bitmap) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 7378351..82d23b5 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -152,7 +152,7 @@
LOG_ALWAYS_FATAL("Not supported");
}
- virtual void punchHole(const SkRRect& rect) = 0;
+ virtual void punchHole(const SkRRect& rect, float alpha) = 0;
// ----------------------------------------------------------------------------
// Canvas state operations
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index fb7d5f7..0513447 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -713,9 +713,10 @@
}
static void punchHole(JNIEnv* env, jobject, jlong canvasPtr, jfloat left, jfloat top, jfloat right,
- jfloat bottom, jfloat rx, jfloat ry) {
+ jfloat bottom, jfloat rx, jfloat ry, jfloat alpha) {
auto canvas = reinterpret_cast<Canvas*>(canvasPtr);
- canvas->punchHole(SkRRect::MakeRectXY(SkRect::MakeLTRB(left, top, right, bottom), rx, ry));
+ canvas->punchHole(SkRRect::MakeRectXY(SkRect::MakeLTRB(left, top, right, bottom), rx, ry),
+ alpha);
}
}; // namespace CanvasJNI
@@ -790,7 +791,7 @@
{"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString},
{"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars},
{"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString},
- {"nPunchHole", "(JFFFFFF)V", (void*) CanvasJNI::punchHole}
+ {"nPunchHole", "(JFFFFFFF)V", (void*) CanvasJNI::punchHole}
};
int register_android_graphics_Canvas(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 3bf2b2e..f2282e66 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -201,6 +201,7 @@
paint.setAlpha((uint8_t)paint.getAlpha() * mAlpha);
return true;
}
+
void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
// We unroll the drawable using "this" canvas, so that draw calls contained inside will
// get their alpha applied. The default SkPaintFilterCanvas::onDrawDrawable does not unroll.
@@ -292,7 +293,7 @@
// with the same canvas transformation + clip into the target
// canvas then draw the layer on top
if (renderNode->hasHolePunches()) {
- TransformCanvas transformCanvas(canvas, SkBlendMode::kClear);
+ TransformCanvas transformCanvas(canvas, SkBlendMode::kDstOut);
displayList->draw(&transformCanvas);
}
canvas->drawImageRect(snapshotImage, SkRect::Make(srcBounds),
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 5c6117d..1f87865 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -69,20 +69,22 @@
mDisplayList->setHasHolePunches(false);
}
-void SkiaRecordingCanvas::punchHole(const SkRRect& rect) {
- // Add the marker annotation to allow HWUI to determine where the current
- // clip/transformation should be applied
+void SkiaRecordingCanvas::punchHole(const SkRRect& rect, float alpha) {
+ // Add the marker annotation to allow HWUI to determine the current
+ // clip/transformation and alpha should be applied
SkVector vector = rect.getSimpleRadii();
- float data[2];
+ float data[3];
data[0] = vector.x();
data[1] = vector.y();
+ data[2] = alpha;
mRecorder.drawAnnotation(rect.rect(), HOLE_PUNCH_ANNOTATION.c_str(),
- SkData::MakeWithCopy(data, 2 * sizeof(float)));
+ SkData::MakeWithCopy(data, sizeof(data)));
// Clear the current rect within the layer itself
SkPaint paint = SkPaint();
- paint.setColor(0);
- paint.setBlendMode(SkBlendMode::kClear);
+ paint.setColor(SkColors::kBlack);
+ paint.setAlphaf(alpha);
+ paint.setBlendMode(SkBlendMode::kDstOut);
mRecorder.drawRRect(rect, paint);
mDisplayList->setHasHolePunches(true);
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 89e3a2c..7844e2c 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -50,7 +50,7 @@
initDisplayList(renderNode, width, height);
}
- virtual void punchHole(const SkRRect& rect) override;
+ virtual void punchHole(const SkRRect& rect, float alpha) override;
virtual void finishRecording(uirenderer::RenderNode* destination) override;
std::unique_ptr<SkiaDisplayList> finishRecording();
diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp
index 33160d0..c320df0 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.cpp
+++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp
@@ -29,13 +29,15 @@
void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkData* value) {
if (HOLE_PUNCH_ANNOTATION == key) {
auto* rectParams = reinterpret_cast<const float*>(value->data());
- float radiusX = rectParams[0];
- float radiusY = rectParams[1];
+ const float radiusX = rectParams[0];
+ const float radiusY = rectParams[1];
+ const float alpha = rectParams[2];
SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY);
SkPaint paint;
paint.setColor(SkColors::kBlack);
paint.setBlendMode(mHolePunchBlendMode);
+ paint.setAlphaf(alpha);
mWrappedCanvas->drawRRect(roundRect, paint);
}
}
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index 59230a7..7d3ca96 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -138,7 +138,7 @@
roundRectPaint.setColor(Color::White);
if (addHolePunch) {
// Punch a hole but then cover it up, we don't want to actually see it
- canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight)));
+ canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight)), 1.f);
}
canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
@@ -235,4 +235,4 @@
StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::UniformScale; }
bool haveHolePunch() override { return true; }
bool forceLayer() override { return true; }
-};
\ No newline at end of file
+};
diff --git a/media/java/android/media/projection/MediaProjectionGlobal.java b/media/java/android/media/projection/MediaProjectionGlobal.java
new file mode 100644
index 0000000..4374a05
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionGlobal.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.IDisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.display.VirtualDisplayConfig;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.view.Surface;
+
+/**
+ * This is a helper for MediaProjection when requests are made from outside an application. This
+ * should only be used by processes running as shell as a way to capture recordings without being
+ * an application. The requests will fail if coming from any process that's not Shell.
+ * @hide
+ */
+@SystemApi
+public class MediaProjectionGlobal {
+ private static final Object sLock = new Object();
+ private static MediaProjectionGlobal sInstance;
+
+ /**
+ * @return The instance of {@link MediaProjectionGlobal}
+ */
+ @NonNull
+ public static MediaProjectionGlobal getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ final IBinder displayBinder = ServiceManager.getService(Context.DISPLAY_SERVICE);
+ final IBinder packageBinder = ServiceManager.getService("package");
+ if (displayBinder != null && packageBinder != null) {
+ sInstance = new MediaProjectionGlobal(
+ IDisplayManager.Stub.asInterface(displayBinder),
+ IPackageManager.Stub.asInterface(packageBinder));
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ private final IDisplayManager mDm;
+ private final IPackageManager mPackageManager;
+
+ private MediaProjectionGlobal(IDisplayManager dm, IPackageManager packageManager) {
+ mDm = dm;
+ mPackageManager = packageManager;
+ }
+
+ /**
+ * Creates a VirtualDisplay that will mirror the content of displayIdToMirror
+ * @param name The name for the virtual display
+ * @param width The initial width for the virtual display
+ * @param height The initial height for the virtual display
+ * @param displayIdToMirror The displayId that will be mirrored into the virtual display.
+ * @return VirtualDisplay that can be used to update properties.
+ */
+ @Nullable
+ public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height,
+ int displayIdToMirror, @Nullable Surface surface) {
+
+ // Density doesn't matter since this virtual display is only used for mirroring.
+ VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
+ height, 1 /* densityDpi */)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setDisplayIdToMirror(displayIdToMirror);
+ if (surface != null) {
+ builder.setSurface(surface);
+ }
+ VirtualDisplayConfig virtualDisplayConfig = builder.build();
+
+ String[] packages;
+ try {
+ packages = mPackageManager.getPackagesForUid(Process.myUid());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
+ // Just use the first one since it just needs to match the package when looking it up by
+ // calling UID in system server.
+ // The call may come from a rooted device, in that case the requesting uid will be root so
+ // it will not have any package name
+ String packageName = packages == null ? null : packages[0];
+ DisplayManagerGlobal.VirtualDisplayCallback
+ callbackWrapper = new DisplayManagerGlobal.VirtualDisplayCallback(null, null);
+ int displayId;
+ try {
+ displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper, null,
+ packageName);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ return DisplayManagerGlobal.getInstance().createVirtualDisplayWrapper(virtualDisplayConfig,
+ null, callbackWrapper, displayId);
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index e34fedd..e583138 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -35,5 +35,13 @@
android:name=".GalleryDebugActivity"
android:exported="true">
</activity>
+
+ <provider
+ android:name=".GalleryEntryProvider"
+ android:authorities="com.android.spa.gallery.provider"
+ android:enabled="true"
+ android:exported="false">
+ </provider>
+
</application>
</manifest>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
new file mode 100644
index 0000000..3210eb5
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery
+
+import com.android.settingslib.spa.framework.EntryProvider
+
+class GalleryEntryProvider : EntryProvider(
+ SpaEnvironment.EntryRepository,
+ "com.android.settingslib.spa.gallery/.MainActivity",
+)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
index bd5aaa7..8aef2c6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.framework
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
@@ -32,6 +33,7 @@
import androidx.navigation.navArgument
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
+import com.android.settingslib.spa.framework.EntryProvider.Companion.PAGE_INFO_QUERY
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryRepository
import com.android.settingslib.spa.framework.common.SettingsPage
@@ -55,21 +57,12 @@
open class DebugActivity(
private val entryRepository: SettingsEntryRepository,
private val browseActivityClass: Class<*>,
+ private val entryProviderAuthorities: String? = null,
) : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_SpaLib_DayNight)
super.onCreate(savedInstanceState)
-
- val packageName = browseActivityClass.packageName
- val className = browseActivityClass.toString().removePrefix("class $packageName")
- for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
- if (pageWithEntry.page.hasRuntimeParam()) continue
- val route = pageWithEntry.page.buildRoute()
- Log.d(
- "DEBUG ACTIVITY",
- "adb shell am start -n $packageName/$className -e $KEY_DESTINATION $route"
- )
- }
+ displayDebugMessage()
setContent {
SettingsTheme {
@@ -78,6 +71,30 @@
}
}
+ private fun displayDebugMessage() {
+ if (entryProviderAuthorities == null) return
+
+ try {
+ contentResolver.query(
+ Uri.parse("content://$entryProviderAuthorities/${PAGE_INFO_QUERY.queryPath}"),
+ null, null, null
+ ).use { cursor ->
+ while (cursor != null && cursor.moveToNext()) {
+ val route = cursor.getString(PAGE_INFO_QUERY.getIndex(ColumnName.PAGE_ROUTE))
+ val entryCount = cursor.getInt(PAGE_INFO_QUERY.getIndex(ColumnName.ENTRY_COUNT))
+ val hasRuntimeParam =
+ cursor.getInt(PAGE_INFO_QUERY.getIndex(ColumnName.HAS_RUNTIME_PARAM)) == 1
+ Log.d(
+ "DEBUG ACTIVITY", "Page Info: $route ($entryCount) " +
+ (if (hasRuntimeParam) "with" else "no") + "-runtime-params"
+ )
+ }
+ }
+ } catch (e: Exception) {
+ Log.e("DEBUG ACTIVITY", "Provider querying exception:", e)
+ }
+ }
+
@Composable
private fun MainContent() {
val navController = rememberNavController()
@@ -122,7 +139,7 @@
for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
Preference(object : PreferenceModel {
override val title =
- "${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})"
+ "${pageWithEntry.page.name} (${pageWithEntry.entries.size})"
override val summary = pageWithEntry.page.formatArguments().toState()
override val onClick =
navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}")
@@ -142,7 +159,7 @@
fun OnePage(arguments: Bundle?) {
val id = arguments!!.getInt(PARAM_NAME_PAGE_ID)
val pageWithEntry = entryRepository.getPageWithEntry(id)!!
- RegularScaffold(title = "Page ${pageWithEntry.page.displayName}") {
+ RegularScaffold(title = "Page ${pageWithEntry.page.name}") {
Text(text = pageWithEntry.page.formatArguments())
Text(text = "Entry size: ${pageWithEntry.entries.size}")
Preference(model = object : PreferenceModel {
@@ -158,7 +175,7 @@
fun OneEntry(arguments: Bundle?) {
val id = arguments!!.getInt(PARAM_NAME_ENTRY_ID)
val entry = entryRepository.getEntry(id)!!
- RegularScaffold(title = "Entry ${entry.displayName}") {
+ RegularScaffold(title = "Entry ${entry.displayName()}") {
Preference(model = object : PreferenceModel {
override val title = "open entry"
override val enabled = (!entry.hasRuntimeParam()).toState()
@@ -172,9 +189,9 @@
private fun EntryList(entries: Collection<SettingsEntry>) {
for (entry in entries) {
Preference(object : PreferenceModel {
- override val title = entry.displayName
+ override val title = entry.displayName()
override val summary =
- "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
+ "${entry.fromPage?.name} -> ${entry.toPage?.name}".toState()
override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
})
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
new file mode 100644
index 0000000..90ce182
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.UriMatcher
+import android.content.pm.ProviderInfo
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.util.Log
+import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+
+/**
+ * Enum to define all column names in provider.
+ */
+enum class ColumnName(val id: String) {
+ PAGE_NAME("pageName"),
+ PAGE_ROUTE("pageRoute"),
+ ENTRY_COUNT("entryCount"),
+ HAS_RUNTIME_PARAM("hasRuntimeParam"),
+ PAGE_START_ADB("pageStartAdb"),
+}
+
+data class QueryDefinition(
+ val queryPath: String,
+ val queryMatchCode: Int,
+ val columnNames: List<ColumnName>,
+) {
+ fun getColumns(): Array<String> {
+ return columnNames.map { it.id }.toTypedArray()
+ }
+
+ fun getIndex(name: ColumnName): Int {
+ return columnNames.indexOf(name)
+ }
+}
+
+open class EntryProvider(
+ private val entryRepository: SettingsEntryRepository,
+ private val browseActivityComponentName: String? = null,
+) : ContentProvider() {
+
+ private var mMatcher: UriMatcher? = null
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
+ TODO("Implement this to handle requests to delete one or more rows")
+ }
+
+ override fun getType(uri: Uri): String? {
+ TODO(
+ "Implement this to handle requests for the MIME type of the data" +
+ "at the given URI"
+ )
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ TODO("Implement this to handle requests to insert a new row.")
+ }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<String>?
+ ): Int {
+ TODO("Implement this to handle requests to update one or more rows.")
+ }
+
+ override fun onCreate(): Boolean {
+ return true
+ }
+
+ override fun attachInfo(context: Context?, info: ProviderInfo?) {
+ mMatcher = UriMatcher(UriMatcher.NO_MATCH)
+ if (info != null) {
+ mMatcher!!.addURI(
+ info.authority,
+ PAGE_START_COMMAND_QUERY.queryPath,
+ PAGE_START_COMMAND_QUERY.queryMatchCode
+ )
+ mMatcher!!.addURI(
+ info.authority,
+ PAGE_INFO_QUERY.queryPath,
+ PAGE_INFO_QUERY.queryMatchCode
+ )
+ }
+ super.attachInfo(context, info)
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array<String>?,
+ selection: String?,
+ selectionArgs: Array<String>?,
+ sortOrder: String?
+ ): Cursor? {
+ return try {
+ when (mMatcher!!.match(uri)) {
+ PAGE_START_COMMAND_QUERY.queryMatchCode -> queryPageStartCommand()
+ PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
+ else -> throw UnsupportedOperationException("Unknown Uri $uri")
+ }
+ } catch (e: UnsupportedOperationException) {
+ throw e
+ } catch (e: Exception) {
+ Log.e("EntryProvider", "Provider querying exception:", e)
+ null
+ }
+ }
+
+ private fun queryPageStartCommand(): Cursor {
+ val componentName = browseActivityComponentName ?: "[component-name]"
+ val cursor = MatrixCursor(PAGE_START_COMMAND_QUERY.getColumns())
+ for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
+ val page = pageWithEntry.page
+ if (!page.hasRuntimeParam()) {
+ cursor.newRow().add(
+ ColumnName.PAGE_START_ADB.id,
+ "adb shell am start -n $componentName" +
+ " -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}"
+ )
+ }
+ }
+ return cursor
+ }
+
+ private fun queryPageInfo(): Cursor {
+ val cursor = MatrixCursor(PAGE_INFO_QUERY.getColumns())
+ for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
+ val page = pageWithEntry.page
+ cursor.newRow().add(ColumnName.PAGE_NAME.id, page.name)
+ .add(ColumnName.PAGE_ROUTE.id, page.buildRoute())
+ .add(ColumnName.ENTRY_COUNT.id, pageWithEntry.entries.size)
+ .add(ColumnName.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
+ }
+ return cursor
+ }
+
+ companion object {
+ val PAGE_START_COMMAND_QUERY = QueryDefinition(
+ "page_start", 1,
+ listOf(ColumnName.PAGE_START_ADB)
+ )
+
+ val PAGE_INFO_QUERY = QueryDefinition(
+ "page_info", 2,
+ listOf(
+ ColumnName.PAGE_NAME,
+ ColumnName.PAGE_ROUTE,
+ ColumnName.ENTRY_COUNT,
+ ColumnName.HAS_RUNTIME_PARAM,
+ )
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index b0a1cbe..445c4eb 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -47,10 +47,6 @@
// The name of the page, which is used to compute the unique id, and need to be stable.
val name: String,
- // The display name of the page, for better readability.
- // By default, it is the same as name.
- val displayName: String,
-
// Defined parameters of this page.
val parameter: List<NamedNavArgument> = emptyList(),
@@ -74,7 +70,7 @@
}
fun formatAll(): String {
- return "$displayName ${formatArguments()}"
+ return "$name ${formatArguments()}"
}
fun buildRoute(highlightEntryName: String? = null): String {
@@ -104,10 +100,6 @@
// The owner page of this entry.
val owner: SettingsPage,
- // The display name of the entry, for better readability.
- // By default, it is $owner:$name
- val displayName: String,
-
// Defines linking of Settings entries
val fromPage: SettingsPage? = null,
val toPage: SettingsPage? = null,
@@ -146,7 +138,7 @@
val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
) {
fun formatAll(): String {
- val content = listOf<String>(
+ val content = listOf(
"owner = ${owner.formatAll()}",
"linkFrom = ${fromPage?.formatAll()}",
"linkTo = ${toPage?.formatAll()}",
@@ -154,6 +146,11 @@
return content.joinToString("\n")
}
+ // The display name of the entry, for better readability.
+ fun displayName(): String {
+ return "${owner.name}:$name"
+ }
+
private fun getDisplayPage(): SettingsPage {
// Display the entry on its from-page, or on its owner page if the from-page is unset.
return fromPage ?: owner
@@ -185,7 +182,6 @@
private val name: String,
private val parameter: List<NamedNavArgument> = emptyList()
) {
- private var displayName: String? = null
private var arguments: Bundle? = null
fun build(): SettingsPage {
@@ -193,7 +189,6 @@
return SettingsPage(
id = "$name:${normArguments?.toString()}".toUniqueId(),
name = name,
- displayName = displayName ?: name,
parameter = parameter,
arguments = arguments,
)
@@ -209,7 +204,6 @@
* The helper to build a Settings Entry instance.
*/
class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) {
- private var displayName: String? = null
private var fromPage: SettingsPage? = null
private var toPage: SettingsPage? = null
private var isAllowSearch: Boolean? = null
@@ -220,7 +214,6 @@
fun build(): SettingsEntry {
return SettingsEntry(
id = "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toUniqueId(),
- displayName = displayName ?: "${owner.displayName}:$name",
name = name,
owner = owner,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index aaf8107..d7d7750 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -25,9 +25,12 @@
}
fun List<NamedNavArgument>.navLink(arguments: Bundle? = null): String {
- if (arguments == null) return ""
val argsArray = mutableListOf<String>()
for (navArg in this) {
+ if (arguments == null || !arguments.containsKey(navArg.name)) {
+ argsArray.add("[rt]")
+ continue
+ }
when (navArg.argument.type) {
NavType.StringType -> {
argsArray.add(arguments.getString(navArg.name, ""))
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
new file mode 100644
index 0000000..0615807
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.enterprise
+
+import android.app.admin.DevicePolicyResources.Strings.Settings
+import android.content.Context
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.lifecycle.LiveData
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.settingslib.spaprivileged.R
+
+data class Restrictions(
+ val userId: Int,
+ val keys: List<String>,
+)
+
+sealed class RestrictedMode
+
+object NoRestricted : RestrictedMode()
+
+object BaseUserRestricted : RestrictedMode()
+
+data class BlockedByAdmin(
+ val enterpriseRepository: EnterpriseRepository,
+ val enforcedAdmin: EnforcedAdmin,
+) : RestrictedMode() {
+ fun getSummary(checked: Boolean?): String = when (checked) {
+ true -> enterpriseRepository.getEnterpriseString(
+ Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.enabled_by_admin
+ )
+ false -> enterpriseRepository.getEnterpriseString(
+ Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.disabled_by_admin
+ )
+ else -> ""
+ }
+}
+
+class RestrictionsProvider(
+ private val context: Context,
+ private val restrictions: Restrictions,
+) {
+ private val userManager by lazy { UserManager.get(context) }
+ private val enterpriseRepository by lazy { EnterpriseRepository(context) }
+
+ val restrictedMode = object : LiveData<RestrictedMode>() {
+ override fun onActive() {
+ postValue(getRestrictedMode())
+ }
+
+ override fun onInactive() {
+ }
+ }
+
+ private fun getRestrictedMode(): RestrictedMode {
+ for (key in restrictions.keys) {
+ if (userManager.hasBaseUserRestriction(key, UserHandle.of(restrictions.userId))) {
+ return BaseUserRestricted
+ }
+ }
+ for (key in restrictions.keys) {
+ RestrictedLockUtilsInternal
+ .checkIfRestrictionEnforced(context, key, restrictions.userId)
+ ?.let {
+ return BlockedByAdmin(
+ enterpriseRepository = enterpriseRepository,
+ enforcedAdmin = it,
+ )
+ }
+ }
+ return NoRestricted
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index c031fe8..ae4f8bd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -34,11 +34,12 @@
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.model.app.PackageManagers
import com.android.settingslib.spaprivileged.model.app.toRoute
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
import kotlinx.coroutines.Dispatchers
private const val ENTRY_NAME = "AllowControl"
@@ -105,7 +106,7 @@
LaunchedEffect(model, Dispatchers.Default) {
model.initState()
}
- SwitchPreference(model)
+ RestrictedSwitchPreference(model, Restrictions(userId, listModel.switchRestrictionKeys))
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 4c748b8..74a50de 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -37,6 +37,8 @@
val pageTitleResId: Int
val switchTitleResId: Int
val footerResId: Int
+ val switchRestrictionKeys: List<String>
+ get() = emptyList()
/**
* Loads the extra info for the App List, and generates the [AppRecord] List.
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 65cc4f9..475e930 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -22,6 +22,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -37,6 +38,10 @@
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.userId
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
import kotlinx.coroutines.flow.Flow
private const val ENTRY_NAME = "AppList"
@@ -127,15 +132,32 @@
@Composable
override fun getSummary(option: Int, record: T): State<String> {
+ val restrictionsProvider = remember {
+ val restrictions = Restrictions(
+ userId = record.app.userId,
+ keys = listModel.switchRestrictionKeys,
+ )
+ RestrictionsProvider(context, restrictions)
+ }
+ val restrictedMode = restrictionsProvider.restrictedMode.observeAsState()
val allowed = listModel.isAllowed(record)
return remember {
derivedStateOf {
- when (allowed.value) {
- true -> context.getString(R.string.app_permission_summary_allowed)
- false -> context.getString(R.string.app_permission_summary_not_allowed)
- else -> ""
- }
+ RestrictedSwitchPreference.getSummary(
+ context = context,
+ restrictedMode = restrictedMode.value,
+ noRestrictedSummary = getNoRestrictedSummary(allowed),
+ checked = allowed,
+ ).value
}
}
}
+
+ private fun getNoRestrictedSummary(allowed: State<Boolean?>) = derivedStateOf {
+ when (allowed.value) {
+ true -> context.getString(R.string.app_permission_summary_allowed)
+ false -> context.getString(R.string.app_permission_summary_not_allowed)
+ else -> context.getString(R.string.summary_placeholder)
+ }
+ }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
new file mode 100644
index 0000000..31fd3ad
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import android.content.Context
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+
+@Composable
+fun RestrictedSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) {
+ if (restrictions.keys.isEmpty()) {
+ SwitchPreference(model)
+ return
+ }
+ val context = LocalContext.current
+ val restrictionsProvider = remember { RestrictionsProvider(context, restrictions) }
+ val restrictedMode = restrictionsProvider.restrictedMode.observeAsState().value ?: return
+ val restrictedSwitchModel = remember(restrictedMode) {
+ RestrictedSwitchPreferenceModel(context, model, restrictedMode)
+ }
+ Box(remember { restrictedSwitchModel.getModifier() }) {
+ SwitchPreference(restrictedSwitchModel)
+ }
+}
+
+object RestrictedSwitchPreference {
+ fun getSummary(
+ context: Context,
+ restrictedMode: RestrictedMode?,
+ noRestrictedSummary: State<String>,
+ checked: State<Boolean?>,
+ ): State<String> = when (restrictedMode) {
+ is NoRestricted -> noRestrictedSummary
+ is BaseUserRestricted -> stateOf(context.getString(R.string.disabled))
+ is BlockedByAdmin -> derivedStateOf { restrictedMode.getSummary(checked.value) }
+ null -> stateOf(context.getString(R.string.summary_placeholder))
+ }
+}
+
+private class RestrictedSwitchPreferenceModel(
+ private val context: Context,
+ model: SwitchPreferenceModel,
+ private val restrictedMode: RestrictedMode,
+) : SwitchPreferenceModel {
+ override val title = model.title
+
+ override val summary = RestrictedSwitchPreference.getSummary(
+ context = context,
+ restrictedMode = restrictedMode,
+ noRestrictedSummary = model.summary,
+ checked = model.checked,
+ )
+
+ override val checked = when (restrictedMode) {
+ is NoRestricted -> model.checked
+ is BaseUserRestricted -> stateOf(false)
+ is BlockedByAdmin -> model.checked
+ }
+
+ override val changeable = when (restrictedMode) {
+ is NoRestricted -> model.changeable
+ is BaseUserRestricted -> stateOf(false)
+ is BlockedByAdmin -> stateOf(false)
+ }
+
+ override val onCheckedChange = when (restrictedMode) {
+ is NoRestricted -> model.onCheckedChange
+ is BaseUserRestricted -> null
+ is BlockedByAdmin -> null
+ }
+
+ fun getModifier(): Modifier = when (restrictedMode) {
+ is BlockedByAdmin -> Modifier.clickable(role = Role.Switch) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+ context, restrictedMode.enforcedAdmin
+ )
+ }
+ else -> Modifier
+ }
+}
diff --git a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml b/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml
deleted file mode 100644
index c8a80ac..0000000
--- a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_background.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:alpha="0.24" android:color="?android:attr/colorBackground" />
-</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml b/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml
deleted file mode 100644
index 8dcfdbb..0000000
--- a/packages/SettingsLib/res/color/dark_mode_icon_color_dual_tone_fill.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:alpha="0.47" android:color="?android:attr/colorBackground" />
-</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml b/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml
deleted file mode 100644
index 34de548..0000000
--- a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_background.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:alpha="0.3" android:color="?android:attr/colorForeground" />
-</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml b/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml
deleted file mode 100644
index 15944c3..0000000
--- a/packages/SettingsLib/res/color/light_mode_icon_color_dual_tone_fill.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="?android:attr/colorForeground" />
-</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_5g_uc_mobiledata.xml b/packages/SettingsLib/res/drawable/ic_5g_uc_mobiledata.xml
deleted file mode 100644
index 93fcad2..0000000
--- a/packages/SettingsLib/res/drawable/ic_5g_uc_mobiledata.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="27dp"
- android:height="16dp"
- android:viewportWidth="27.0"
- android:viewportHeight="16.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5.3,13.22c-0.57,0 -1.1,-0.11 -1.58,-0.34c-0.48,-0.22 -0.87,-0.55 -1.18,-0.98C2.24,11.47 2.06,10.93 2,10.3l1.48,-0.2c0.07,0.5 0.25,0.92 0.56,1.25c0.32,0.32 0.74,0.48 1.26,0.48c0.57,0 1.02,-0.18 1.34,-0.55c0.33,-0.37 0.49,-0.87 0.49,-1.48c0,-0.61 -0.16,-1.09 -0.49,-1.46C6.32,7.96 5.88,7.78 5.32,7.78C5,7.78 4.7,7.85 4.42,8C4.15,8.14 3.93,8.33 3.76,8.56L2.28,7.92l0.6,-4.94h5.26v1.36H4.1L3.72,7.02l0.08,0.03C4,6.87 4.25,6.73 4.55,6.62c0.3,-0.12 0.63,-0.18 1.01,-0.18c0.6,0 1.13,0.14 1.6,0.41C7.62,7.11 7.98,7.5 8.24,8c0.27,0.5 0.41,1.1 0.41,1.79c0,0.66 -0.14,1.26 -0.43,1.78c-0.28,0.51 -0.67,0.92 -1.18,1.22C6.55,13.08 5.97,13.22 5.3,13.22z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.51,13.22c-0.88,0 -1.66,-0.21 -2.34,-0.64c-0.67,-0.44 -1.2,-1.05 -1.6,-1.83C10.19,9.95 10,9.03 10,7.99c0,-1.05 0.21,-1.96 0.62,-2.74c0.41,-0.79 0.97,-1.4 1.67,-1.83c0.7,-0.44 1.5,-0.66 2.39,-0.66c1.09,0 2.01,0.25 2.74,0.76c0.75,0.5 1.22,1.21 1.41,2.11l-1.47,0.39c-0.17,-0.56 -0.48,-1 -0.94,-1.32c-0.45,-0.33 -1.03,-0.49 -1.75,-0.49c-0.57,0 -1.09,0.14 -1.57,0.43c-0.48,0.29 -0.85,0.71 -1.13,1.27s-0.42,1.25 -0.42,2.07c0,0.81 0.14,1.5 0.41,2.07c0.28,0.56 0.65,0.98 1.11,1.27c0.47,0.29 0.98,0.43 1.54,0.43c0.57,0 1.06,-0.11 1.47,-0.34c0.42,-0.23 0.75,-0.55 0.99,-0.94c0.25,-0.4 0.41,-0.85 0.46,-1.36h-2.88V7.79h4.37c0,0.87 0,1.74 0,2.6c0,0.87 0,1.74 0,2.6H17.6v-1.4h-0.08c-0.28,0.49 -0.67,0.88 -1.18,1.18C15.85,13.07 15.24,13.22 14.51,13.22z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M23,7.39c-0.53,0 -0.94,-0.16 -1.25,-0.47C21.45,6.6 21.3,6.16 21.3,5.6V3h0.8v2.66c0,0.3 0.08,0.54 0.23,0.71C22.48,6.54 22.7,6.62 23,6.62c0.3,0 0.52,-0.08 0.67,-0.25c0.15,-0.17 0.23,-0.41 0.23,-0.71V3h0.8v2.6c0,0.36 -0.07,0.67 -0.2,0.94s-0.33,0.48 -0.58,0.62C23.65,7.32 23.35,7.39 23,7.39z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M22.99,13.1c-0.39,0 -0.73,-0.09 -1.03,-0.28c-0.3,-0.19 -0.53,-0.45 -0.7,-0.79C21.08,11.7 21,11.3 21,10.85c0,-0.46 0.08,-0.85 0.25,-1.19c0.17,-0.34 0.41,-0.6 0.71,-0.78c0.3,-0.18 0.65,-0.28 1.04,-0.28c0.31,0 0.59,0.05 0.86,0.16c0.26,0.11 0.48,0.27 0.65,0.48c0.18,0.21 0.28,0.48 0.32,0.8l-0.83,0.14c-0.06,-0.26 -0.17,-0.46 -0.35,-0.6C23.49,9.44 23.27,9.37 23,9.37c-0.22,0 -0.42,0.06 -0.61,0.17c-0.18,0.11 -0.32,0.28 -0.43,0.5c-0.1,0.22 -0.16,0.49 -0.16,0.81c0,0.32 0.05,0.58 0.16,0.8s0.25,0.39 0.43,0.5c0.18,0.11 0.38,0.17 0.61,0.17c0.26,0 0.47,-0.07 0.65,-0.21c0.18,-0.14 0.3,-0.34 0.35,-0.59l0.85,0.09c-0.06,0.29 -0.17,0.54 -0.32,0.77c-0.15,0.22 -0.36,0.39 -0.61,0.52C23.66,13.03 23.35,13.1 22.99,13.1z"/>
-</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_5g_uw_mobiledata.xml b/packages/SettingsLib/res/drawable/ic_5g_uw_mobiledata.xml
deleted file mode 100644
index ca47b6f..0000000
--- a/packages/SettingsLib/res/drawable/ic_5g_uw_mobiledata.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="27dp"
- android:height="16dp"
- android:viewportWidth="27.0"
- android:viewportHeight="16.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M5.3,13.22c-0.57,0 -1.1,-0.11 -1.58,-0.34c-0.48,-0.22 -0.87,-0.55 -1.18,-0.98C2.24,11.47 2.06,10.93 2,10.3l1.48,-0.2c0.07,0.5 0.25,0.92 0.56,1.25c0.32,0.32 0.74,0.48 1.26,0.48c0.57,0 1.02,-0.18 1.34,-0.55c0.33,-0.37 0.49,-0.87 0.49,-1.48c0,-0.61 -0.16,-1.09 -0.49,-1.46C6.32,7.96 5.88,7.78 5.32,7.78C5,7.78 4.7,7.85 4.42,8C4.15,8.14 3.93,8.33 3.76,8.56L2.28,7.92l0.6,-4.94h5.26v1.36H4.1L3.72,7.02l0.08,0.03C4,6.87 4.25,6.73 4.55,6.62c0.3,-0.12 0.63,-0.18 1.01,-0.18c0.6,0 1.13,0.14 1.6,0.41C7.62,7.11 7.98,7.5 8.24,8c0.27,0.5 0.41,1.1 0.41,1.79c0,0.66 -0.14,1.26 -0.43,1.78c-0.28,0.51 -0.67,0.92 -1.18,1.22C6.55,13.08 5.97,13.22 5.3,13.22z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M14.51,13.22c-0.88,0 -1.66,-0.21 -2.34,-0.64c-0.67,-0.44 -1.2,-1.05 -1.6,-1.83C10.19,9.95 10,9.03 10,7.99c0,-1.05 0.21,-1.96 0.62,-2.74c0.41,-0.79 0.97,-1.4 1.67,-1.83c0.7,-0.44 1.5,-0.66 2.39,-0.66c1.09,0 2.01,0.25 2.74,0.76c0.75,0.5 1.22,1.21 1.41,2.11l-1.47,0.39c-0.17,-0.56 -0.48,-1 -0.94,-1.32c-0.45,-0.33 -1.03,-0.49 -1.75,-0.49c-0.57,0 -1.09,0.14 -1.57,0.43c-0.48,0.29 -0.85,0.71 -1.13,1.27s-0.42,1.25 -0.42,2.07c0,0.81 0.14,1.5 0.41,2.07c0.28,0.56 0.65,0.98 1.11,1.27c0.47,0.29 0.98,0.43 1.54,0.43c0.57,0 1.06,-0.11 1.47,-0.34c0.42,-0.23 0.75,-0.55 0.99,-0.94c0.25,-0.4 0.41,-0.85 0.46,-1.36h-2.88V7.79h4.37c0,0.87 0,1.74 0,2.6c0,0.87 0,1.74 0,2.6H17.6v-1.4h-0.08c-0.28,0.49 -0.67,0.88 -1.18,1.18C15.85,13.07 15.24,13.22 14.51,13.22z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M23,7.39c-0.53,0 -0.94,-0.16 -1.25,-0.47C21.45,6.6 21.3,6.16 21.3,5.6V3h0.8v2.66c0,0.3 0.08,0.54 0.23,0.71C22.48,6.54 22.7,6.62 23,6.62c0.3,0 0.52,-0.08 0.67,-0.25c0.15,-0.17 0.23,-0.41 0.23,-0.71V3h0.8v2.6c0,0.36 -0.07,0.67 -0.2,0.94s-0.33,0.48 -0.58,0.62C23.65,7.32 23.35,7.39 23,7.39z"/>
- <path
- android:fillColor="#FF000000"
- android:pathData="M21.41,13L20.3,8.7h0.73l0.64,2.78l0.07,0.38h0.04l0.09,-0.38l0.81,-2.78h0.66l0.79,2.78l0.09,0.37h0.04l0.07,-0.37l0.65,-2.78h0.72L24.59,13H23.9l-0.78,-2.84l-0.1,-0.41h-0.04l-0.1,0.41L22.08,13H21.41z"/>
-</vector>
diff --git a/packages/SettingsLib/res/layout/preference_category_divider.xml b/packages/SettingsLib/res/layout/preference_category_divider.xml
deleted file mode 100644
index 6644eec..0000000
--- a/packages/SettingsLib/res/layout/preference_category_divider.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/two_target_divider"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:gravity="start|center_vertical"
- android:orientation="horizontal">
- <View
- android:layout_height="1dp"
- android:layout_width="match_parent"
- android:background="?android:attr/dividerHorizontal" />
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout/preference_category_material_settings_with_divider.xml b/packages/SettingsLib/res/layout/preference_category_material_settings_with_divider.xml
deleted file mode 100644
index 5b5d474..0000000
--- a/packages/SettingsLib/res/layout/preference_category_material_settings_with_divider.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<!-- Similar to preference_category_material_settings.xml, except that this always adds
- a divider above category. -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <include layout="@layout/preference_category_divider"/>
- <include layout="@layout/preference_category_material"/>
-</LinearLayout>
diff --git a/packages/SettingsLib/res/layout/restricted_popup_menu_item.xml b/packages/SettingsLib/res/layout/restricted_popup_menu_item.xml
deleted file mode 100644
index 52d7775..0000000
--- a/packages/SettingsLib/res/layout/restricted_popup_menu_item.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2016, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:paddingEnd="16dp"
- android:paddingStart="16dp">
-
- <TextView
- android:id="@+id/text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceListItemSmall"
- android:textColor="?android:attr/textColorAlertDialogListItem" />
-
-</RelativeLayout>
diff --git a/packages/SettingsLib/res/layout/settings_dialog_title.xml b/packages/SettingsLib/res/layout/settings_dialog_title.xml
deleted file mode 100644
index 1e065e0..0000000
--- a/packages/SettingsLib/res/layout/settings_dialog_title.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/settings_title_panel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/settings_title_template"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingStart="?android:attr/dialogPreferredPadding"
- android:paddingEnd="?android:attr/dialogPreferredPadding"
- android:paddingTop="@*android:dimen/dialog_padding_top_material">
-
- <ImageView
- android:id="@+id/settings_icon"
- android:layout_width="24dip"
- android:layout_height="24dip"
- android:layout_marginBottom="12dip" />
-
- <TextView
- android:id="@+id/settings_title"
- android:singleLine="true"
- android:ellipsize="end"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAlignment="center"
- style="?android:attr/windowTitleStyle" />
- </LinearLayout>
-
- <Space
- android:id="@+id/settings_title_divider"
- android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="@*android:dimen/dialog_title_divider_material" />
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 663e8e4..179573b 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -158,18 +158,6 @@
<item>Opus</item>
</string-array>
- <!-- Values for Bluetooth Audio Codec selection preference. -->
- <string-array name="bluetooth_a2dp_codec_values" translatable="false" >
- <item>1000000</item>
- <item>0</item>
- <item>1</item>
- <item>2</item>
- <item>3</item>
- <item>4</item>
- <item>5</item>
- <item>6</item>
- </string-array>
-
<!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_summaries" >
<item>Use System Selection (Default)</item>
@@ -191,15 +179,6 @@
<item>96.0 kHz</item>
</string-array>
- <!-- Values for Bluetooth Audio Codec Sample Rate selection preference. -->
- <string-array name="bluetooth_a2dp_codec_sample_rate_values" translatable="false" >
- <item>0</item>
- <item>1</item>
- <item>2</item>
- <item>4</item>
- <item>8</item>
- </string-array>
-
<!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries" >
<item>Use System Selection (Default)</item>
@@ -217,14 +196,6 @@
<item>32 bits/sample</item>
</string-array>
- <!-- Values for Bluetooth Audio Codec Bits Per Sample selection preference. -->
- <string-array name="bluetooth_a2dp_codec_bits_per_sample_values" translatable="false" >
- <item>0</item>
- <item>1</item>
- <item>2</item>
- <item>4</item>
- </string-array>
-
<!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries" >
<item>Use System Selection (Default)</item>
@@ -240,13 +211,6 @@
<item>Stereo</item>
</string-array>
- <!-- Values for Bluetooth Audio Codec Channel Mode selection preference. -->
- <string-array name="bluetooth_a2dp_codec_channel_mode_values" translatable="false" >
- <item>0</item>
- <item>1</item>
- <item>2</item>
- </string-array>
-
<!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries" >
<item>Use System Selection (Default)</item>
@@ -262,14 +226,6 @@
<item>Best Effort (Adaptive Bit Rate)</item>
</string-array>
- <!-- Values for Bluetooth Audio Codec LDAC Playback Quaility selection preference. -->
- <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_values" translatable="false" >
- <item>1000</item>
- <item>1001</item>
- <item>1002</item>
- <item>1003</item>
- </string-array>
-
<!-- Summaries for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70]-->
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries" >
<item>Optimized for Audio Quality</item>
diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml
index d2d7471..6b7e918 100644
--- a/packages/SettingsLib/res/values/colors.xml
+++ b/packages/SettingsLib/res/values/colors.xml
@@ -17,9 +17,6 @@
<resources>
<color name="disabled_text_color">#66000000</color> <!-- 38% black -->
- <color name="usage_graph_dots">@*android:color/tertiary_device_default_settings</color>
- <color name="list_divider_color">#64000000</color>
-
<color name="bt_color_icon_1">#b4a50e0e</color> <!-- 72% Material Red 900 -->
<color name="bt_color_icon_2">#b40d652d</color> <!-- 72% Material Green 900 -->
<color name="bt_color_icon_3">#b4e37400</color> <!-- 72% Material Yellow 900 -->
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 226b119..3ef3d36 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -19,16 +19,11 @@
<!-- The y translation to apply at the start in appear animations. -->
<dimen name="appear_y_translation_start">32dp</dimen>
- <!-- The translation for disappearing security views after having solved them. -->
- <dimen name="disappear_y_translation">-32dp</dimen>
-
<!-- Height of a user icon view -->
<dimen name="user_icon_view_height">24dp</dimen>
<!-- User spinner -->
- <dimen name="user_spinner_height">72dp</dimen>
<dimen name="user_spinner_padding">4dp</dimen>
<dimen name="user_spinner_padding_sides">20dp</dimen>
- <dimen name="user_spinner_item_height">56dp</dimen>
<!-- Lock icon for preferences locked by admin -->
<dimen name="restricted_icon_padding">4dp</dimen>
@@ -36,10 +31,8 @@
<dimen name="wifi_preference_badge_padding">8dip</dimen>
<!-- Usage graph dimens -->
- <dimen name="usage_graph_area_height">122dp</dimen>
<dimen name="usage_graph_margin_top_bottom">9dp</dimen>
<dimen name="usage_graph_labels_width">56dp</dimen>
- <dimen name="usage_graph_labels_padding">16dp</dimen>
<dimen name="usage_graph_divider_size">1dp</dimen>
@@ -64,22 +57,13 @@
<!-- Ratio between width and height -->
<fraction name="bt_battery_ratio_fraction">35%</fraction>
- <!-- Ratio of height between battery icon and bluetooth icon -->
- <fraction name="bt_battery_scale_fraction">75%</fraction>
-
<!-- Fraction value to smooth the edges of the battery icon. The path will be inset by this
fraction of a pixel.-->
<fraction name="battery_subpixel_smoothing_left">0%</fraction>
<fraction name="battery_subpixel_smoothing_right">0%</fraction>
- <!-- Zen mode panel: condition item button padding -->
- <dimen name="zen_mode_condition_detail_button_padding">8dp</dimen>
- <!-- Zen mode panel: spacing between condition items -->
- <dimen name="zen_mode_condition_detail_item_spacing">12dp</dimen>
<!-- Zen mode panel: spacing between two-line condition upper and lower lines -->
<dimen name="zen_mode_condition_detail_item_interline_spacing">4dp</dimen>
- <!-- Zen mode panel: bottom padding, a bit less than qs_panel_padding -->
- <dimen name="zen_mode_condition_detail_bottom_padding">4dp</dimen>
<!-- SignalDrawable -->
<dimen name="signal_icon_size">15dp</dimen>
@@ -93,9 +77,6 @@
<!-- Size of advanced icon -->
<dimen name="advanced_icon_size">18dp</dimen>
- <!-- Minimum width for the popup for updating a user's photo. -->
- <dimen name="update_user_photo_popup_min_width">300dp</dimen>
-
<dimen name="add_a_photo_circled_padding">6dp</dimen>
<dimen name="user_photo_size_in_user_info_dialog">112dp</dimen>
<dimen name="add_a_photo_icon_size_in_user_info_dialog">32dp</dimen>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 06d7bb4..2e0155b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -91,10 +91,6 @@
<string name="wifi_disabled_generic">Disabled</string>
<!-- Status for networked disabled from a DNS or DHCP failure -->
<string name="wifi_disabled_network_failure">IP Configuration Failure</string>
- <!-- Status for networks disabled by the network recommendation provider -->
- <string name="wifi_disabled_by_recommendation_provider">Not connected due to low quality network</string>
- <!-- Status for networked disabled from a wifi association failure -->
- <string name="wifi_disabled_wifi_failure">WiFi Connection Failure</string>
<!-- Status for networks disabled from authentication failure (wrong password
or certificate). -->
<string name="wifi_disabled_password_failure">Authentication problem</string>
@@ -114,20 +110,14 @@
<string name="wifi_no_internet">No internet access</string>
<!-- Summary for saved networks -->
<string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string>
- <!-- Summary for connect to metered access point [CHAR LIMIT=NONE] -->
- <string name="connected_to_metered_access_point">Connected to metered network</string>
<!-- Status message of Wi-Fi when it is automatically connected by a network recommendation provider. [CHAR LIMIT=NONE] -->
<string name="connected_via_network_scorer">Automatically connected via %1$s</string>
<!-- Status message of Wi-Fi when it is automatically connected by a default network recommendation provider. [CHAR LIMIT=NONE] -->
<string name="connected_via_network_scorer_default">Automatically connected via network rating provider</string>
- <!-- Status message of Wi-Fi when it is connected by Passpoint configuration. [CHAR LIMIT=NONE] -->
- <string name="connected_via_passpoint">Connected via %1$s</string>
<!-- Status message of Wi-Fi when it is connected by a app (via suggestion or network request). [CHAR LIMIT=NONE] -->
<string name="connected_via_app">Connected via <xliff:g id="name" example="Wifi App">%1$s</xliff:g></string>
- <!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] -->
- <string name="available_via_passpoint">Available via %1$s</string>
<!-- Status message of OSU Provider network when not connected. [CHAR LIMIT=NONE] -->
<string name="tap_to_sign_up">Tap to sign up</string>
<!-- Package name for Settings app-->
@@ -153,11 +143,6 @@
<!-- Summary for networks failing to connect due to association rejection status 17, AP full -->
<string name="wifi_ap_unable_to_handle_new_sta">Access point temporarily full</string>
- <!-- Status message of Wi-Fi when it is connected to a Carrier Network. [CHAR LIMIT=NONE] -->
- <string name="connected_via_carrier">Connected via %1$s</string>
- <!-- Status message of Wi-Fi when an available network is a carrier network. [CHAR LIMIT=NONE] -->
- <string name="available_via_carrier">Available via %1$s</string>
-
<!-- Status message of OSU Provider upon initiating provisioning flow [CHAR LIMIT=NONE] -->
<string name="osu_opening_provider">Opening <xliff:g id="passpointProvider" example="Passpoint Provider">%1$s</xliff:g></string>
<!-- Status message of OSU Provider when connection fails [CHAR LIMIT=NONE] -->
@@ -169,14 +154,10 @@
<!-- Status message of OSU Provider on completing provisioning. [CHAR LIMIT=NONE] -->
<string name="osu_sign_up_complete">Sign-up complete. Connecting\u2026</string>
- <!-- Speed label for very slow network speed -->
- <string name="speed_label_very_slow">Very Slow</string>
<!-- Speed label for slow network speed -->
<string name="speed_label_slow">Slow</string>
<!-- Speed label for okay network speed -->
<string name="speed_label_okay">OK</string>
- <!-- Speed label for medium network speed -->
- <string name="speed_label_medium">Medium</string>
<!-- Speed label for fast network speed -->
<string name="speed_label_fast">Fast</string>
<!-- Speed label for very fast network speed -->
@@ -203,8 +184,6 @@
<string name="bluetooth_connected_no_headset">Connected (no phone)<xliff:g id="active_device">%1$s</xliff:g></string>
<!-- Bluetooth settings. Message when connected to a device, except for media audio. [CHAR LIMIT=40] -->
<string name="bluetooth_connected_no_a2dp">Connected (no media)<xliff:g id="active_device">%1$s</xliff:g></string>
- <!-- Bluetooth settings. Message when connected to a device, except for map. [CHAR LIMIT=40] -->
- <string name="bluetooth_connected_no_map">Connected (no message access)<xliff:g id="active_device">%1$s</xliff:g></string>
<!-- Bluetooth settings. Message when connected to a device, except for phone/media audio. [CHAR LIMIT=40] -->
<string name="bluetooth_connected_no_headset_no_a2dp">Connected (no phone or media)<xliff:g id="active_device">%1$s</xliff:g></string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index 59735f41..85e8aad 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -183,7 +183,7 @@
final String currentShortcutServiceId = Settings.Secure.getStringForUser(
context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
userId);
- if (currentShortcutServiceId != null) {
+ if (!TextUtils.isEmpty(currentShortcutServiceId)) {
return currentShortcutServiceId;
}
return context.getString(R.string.config_defaultAccessibilityService);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppHeaderPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppHeaderPreferenceTest.java
index fd181ff..e2b242c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppHeaderPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppHeaderPreferenceTest.java
@@ -65,7 +65,6 @@
@Test
public void setIsInstantApp_shouldUpdateInstallType() {
-
mPreference.onBindViewHolder(mHolder);
mPreference.setIsInstantApp(true);
@@ -77,8 +76,8 @@
public void setSecondSummary_shouldUpdateSecondSummary() {
final String defaultTestText = "Test second summary";
- mPreference.onBindViewHolder(mHolder);
mPreference.setSecondSummary(defaultTestText);
+ mPreference.onBindViewHolder(mHolder);
assertThat(((TextView) mRootView.findViewById(R.id.second_summary)).getText().toString())
.isEqualTo(defaultTestText);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b31e36c..6c17036 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -180,6 +180,7 @@
<uses-permission android:name="android.permission.MANAGE_ROLLBACKS" />
<uses-permission android:name="android.permission.TEST_MANAGE_ROLLBACKS" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.REBOOT" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
index 1ab0d4d..92c9527 100644
--- a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
@@ -14,12 +14,12 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:width="24dp"
- android:height="24dp">
- <path
- android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 15L16.75 15L16.75 6L4 6L4 15Z"
- android:fillType="evenOdd"
- android:fillColor="#000000" />
-</vector>
\ No newline at end of file
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9.7,29.6H33.75V13.7H9.7ZM7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H41Q42.2,8 43.1,8.9Q44,9.8 44,11V37Q44,38.2 43.1,39.1Q42.2,40 41,40ZM7,37H41Q41,37 41,37Q41,37 41,37V11Q41,11 41,11Q41,11 41,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
index 6fc89a6..209681f 100644
--- a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
@@ -14,12 +14,12 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:width="24dp"
- android:height="24dp">
- <path
- android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 12.75L13.75 12.75L13.75 6L4 6L4 12.75Z"
- android:fillType="evenOdd"
- android:fillColor="#000000" />
-</vector>
\ No newline at end of file
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9.7,27.6H27.75V13.7H9.7ZM7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H41Q42.2,8 43.1,8.9Q44,9.8 44,11V37Q44,38.2 43.1,39.1Q42.2,40 41,40ZM7,37H41Q41,37 41,37Q41,37 41,37V11Q41,11 41,11Q41,11 41,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
index fd73574..a3dc816 100644
--- a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
@@ -14,12 +14,12 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:width="24dp"
- android:height="24dp">
- <path
- android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 10.5L8.5 10.5L8.5 6L4 6L4 10.5Z"
- android:fillType="evenOdd"
- android:fillColor="#000000" />
-</vector>
\ No newline at end of file
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9.7,21.6H17.75V13.7H9.7ZM7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H41Q42.2,8 43.1,8.9Q44,9.8 44,11V37Q44,38.2 43.1,39.1Q42.2,40 41,40ZM7,37H41Q41,37 41,37Q41,37 41,37V11Q41,11 41,11Q41,11 41,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_move_magnification.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
index 1bff559..dfedd28 100644
--- a/packages/SystemUI/res/drawable/ic_move_magnification.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -1,43 +1,25 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 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.
- -->
+ Copyright (C) 2020 The Android Open Source Project
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item>
- <shape android:shape="rectangle">
- <solid
- android:color="@color/magnification_drag_handle_color" />
- <size
- android:height="@dimen/magnification_drag_view_size"
- android:width="@dimen/magnification_drag_view_size"/>
- <corners android:topLeftRadius="12dp"/>
+ 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
- </shape>
- </item>
- <item
- android:gravity="center">
+ http://www.apache.org/licenses/LICENSE-2.0
- <vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:width="24dp"
- android:height="24dp">
- <path
- android:pathData="M13.2217 21.7734C12.8857 22.1094 12.288 22.712 12 23C12 23 11.1143 22.1094 10.7783 21.7734L8.33494 19.3301L9.55662 18.1084L12 20.5518L14.4434 18.1084L15.665 19.3301L13.2217 21.7734ZM19.3301 15.665L18.1084 14.4433L20.5518 12L18.1084 9.5566L19.3301 8.33492L21.7735 10.7783C22.1094 11.1142 22.4241 11.4241 23 12C22.4241 12.5759 22.3963 12.5988 21.7735 13.2217L19.3301 15.665ZM14.4434 14.4433C13.7714 15.1153 12.957 15.4512 12 15.4512C11.043 15.4512 10.2285 15.1153 9.55662 14.4433C8.88469 13.7714 8.54873 12.957 8.54873 12C8.54873 11.043 8.88469 10.2285 9.55662 9.5566C10.2285 8.88468 11.043 8.54871 12 8.54871C12.957 8.54871 13.7714 8.88467 14.4434 9.5566C15.1153 10.2285 15.4512 11.043 15.4512 12C15.4512 12.957 15.1153 13.7714 14.4434 14.4433ZM4.66988 15.665L2.22651 13.2217C1.89055 12.8857 1.28791 12.288 1 12C1.28791 11.712 1.89055 11.1143 2.22651 10.7783L4.66988 8.33492L5.89157 9.5566L3.4482 12L5.89157 14.4433L4.66988 15.665ZM14.4434 5.89155L12 3.44818L9.55662 5.89155L8.33494 4.66987L10.7783 2.2265C11.1389 1.86592 11.2963 1.70369 12 1C12.5758 1.57585 12.8857 1.89053 13.2217 2.2265L15.665 4.66986L14.4434 5.89155Z"
- android:fillColor="#ffffff" />
- </vector>
- </item>
-</layer-list>
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,15Q10.75,15 9.875,14.125Q9,13.25 9,12Q9,10.75 9.875,9.875Q10.75,9 12,9Q13.25,9 14.125,9.875Q15,10.75 15,12Q15,13.25 14.125,14.125Q13.25,15 12,15ZM12,22 L7.75,17.75 9.15,16.35 12,19.15 14.85,16.35 16.25,17.75ZM6.25,16.25 L2,12 6.25,7.75 7.65,9.15 4.85,12 7.65,14.85ZM9.15,7.65 L7.75,6.25 12,2 16.25,6.25 14.85,7.65 12,4.85ZM17.75,16.25 L16.35,14.85 19.15,12 16.35,9.15 17.75,7.75 22,12Z"/>
+</vector>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index a802723..37549c9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -611,6 +611,33 @@
2 - Override the setting to never bypass keyguard -->
<integer name="config_face_unlock_bypass_override">0</integer>
+ <!-- Messages that should NOT be shown to the user during face authentication on keyguard.
+ This includes both lockscreen and bouncer. This should be used to hide messages that may be
+ too chatty or messages that the user can't do much about. Entries are defined in
+ android.hardware.biometrics.face@1.0 types.hal.
+
+ Although not visibly shown to the user, these acquired messages (sent per face auth frame)
+ are still counted towards the total frames to determine whether a deferred message
+ (see config_face_help_msgs_defer_until_timeout) meets the threshold % of frames to show on
+ face timeout. -->
+ <integer-array name="config_face_acquire_device_entry_ignorelist" translatable="false" >
+ </integer-array>
+
+ <!-- Which face help messages to defer until face auth times out. If face auth is cancelled
+ or ends on another error, then the message is never surfaced. May also never surface
+ if it doesn't meet a threshold % of authentication frames specified by.
+ config_face_help_msgs_defer_until_timeout_threshold. -->
+ <integer-array name="config_face_help_msgs_defer_until_timeout">
+ </integer-array>
+
+ <!-- Percentage of face auth frames received required to show a deferred message at
+ FACE_ERROR_TIMEOUT. See config_face_help_msgs_defer_until_timeout for messages
+ that are deferred.-->
+ <item name="config_face_help_msgs_defer_until_timeout_threshold"
+ translatable="false" format="float" type="dimen">
+ .75
+ </item>
+
<!-- Which face help messages to surface when fingerprint is also enrolled.
Message ids correspond with the acquired ids in BiometricFaceConstants -->
<integer-array name="config_face_help_msgs_when_fingerprint_enrolled">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
new file mode 100644
index 0000000..30c062b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import android.graphics.HardwareRenderer;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.Message;
+import android.os.Trace;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.View;
+import android.view.ViewRootImpl;
+
+import java.util.function.Consumer;
+
+/**
+ * Helper class to apply surface transactions in sync with RenderThread.
+ *
+ * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't
+ * currently reference that class from the shared lib as it is hidden.
+ */
+public class SyncRtSurfaceTransactionApplierCompat {
+
+ public static final int FLAG_ALL = 0xffffffff;
+ public static final int FLAG_ALPHA = 1;
+ public static final int FLAG_MATRIX = 1 << 1;
+ public static final int FLAG_WINDOW_CROP = 1 << 2;
+ public static final int FLAG_LAYER = 1 << 3;
+ public static final int FLAG_CORNER_RADIUS = 1 << 4;
+ public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5;
+ public static final int FLAG_VISIBILITY = 1 << 6;
+ public static final int FLAG_RELATIVE_LAYER = 1 << 7;
+ public static final int FLAG_SHADOW_RADIUS = 1 << 8;
+
+ private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
+
+ private final SurfaceControl mBarrierSurfaceControl;
+ private final ViewRootImpl mTargetViewRootImpl;
+ private final Handler mApplyHandler;
+
+ private int mSequenceNumber = 0;
+ private int mPendingSequenceNumber = 0;
+ private Runnable mAfterApplyCallback;
+
+ /**
+ * @param targetView The view in the surface that acts as synchronization anchor.
+ */
+ public SyncRtSurfaceTransactionApplierCompat(View targetView) {
+ mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
+ mBarrierSurfaceControl = mTargetViewRootImpl != null
+ ? mTargetViewRootImpl.getSurfaceControl() : null;
+
+ mApplyHandler = new Handler(new Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) {
+ onApplyMessage(msg.arg1);
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ private void onApplyMessage(int seqNo) {
+ mSequenceNumber = seqNo;
+ if (mSequenceNumber == mPendingSequenceNumber && mAfterApplyCallback != null) {
+ Runnable r = mAfterApplyCallback;
+ mAfterApplyCallback = null;
+ r.run();
+ }
+ }
+
+ /**
+ * Schedules applying surface parameters on the next frame.
+ *
+ * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into
+ * this method to avoid synchronization issues.
+ */
+ public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) {
+ if (mTargetViewRootImpl == null || mTargetViewRootImpl.getView() == null) {
+ return;
+ }
+
+ mPendingSequenceNumber++;
+ final int toApplySeqNo = mPendingSequenceNumber;
+ mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
+ @Override
+ public void onFrameDraw(long frame) {
+ if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {
+ Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
+ .sendToTarget();
+ return;
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Sync transaction frameNumber=" + frame);
+ Transaction t = new Transaction();
+ for (int i = params.length - 1; i >= 0; i--) {
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams =
+ params[i];
+ surfaceParams.applyTo(t);
+ }
+ if (mTargetViewRootImpl != null) {
+ mTargetViewRootImpl.mergeWithNextTransaction(t, frame);
+ } else {
+ t.apply();
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
+ .sendToTarget();
+ }
+ });
+
+ // Make sure a frame gets scheduled.
+ mTargetViewRootImpl.getView().invalidate();
+ }
+
+ /**
+ * Calls the runnable when any pending apply calls have completed
+ */
+ public void addAfterApplyCallback(final Runnable afterApplyCallback) {
+ if (mSequenceNumber == mPendingSequenceNumber) {
+ afterApplyCallback.run();
+ } else {
+ if (mAfterApplyCallback == null) {
+ mAfterApplyCallback = afterApplyCallback;
+ } else {
+ final Runnable oldCallback = mAfterApplyCallback;
+ mAfterApplyCallback = new Runnable() {
+ @Override
+ public void run() {
+ afterApplyCallback.run();
+ oldCallback.run();
+ }
+ };
+ }
+ }
+ }
+
+ public static void applyParams(TransactionCompat t,
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
+ params.applyTo(t.mTransaction);
+ }
+
+ /**
+ * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
+ * attached if necessary.
+ */
+ public static void create(final View targetView,
+ final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) {
+ if (targetView == null) {
+ // No target view, no applier
+ callback.accept(null);
+ } else if (targetView.getViewRootImpl() != null) {
+ // Already attached, we're good to go
+ callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
+ } else {
+ // Haven't been attached before we can get the view root
+ targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ targetView.removeOnAttachStateChangeListener(this);
+ callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ // Do nothing
+ }
+ });
+ }
+ }
+
+ public static class SurfaceParams {
+ public static class Builder {
+ final SurfaceControl surface;
+ int flags;
+ float alpha;
+ float cornerRadius;
+ int backgroundBlurRadius;
+ Matrix matrix;
+ Rect windowCrop;
+ int layer;
+ SurfaceControl relativeTo;
+ int relativeLayer;
+ boolean visible;
+ float shadowRadius;
+
+ /**
+ * @param surface The surface to modify.
+ */
+ public Builder(SurfaceControl surface) {
+ this.surface = surface;
+ }
+
+ /**
+ * @param alpha The alpha value to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withAlpha(float alpha) {
+ this.alpha = alpha;
+ flags |= FLAG_ALPHA;
+ return this;
+ }
+
+ /**
+ * @param matrix The matrix to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withMatrix(Matrix matrix) {
+ this.matrix = new Matrix(matrix);
+ flags |= FLAG_MATRIX;
+ return this;
+ }
+
+ /**
+ * @param windowCrop The window crop to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withWindowCrop(Rect windowCrop) {
+ this.windowCrop = new Rect(windowCrop);
+ flags |= FLAG_WINDOW_CROP;
+ return this;
+ }
+
+ /**
+ * @param layer The layer to assign the surface.
+ * @return this Builder
+ */
+ public Builder withLayer(int layer) {
+ this.layer = layer;
+ flags |= FLAG_LAYER;
+ return this;
+ }
+
+ /**
+ * @param relativeTo The surface that's set relative layer to.
+ * @param relativeLayer The relative layer.
+ * @return this Builder
+ */
+ public Builder withRelativeLayerTo(SurfaceControl relativeTo, int relativeLayer) {
+ this.relativeTo = relativeTo;
+ this.relativeLayer = relativeLayer;
+ flags |= FLAG_RELATIVE_LAYER;
+ return this;
+ }
+
+ /**
+ * @param radius the Radius for rounded corners to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withCornerRadius(float radius) {
+ this.cornerRadius = radius;
+ flags |= FLAG_CORNER_RADIUS;
+ return this;
+ }
+
+ /**
+ * @param radius the Radius for the shadows to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withShadowRadius(float radius) {
+ this.shadowRadius = radius;
+ flags |= FLAG_SHADOW_RADIUS;
+ return this;
+ }
+
+ /**
+ * @param radius the Radius for blur to apply to the background surfaces.
+ * @return this Builder
+ */
+ public Builder withBackgroundBlur(int radius) {
+ this.backgroundBlurRadius = radius;
+ flags |= FLAG_BACKGROUND_BLUR_RADIUS;
+ return this;
+ }
+
+ /**
+ * @param visible The visibility to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withVisibility(boolean visible) {
+ this.visible = visible;
+ flags |= FLAG_VISIBILITY;
+ return this;
+ }
+
+ /**
+ * @return a new SurfaceParams instance
+ */
+ public SurfaceParams build() {
+ return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
+ relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible,
+ shadowRadius);
+ }
+ }
+
+ private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix,
+ Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer,
+ float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) {
+ this.flags = flags;
+ this.surface = surface;
+ this.alpha = alpha;
+ this.matrix = matrix;
+ this.windowCrop = windowCrop;
+ this.layer = layer;
+ this.relativeTo = relativeTo;
+ this.relativeLayer = relativeLayer;
+ this.cornerRadius = cornerRadius;
+ this.backgroundBlurRadius = backgroundBlurRadius;
+ this.visible = visible;
+ this.shadowRadius = shadowRadius;
+ }
+
+ private final int flags;
+ private final float[] mTmpValues = new float[9];
+
+ public final SurfaceControl surface;
+ public final float alpha;
+ public final float cornerRadius;
+ public final int backgroundBlurRadius;
+ public final Matrix matrix;
+ public final Rect windowCrop;
+ public final int layer;
+ public final SurfaceControl relativeTo;
+ public final int relativeLayer;
+ public final boolean visible;
+ public final float shadowRadius;
+
+ public void applyTo(SurfaceControl.Transaction t) {
+ if ((flags & FLAG_MATRIX) != 0) {
+ t.setMatrix(surface, matrix, mTmpValues);
+ }
+ if ((flags & FLAG_WINDOW_CROP) != 0) {
+ t.setWindowCrop(surface, windowCrop);
+ }
+ if ((flags & FLAG_ALPHA) != 0) {
+ t.setAlpha(surface, alpha);
+ }
+ if ((flags & FLAG_LAYER) != 0) {
+ t.setLayer(surface, layer);
+ }
+ if ((flags & FLAG_CORNER_RADIUS) != 0) {
+ t.setCornerRadius(surface, cornerRadius);
+ }
+ if ((flags & FLAG_BACKGROUND_BLUR_RADIUS) != 0) {
+ t.setBackgroundBlurRadius(surface, backgroundBlurRadius);
+ }
+ if ((flags & FLAG_VISIBILITY) != 0) {
+ if (visible) {
+ t.show(surface);
+ } else {
+ t.hide(surface);
+ }
+ }
+ if ((flags & FLAG_RELATIVE_LAYER) != 0) {
+ t.setRelativeLayer(surface, relativeTo, relativeLayer);
+ }
+ if ((flags & FLAG_SHADOW_RADIUS) != 0) {
+ t.setShadowRadius(surface, shadowRadius);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
new file mode 100644
index 0000000..43a882a5
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+
+public class TransactionCompat {
+
+ final Transaction mTransaction;
+
+ final float[] mTmpValues = new float[9];
+
+ public TransactionCompat() {
+ mTransaction = new Transaction();
+ }
+
+ public void apply() {
+ mTransaction.apply();
+ }
+
+ public TransactionCompat show(SurfaceControl surfaceControl) {
+ mTransaction.show(surfaceControl);
+ return this;
+ }
+
+ public TransactionCompat hide(SurfaceControl surfaceControl) {
+ mTransaction.hide(surfaceControl);
+ return this;
+ }
+
+ public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) {
+ mTransaction.setPosition(surfaceControl, x, y);
+ return this;
+ }
+
+ public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) {
+ mTransaction.setBufferSize(surfaceControl, w, h);
+ return this;
+ }
+
+ public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) {
+ mTransaction.setLayer(surfaceControl, z);
+ return this;
+ }
+
+ public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) {
+ mTransaction.setAlpha(surfaceControl, alpha);
+ return this;
+ }
+
+ public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) {
+ mTransaction.setOpaque(surfaceControl, opaque);
+ return this;
+ }
+
+ public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx,
+ float dtdy, float dsdy) {
+ mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy);
+ return this;
+ }
+
+ public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) {
+ mTransaction.setMatrix(surfaceControl, matrix, mTmpValues);
+ return this;
+ }
+
+ public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) {
+ mTransaction.setWindowCrop(surfaceControl, crop);
+ return this;
+ }
+
+ public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) {
+ mTransaction.setCornerRadius(surfaceControl, radius);
+ return this;
+ }
+
+ public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) {
+ mTransaction.setBackgroundBlurRadius(surfaceControl, radius);
+ return this;
+ }
+
+ public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) {
+ mTransaction.setColor(surfaceControl, color);
+ return this;
+ }
+
+ public static void setRelativeLayer(Transaction t, SurfaceControl surfaceControl,
+ SurfaceControl relativeTo, int z) {
+ t.setRelativeLayer(surfaceControl, relativeTo, z);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5ccab54..7e04f04 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -157,6 +157,7 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -167,6 +168,7 @@
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -266,6 +268,7 @@
private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
private final UiEventLogger mUiEventLogger;
+ private final Set<Integer> mFaceAcquiredInfoIgnoreList;
private int mStatusBarState;
private final StatusBarStateController.StateListener mStatusBarStateControllerListener =
new StatusBarStateController.StateListener() {
@@ -1008,6 +1011,7 @@
private void handleFaceAuthFailed() {
Assert.isMainThread();
+ mLogger.d("onFaceAuthFailed");
mFaceCancelSignal = null;
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1624,6 +1628,9 @@
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+ if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) {
+ return;
+ }
handleFaceHelp(helpMsgId, helpString.toString());
}
@@ -1916,6 +1923,11 @@
mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
mWakeOnFingerprintAcquiredStart = context.getResources()
.getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start);
+ mFaceAcquiredInfoIgnoreList = Arrays.stream(
+ mContext.getResources().getIntArray(
+ R.array.config_face_acquire_device_entry_ignorelist))
+ .boxed()
+ .collect(Collectors.toSet());
mHandler = new Handler(mainLooper) {
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
new file mode 100644
index 0000000..2c2ab7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.dagger.BiometricMessagesLog
+import javax.inject.Inject
+
+/** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */
+@SysUISingleton
+class FaceMessageDeferralLogger
+@Inject
+constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) :
+ BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger")
+
+open class BiometricMessageDeferralLogger(
+ private val logBuffer: LogBuffer,
+ private val tag: String
+) {
+ fun reset() {
+ logBuffer.log(tag, DEBUG, "reset")
+ }
+
+ fun logUpdateMessage(acquiredInfo: Int, helpString: String) {
+ logBuffer.log(
+ tag,
+ DEBUG,
+ {
+ int1 = acquiredInfo
+ str1 = helpString
+ },
+ { "updateMessage acquiredInfo=$int1 helpString=$str1" }
+ )
+ }
+
+ fun logFrameProcessed(
+ acquiredInfo: Int,
+ totalFrames: Int,
+ mostFrequentAcquiredInfoToDefer: String? // may not meet the threshold
+ ) {
+ logBuffer.log(
+ tag,
+ DEBUG,
+ {
+ int1 = acquiredInfo
+ int2 = totalFrames
+ str1 = mostFrequentAcquiredInfoToDefer
+ },
+ {
+ "frameProcessed acquiredInfo=$int1 totalFrames=$int2 " +
+ "messageToShowOnTimeout=$str1"
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt
deleted file mode 100644
index f2d8aaa..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricMessageDeferral.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-/**
- * Provides whether an acquired error message should be shown immediately when its received (see
- * [shouldDefer]) or should be shown when the biometric error is received [getDeferredMessage].
- * @property excludedMessages messages that are excluded from counts
- * @property messagesToDefer messages that shouldn't show immediately when received, but may be
- * shown later if the message is the most frequent message processed and meets [THRESHOLD]
- * percentage of all messages (excluding [excludedMessages])
- */
-class BiometricMessageDeferral(
- private val excludedMessages: Set<Int>,
- private val messagesToDefer: Set<Int>
-) {
- private val msgCounts: MutableMap<Int, Int> = HashMap() // msgId => frequency of msg
- private val msgIdToCharSequence: MutableMap<Int, CharSequence> = HashMap() // msgId => message
- private var totalRelevantMessages = 0
- private var mostFrequentMsgIdToDefer: Int? = null
-
- /** Reset all saved counts. */
- fun reset() {
- totalRelevantMessages = 0
- msgCounts.clear()
- msgIdToCharSequence.clear()
- }
-
- /** Whether the given message should be deferred instead of being shown immediately. */
- fun shouldDefer(acquiredMsgId: Int): Boolean {
- return messagesToDefer.contains(acquiredMsgId)
- }
-
- /**
- * Adds the acquiredMsgId to the counts if it's not in [excludedMessages]. We still count
- * messages that shouldn't be deferred in these counts.
- */
- fun processMessage(acquiredMsgId: Int, helpString: CharSequence) {
- if (excludedMessages.contains(acquiredMsgId)) {
- return
- }
-
- totalRelevantMessages++
- msgIdToCharSequence[acquiredMsgId] = helpString
-
- val newAcquiredMsgCount = msgCounts.getOrDefault(acquiredMsgId, 0) + 1
- msgCounts[acquiredMsgId] = newAcquiredMsgCount
- if (
- messagesToDefer.contains(acquiredMsgId) &&
- (mostFrequentMsgIdToDefer == null ||
- newAcquiredMsgCount > msgCounts.getOrDefault(mostFrequentMsgIdToDefer!!, 0))
- ) {
- mostFrequentMsgIdToDefer = acquiredMsgId
- }
- }
-
- /**
- * Get the most frequent deferred message that meets the [THRESHOLD] percentage of processed
- * messages excluding [excludedMessages].
- * @return null if no messages have been deferred OR deferred messages didn't meet the
- * [THRESHOLD] percentage of messages to show.
- */
- fun getDeferredMessage(): CharSequence? {
- mostFrequentMsgIdToDefer?.let {
- if (msgCounts.getOrDefault(it, 0) > (THRESHOLD * totalRelevantMessages)) {
- return msgIdToCharSequence[mostFrequentMsgIdToDefer]
- }
- }
-
- return null
- }
- companion object {
- const val THRESHOLD = .5f
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
new file mode 100644
index 0000000..fabc1c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.content.res.Resources
+import com.android.keyguard.logging.BiometricMessageDeferralLogger
+import com.android.keyguard.logging.FaceMessageDeferralLogger
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import java.io.PrintWriter
+import java.util.*
+import javax.inject.Inject
+
+/**
+ * Provides whether a face acquired help message should be shown immediately when its received or
+ * should be shown when face auth times out. See [updateMessage] and [getDeferredMessage].
+ */
+@SysUISingleton
+class FaceHelpMessageDeferral
+@Inject
+constructor(
+ @Main resources: Resources,
+ logBuffer: FaceMessageDeferralLogger,
+ dumpManager: DumpManager
+) :
+ BiometricMessageDeferral(
+ resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(),
+ resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold),
+ logBuffer,
+ dumpManager
+ )
+
+/**
+ * @property messagesToDefer messages that shouldn't show immediately when received, but may be
+ * shown later if the message is the most frequent acquiredInfo processed and meets [threshold]
+ * percentage of all passed acquired frames.
+ */
+open class BiometricMessageDeferral(
+ private val messagesToDefer: Set<Int>,
+ private val threshold: Float,
+ private val logBuffer: BiometricMessageDeferralLogger,
+ dumpManager: DumpManager
+) : Dumpable {
+ private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap()
+ private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap()
+ private var mostFrequentAcquiredInfoToDefer: Int? = null
+ private var totalFrames = 0
+
+ init {
+ dumpManager.registerDumpable(this.javaClass.name, this)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("messagesToDefer=$messagesToDefer")
+ pw.println("totalFrames=$totalFrames")
+ pw.println("threshold=$threshold")
+ }
+
+ /** Reset all saved counts. */
+ fun reset() {
+ totalFrames = 0
+ mostFrequentAcquiredInfoToDefer = null
+ acquiredInfoToFrequency.clear()
+ acquiredInfoToHelpString.clear()
+ logBuffer.reset()
+ }
+
+ /** Updates the message associated with the acquiredInfo if it's a message we may defer. */
+ fun updateMessage(acquiredInfo: Int, helpString: String) {
+ if (!messagesToDefer.contains(acquiredInfo)) {
+ return
+ }
+ if (!Objects.equals(acquiredInfoToHelpString[acquiredInfo], helpString)) {
+ logBuffer.logUpdateMessage(acquiredInfo, helpString)
+ acquiredInfoToHelpString[acquiredInfo] = helpString
+ }
+ }
+
+ /** Whether the given message should be deferred instead of being shown immediately. */
+ fun shouldDefer(acquiredMsgId: Int): Boolean {
+ return messagesToDefer.contains(acquiredMsgId)
+ }
+
+ /** Adds the acquiredInfo frame to the counts. We account for all frames. */
+ fun processFrame(acquiredInfo: Int) {
+ if (messagesToDefer.isEmpty()) {
+ return
+ }
+
+ totalFrames++
+
+ val newAcquiredInfoCount = acquiredInfoToFrequency.getOrDefault(acquiredInfo, 0) + 1
+ acquiredInfoToFrequency[acquiredInfo] = newAcquiredInfoCount
+ if (
+ messagesToDefer.contains(acquiredInfo) &&
+ (mostFrequentAcquiredInfoToDefer == null ||
+ newAcquiredInfoCount >
+ acquiredInfoToFrequency.getOrDefault(mostFrequentAcquiredInfoToDefer!!, 0))
+ ) {
+ mostFrequentAcquiredInfoToDefer = acquiredInfo
+ }
+
+ logBuffer.logFrameProcessed(
+ acquiredInfo,
+ totalFrames,
+ mostFrequentAcquiredInfoToDefer?.toString()
+ )
+ }
+
+ /**
+ * Get the most frequent deferred message that meets the [threshold] percentage of processed
+ * frames.
+ * @return null if no acquiredInfo have been deferred OR deferred messages didn't meet the
+ * [threshold] percentage.
+ */
+ fun getDeferredMessage(): CharSequence? {
+ mostFrequentAcquiredInfoToDefer?.let {
+ if (acquiredInfoToFrequency.getOrDefault(it, 0) > (threshold * totalFrames)) {
+ return acquiredInfoToHelpString[it]
+ }
+ }
+ return null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
new file mode 100644
index 0000000..7f1ad6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as
+ * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}
+ */
+@Qualifier
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BiometricMessagesLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index c2a8764..756feb0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -287,6 +287,17 @@
}
/**
+ * Provides a {@link LogBuffer} for use by
+ * {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}.
+ */
+ @Provides
+ @SysUISingleton
+ @BiometricMessagesLog
+ public static LogBuffer provideBiometricMessagesLogBuffer(LogBufferFactory factory) {
+ return factory.create("BiometricMessagesLog", 150);
+ }
+
+ /**
* Provides a {@link LogBuffer} for use by the status bar network controller.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 6fe06e0..7d3e82c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -107,7 +107,8 @@
SysUiStatsLog.write(
SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__ADJUST_VOLUME,
- getInteractionDeviceType(source));
+ getInteractionDeviceType(source),
+ getLoggingPackageName());
}
/**
@@ -121,7 +122,8 @@
SysUiStatsLog.write(
SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__STOP_CASTING,
- SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE);
+ SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE,
+ getLoggingPackageName());
}
/**
@@ -135,7 +137,8 @@
SysUiStatsLog.write(
SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__EXPANSION,
- getInteractionDeviceType(source));
+ getInteractionDeviceType(source),
+ getLoggingPackageName());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index aa10f7e..b565f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -18,38 +18,57 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
+import com.android.systemui.temporarydisplay.TemporaryViewLogger
/**
* A logger for media tap-to-transfer events.
*
- * @property deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
+ * @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
*/
class MediaTttLogger(
- private val deviceTypeTag: String,
- private val buffer: LogBuffer
-){
+ deviceTypeTag: String,
+ buffer: LogBuffer
+) : TemporaryViewLogger(buffer, BASE_TAG + deviceTypeTag) {
/** Logs a change in the chip state for the given [mediaRouteId]. */
- fun logStateChange(stateName: String, mediaRouteId: String) {
+ fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) {
buffer.log(
- BASE_TAG + deviceTypeTag,
+ tag,
LogLevel.DEBUG,
{
str1 = stateName
str2 = mediaRouteId
+ str3 = packageName
},
- { "State changed to $str1 for ID=$str2" }
+ { "State changed to $str1 for ID=$str2 package=$str3" }
)
}
- /** Logs that we removed the chip for the given [reason]. */
- fun logChipRemoval(reason: String) {
+ /** Logs that we couldn't find information for [packageName]. */
+ fun logPackageNotFound(packageName: String) {
buffer.log(
- BASE_TAG + deviceTypeTag,
+ tag,
LogLevel.DEBUG,
- { str1 = reason },
- { "Chip removed due to $str1" }
+ { str1 = packageName },
+ { "Package $str1 could not be found" }
)
}
+
+ /**
+ * Logs that a removal request has been bypassed (ignored).
+ *
+ * @param removalReason the reason that the chip removal was requested.
+ * @param bypassReason the reason that the request was bypassed.
+ */
+ fun logRemovalBypass(removalReason: String, bypassReason: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = removalReason
+ str2 = bypassReason
+ },
+ { "Chip removal requested due to $str1; however, removal was ignored because $str2" })
+ }
}
private const val BASE_TAG = "MediaTtt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
new file mode 100644
index 0000000..792ae7c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.common
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import com.android.internal.widget.CachingIconView
+import com.android.settingslib.Utils
+import com.android.systemui.R
+
+/** Utility methods for media tap-to-transfer. */
+class MediaTttUtils {
+ companion object {
+ // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
+ // UpdateMediaTapToTransferReceiverDisplayTest
+ const val WINDOW_TITLE = "Media Transfer Chip View"
+ const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED"
+
+ /**
+ * Returns the information needed to display the icon.
+ *
+ * The information will either contain app name and icon of the app playing media, or a
+ * default name and icon if we can't find the app name/icon.
+ *
+ * @param appPackageName the package name of the app playing the media.
+ * @param logger the logger to use for any errors.
+ */
+ fun getIconInfoFromPackageName(
+ context: Context,
+ appPackageName: String?,
+ logger: MediaTttLogger
+ ): IconInfo {
+ if (appPackageName != null) {
+ try {
+ val contentDescription =
+ context.packageManager
+ .getApplicationInfo(
+ appPackageName,
+ PackageManager.ApplicationInfoFlags.of(0)
+ )
+ .loadLabel(context.packageManager)
+ .toString()
+ return IconInfo(
+ contentDescription,
+ drawable = context.packageManager.getApplicationIcon(appPackageName),
+ isAppIcon = true
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ logger.logPackageNotFound(appPackageName)
+ }
+ }
+ return IconInfo(
+ contentDescription =
+ context.getString(R.string.media_output_dialog_unknown_launch_app_name),
+ drawable =
+ context.resources.getDrawable(R.drawable.ic_cast).apply {
+ this.setTint(
+ Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+ )
+ },
+ isAppIcon = false
+ )
+ }
+
+ /**
+ * Sets an icon to be displayed by the given view.
+ *
+ * @param iconSize the size in pixels that the icon should be. If null, the size of
+ * [appIconView] will not be adjusted.
+ */
+ fun setIcon(
+ appIconView: CachingIconView,
+ icon: Drawable,
+ iconContentDescription: CharSequence,
+ iconSize: Int? = null,
+ ) {
+ iconSize?.let { size ->
+ val lp = appIconView.layoutParams
+ lp.width = size
+ lp.height = size
+ appIconView.layoutParams = lp
+ }
+
+ appIconView.contentDescription = iconContentDescription
+ appIconView.setImageDrawable(icon)
+ }
+ }
+}
+
+data class IconInfo(
+ val contentDescription: String,
+ val drawable: Drawable,
+ /**
+ * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
+ */
+ val isAppIcon: Boolean
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 5d6d683..dfd9e22 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -35,6 +35,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
@@ -61,7 +62,7 @@
powerManager: PowerManager,
@Main private val mainHandler: Handler,
private val uiEventLogger: MediaTttReceiverUiEventLogger,
-) : TemporaryViewDisplayController<ChipReceiverInfo>(
+) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
context,
logger,
windowManager,
@@ -70,6 +71,8 @@
configurationController,
powerManager,
R.layout.media_ttt_chip_receiver,
+ MediaTttUtils.WINDOW_TITLE,
+ MediaTttUtils.WAKE_REASON,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -107,7 +110,7 @@
) {
val chipState: ChipStateReceiver? = ChipStateReceiver.getReceiverStateFromId(displayState)
val stateName = chipState?.name ?: "Invalid"
- logger.logStateChange(stateName, routeInfo.id)
+ logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
if (chipState == null) {
Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
@@ -137,13 +140,26 @@
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
super.updateView(newInfo, currentView)
- val iconName = setIcon(
- currentView,
- newInfo.routeInfo.clientPackageName,
- newInfo.appIconDrawableOverride,
- newInfo.appNameOverride
+
+ val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
+ context, newInfo.routeInfo.clientPackageName, logger
)
- currentView.contentDescription = iconName
+ val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable
+ val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription
+ val iconSize = context.resources.getDimensionPixelSize(
+ if (iconInfo.isAppIcon) {
+ R.dimen.media_ttt_icon_size_receiver
+ } else {
+ R.dimen.media_ttt_generic_icon_size_receiver
+ }
+ )
+
+ MediaTttUtils.setIcon(
+ currentView.requireViewById(R.id.app_icon),
+ iconDrawable,
+ iconContentDescription,
+ iconSize,
+ )
}
override fun animateViewIn(view: ViewGroup) {
@@ -161,15 +177,6 @@
startRipple(view.requireViewById(R.id.ripple))
}
- override fun getIconSize(isAppIcon: Boolean): Int? =
- context.resources.getDimensionPixelSize(
- if (isAppIcon) {
- R.dimen.media_ttt_icon_size_receiver
- } else {
- R.dimen.media_ttt_generic_icon_size_receiver
- }
- )
-
/** Returns the amount that the chip will be translated by in its intro animation. */
private fun getTranslationAmount(): Int {
return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index bde588c..4379d25 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -34,8 +34,7 @@
* @property stateInt the integer from [StatusBarManager] corresponding with this state.
* @property stringResId the res ID of the string that should be displayed in the chip. Null if the
* state should not have the chip be displayed.
- * @property isMidTransfer true if the state represents that a transfer is currently ongoing.
- * @property isTransferFailure true if the state represents that the transfer has failed.
+ * @property transferStatus the transfer status that the chip state represents.
* @property timeout the amount of time this chip should display on the screen before it times out
* and disappears.
*/
@@ -43,8 +42,7 @@
@StatusBarManager.MediaTransferSenderState val stateInt: Int,
val uiEvent: UiEventLogger.UiEventEnum,
@StringRes val stringResId: Int?,
- val isMidTransfer: Boolean = false,
- val isTransferFailure: Boolean = false,
+ val transferStatus: TransferStatus,
val timeout: Long = DEFAULT_TIMEOUT_MILLIS
) {
/**
@@ -56,6 +54,7 @@
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST,
R.string.media_move_closer_to_start_cast,
+ transferStatus = TransferStatus.NOT_STARTED,
),
/**
@@ -68,6 +67,7 @@
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST,
R.string.media_move_closer_to_end_cast,
+ transferStatus = TransferStatus.NOT_STARTED,
),
/**
@@ -78,7 +78,7 @@
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED,
R.string.media_transfer_playing_different_device,
- isMidTransfer = true,
+ transferStatus = TransferStatus.IN_PROGRESS,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
),
@@ -90,7 +90,7 @@
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
R.string.media_transfer_playing_this_device,
- isMidTransfer = true,
+ transferStatus = TransferStatus.IN_PROGRESS,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
),
@@ -100,7 +100,8 @@
TRANSFER_TO_RECEIVER_SUCCEEDED(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED,
- R.string.media_transfer_playing_different_device
+ R.string.media_transfer_playing_different_device,
+ transferStatus = TransferStatus.SUCCEEDED,
) {
override fun undoClickListener(
controllerSender: MediaTttChipControllerSender,
@@ -135,7 +136,8 @@
TRANSFER_TO_THIS_DEVICE_SUCCEEDED(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
- R.string.media_transfer_playing_this_device
+ R.string.media_transfer_playing_this_device,
+ transferStatus = TransferStatus.SUCCEEDED,
) {
override fun undoClickListener(
controllerSender: MediaTttChipControllerSender,
@@ -169,7 +171,7 @@
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED,
R.string.media_transfer_failed,
- isTransferFailure = true
+ transferStatus = TransferStatus.FAILED,
),
/** A state representing that a transfer back to this device has failed. */
@@ -177,14 +179,15 @@
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED,
R.string.media_transfer_failed,
- isTransferFailure = true
+ transferStatus = TransferStatus.FAILED,
),
/** A state representing that this device is far away from any receiver device. */
FAR_FROM_RECEIVER(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER,
- stringResId = null
+ stringResId = null,
+ transferStatus = TransferStatus.TOO_FAR,
);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 0c1ebd7..e539f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -34,6 +34,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
@@ -57,7 +58,7 @@
configurationController: ConfigurationController,
powerManager: PowerManager,
private val uiEventLogger: MediaTttSenderUiEventLogger
-) : TemporaryViewDisplayController<ChipSenderInfo>(
+) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
context,
logger,
windowManager,
@@ -66,6 +67,8 @@
configurationController,
powerManager,
R.layout.media_ttt_chip,
+ MediaTttUtils.WINDOW_TITLE,
+ MediaTttUtils.WAKE_REASON,
) {
override val windowLayoutParams = commonWindowLayoutParams.apply {
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
@@ -94,7 +97,7 @@
) {
val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState)
val stateName = chipState?.name ?: "Invalid"
- logger.logStateChange(stateName, routeInfo.id)
+ logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
if (chipState == null) {
Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
@@ -103,7 +106,7 @@
uiEventLogger.logSenderStateChange(chipState)
if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
- removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER::class.simpleName!!)
+ removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER.name)
} else {
displayView(ChipSenderInfo(chipState, routeInfo, undoCallback))
}
@@ -118,7 +121,14 @@
val chipState = newInfo.state
// App icon
- val iconName = setIcon(currentView, newInfo.routeInfo.clientPackageName)
+ val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
+ context, newInfo.routeInfo.clientPackageName, logger
+ )
+ MediaTttUtils.setIcon(
+ currentView.requireViewById(R.id.app_icon),
+ iconInfo.drawable,
+ iconInfo.contentDescription
+ )
// Text
val otherDeviceName = newInfo.routeInfo.name.toString()
@@ -127,7 +137,7 @@
// Loading
currentView.requireViewById<View>(R.id.loading).visibility =
- chipState.isMidTransfer.visibleIfTrue()
+ (chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue()
// Undo
val undoView = currentView.requireViewById<View>(R.id.undo)
@@ -139,12 +149,12 @@
// Failure
currentView.requireViewById<View>(R.id.failure_icon).visibility =
- chipState.isTransferFailure.visibleIfTrue()
+ (chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue()
// For accessibility
currentView.requireViewById<ViewGroup>(
R.id.media_ttt_sender_chip_inner
- ).contentDescription = "$iconName $chipText"
+ ).contentDescription = "${iconInfo.contentDescription} $chipText"
}
override fun animateViewIn(view: ViewGroup) {
@@ -162,10 +172,17 @@
}
override fun removeView(removalReason: String) {
- // Don't remove the chip if we're mid-transfer since the user should still be able to
- // see the status of the transfer. (But do remove it if it's finally timed out.)
- if (info?.state?.isMidTransfer == true &&
- removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT) {
+ // Don't remove the chip if we're in progress or succeeded, since the user should still be
+ // able to see the status of the transfer. (But do remove it if it's finally timed out.)
+ val transferStatus = info?.state?.transferStatus
+ if (
+ (transferStatus == TransferStatus.IN_PROGRESS ||
+ transferStatus == TransferStatus.SUCCEEDED) &&
+ removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT
+ ) {
+ logger.logRemovalBypass(
+ removalReason, bypassReason = "transferStatus=${transferStatus.name}"
+ )
return
}
super.removeView(removalReason)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
new file mode 100644
index 0000000..f15720d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.sender
+
+/** Represents the different possible transfer states that we could be in. */
+enum class TransferStatus {
+ /** The transfer hasn't started yet. */
+ NOT_STARTED,
+ /** The transfer is currently ongoing but hasn't completed yet. */
+ IN_PROGRESS,
+ /** The transfer has completed successfully. */
+ SUCCEEDED,
+ /** The transfer has completed with a failure. */
+ FAILED,
+ /** The device is too far away to do a transfer. */
+ TOO_FAR,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 5842665..e9a6c25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -50,6 +50,7 @@
protected int mIconSizePx;
private boolean mAnimationEnabled = true;
private int mState = -1;
+ private boolean mDisabledByPolicy = false;
private int mTint;
@Nullable
private QSTile.Icon mLastIcon;
@@ -159,14 +160,10 @@
}
protected void setIcon(ImageView iv, QSTile.State state, boolean allowAnimations) {
- if (state.disabledByPolicy) {
- iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color));
- } else {
- iv.clearColorFilter();
- }
- if (state.state != mState) {
+ if (state.state != mState || state.disabledByPolicy != mDisabledByPolicy) {
int color = getColor(state);
mState = state.state;
+ mDisabledByPolicy = state.disabledByPolicy;
if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
} else {
@@ -241,8 +238,8 @@
*/
private static int getIconColorForState(Context context, QSTile.State state) {
if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) {
- return Utils.applyAlpha(QSTileViewImpl.UNAVAILABLE_ALPHA,
- Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary));
+ return Utils.getColorAttrDefaultColor(
+ context, com.android.internal.R.attr.textColorTertiary);
} else if (state.state == Tile.STATE_INACTIVE) {
return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
} else if (state.state == Tile.STATE_ACTIVE) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 163ee2a..972b243 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -100,14 +100,15 @@
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent)
private val colorLabelInactive =
Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
- private val colorLabelUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorLabelInactive)
+ private val colorLabelUnavailable =
+ Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary)
private val colorSecondaryLabelActive =
Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse)
private val colorSecondaryLabelInactive =
Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary)
private val colorSecondaryLabelUnavailable =
- Utils.applyAlpha(UNAVAILABLE_ALPHA, colorSecondaryLabelInactive)
+ Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary)
private lateinit var label: TextView
protected lateinit var secondaryLabel: TextView
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 408c61f..e06c977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -19,9 +19,6 @@
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
-import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_FIRST_FRAME_RECEIVED;
-import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_GOOD;
-import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
import static android.hardware.biometrics.BiometricSourceType.FACE;
import static android.view.View.GONE;
@@ -53,6 +50,7 @@
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
+import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
@@ -82,7 +80,7 @@
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
-import com.android.systemui.biometrics.BiometricMessageDeferral;
+import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -184,6 +182,7 @@
private long mChargingTimeRemaining;
private String mBiometricErrorMessageToShowOnScreenOn;
private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow;
+ private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral;
private boolean mInited;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -233,7 +232,8 @@
LockPatternUtils lockPatternUtils,
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
- AccessibilityManager accessibilityManager) {
+ AccessibilityManager accessibilityManager,
+ FaceHelpMessageDeferral faceHelpMessageDeferral) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mDevicePolicyManager = devicePolicyManager;
@@ -254,6 +254,7 @@
mScreenLifecycle = screenLifecycle;
mScreenLifecycle.addObserver(mScreenObserver);
+ mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
int[] msgIds = context.getResources().getIntArray(
com.android.systemui.R.array.config_face_help_msgs_when_fingerprint_enrolled);
@@ -1041,8 +1042,22 @@
}
@Override
+ public void onBiometricAcquired(BiometricSourceType biometricSourceType, int acquireInfo) {
+ if (biometricSourceType == FACE) {
+ mFaceAcquiredMessageDeferral.processFrame(acquireInfo);
+ }
+ }
+
+ @Override
public void onBiometricHelp(int msgId, String helpString,
BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == FACE) {
+ mFaceAcquiredMessageDeferral.updateMessage(msgId, helpString);
+ if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) {
+ return;
+ }
+ }
+
// TODO(b/141025588): refactor to reduce repetition of code/comments
// Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
// as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
@@ -1053,17 +1068,6 @@
return;
}
- if (biometricSourceType == FACE) {
- if (msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED) {
- mFaceAcquiredMessageDeferral.reset();
- } else {
- mFaceAcquiredMessageDeferral.processMessage(msgId, helpString);
- if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) {
- return;
- }
- }
- }
-
final boolean faceAuthSoftError = biometricSourceType == FACE
&& msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
final boolean faceAuthFailed = biometricSourceType == FACE
@@ -1109,11 +1113,23 @@
}
@Override
+ public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == FACE) {
+ mFaceAcquiredMessageDeferral.reset();
+ }
+ }
+
+ @Override
public void onBiometricError(int msgId, String errString,
BiometricSourceType biometricSourceType) {
CharSequence deferredFaceMessage = null;
if (biometricSourceType == FACE) {
- deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
+ if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) {
+ deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
+ if (DEBUG) {
+ Log.d(TAG, "showDeferredFaceMessage msgId=" + deferredFaceMessage);
+ }
+ }
mFaceAcquiredMessageDeferral.reset();
}
@@ -1308,14 +1324,4 @@
}
}
};
-
- private final BiometricMessageDeferral mFaceAcquiredMessageDeferral =
- new BiometricMessageDeferral(
- Set.of(
- FACE_ACQUIRED_GOOD,
- FACE_ACQUIRED_START,
- FACE_ACQUIRED_FIRST_FRAME_RECEIVED
- ),
- Set.of(FACE_ACQUIRED_TOO_DARK)
- );
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 734eeec..a52e2af 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -19,12 +19,10 @@
import android.annotation.LayoutRes
import android.annotation.SuppressLint
import android.content.Context
-import android.content.pm.PackageManager
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.os.SystemClock
-import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.WindowManager
@@ -33,11 +31,7 @@
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
import androidx.annotation.CallSuper
-import com.android.internal.widget.CachingIconView
-import com.android.settingslib.Utils
-import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -50,17 +44,22 @@
* The generic type T is expected to contain all the information necessary for the subclasses to
* display the view in a certain state, since they receive <T> in [updateView].
*
- * TODO(b/245610654): Remove all the media-specific logic from this class.
+ * @property windowTitle the title to use for the window that displays the temporary view. Should be
+ * normally cased, like "Window Title".
+ * @property wakeReason a string used for logging if we needed to wake the screen in order to
+ * display the temporary view. Should be screaming snake cased, like WAKE_REASON.
*/
-abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>(
+abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
internal val context: Context,
- internal val logger: MediaTttLogger,
+ internal val logger: U,
internal val windowManager: WindowManager,
@Main private val mainExecutor: DelayableExecutor,
private val accessibilityManager: AccessibilityManager,
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
@LayoutRes private val viewLayoutRes: Int,
+ private val windowTitle: String,
+ private val wakeReason: String,
) {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -72,7 +71,7 @@
height = WindowManager.LayoutParams.WRAP_CONTENT
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- title = WINDOW_TITLE
+ title = windowTitle
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
@@ -115,10 +114,10 @@
powerManager.wakeUp(
SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION,
- "com.android.systemui:media_tap_to_transfer_activated"
+ "com.android.systemui:$wakeReason",
)
}
-
+ logger.logChipAddition()
inflateAndUpdateView(newInfo)
}
@@ -192,80 +191,8 @@
* appears.
*/
open fun animateViewIn(view: ViewGroup) {}
-
- /**
- * Returns the size that the icon should be, or null if no size override is needed.
- */
- open fun getIconSize(isAppIcon: Boolean): Int? = null
-
- /**
- * An internal method to set the icon on the view.
- *
- * This is in the common superclass since both the sender and the receiver show an icon.
- *
- * @param appPackageName the package name of the app playing the media. Will be used to fetch
- * the app icon and app name if overrides aren't provided.
- *
- * @return the content description of the icon.
- */
- internal fun setIcon(
- currentView: ViewGroup,
- appPackageName: String?,
- appIconDrawableOverride: Drawable? = null,
- appNameOverride: CharSequence? = null,
- ): CharSequence {
- val appIconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
- val iconInfo = getIconInfo(appPackageName)
-
- getIconSize(iconInfo.isAppIcon)?.let { size ->
- val lp = appIconView.layoutParams
- lp.width = size
- lp.height = size
- appIconView.layoutParams = lp
- }
-
- appIconView.contentDescription = appNameOverride ?: iconInfo.iconName
- appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon)
- return appIconView.contentDescription
- }
-
- /**
- * Returns the information needed to display the icon.
- *
- * The information will either contain app name and icon of the app playing media, or a default
- * name and icon if we can't find the app name/icon.
- */
- private fun getIconInfo(appPackageName: String?): IconInfo {
- if (appPackageName != null) {
- try {
- return IconInfo(
- iconName = context.packageManager.getApplicationInfo(
- appPackageName, PackageManager.ApplicationInfoFlags.of(0)
- ).loadLabel(context.packageManager).toString(),
- icon = context.packageManager.getApplicationIcon(appPackageName),
- isAppIcon = true
- )
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "Cannot find package $appPackageName", e)
- }
- }
- return IconInfo(
- iconName = context.getString(R.string.media_output_dialog_unknown_launch_app_name),
- icon = context.resources.getDrawable(R.drawable.ic_cast).apply {
- this.setTint(
- Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
- )
- },
- isAppIcon = false
- )
- }
}
-// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
-// UpdateMediaTapToTransferReceiverDisplayTest
-private const val WINDOW_TITLE = "Media Transfer Chip View"
-private val TAG = TemporaryViewDisplayController::class.simpleName!!
-
object TemporaryDisplayRemovalReason {
const val REASON_TIMEOUT = "TIMEOUT"
const val REASON_SCREEN_TAP = "SCREEN_TAP"
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
new file mode 100644
index 0000000..606a11a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+
+/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
+open class TemporaryViewLogger(
+ internal val buffer: LogBuffer,
+ internal val tag: String,
+) {
+ /** Logs that we added the chip to a new window. */
+ fun logChipAddition() {
+ buffer.log(tag, LogLevel.DEBUG, {}, { "Chip added" })
+ }
+
+ /** Logs that we removed the chip for the given [reason]. */
+ fun logChipRemoval(reason: String) {
+ buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" })
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt
deleted file mode 100644
index 419fedf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricMessageDeferralTest.kt
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNull
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class BiometricMessageDeferralTest : SysuiTestCase() {
-
- @Test
- fun testProcessNoMessages_noDeferredMessage() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf())
-
- assertNull(biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testProcessNonDeferredMessages_noDeferredMessage() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2))
-
- // WHEN there are no deferred messages processed
- for (i in 0..3) {
- biometricMessageDeferral.processMessage(4, "test")
- }
-
- // THEN getDeferredMessage is null
- assertNull(biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testAllProcessedMessagesWereDeferred() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1))
-
- // WHEN all the processed messages are a deferred message
- for (i in 0..3) {
- biometricMessageDeferral.processMessage(1, "test")
- }
-
- // THEN deferredMessage will return the string associated with the deferred msgId
- assertEquals("test", biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testReturnsMostFrequentDeferredMessage() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2))
-
- // WHEN there's two msgId=1 processed and one msgId=2 processed
- biometricMessageDeferral.processMessage(1, "msgId-1")
- biometricMessageDeferral.processMessage(1, "msgId-1")
- biometricMessageDeferral.processMessage(1, "msgId-1")
- biometricMessageDeferral.processMessage(2, "msgId-2")
-
- // THEN the most frequent deferred message is that meets the threshold is returned
- assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testDeferredMessage_mustMeetThreshold() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1))
-
- // WHEN more nonDeferredMessages are shown than the deferred message
- val totalMessages = 10
- val nonDeferredMessagesCount =
- (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1
- for (i in 0 until nonDeferredMessagesCount) {
- biometricMessageDeferral.processMessage(4, "non-deferred-msg")
- }
- for (i in nonDeferredMessagesCount until totalMessages) {
- biometricMessageDeferral.processMessage(1, "msgId-1")
- }
-
- // THEN there's no deferred message because it didn't meet the threshold
- assertNull(biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testDeferredMessage_manyExcludedMessages_getDeferredMessage() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1))
-
- // WHEN more excludedMessages are shown than the deferred message
- val totalMessages = 10
- val excludedMessagesCount = (totalMessages * BiometricMessageDeferral.THRESHOLD).toInt() + 1
- for (i in 0 until excludedMessagesCount) {
- biometricMessageDeferral.processMessage(3, "excluded-msg")
- }
- for (i in excludedMessagesCount until totalMessages) {
- biometricMessageDeferral.processMessage(1, "msgId-1")
- }
-
- // THEN there IS a deferred message because the deferred msg meets the threshold amongst the
- // non-excluded messages
- assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testResetClearsOutCounts() {
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(), setOf(1, 2))
-
- // GIVEN two msgId=1 events processed
- biometricMessageDeferral.processMessage(1, "msgId-1")
- biometricMessageDeferral.processMessage(1, "msgId-1")
-
- // WHEN counts are reset and then a single deferred message is processed (msgId=2)
- biometricMessageDeferral.reset()
- biometricMessageDeferral.processMessage(2, "msgId-2")
-
- // THEN msgId-2 is the deferred message since the two msgId=1 events were reset
- assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage())
- }
-
- @Test
- fun testShouldDefer() {
- // GIVEN should defer msgIds 1 and 2
- val biometricMessageDeferral = BiometricMessageDeferral(setOf(3), setOf(1, 2))
-
- // THEN shouldDefer returns true for ids 1 & 2
- assertTrue(biometricMessageDeferral.shouldDefer(1))
- assertTrue(biometricMessageDeferral.shouldDefer(2))
-
- // THEN should defer returns false for ids 3 & 4
- assertFalse(biometricMessageDeferral.shouldDefer(3))
- assertFalse(biometricMessageDeferral.shouldDefer(4))
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
new file mode 100644
index 0000000..c9ccdb3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.BiometricMessageDeferralLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FaceHelpMessageDeferralTest : SysuiTestCase() {
+ val threshold = .75f
+ @Mock lateinit var logger: BiometricMessageDeferralLogger
+ @Mock lateinit var dumpManager: DumpManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testProcessFrame_logs() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+ biometricMessageDeferral.processFrame(1)
+ verify(logger).logFrameProcessed(1, 1, "1")
+ }
+
+ @Test
+ fun testUpdateMessage_logs() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+ biometricMessageDeferral.updateMessage(1, "hi")
+ verify(logger).logUpdateMessage(1, "hi")
+ }
+
+ @Test
+ fun testReset_logs() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+ biometricMessageDeferral.reset()
+ verify(logger).reset()
+ }
+
+ @Test
+ fun testProcessNoMessages_noDeferredMessage() {
+ val biometricMessageDeferral = createMsgDeferral(emptySet())
+
+ assertNull(biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testProcessNonDeferredMessages_noDeferredMessage() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // WHEN there are no deferred messages processed
+ for (i in 0..3) {
+ biometricMessageDeferral.processFrame(4)
+ biometricMessageDeferral.updateMessage(4, "test")
+ }
+
+ // THEN getDeferredMessage is null
+ assertNull(biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testProcessMessagesWithDeferredMessage_deferredMessageWasNeverGivenAString() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ biometricMessageDeferral.processFrame(1)
+
+ assertNull(biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testAllProcessedMessagesWereDeferred() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+
+ // WHEN all the processed messages are a deferred message
+ for (i in 0..3) {
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.updateMessage(1, "test")
+ }
+
+ // THEN deferredMessage will return the string associated with the deferred msgId
+ assertEquals("test", biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testReturnsMostFrequentDeferredMessage() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // WHEN there's 80%of the messages are msgId=1 and 20% is msgId=2
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+
+ biometricMessageDeferral.processFrame(2)
+ biometricMessageDeferral.updateMessage(2, "msgId-2")
+
+ // THEN the most frequent deferred message is that meets the threshold is returned
+ assertEquals("msgId-1", biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testDeferredMessage_mustMeetThreshold() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1))
+
+ // WHEN more nonDeferredMessages are shown than the deferred message
+ val totalMessages = 10
+ val nonDeferredMessagesCount = (totalMessages * threshold).toInt() + 1
+ for (i in 0 until nonDeferredMessagesCount) {
+ biometricMessageDeferral.processFrame(4)
+ biometricMessageDeferral.updateMessage(4, "non-deferred-msg")
+ }
+ for (i in nonDeferredMessagesCount until totalMessages) {
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+ }
+
+ // THEN there's no deferred message because it didn't meet the threshold
+ assertNull(biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testResetClearsOutCounts() {
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // GIVEN two msgId=1 events processed
+ biometricMessageDeferral.processFrame(
+ 1,
+ )
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+ biometricMessageDeferral.processFrame(1)
+ biometricMessageDeferral.updateMessage(1, "msgId-1")
+
+ // WHEN counts are reset and then a single deferred message is processed (msgId=2)
+ biometricMessageDeferral.reset()
+ biometricMessageDeferral.processFrame(2)
+ biometricMessageDeferral.updateMessage(2, "msgId-2")
+
+ // THEN msgId-2 is the deferred message since the two msgId=1 events were reset
+ assertEquals("msgId-2", biometricMessageDeferral.getDeferredMessage())
+ }
+
+ @Test
+ fun testShouldDefer() {
+ // GIVEN should defer msgIds 1 and 2
+ val biometricMessageDeferral = createMsgDeferral(setOf(1, 2))
+
+ // THEN shouldDefer returns true for ids 1 & 2
+ assertTrue(biometricMessageDeferral.shouldDefer(1))
+ assertTrue(biometricMessageDeferral.shouldDefer(2))
+
+ // THEN should defer returns false for ids 3 & 4
+ assertFalse(biometricMessageDeferral.shouldDefer(3))
+ assertFalse(biometricMessageDeferral.shouldDefer(4))
+ }
+
+ private fun createMsgDeferral(messagesToDefer: Set<Int>): BiometricMessageDeferral {
+ return BiometricMessageDeferral(messagesToDefer, threshold, logger, dumpManager)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
index d95e5c4..1078cda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
@@ -23,11 +23,11 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.mock
-import java.io.PrintWriter
-import java.io.StringWriter
@SmallTest
class MediaTttLoggerTest : SysuiTestCase() {
@@ -43,32 +43,46 @@
}
@Test
- fun logStateChange_bufferHasDeviceTypeTagAndStateNameAndId() {
+ fun logStateChange_bufferHasDeviceTypeTagAndParamInfo() {
val stateName = "test state name"
val id = "test id"
+ val packageName = "this.is.a.package"
- logger.logStateChange(stateName, id)
+ logger.logStateChange(stateName, id, packageName)
- val stringWriter = StringWriter()
- buffer.dump(PrintWriter(stringWriter), tailLength = 0)
- val actualString = stringWriter.toString()
-
+ val actualString = getStringFromBuffer()
assertThat(actualString).contains(DEVICE_TYPE_TAG)
assertThat(actualString).contains(stateName)
assertThat(actualString).contains(id)
+ assertThat(actualString).contains(packageName)
}
@Test
- fun logChipRemoval_bufferHasDeviceTypeAndReason() {
- val reason = "test reason"
- logger.logChipRemoval(reason)
+ fun logPackageNotFound_bufferHasPackageName() {
+ val packageName = "this.is.a.package"
+ logger.logPackageNotFound(packageName)
+
+ val actualString = getStringFromBuffer()
+ assertThat(actualString).contains(packageName)
+ }
+
+ @Test
+ fun logRemovalBypass_bufferHasReasons() {
+ val removalReason = "fakeRemovalReason"
+ val bypassReason = "fakeBypassReason"
+
+ logger.logRemovalBypass(removalReason, bypassReason)
+
+ val actualString = getStringFromBuffer()
+ assertThat(actualString).contains(removalReason)
+ assertThat(actualString).contains(bypassReason)
+ }
+
+ private fun getStringFromBuffer(): String {
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
- val actualString = stringWriter.toString()
-
- assertThat(actualString).contains(DEVICE_TYPE_TAG)
- assertThat(actualString).contains(reason)
+ return stringWriter.toString()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
new file mode 100644
index 0000000..37f6434
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.common
+
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.CachingIconView
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MediaTttUtilsTest : SysuiTestCase() {
+
+ private lateinit var appIconFromPackageName: Drawable
+ @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var applicationInfo: ApplicationInfo
+ @Mock private lateinit var logger: MediaTttLogger
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ // Set up our package manager to give valid information for [PACKAGE_NAME] only
+ appIconFromPackageName = context.getDrawable(R.drawable.ic_cake)!!
+ whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(appIconFromPackageName)
+ whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
+ whenever(
+ packageManager.getApplicationInfo(any(), any<PackageManager.ApplicationInfoFlags>())
+ )
+ .thenThrow(PackageManager.NameNotFoundException())
+ whenever(
+ packageManager.getApplicationInfo(
+ Mockito.eq(PACKAGE_NAME),
+ any<PackageManager.ApplicationInfoFlags>()
+ )
+ )
+ .thenReturn(applicationInfo)
+ context.setMockPackageManager(packageManager)
+ }
+
+ @Test
+ fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
+ val iconInfo =
+ MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger)
+
+ assertThat(iconInfo.isAppIcon).isFalse()
+ assertThat(iconInfo.contentDescription)
+ .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
+ }
+
+ @Test
+ fun getIconInfoFromPackageName_invalidPackageName_returnsDefault() {
+ val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger)
+
+ assertThat(iconInfo.isAppIcon).isFalse()
+ assertThat(iconInfo.contentDescription)
+ .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
+ }
+
+ @Test
+ fun getIconInfoFromPackageName_validPackageName_returnsAppInfo() {
+ val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger)
+
+ assertThat(iconInfo.isAppIcon).isTrue()
+ assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName)
+ assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME)
+ }
+
+ @Test
+ fun setIcon_viewHasIconAndContentDescription() {
+ val view = CachingIconView(context)
+ val icon = context.getDrawable(R.drawable.ic_celebration)!!
+ val contentDescription = "Happy birthday!"
+
+ MediaTttUtils.setIcon(view, icon, contentDescription)
+
+ assertThat(view.drawable).isEqualTo(icon)
+ assertThat(view.contentDescription).isEqualTo(contentDescription)
+ }
+
+ @Test
+ fun setIcon_iconSizeNull_viewSizeDoesNotChange() {
+ val view = CachingIconView(context)
+ val size = 456
+ view.layoutParams = FrameLayout.LayoutParams(size, size)
+
+ MediaTttUtils.setIcon(view, context.getDrawable(R.drawable.ic_cake)!!, "desc")
+
+ assertThat(view.layoutParams.width).isEqualTo(size)
+ assertThat(view.layoutParams.height).isEqualTo(size)
+ }
+
+ @Test
+ fun setIcon_iconSizeProvided_viewSizeUpdates() {
+ val view = CachingIconView(context)
+ val size = 456
+ view.layoutParams = FrameLayout.LayoutParams(size, size)
+
+ val newSize = 40
+ MediaTttUtils.setIcon(
+ view,
+ context.getDrawable(R.drawable.ic_cake)!!,
+ "desc",
+ iconSize = newSize
+ )
+
+ assertThat(view.layoutParams.width).isEqualTo(newSize)
+ assertThat(view.layoutParams.height).isEqualTo(newSize)
+ }
+}
+
+private const val PACKAGE_NAME = "com.android.systemui"
+private const val APP_NAME = "Fake App Name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index e7b4593..d41ad48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -173,37 +173,72 @@
null
)
- verify(logger).logStateChange(any(), any())
+ verify(logger).logStateChange(any(), any(), any())
}
@Test
- fun setIcon_isAppIcon_usesAppIconSize() {
- controllerReceiver.displayView(getChipReceiverInfo())
+ fun updateView_noOverrides_usesInfoFromAppIcon() {
+ controllerReceiver.displayView(
+ ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride = null)
+ )
+
+ val view = getChipView()
+ assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ }
+
+ @Test
+ fun updateView_appIconOverride_usesOverride() {
+ val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!!
+
+ controllerReceiver.displayView(
+ ChipReceiverInfo(routeInfo, drawableOverride, appNameOverride = null)
+ )
+
+ val view = getChipView()
+ assertThat(view.getAppIconView().drawable).isEqualTo(drawableOverride)
+ }
+
+ @Test
+ fun updateView_appNameOverride_usesOverride() {
+ val appNameOverride = "Sweet New App"
+
+ controllerReceiver.displayView(
+ ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride)
+ )
+
+ val view = getChipView()
+ assertThat(view.getAppIconView().contentDescription).isEqualTo(appNameOverride)
+ }
+
+ @Test
+ fun updateView_isAppIcon_usesAppIconSize() {
+ controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME))
val chipView = getChipView()
- controllerReceiver.setIcon(chipView, PACKAGE_NAME)
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
- val expectedSize = controllerReceiver.getIconSize(isAppIcon = true)
+ val expectedSize =
+ context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver)
assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
}
@Test
- fun setIcon_notAppIcon_usesGenericIconSize() {
- controllerReceiver.displayView(getChipReceiverInfo())
+ fun updateView_notAppIcon_usesGenericIconSize() {
+ controllerReceiver.displayView(getChipReceiverInfo(packageName = null))
val chipView = getChipView()
- controllerReceiver.setIcon(chipView, appPackageName = null)
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
- val expectedSize = controllerReceiver.getIconSize(isAppIcon = false)
+ val expectedSize =
+ context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_size_receiver)
assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize)
assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize)
}
@@ -226,8 +261,13 @@
return viewCaptor.value as ViewGroup
}
- private fun getChipReceiverInfo(): ChipReceiverInfo =
- ChipReceiverInfo(routeInfo, null, null)
+ private fun getChipReceiverInfo(packageName: String?): ChipReceiverInfo {
+ val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
+ .addFeature("feature")
+ .setClientPackageName(packageName)
+ .build()
+ return ChipReceiverInfo(routeInfo, null, null)
+ }
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 52b6eed..ff0faf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -299,7 +299,7 @@
null
)
- verify(logger).logStateChange(any(), any())
+ verify(logger).logStateChange(any(), any(), any())
}
@Test
@@ -587,15 +587,29 @@
fakeExecutor.runAllReady()
verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
}
@Test
- fun transferToReceiverTriggeredThenFarFromReceiver_eventuallyTimesOut() {
- val state = transferToReceiverTriggered()
- controllerSender.displayView(state)
- fakeClock.advanceTime(1000L)
- controllerSender.removeView("fakeRemovalReason")
+ fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayed() {
+ controllerSender.displayView(transferToReceiverTriggered())
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+ }
+
+ @Test
+ fun transferToReceiverTriggeredThenRemoveView_eventuallyTimesOut() {
+ controllerSender.displayView(transferToReceiverTriggered())
+
+ controllerSender.removeView("fakeRemovalReason")
fakeClock.advanceTime(TIMEOUT + 1L)
verify(windowManager).removeView(any())
@@ -610,20 +624,106 @@
fakeExecutor.runAllReady()
verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
}
@Test
- fun transferToThisDeviceTriggeredThenFarFromReceiver_eventuallyTimesOut() {
- val state = transferToThisDeviceTriggered()
- controllerSender.displayView(state)
- fakeClock.advanceTime(1000L)
- controllerSender.removeView("fakeRemovalReason")
+ fun transferToThisDeviceTriggeredThenRemoveView_eventuallyTimesOut() {
+ controllerSender.displayView(transferToThisDeviceTriggered())
+ controllerSender.removeView("fakeRemovalReason")
fakeClock.advanceTime(TIMEOUT + 1L)
verify(windowManager).removeView(any())
}
+ @Test
+ fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayed() {
+ controllerSender.displayView(transferToThisDeviceTriggered())
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+ }
+
+ @Test
+ fun transferToReceiverSucceededThenRemoveView_viewStillDisplayed() {
+ controllerSender.displayView(transferToReceiverSucceeded())
+
+ controllerSender.removeView("fakeRemovalReason")
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+ }
+
+ @Test
+ fun transferToReceiverSucceededThenRemoveView_eventuallyTimesOut() {
+ controllerSender.displayView(transferToReceiverSucceeded())
+
+ controllerSender.removeView("fakeRemovalReason")
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayed() {
+ controllerSender.displayView(transferToReceiverSucceeded())
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+ }
+
+ @Test
+ fun transferToThisDeviceSucceededThenRemoveView_viewStillDisplayed() {
+ controllerSender.displayView(transferToThisDeviceSucceeded())
+
+ controllerSender.removeView("fakeRemovalReason")
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+ }
+
+ @Test
+ fun transferToThisDeviceSucceededThenRemoveView_eventuallyTimesOut() {
+ controllerSender.displayView(transferToThisDeviceSucceeded())
+
+ controllerSender.removeView("fakeRemovalReason")
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayed() {
+ controllerSender.displayView(transferToThisDeviceSucceeded())
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+ }
+
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
private fun ViewGroup.getChipText(): String =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 74e2747..464dfe2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -19,8 +19,10 @@
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
@@ -86,6 +88,7 @@
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardIndication;
@@ -167,6 +170,8 @@
@Mock
private AccessibilityManager mAccessibilityManager;
@Mock
+ private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
+ @Mock
private ScreenLifecycle mScreenLifecycle;
@Captor
private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
@@ -259,7 +264,8 @@
mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils,
- mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager);
+ mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager,
+ mFaceHelpMessageDeferral);
mController.init();
mController.setIndicationArea(mIndicationArea);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -535,7 +541,7 @@
mController.setVisible(true);
mController.getKeyguardCallback().onBiometricHelp(
- KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message,
+ BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message,
BiometricSourceType.FACE);
verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
reset(mRotateTextViewController);
@@ -582,7 +588,7 @@
// WHEN there's a face not recognized message
mController.getKeyguardCallback().onBiometricHelp(
- KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
+ BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
message,
BiometricSourceType.FACE);
@@ -748,8 +754,10 @@
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
0)).thenReturn(false);
- // WHEN help message received
+ // WHEN help message received and deferred message is valid
final String helpString = "helpMsg";
+ when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString);
+ when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true);
mKeyguardUpdateMonitorCallback.onBiometricHelp(
BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK,
helpString,
@@ -777,8 +785,10 @@
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
0)).thenReturn(true);
- // WHEN help message received
+ // WHEN help message received and deferredMessage is valid
final String helpString = "helpMsg";
+ when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString);
+ when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true);
mKeyguardUpdateMonitorCallback.onBiometricHelp(
BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK,
helpString,
@@ -1126,7 +1136,6 @@
verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen);
}
-
@Test
public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() {
// GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled
@@ -1272,6 +1281,87 @@
verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
}
+ @Test
+ public void faceOnAcquired_processFrame() {
+ createController();
+
+ // WHEN face sends an acquired message
+ final int acquireInfo = 1;
+ mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo);
+
+ // THEN face help message deferral should process the acquired frame
+ verify(mFaceHelpMessageDeferral).processFrame(acquireInfo);
+ }
+
+ @Test
+ public void fingerprintOnAcquired_noProcessFrame() {
+ createController();
+
+ // WHEN fingerprint sends an acquired message
+ mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FINGERPRINT, 1);
+
+ // THEN face help message deferral should NOT process any acquired frames
+ verify(mFaceHelpMessageDeferral, never()).processFrame(anyInt());
+ }
+
+ @Test
+ public void onBiometricHelp_fingerprint_faceHelpMessageDeferralDoesNothing() {
+ createController();
+
+ // WHEN fingerprint sends an onBiometricHelp
+ mKeyguardUpdateMonitorCallback.onBiometricHelp(
+ 1,
+ "placeholder",
+ BiometricSourceType.FINGERPRINT);
+
+ // THEN face help message deferral is NOT: reset, updated, or checked for shouldDefer
+ verify(mFaceHelpMessageDeferral, never()).reset();
+ verify(mFaceHelpMessageDeferral, never()).updateMessage(anyInt(), anyString());
+ verify(mFaceHelpMessageDeferral, never()).shouldDefer(anyInt());
+ }
+
+ @Test
+ public void onBiometricFailed_resetFaceHelpMessageDeferral() {
+ createController();
+
+ // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
+ mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE);
+
+ // THEN face help message deferral is reset
+ verify(mFaceHelpMessageDeferral).reset();
+ }
+
+ @Test
+ public void onBiometricError_resetFaceHelpMessageDeferral() {
+ createController();
+
+ // WHEN face has an error
+ mKeyguardUpdateMonitorCallback.onBiometricError(4, "string",
+ BiometricSourceType.FACE);
+
+ // THEN face help message deferral is reset
+ verify(mFaceHelpMessageDeferral).reset();
+ }
+
+ @Test
+ public void onBiometricHelp_faceAcquiredInfo_faceHelpMessageDeferral() {
+ createController();
+
+ // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
+ final int msgId = 1;
+ final String helpString = "test";
+ mKeyguardUpdateMonitorCallback.onBiometricHelp(
+ msgId,
+ "test",
+ BiometricSourceType.FACE);
+
+ // THEN face help message deferral is NOT reset and message IS updated
+ verify(mFaceHelpMessageDeferral, never()).reset();
+ verify(mFaceHelpMessageDeferral).updateMessage(msgId, helpString);
+ }
+
+
+
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index e616c26..921b7ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -17,20 +17,14 @@
package com.android.systemui.temporarydisplay
import android.content.Context
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
import android.os.PowerManager
-import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
-import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -42,9 +36,7 @@
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
-import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -58,13 +50,8 @@
private lateinit var fakeClock: FakeSystemClock
private lateinit var fakeExecutor: FakeExecutor
- private lateinit var appIconFromPackageName: Drawable
@Mock
- private lateinit var packageManager: PackageManager
- @Mock
- private lateinit var applicationInfo: ApplicationInfo
- @Mock
- private lateinit var logger: MediaTttLogger
+ private lateinit var logger: TemporaryViewLogger
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
@@ -78,17 +65,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- appIconFromPackageName = context.getDrawable(R.drawable.ic_cake)!!
- whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(appIconFromPackageName)
- whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
- whenever(packageManager.getApplicationInfo(
- any(), any<PackageManager.ApplicationInfoFlags>()
- )).thenThrow(PackageManager.NameNotFoundException())
- whenever(packageManager.getApplicationInfo(
- eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
- )).thenReturn(applicationInfo)
- context.setMockPackageManager(packageManager)
-
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
.thenReturn(TIMEOUT_MS.toInt())
@@ -229,117 +205,8 @@
verify(windowManager, never()).removeView(any())
}
- @Test
- fun setIcon_nullAppIconDrawableAndNullPackageName_stillHasIcon() {
- underTest.displayView(getState())
- val view = getView()
-
- underTest.setIcon(view, appPackageName = null, appIconDrawableOverride = null)
-
- assertThat(view.getAppIconView().drawable).isNotNull()
- }
-
- @Test
- fun setIcon_nullAppIconDrawableAndInvalidPackageName_stillHasIcon() {
- underTest.displayView(getState())
- val view = getView()
-
- underTest.setIcon(
- view, appPackageName = "fakePackageName", appIconDrawableOverride = null
- )
-
- assertThat(view.getAppIconView().drawable).isNotNull()
- }
-
- @Test
- fun setIcon_nullAppIconDrawable_iconIsFromPackageName() {
- underTest.displayView(getState())
- val view = getView()
-
- underTest.setIcon(view, PACKAGE_NAME, appIconDrawableOverride = null, null)
-
- assertThat(view.getAppIconView().drawable).isEqualTo(appIconFromPackageName)
- }
-
- @Test
- fun setIcon_hasAppIconDrawable_iconIsDrawable() {
- underTest.displayView(getState())
- val view = getView()
-
- val drawable = context.getDrawable(R.drawable.ic_alarm)!!
- underTest.setIcon(view, PACKAGE_NAME, drawable, null)
-
- assertThat(view.getAppIconView().drawable).isEqualTo(drawable)
- }
-
- @Test
- fun setIcon_nullAppNameAndNullPackageName_stillHasContentDescription() {
- underTest.displayView(getState())
- val view = getView()
-
- underTest.setIcon(view, appPackageName = null, appNameOverride = null)
-
- assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty()
- }
-
- @Test
- fun setIcon_nullAppNameAndInvalidPackageName_stillHasContentDescription() {
- underTest.displayView(getState())
- val view = getView()
-
- underTest.setIcon(
- view, appPackageName = "fakePackageName", appNameOverride = null
- )
-
- assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty()
- }
-
- @Test
- fun setIcon_nullAppName_iconContentDescriptionIsFromPackageName() {
- underTest.displayView(getState())
- val view = getView()
-
- underTest.setIcon(view, PACKAGE_NAME, null, appNameOverride = null)
-
- assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- }
-
- @Test
- fun setIcon_hasAppName_iconContentDescriptionIsAppNameOverride() {
- underTest.displayView(getState())
- val view = getView()
-
- val appName = "Override App Name"
- underTest.setIcon(view, PACKAGE_NAME, null, appName)
-
- assertThat(view.getAppIconView().contentDescription).isEqualTo(appName)
- }
-
- @Test
- fun setIcon_iconSizeMatchesGetIconSize() {
- underTest.displayView(getState())
- val view = getView()
-
- underTest.setIcon(view, PACKAGE_NAME)
- view.measure(
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
- )
-
- assertThat(view.getAppIconView().measuredWidth).isEqualTo(ICON_SIZE)
- assertThat(view.getAppIconView().measuredHeight).isEqualTo(ICON_SIZE)
- }
-
private fun getState(name: String = "name") = ViewInfo(name)
- private fun getView(): ViewGroup {
- val viewCaptor = ArgumentCaptor.forClass(View::class.java)
- verify(windowManager).addView(viewCaptor.capture(), any())
- return viewCaptor.value as ViewGroup
- }
-
- private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
-
private fun getConfigurationListener(): ConfigurationListener {
val callbackCaptor = argumentCaptor<ConfigurationListener>()
verify(configurationController).addCallback(capture(callbackCaptor))
@@ -348,13 +215,13 @@
inner class TestController(
context: Context,
- logger: MediaTttLogger,
+ logger: TemporaryViewLogger,
windowManager: WindowManager,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
- ) : TemporaryViewDisplayController<ViewInfo>(
+ ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>(
context,
logger,
windowManager,
@@ -363,6 +230,8 @@
configurationController,
powerManager,
R.layout.media_ttt_chip,
+ "Window Title",
+ "WAKE_REASON",
) {
var mostRecentViewInfo: ViewInfo? = null
@@ -371,7 +240,6 @@
super.updateView(newInfo, currentView)
mostRecentViewInfo = newInfo
}
- override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
}
inner class ViewInfo(val name: String) : TemporaryViewInfo {
@@ -379,7 +247,4 @@
}
}
-private const val PACKAGE_NAME = "com.android.systemui"
-private const val APP_NAME = "Fake App Name"
private const val TIMEOUT_MS = 10000L
-private const val ICON_SIZE = 47
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
new file mode 100644
index 0000000..c9f2b4d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.LogcatEchoTracker
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito
+
+@SmallTest
+class TemporaryViewLoggerTest : SysuiTestCase() {
+ private lateinit var buffer: LogBuffer
+ private lateinit var logger: TemporaryViewLogger
+
+ @Before
+ fun setUp() {
+ buffer =
+ LogBufferFactory(DumpManager(), Mockito.mock(LogcatEchoTracker::class.java))
+ .create("buffer", 10)
+ logger = TemporaryViewLogger(buffer, TAG)
+ }
+
+ @Test
+ fun logChipAddition_bufferHasLog() {
+ logger.logChipAddition()
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains(TAG)
+ }
+
+ @Test
+ fun logChipRemoval_bufferHasTagAndReason() {
+ val reason = "test reason"
+ logger.logChipRemoval(reason)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains(TAG)
+ assertThat(actualString).contains(reason)
+ }
+}
+
+private const val TAG = "TestTag"
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 7ca6254..9f3f761 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -577,6 +577,14 @@
int filterCallingUid);
/**
+ * Resolves an exported activity intent, allowing instant apps to be resolved.
+ */
+ public abstract ResolveInfo resolveIntentExported(Intent intent, String resolvedType,
+ @PackageManager.ResolveInfoFlagsBits long flags,
+ @PrivateResolveFlags long privateResolveFlags, int userId, boolean resolveForStart,
+ int filterCallingUid);
+
+ /**
* Resolves a service intent, allowing instant apps to be resolved.
*/
public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 842498d..1b68aba 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -215,6 +215,7 @@
import android.appwidget.AppWidgetManagerInternal;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
@@ -585,6 +586,17 @@
private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
/**
+ * Apps targeting Android U and above will need to export components in order to invoke them
+ * through implicit intents.
+ *
+ * If a component is not exported and invoked, it will be removed from the list of receivers.
+ * This applies specifically to activities and broadcasts.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS = 229362273;
+
+ /**
* The maximum number of bytes that {@link #setProcessStateSummary} accepts.
*
* @see {@link android.app.ActivityManager#setProcessStateSummary(byte[])}
@@ -12379,6 +12391,58 @@
}
/**
+ * Filters out non-exported components in a given list of broadcast filters
+ * @param intent the original intent
+ * @param callingUid the calling UID
+ * @param query the list of broadcast filters
+ * @param platformCompat the instance of platform compat
+ */
+ private static void filterNonExportedComponents(Intent intent, int callingUid,
+ List query, PlatformCompat platformCompat, String callerPackage) {
+ if (query == null
+ || intent.getPackage() != null
+ || intent.getComponent() != null
+ || ActivityManager.canAccessUnexportedComponents(callingUid)) {
+ return;
+ }
+ for (int i = query.size() - 1; i >= 0; i--) {
+ String componentInfo;
+ ResolveInfo resolveInfo;
+ BroadcastFilter broadcastFilter;
+ if (query.get(i) instanceof ResolveInfo) {
+ resolveInfo = (ResolveInfo) query.get(i);
+ if (resolveInfo.getComponentInfo().exported) {
+ continue;
+ }
+ componentInfo = resolveInfo.getComponentInfo()
+ .getComponentName().flattenToShortString();
+ } else if (query.get(i) instanceof BroadcastFilter) {
+ broadcastFilter = (BroadcastFilter) query.get(i);
+ if (broadcastFilter.exported) {
+ continue;
+ }
+ componentInfo = broadcastFilter.packageName;
+ } else {
+ continue;
+ }
+ if (!platformCompat.isChangeEnabledByUid(
+ IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS, callingUid)) {
+ Slog.w(TAG, "Non-exported component not filtered out "
+ + "(will be filtered out once the app targets U+)- intent: "
+ + intent.getAction() + ", component: "
+ + componentInfo + ", sender: "
+ + callerPackage);
+ return;
+ }
+ Slog.w(TAG, "Non-exported component filtered out - intent: "
+ + intent.getAction() + ", component: "
+ + componentInfo + ", sender: "
+ + callerPackage);
+ query.remove(i);
+ }
+ }
+
+ /**
* Main code for cleaning up a process when it has gone away. This is
* called both as a result of the process dying, or directly when stopping
* a process when running in single process mode.
@@ -14217,6 +14281,8 @@
}
}
+ filterNonExportedComponents(intent, callingUid, registeredReceivers,
+ mPlatformCompat, callerPackage);
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
if (!ordered && NR > 0) {
// If we are not serializing this broadcast, then send the
@@ -14319,6 +14385,8 @@
if ((receivers != null && receivers.size() > 0)
|| resultTo != null) {
BroadcastQueue queue = broadcastQueueForIntent(intent);
+ filterNonExportedComponents(intent, callingUid, receivers,
+ mPlatformCompat, callerPackage);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index fd99f8f..4a7e631 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -43,6 +43,7 @@
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_VIBRATE;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -228,19 +229,13 @@
OP_PLAY_AUDIO,
OP_RECORD_AUDIO,
OP_CAMERA,
+ OP_VIBRATE,
};
private static final int MAX_UNFORWARDED_OPS = 10;
private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
- /*
- * TODO b/246429313
- * android.app.appops.cts.AppOpEventCollectionTest#switchUidStateWhileOpsAreRunning breaks when
- * set to true
- */
- private static final boolean USE_DELAYED_UID_STATE_CHANGES_FOR_ATTR_OP_UPDATES = false;
-
final Context mContext;
final AtomicFile mFile;
private final @Nullable File mNoteOpCallerStacktracesFile;
@@ -2073,28 +2068,22 @@
}
}
- if (USE_DELAYED_UID_STATE_CHANGES_FOR_ATTR_OP_UPDATES) {
- // Proposed behavior change, previously there were two rounds of UID state change
- // operations. It seems like there is no need for
- // attributedOp.onUidStateChanged(state) to update before settle time is reached,
- // which if true then this is the preferred route.
- if (uidState != null && uidState.pkgOps != null) {
- int numPkgs = uidState.pkgOps.size();
- for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
- Ops ops = uidState.pkgOps.valueAt(pkgNum);
+ if (uidState != null && uidState.pkgOps != null) {
+ int numPkgs = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
- int numOps = ops.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- Op op = ops.valueAt(opNum);
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(
- attributionNum);
+ int numAttributions = op.mAttributions.size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ AttributedOp attributedOp = op.mAttributions.valueAt(
+ attributionNum);
- attributedOp.onUidStateChanged(state);
- }
+ attributedOp.onUidStateChanged(state);
}
}
}
@@ -2115,33 +2104,6 @@
onUidStateChanged(uid,
AppOpsUidStateTracker.processStateToUidState(procState), false);
}
-
- if (!USE_DELAYED_UID_STATE_CHANGES_FOR_ATTR_OP_UPDATES) {
- UidState uidState = mUidStates.get(uid);
-
- if (uidState != null && uidState.pkgOps != null) {
- int numPkgs = uidState.pkgOps.size();
- for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
- Ops ops = uidState.pkgOps.valueAt(pkgNum);
-
- int numOps = ops.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(
- attributionNum);
-
- attributedOp.onUidStateChanged(
- AppOpsUidStateTracker.processStateToUidState(
- procState));
- }
- }
- }
- }
- }
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index a308c81..ca5bfb3 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -66,6 +66,7 @@
private SparseBooleanArray mVisibleAppWidget = new SparseBooleanArray();
private SparseBooleanArray mPendingVisibleAppWidget = new SparseBooleanArray();
private SparseLongArray mPendingCommitTime = new SparseLongArray();
+ private SparseBooleanArray mPendingGone = new SparseBooleanArray();
private ArrayMap<UidStateChangedCallback, Handler> mUidStateChangedCallbacks = new ArrayMap<>();
@@ -185,16 +186,6 @@
@Override
public void updateUidProcState(int uid, int procState, int capability) {
mEventLog.logUpdateUidProcState(uid, procState, capability);
- if (procState == PROCESS_STATE_NONEXISTENT) {
- mUidStates.delete(uid);
- mPendingUidStates.delete(uid);
- mCapability.delete(uid);
- mPendingCapability.delete(uid);
- mVisibleAppWidget.delete(uid);
- mPendingVisibleAppWidget.delete(uid);
- mPendingCommitTime.delete(uid);
- return;
- }
int uidState = processStateToUidState(procState);
@@ -205,7 +196,10 @@
mPendingUidStates.put(uid, uidState);
mPendingCapability.put(uid, capability);
- if (uidState < prevUidState
+ if (procState == PROCESS_STATE_NONEXISTENT) {
+ mPendingGone.put(uid, true);
+ commitUidPendingState(uid);
+ } else if (uidState < prevUidState
|| (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
&& prevUidState > UID_STATE_MAX_LAST_NON_RESTRICTED)) {
// We are moving to a more important state, or the new state may be in the
@@ -312,7 +306,7 @@
|| capability != pendingCapability
|| visibleAppWidget != pendingVisibleAppWidget) {
boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
- != pendingUidState > UID_STATE_MAX_LAST_NON_RESTRICTED
+ != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
|| capability != pendingCapability
|| visibleAppWidget != pendingVisibleAppWidget;
@@ -327,13 +321,20 @@
Handler h = mUidStateChangedCallbacks.valueAt(i);
h.sendMessage(PooledLambda.obtainMessage(UidStateChangedCallback::onUidStateChanged,
- cb, uid, uidState, foregroundChange));
+ cb, uid, pendingUidState, foregroundChange));
}
}
- mUidStates.put(uid, pendingUidState);
- mCapability.put(uid, pendingCapability);
- mVisibleAppWidget.put(uid, pendingVisibleAppWidget);
+ if (mPendingGone.get(uid, false)) {
+ mUidStates.delete(uid);
+ mCapability.delete(uid);
+ mVisibleAppWidget.delete(uid);
+ mPendingGone.delete(uid);
+ } else {
+ mUidStates.put(uid, pendingUidState);
+ mCapability.put(uid, pendingCapability);
+ mVisibleAppWidget.put(uid, pendingVisibleAppWidget);
+ }
mPendingUidStates.delete(uid);
mPendingCapability.delete(uid);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 6c211b6..3a037ed 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -35,6 +35,7 @@
import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
+import static android.os.Process.ROOT_UID;
import android.Manifest;
import android.annotation.NonNull;
@@ -1172,6 +1173,10 @@
}
private boolean validatePackageName(int uid, String packageName) {
+ // Root doesn't have a package name.
+ if (uid == ROOT_UID) {
+ return true;
+ }
if (packageName != null) {
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
if (packageNames != null) {
@@ -3488,6 +3493,17 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public void setDisplayIdToMirror(IBinder token, int displayId) {
+ synchronized (mSyncRoot) {
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (mVirtualDisplayAdapter != null) {
+ mVirtualDisplayAdapter.setDisplayIdToMirror(token,
+ display == null ? Display.INVALID_DISPLAY : displayId);
+ }
+ }
+ }
}
private static boolean isValidBrightness(float brightness) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 38728ce..20b82c3 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -161,6 +161,13 @@
}
}
+ void setDisplayIdToMirror(IBinder appToken, int displayId) {
+ VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
+ if (device != null) {
+ device.setDisplayIdToMirror(displayId);
+ }
+ }
+
public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
if (device != null) {
@@ -313,6 +320,15 @@
return mDisplayIdToMirror;
}
+ void setDisplayIdToMirror(int displayIdToMirror) {
+ if (mDisplayIdToMirror != displayIdToMirror) {
+ mDisplayIdToMirror = displayIdToMirror;
+ mInfo = null;
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
+ sendTraversalRequestLocked();
+ }
+ }
+
@Override
public boolean isWindowManagerMirroringLocked() {
return mIsWindowManagerMirroring;
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index ba71438..1c78528 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1098,6 +1098,7 @@
private void enforceActor(@NonNull OverlayIdentifier overlay, @NonNull String methodName,
int realUserId) throws SecurityException {
OverlayInfo overlayInfo = mImpl.getOverlayInfo(overlay, realUserId);
+
if (overlayInfo == null) {
throw new IllegalArgumentException("Unable to retrieve overlay information for "
+ overlay);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index ae305ca..8e672c3 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -519,6 +519,7 @@
throws OperationFailedException {
final OverlayIdentifier overlayIdentifier = new OverlayIdentifier(
info.packageName, info.overlayName);
+
final Set<PackageAndUser> updatedTargets = new ArraySet<>();
OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId);
if (oi != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 34d6d29..65e7ce1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -465,6 +465,20 @@
filterCallingUid);
}
+ /**
+ * @deprecated similar to {@link resolveIntent} but limits the matches to exported components.
+ */
+ @Override
+ @Deprecated
+ public final ResolveInfo resolveIntentExported(Intent intent, String resolvedType,
+ @PackageManager.ResolveInfoFlagsBits long flags,
+ @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
+ boolean resolveForStart, int filterCallingUid) {
+ return getResolveIntentHelper().resolveIntentInternal(snapshot(),
+ intent, resolvedType, flags, privateResolveFlags, userId, resolveForStart,
+ filterCallingUid, true);
+ }
+
@Override
@Deprecated
public final ResolveInfo resolveService(Intent intent, String resolvedType,
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index c2fd637..fada577 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -52,6 +52,7 @@
import com.android.internal.app.ResolverActivity;
import com.android.internal.util.ArrayUtils;
+import com.android.server.am.ActivityManagerService;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -100,6 +101,38 @@
mInstantAppInstallerActivitySupplier = instantAppInstallerActivitySupplier;
}
+ private static void filterNonExportedComponents(Intent intent, int filterCallingUid,
+ List<ResolveInfo> query, PlatformCompat platformCompat, Computer computer) {
+ if (query == null
+ || intent.getPackage() != null
+ || intent.getComponent() != null
+ || ActivityManager.canAccessUnexportedComponents(filterCallingUid)) {
+ return;
+ }
+ AndroidPackage caller = computer.getPackage(filterCallingUid);
+ String callerPackage = caller == null ? "Not specified" : caller.getPackageName();
+ for (int i = query.size() - 1; i >= 0; i--) {
+ if (!query.get(i).getComponentInfo().exported) {
+ if (!platformCompat.isChangeEnabledByUid(
+ ActivityManagerService.IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS,
+ filterCallingUid)) {
+ Slog.w(TAG, "Non-exported component not filtered out "
+ + "(will be filtered out once the app targets U+)- intent: "
+ + intent.getAction() + ", component: "
+ + query.get(i).getComponentInfo()
+ .getComponentName().flattenToShortString()
+ + ", starter: " + callerPackage);
+ return;
+ }
+ Slog.w(TAG, "Non-exported component filtered out - intent: "
+ + intent.getAction() + ", component: "
+ + query.get(i).getComponentInfo().getComponentName().flattenToShortString()
+ + ", starter: " + callerPackage);
+ query.remove(i);
+ }
+ }
+ }
+
/**
* Normally instant apps can only be resolved when they're visible to the caller.
* However, if {@code resolveForStart} is {@code true}, all instant apps are visible
@@ -109,6 +142,20 @@
@PackageManager.ResolveInfoFlagsBits long flags,
@PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
boolean resolveForStart, int filterCallingUid) {
+ return resolveIntentInternal(computer, intent, resolvedType, flags,
+ privateResolveFlags, userId, resolveForStart, filterCallingUid, false);
+ }
+
+ /**
+ * Normally instant apps can only be resolved when they're visible to the caller.
+ * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
+ * since we need to allow the system to start any installed application.
+ * Allows picking exported components only.
+ */
+ public ResolveInfo resolveIntentInternal(Computer computer, Intent intent, String resolvedType,
+ @PackageManager.ResolveInfoFlagsBits long flags,
+ @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
+ boolean resolveForStart, int filterCallingUid, boolean exportedComponentsOnly) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
@@ -124,6 +171,10 @@
final List<ResolveInfo> query = computer.queryIntentActivitiesInternal(intent,
resolvedType, flags, privateResolveFlags, filterCallingUid, userId,
resolveForStart, true /*allowDynamicSplits*/);
+ if (exportedComponentsOnly) {
+ filterNonExportedComponents(intent, filterCallingUid, query,
+ mPlatformCompat, computer);
+ }
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
final boolean queryMayBeFiltered =
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 4f5fd02..b620249 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -328,8 +328,10 @@
* <p>On most devices this call will be a no-op, but it will be used on devices that support
* multiple users on multiple displays (like automotives with passenger displays).
*
- * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} when a user is
- * started and it doesn't validate if the display exists.
+ * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
+ * check it. In fact, one of the intended clients for this method is
+ * {@code DisplayManagerService}, which will call it when a virtual display is created (another
+ * client is {@code UserController}, which will call it when a user is started).
*
*/
public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
@@ -340,8 +342,8 @@
* <p>On most devices this call will be a no-op, but it will be used on devices that support
* multiple users on multiple displays (like automotives with passenger displays).
*
- * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} when a user is
- * stopped.
+ * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
+ * is stopped) and {@code DisplayManagerService} (when a virtual display is destroyed).
*/
public abstract void unassignUserFromDisplay(@UserIdInt int userId);
@@ -361,11 +363,14 @@
* Returns the display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
* user is not assigned to any display.
*
- * <p>The current foreground user is associated with the
+ * <p>The current foreground user and its running profiles are associated with the
* {@link android.view.Display#DEFAULT_DISPLAY default display}, while other users would only be
- * assigned to a display if they were started with
- * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}. If the user is a profile
- * and is running, it's assigned to its parent display.
+ * assigned to a display if a call to {@link #assignUserToDisplay(int, int)} is made for such
+ * user / display combination (for example, if the user was started with
+ * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}, {@code UserController}
+ * would make such call).
+ *
+ * <p>If the user is a profile and is running, it's assigned to its parent display.
*/
public abstract int getDisplayAssignedToUser(@UserIdInt int userId);
@@ -375,8 +380,11 @@
* associated with the display.
*
* <p>The {@link android.view.Display#DEFAULT_DISPLAY default display} is always assigned to
- * the current foreground user, while other displays would be associated with the user that was
- * started with {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}.
+ * the current foreground user, while other displays would only be associated with users through
+ * a explicit {@link #assignUserToDisplay(int, int)} call with that user / display combination
+ * (for example, if the user was started with
+ * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}, {@code UserController}
+ * would make such call).
*/
public abstract @UserIdInt int getUserAssignedToDisplay(int displayId);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 07ec80b..3b3e1db 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1785,18 +1785,10 @@
@VisibleForTesting
int getUserAssignedToDisplay(int displayId) {
- if (displayId == Display.DEFAULT_DISPLAY) {
+ if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
return getCurrentUserId();
}
- if (!mUsersOnSecondaryDisplaysEnabled) {
- int currentUserId = getCurrentUserId();
- Slogf.w(LOG_TAG, "getUsersAssignedToDisplay(%d) called with non-DEFAULT_DISPLAY on "
- + "system that doesn't support that; returning current user (%d)", displayId,
- currentUserId);
- return currentUserId;
- }
-
synchronized (mUsersOnSecondaryDisplays) {
for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) {
@@ -6844,20 +6836,22 @@
// Check if display is available
for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
- // Make sure display is not used by other users...
- // TODO(b/240736142); currently, if a user was started in a display, it
- // would need to be stopped first, so "switching" a user on secondary
- // diplay requires 2 non-atomic operations (stop and start). Once this logic
- // is refactored, it should be atomic.
- if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
- throw new IllegalStateException("Cannot assign " + userId + " to "
- + "display " + displayId + " as it's already assigned to "
- + "user " + mUsersOnSecondaryDisplays.keyAt(i));
+ int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
+ int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
+ if (DBG_MUMD) {
+ Slogf.d(LOG_TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
+ i, assignedUserId, assignedDisplayId);
}
- // TODO(b/239982558) also check that user is not already assigned to other
- // display (including 0). That would be harder to tested under CTS though
- // (for example, would need to add a new AM method to start user in bg on
- // main display), so it's better to test on unit tests
+ if (displayId == assignedDisplayId) {
+ throw new IllegalStateException("Cannot assign user " + userId + " to "
+ + "display " + displayId + " because such display is already "
+ + "assigned to user " + assignedUserId);
+ }
+ if (userId == assignedUserId) {
+ throw new IllegalStateException("Cannot assign user " + userId + " to "
+ + "display " + displayId + " because such user is as already "
+ + "assigned to display " + assignedDisplayId);
+ }
}
if (DBG_MUMD) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e332ac7..675819a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3052,6 +3052,13 @@
}
}
break;
+ case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
+ case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
+ case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
+ case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL:
+ Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
+ + " interceptKeyBeforeQueueing");
+ return key_consumed;
}
if (isValidGlobalKey(keyCode)
@@ -4104,6 +4111,14 @@
result &= ~ACTION_PASS_TO_USER;
break;
}
+ case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
+ case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
+ case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
+ case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
+ // TODO(go/android-stylus-buttons): Handle stylus button presses.
+ result &= ~ACTION_PASS_TO_USER;
+ break;
+ }
}
if (useHapticFeedback) {
diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java
index 3f4def6..41373cd 100644
--- a/services/core/java/com/android/server/utils/AlarmQueue.java
+++ b/services/core/java/com/android/server/utils/AlarmQueue.java
@@ -34,6 +34,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.function.Predicate;
@@ -52,6 +53,11 @@
private static final boolean DEBUG = false;
private static final long NOT_SCHEDULED = -1;
+ /**
+ * The threshold used to consider a new trigger time to be significantly different from the
+ * currently used trigger time.
+ */
+ private static final long SIGNIFICANT_TRIGGER_TIME_CHANGE_THRESHOLD_MS = MINUTE_IN_MILLIS;
/**
* Internal priority queue for each key's alarm, ordered by the time the alarm should go off.
@@ -59,7 +65,7 @@
*/
private static class AlarmPriorityQueue<Q> extends PriorityQueue<Pair<Q, Long>> {
AlarmPriorityQueue() {
- super(1, (o1, o2) -> (int) (o1.second - o2.second));
+ super(1, Comparator.comparingLong(o -> o.second));
}
/**
@@ -306,8 +312,10 @@
// earlier but not significantly so, then we essentially delay the check for some
// apps by up to a minute.
// 3. The alarm is after the current alarm.
+ final long timeShiftThresholdMs =
+ Math.min(SIGNIFICANT_TRIGGER_TIME_CHANGE_THRESHOLD_MS, mMinTimeBetweenAlarmsMs);
if (mTriggerTimeElapsed == NOT_SCHEDULED
- || nextTriggerTimeElapsed < mTriggerTimeElapsed - MINUTE_IN_MILLIS
+ || nextTriggerTimeElapsed < mTriggerTimeElapsed - timeShiftThresholdMs
|| mTriggerTimeElapsed < nextTriggerTimeElapsed) {
if (DEBUG) {
Slog.d(TAG, "Scheduling alarm at " + nextTriggerTimeElapsed
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index dc91c15..28cd001 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -744,7 +744,7 @@
// (e.g. AMS.startActivityAsUser).
final long token = Binder.clearCallingIdentity();
try {
- return mService.getPackageManagerInternalLocked().resolveIntent(
+ return mService.getPackageManagerInternalLocked().resolveIntentExported(
intent, resolvedType, modifiedFlags, privateResolveFlags, userId, true,
filterCallingUid);
} finally {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 317c93e..ea82417 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -481,7 +481,7 @@
}
private void updateRoundedCorners(WindowState mainWindow) {
- final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
+ final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
if (windowSurface != null && windowSurface.isValid()) {
final Transaction transaction = mActivityRecord.getSyncTransaction();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e241723..723aa19 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -40,8 +40,9 @@
import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+import static android.window.TransitionInfo.FLAG_FILLS_TASK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
-import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -1839,8 +1840,10 @@
flags |= FLAG_WILL_IME_SHOWN;
}
}
+ Task parentTask = null;
final ActivityRecord record = wc.asActivityRecord();
if (record != null) {
+ parentTask = record.getTask();
if (record.mUseTransferredAnimation) {
flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
}
@@ -1848,6 +1851,26 @@
flags |= FLAG_IS_VOICE_INTERACTION;
}
}
+ final TaskFragment taskFragment = wc.asTaskFragment();
+ if (taskFragment != null && task == null) {
+ parentTask = taskFragment.getTask();
+ }
+ if (parentTask != null) {
+ if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {
+ // Whether this is in a Task with embedded activity.
+ flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+ }
+ final Rect taskBounds = parentTask.getBounds();
+ final Rect startBounds = mAbsoluteBounds;
+ final Rect endBounds = wc.getBounds();
+ if (taskBounds.width() == startBounds.width()
+ && taskBounds.height() == startBounds.height()
+ && taskBounds.width() == endBounds.width()
+ && taskBounds.height() == endBounds.height()) {
+ // Whether the container fills the Task bounds before and after the transition.
+ flags |= FLAG_FILLS_TASK;
+ }
+ }
final DisplayContent dc = wc.asDisplayContent();
if (dc != null) {
flags |= FLAG_IS_DISPLAY;
@@ -1864,9 +1887,6 @@
if (occludesKeyguard(wc)) {
flags |= FLAG_OCCLUDES_KEYGUARD;
}
- if (wc.isEmbedded()) {
- flags |= FLAG_IS_EMBEDDED;
- }
return flags;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 88d6d85..88c47db 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5811,6 +5811,21 @@
return -1;
}
+ /**
+ * Return the display Id that has the given uniqueId. Unique ID is defined in
+ * {@link DisplayInfo#uniqueId}.
+ */
+ @Override
+ public int getDisplayIdByUniqueId(String uniqueId) {
+ synchronized (mGlobalLock) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(uniqueId);
+ if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
+ return displayContent.mDisplayId;
+ }
+ }
+ return -1;
+ }
+
@Override
public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS)
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index c22091b..2838304 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -264,8 +264,23 @@
private int runDisplayDensity(PrintWriter pw) throws RemoteException {
String densityStr = getNextArg();
+ String option = getNextOption();
+ String arg = getNextArg();
int density;
- final int displayId = getDisplayId(densityStr);
+ int displayId = Display.DEFAULT_DISPLAY;
+ if ("-d".equals(option) && arg != null) {
+ try {
+ displayId = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad number " + e);
+ }
+ } else if ("-u".equals(option) && arg != null) {
+ displayId = mInterface.getDisplayIdByUniqueId(arg);
+ if (displayId == Display.INVALID_DISPLAY) {
+ getErrPrintWriter().println("Error: the uniqueId is invalid ");
+ return -1;
+ }
+ }
if (densityStr == null) {
printInitialDisplayDensity(pw, displayId);
@@ -1312,7 +1327,7 @@
pw.println(" size [reset|WxH|WdpxHdp] [-d DISPLAY_ID]");
pw.println(" Return or override display size.");
pw.println(" width and height in pixels unless suffixed with 'dp'.");
- pw.println(" density [reset|DENSITY] [-d DISPLAY_ID]");
+ pw.println(" density [reset|DENSITY] [-d DISPLAY_ID] [-u UNIQUE_ID]");
pw.println(" Return or override display density.");
pw.println(" folded-area [reset|LEFT,TOP,RIGHT,BOTTOM]");
pw.println(" Return or override folded area.");
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 3bf2db2..86e12647 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -24,11 +24,26 @@
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_WIFI_SCAN;
+import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
+import static android.app.AppOpsManager.UID_STATE_CACHED;
+import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
+import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
+import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
+import static android.app.AppOpsManager.UID_STATE_TOP;
+
+import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
@@ -41,12 +56,14 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.os.Clock;
+import com.android.server.appop.AppOpsUidStateTracker.UidStateChangedCallback;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.quality.Strictness;
import java.util.concurrent.atomic.AtomicLong;
@@ -101,7 +118,7 @@
@Test
public void testConstantFirstUnrestrictedUidState() {
for (int i = 0; i < AppOpsManager.getNumOps(); i++) {
- assertEquals(AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED,
+ assertEquals(UID_STATE_MAX_LAST_NON_RESTRICTED,
AppOpsManager.resolveFirstUnrestrictedUidState(i));
}
}
@@ -109,7 +126,7 @@
@Test
public void testNoCapability() {
procStateBuilder(UID)
- .foregroundState()
+ .topState()
.update();
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
@@ -121,7 +138,7 @@
@Test
public void testForegroundWithMicrophoneCapability() {
procStateBuilder(UID)
- .foregroundState()
+ .topState()
.microphoneCapability()
.update();
@@ -149,7 +166,7 @@
@Test
public void testForegroundWithCameraCapability() {
procStateBuilder(UID)
- .foregroundState()
+ .topState()
.cameraCapability()
.update();
@@ -177,7 +194,7 @@
@Test
public void testForegroundWithLocationCapability() {
procStateBuilder(UID)
- .foregroundState()
+ .topState()
.locationCapability()
.update();
@@ -205,7 +222,7 @@
@Test
public void testForegroundNotCapabilitiesTracked() {
procStateBuilder(UID)
- .foregroundState()
+ .topState()
.update();
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_NO_CAPABILITIES, MODE_FOREGROUND));
@@ -228,7 +245,7 @@
assertBackground(UID);
procStateBuilder(UID)
- .foregroundState()
+ .topState()
.update();
assertForeground(UID);
}
@@ -236,7 +253,7 @@
@Test
public void testForegroundToBackgroundTransition() {
procStateBuilder(UID)
- .foregroundState()
+ .topState()
.update();
assertForeground(UID);
@@ -446,6 +463,328 @@
assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
}
+ @Test
+ public void testUidStateChangedCallbackNewProcessTop() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ getLatestPostMessageArgument().getCallback().run();
+
+ verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true));
+ }
+
+ @Test
+ public void testUidStateChangedCallbackNewProcessForegroundService() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .foregroundServiceState()
+ .update();
+
+ getLatestPostMessageArgument().getCallback().run();
+
+ verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true));
+ }
+
+ @Test
+ public void testUidStateChangedCallbackNewProcessForeground() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .foregroundState()
+ .update();
+
+ getLatestPostMessageArgument().getCallback().run();
+
+ verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true));
+ }
+
+ @Test
+ public void testUidStateChangedCallbackNewProcessBackground() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .backgroundState()
+ .update();
+
+ getLatestPostMessageArgument().getCallback().run();
+
+ verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false));
+ }
+
+ @Test
+ public void testUidStateChangedCallbackNewProcessCached() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .cachedState()
+ .update();
+
+ // Cached is the default, no change in uid state.
+ verify(cb, times(0)).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testUidStateChangedCallbackCachedToBackground() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
+ ActivityManager.PROCESS_STATE_RECEIVER);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackCachedToForeground() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
+ ActivityManager.PROCESS_STATE_BOUND_TOP);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackCachedToForegroundService() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackCachedToTop() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
+ ActivityManager.PROCESS_STATE_TOP);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackBackgroundToCached() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_RECEIVER,
+ ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackBackgroundToForeground() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_RECEIVER,
+ ActivityManager.PROCESS_STATE_BOUND_TOP);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackBackgroundToForegroundService() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_RECEIVER,
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackBackgroundToTop() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_RECEIVER,
+ ActivityManager.PROCESS_STATE_TOP);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundToCached() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_BOUND_TOP,
+ ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundToBackground() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_BOUND_TOP,
+ ActivityManager.PROCESS_STATE_RECEIVER);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundToForegroundService() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_BOUND_TOP,
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundToTop() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_BOUND_TOP,
+ ActivityManager.PROCESS_STATE_TOP);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundServiceToCached() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+ ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundServiceToBackground() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+ ActivityManager.PROCESS_STATE_RECEIVER);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundServiceToForeground() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+ ActivityManager.PROCESS_STATE_BOUND_TOP);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundServiceToTop() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+ ActivityManager.PROCESS_STATE_TOP);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackTopToCached() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_TOP,
+ ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackTopToBackground() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_TOP,
+ ActivityManager.PROCESS_STATE_RECEIVER);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackTopToForeground() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_TOP,
+ ActivityManager.PROCESS_STATE_BOUND_TOP);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackTopToForegroundService() {
+ testUidStateChangedCallback(
+ ActivityManager.PROCESS_STATE_TOP,
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ }
+
+ @Test
+ public void testUidStateChangedCallbackCachedToNonexistent() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .cachedState()
+ .update();
+
+ procStateBuilder(UID)
+ .nonExistentState()
+ .update();
+
+ verify(mHandler, never()).post(any());
+ verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testUidStateChangedCallbackBackgroundToNonexistent() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .backgroundState()
+ .update();
+
+ procStateBuilder(UID)
+ .nonExistentState()
+ .update();
+
+ getLatestPostMessageArgument().getCallback().run();
+ verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false));
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundToNonexistent() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .foregroundState()
+ .update();
+
+ procStateBuilder(UID)
+ .nonExistentState()
+ .update();
+
+ getLatestPostMessageArgument().getCallback().run();
+ verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
+ }
+
+ @Test
+ public void testUidStateChangedCallbackForegroundServiceToNonexistent() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .foregroundServiceState()
+ .update();
+
+ procStateBuilder(UID)
+ .nonExistentState()
+ .update();
+
+ getLatestPostMessageArgument().getCallback().run();
+ verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
+ }
+
+ @Test
+ public void testUidStateChangedCallbackTopToNonexistent() {
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ procStateBuilder(UID)
+ .nonExistentState()
+ .update();
+
+ getLatestPostMessageArgument().getCallback().run();
+ verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
+ }
+
+ public void testUidStateChangedCallback(int initialState, int finalState) {
+ int initialUidState = processStateToUidState(initialState);
+ int finalUidState = processStateToUidState(finalState);
+ boolean foregroundChange = initialUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ != finalUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED;
+ boolean finalUidStateIsBackgroundAndLessImportant =
+ finalUidState > UID_STATE_MAX_LAST_NON_RESTRICTED
+ && finalUidState > initialUidState;
+
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .setState(initialState)
+ .update();
+
+ procStateBuilder(UID)
+ .setState(finalState)
+ .update();
+
+ if (finalUidStateIsBackgroundAndLessImportant) {
+ AtomicReference<Message> delayedMessage = new AtomicReference<>();
+ getPostDelayedMessageArguments(delayedMessage, new AtomicLong());
+ mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1);
+ delayedMessage.get().getCallback().run();
+ }
+
+ getLatestPostMessageArgument().getCallback().run();
+ verify(cb, atLeastOnce())
+ .onUidStateChanged(eq(UID), eq(finalUidState), eq(foregroundChange));
+ }
+
+ private UidStateChangedCallback addUidStateChangeCallback() {
+ UidStateChangedCallback cb =
+ Mockito.mock(UidStateChangedCallback.class);
+ mIntf.addUidStateChangedCallback(mHandler, cb);
+ return cb;
+ }
+
/* If testForegroundNotCapabilitiesTracked fails, this assertion is probably incorrect */
private void assertForeground(int uid) {
assertEquals(MODE_ALLOWED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND));
@@ -472,6 +811,14 @@
}
}
+ private Message getLatestPostMessageArgument() {
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+ verify(mHandler, atLeast(1)).sendMessage(messageCaptor.capture());
+
+ return messageCaptor.getValue();
+ }
+
private UidProcStateUpdateBuilder procStateBuilder(int uid) {
return new UidProcStateUpdateBuilder(mIntf, uid);
}
@@ -496,7 +843,12 @@
return this;
}
- public UidProcStateUpdateBuilder foregroundState() {
+ public UidProcStateUpdateBuilder setState(int procState) {
+ mProcState = procState;
+ return this;
+ }
+
+ public UidProcStateUpdateBuilder topState() {
mProcState = ActivityManager.PROCESS_STATE_TOP;
return this;
}
@@ -506,6 +858,11 @@
return this;
}
+ public UidProcStateUpdateBuilder foregroundState() {
+ mProcState = ActivityManager.PROCESS_STATE_BOUND_TOP;
+ return this;
+ }
+
public UidProcStateUpdateBuilder backgroundState() {
mProcState = ActivityManager.PROCESS_STATE_SERVICE;
return this;
@@ -516,6 +873,11 @@
return this;
}
+ public UidProcStateUpdateBuilder nonExistentState() {
+ mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ return this;
+ }
+
public UidProcStateUpdateBuilder locationCapability() {
mCapability |= ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
return this;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
index 574bab2..245b4dc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
@@ -163,6 +163,23 @@
}
@Test
+ public void testAssignUserToDisplay_userAlreadyAssigned() {
+ enableUsersOnSecondaryDisplays();
+
+ mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> mUmi.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
+ .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*"
+ + SECONDARY_DISPLAY_ID + ".*");
+
+ assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
enableUsersOnSecondaryDisplays();
addDefaultProfileAndParent();
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
index 849e673..00d7541 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
@@ -155,6 +155,27 @@
anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
}
+ @Test
+ public void testAddingLargeAlarmTimes() {
+ final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0);
+ final long nowElapsed = mInjector.getElapsedRealtime();
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ alarmQueue.addAlarm("com.android.test.1", Long.MAX_VALUE - 5);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .setExact(anyInt(), eq(Long.MAX_VALUE - 5), eq(ALARM_TAG), any(), any());
+ alarmQueue.addAlarm("com.android.test.2", Long.MAX_VALUE - 4);
+ inOrder.verify(mAlarmManager, never())
+ .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any());
+ alarmQueue.addAlarm("com.android.test.3", nowElapsed + 5);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .setExact(anyInt(), eq(nowElapsed + 5), eq(ALARM_TAG), any(), any());
+ alarmQueue.addAlarm("com.android.test.4", nowElapsed + 6);
+ inOrder.verify(mAlarmManager, never())
+ .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any());
+ }
+
/**
* Verify that updating the alarm time for a key will result in the AlarmManager alarm changing,
* if needed.
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index d5b923a..4100ff1 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -148,6 +148,25 @@
}
@Test
+ public void testAtraceInstantEvent() {
+ mHistory.forceRecordAllHistory();
+
+ InOrder inOrder = Mockito.inOrder(mTracer);
+ Mockito.when(mTracer.tracingEnabled()).thenReturn(true);
+
+ mHistory.recordEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ HistoryItem.EVENT_WAKEUP_AP, "", 1234);
+ mHistory.recordEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ HistoryItem.EVENT_JOB_START, "jobname", 2468);
+ mHistory.recordEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ HistoryItem.EVENT_JOB_FINISH, "jobname", 2468);
+
+ inOrder.verify(mTracer).traceInstantEvent("battery_stats.wakeupap", "wakeupap=1234:\"\"");
+ inOrder.verify(mTracer).traceInstantEvent("battery_stats.job", "+job=2468:\"jobname\"");
+ inOrder.verify(mTracer).traceInstantEvent("battery_stats.job", "-job=2468:\"jobname\"");
+ }
+
+ @Test
public void testConstruct() {
createActiveFile(mHistory);
verifyFileNumbers(mHistory, Arrays.asList(0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 85e5bfd..2b0e76c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -379,6 +379,8 @@
doReturn(false).when(mMockPackageManager).isInstantAppInstallerComponent(any());
doReturn(null).when(mMockPackageManager).resolveIntent(any(), any(), anyLong(), anyLong(),
anyInt(), anyBoolean(), anyInt());
+ doReturn(null).when(mMockPackageManager).resolveIntentExported(any(), any(),
+ anyLong(), anyLong(), anyInt(), anyBoolean(), anyInt());
doReturn(new ComponentName("", "")).when(mMockPackageManager).getSystemUiServiceComponent();
// Never review permissions
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 048cd7c..64e15f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -31,7 +32,8 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
+import static android.window.TransitionInfo.FLAG_FILLS_TASK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -1065,12 +1067,14 @@
}
@Test
- public void testIsEmbeddedChange() {
+ public void testFlagInTaskWithEmbeddedActivity() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
+ final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+ assertFalse(nonEmbeddedActivity.isEmbedded());
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
@@ -1085,20 +1089,72 @@
changes.put(embeddedTf, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
changes.put(closingActivity, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
changes.put(openingActivity, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
+ false /* exChg */));
// End states.
closingActivity.mVisibleRequested = false;
openingActivity.mVisibleRequested = true;
+ nonEmbeddedActivity.mVisibleRequested = false;
participants.add(closingActivity);
participants.add(openingActivity);
+ participants.add(nonEmbeddedActivity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, changes, mMockT);
+ // All windows in the Task should have FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY because the Task
+ // contains embedded activity.
+ assertEquals(3, info.getChanges().size());
+ assertTrue(info.getChanges().get(0).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
+ assertTrue(info.getChanges().get(1).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
+ assertTrue(info.getChanges().get(2).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
+ }
+
+ @Test
+ public void testFlagFillsTask() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task task = createTask(mDisplayContent);
+ // Set to multi-windowing mode in order to set bounds.
+ task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ final Rect taskBounds = new Rect(0, 0, 500, 1000);
+ task.setBounds(taskBounds);
+ final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(1)
+ .setOrganizer(organizer)
+ .build();
+ final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
+ // Start states.
+ changes.put(task, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
+ false /* exChg */));
+ changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ // End states.
+ nonEmbeddedActivity.mVisibleRequested = false;
+ embeddedActivity.mVisibleRequested = true;
+ embeddedTf.setBounds(new Rect(0, 0, 500, 500));
+
+ participants.add(nonEmbeddedActivity);
+ participants.add(embeddedTf);
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ participants, changes);
+ final TransitionInfo info = Transition.calculateTransitionInfo(
+ transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+ // The embedded with bounds overridden should not have the flag.
assertEquals(2, info.getChanges().size());
- assertTrue((info.getChanges().get(0).getFlags() & FLAG_IS_EMBEDDED) != 0);
- assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0);
+ assertFalse(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
+ assertEquals(embeddedTf.getBounds(), info.getChanges().get(0).getEndAbsBounds());
+ assertFalse(info.getChanges().get(1).hasFlags(FLAG_FILLS_TASK));
}
@Test
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index b0ccbd1..939c7de 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -436,6 +436,15 @@
</intent-filter>
</activity>
+ <activity android:name="SurfaceViewAlphaActivity"
+ android:label="SurfaceView/SurfaceView with Alpha"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
+
<activity android:name=".PenStylusActivity"
android:label="Pen/Draw"
android:exported="true">
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/SurfaceViewAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/SurfaceViewAlphaActivity.java
new file mode 100644
index 0000000..01fe6ae0
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/SurfaceViewAlphaActivity.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class SurfaceViewAlphaActivity extends Activity implements Callback {
+ SurfaceView mSurfaceView;
+
+ private enum ZOrder {
+ ABOVE,
+ BELOW
+ }
+
+ private float mAlpha = 127f / 255f;
+ private ZOrder mZOrder = ZOrder.BELOW;
+
+
+ private String getAlphaText() {
+ return "Alpha: " + mAlpha;
+ }
+
+ private void toggleZOrder() {
+ if (ZOrder.ABOVE.equals(mZOrder)) {
+ mZOrder = ZOrder.BELOW;
+ } else {
+ mZOrder = ZOrder.ABOVE;
+ }
+ }
+
+ // Overlaps a blue view on the left, then the SurfaceView in the center, then a blue view on the
+ // right.
+ private void overlapViews(SurfaceView view, LinearLayout parent) {
+ float density = getResources().getDisplayMetrics().density;
+ int surfaceViewSize = (int) (200 * density);
+ int blueViewSize = (int) (surfaceViewSize * 2 / 3f);
+ int totalSize = (int) (surfaceViewSize * 5 / 3f);
+
+ RelativeLayout overlapLayout = new RelativeLayout(this);
+
+ RelativeLayout.LayoutParams leftViewLayoutParams = new RelativeLayout.LayoutParams(
+ blueViewSize, surfaceViewSize);
+ leftViewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
+
+ View leftBlueView = new View(this);
+ leftBlueView.setBackgroundColor(Color.BLUE);
+ overlapLayout.addView(leftBlueView, leftViewLayoutParams);
+
+ RelativeLayout.LayoutParams sVLayoutParams = new RelativeLayout.LayoutParams(
+ surfaceViewSize, surfaceViewSize);
+ sVLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
+ overlapLayout.addView(view, sVLayoutParams);
+
+ RelativeLayout.LayoutParams rightViewLayoutParams = new RelativeLayout.LayoutParams(
+ blueViewSize, surfaceViewSize);
+ rightViewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+
+ View rightBlueView = new View(this);
+ rightBlueView.setBackgroundColor(Color.BLUE);
+ overlapLayout.addView(rightBlueView, rightViewLayoutParams);
+
+ parent.addView(overlapLayout, new LinearLayout.LayoutParams(
+ totalSize, surfaceViewSize));
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mSurfaceView = new SurfaceView(this);
+ mSurfaceView.getHolder().addCallback(this);
+ mSurfaceView.setAlpha(mAlpha);
+
+ LinearLayout content = new LinearLayout(this);
+ content.setOrientation(LinearLayout.VERTICAL);
+
+ TextView alphaText = new TextView(this);
+ alphaText.setText(getAlphaText());
+
+ SeekBar alphaToggle = new SeekBar(this);
+ alphaToggle.setMin(0);
+ alphaToggle.setMax(255);
+ alphaToggle.setProgress(Math.round(mAlpha * 255));
+ alphaToggle.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mAlpha = progress / 255f;
+ alphaText.setText(getAlphaText());
+ mSurfaceView.setAlpha(mAlpha);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+
+ content.addView(alphaText, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ content.addView(alphaToggle, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ Button button = new Button(this);
+ button.setText("Z " + mZOrder.toString());
+ button.setOnClickListener(v -> {
+ toggleZOrder();
+ mSurfaceView.setZOrderOnTop(ZOrder.ABOVE.equals(mZOrder));
+ button.setText("Z " + mZOrder.toString());
+ });
+
+ content.addView(button, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ overlapViews(mSurfaceView, content);
+
+ setContentView(content);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Canvas canvas = holder.lockCanvas();
+ canvas.drawColor(Color.RED);
+ holder.unlockCanvasAndPost(canvas);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+}