diff options
31 files changed, 1294 insertions, 203 deletions
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index a8b99ceecd7f..a52726396b53 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -564,7 +564,8 @@ status_t IncidentService::dump(int fd, const Vector<String16>& args) { int skipped[] = SKIPPED_SECTIONS; for (const Section** section = SECTION_LIST; *section; section++) { const int id = (*section)->id; - if (std::find(std::begin(skipped), std::end(skipped), id) == std::end(skipped)) { + if (std::find(std::begin(skipped), std::end(skipped), id) == std::end(skipped) + && !section_requires_specific_mention(id)) { incidentArgs.addSection(id); } } diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index ce75f78c04ea..f0db1b0128a1 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -272,6 +272,7 @@ private: FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index 8cbc9c45ca27..0e3cde5364b1 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -124,7 +124,8 @@ void MetricProducer::addActivation(int activationTrackerIndex, const ActivationT std::make_shared<Activation>(activationType, ttl_seconds * NS_PER_SEC); mEventActivationMap.emplace(activationTrackerIndex, activation); if (-1 != deactivationTrackerIndex) { - mEventDeactivationMap.emplace(deactivationTrackerIndex, activation); + auto& deactivationList = mEventDeactivationMap[deactivationTrackerIndex]; + deactivationList.push_back(activation); } } @@ -155,7 +156,9 @@ void MetricProducer::cancelEventActivationLocked(int deactivationTrackerIndex) { if (it == mEventDeactivationMap.end()) { return; } - it->second->state = ActivationState::kNotActive; + for (auto activationToCancelIt : it->second) { + activationToCancelIt->state = ActivationState::kNotActive; + } } void MetricProducer::loadActiveMetricLocked(const ActiveMetric& activeMetric, diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index aa757618435c..09ad2903fa4c 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -407,8 +407,8 @@ protected: // whether the metric producer is ready to generate metrics. std::unordered_map<int, std::shared_ptr<Activation>> mEventActivationMap; - // Maps index of atom matcher for deactivation to Activation struct. - std::unordered_map<int, std::shared_ptr<Activation>> mEventDeactivationMap; + // Maps index of atom matcher for deactivation to a list of Activation structs. + std::unordered_map<int, std::vector<std::shared_ptr<Activation>>> mEventDeactivationMap; bool mIsActive; @@ -422,6 +422,7 @@ protected: FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 6fc2c1379645..8efca1e10de5 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -283,6 +283,7 @@ private: FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation); FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 01362b6b4fed..0e33a0f9f29b 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -109,7 +109,8 @@ ValueMetricProducer::ValueMetricProducer( mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC : StatsdStats::kPullMaxDelayNs), mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()), - mConditionTimer(mIsActive && mCondition == ConditionState::kTrue, timeBaseNs) { + // Condition timer will be set in prepareFirstBucketLocked. + mConditionTimer(false, timeBaseNs) { int64_t bucketSizeMills = 0; if (metric.has_bucket()) { bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()); @@ -175,6 +176,9 @@ void ValueMetricProducer::prepareFirstBucketLocked() { if (mIsActive && mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) { pullAndMatchEventsLocked(mCurrentBucketStartTimeNs, mCondition); } + // Now that activations are processed, start the condition timer if needed. + mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue, + mCurrentBucketStartTimeNs); } void ValueMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition, diff --git a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp index d99d281afc68..f1b6029f0ab0 100644 --- a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp @@ -134,6 +134,42 @@ StatsdConfig CreateStatsdConfigWithTwoDeactivations() { return config; } +StatsdConfig CreateStatsdConfigWithSameDeactivations() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); + + *config.add_atom_matcher() = saverModeMatcher; + *config.add_atom_matcher() = crashMatcher; + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher; + + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(crashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + auto event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + + return config; +} + StatsdConfig CreateStatsdConfigWithTwoMetricsTwoDeactivations() { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. @@ -467,7 +503,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); EXPECT_EQ(eventDeactivationMap.size(), 1u); EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3].size(), 1u); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); std::unique_ptr<LogEvent> event; @@ -491,7 +528,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, 0); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // First processed event. event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); @@ -509,7 +546,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // 2nd processed event. // The activation by screen_on event expires, but the one by battery save mode is still active. @@ -523,7 +560,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // No new broadcast since the config should still be active. EXPECT_EQ(broadcastCount, 1); @@ -545,7 +582,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // Re-activate metric via screen on. event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, @@ -562,7 +599,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // 4th processed event. event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); @@ -582,7 +619,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // 5th processed event. event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); @@ -602,7 +639,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // Screen-on activation expired. event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); @@ -618,7 +655,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); processor.OnLogEvent(event.get()); @@ -637,7 +674,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); // Cancel battery saver mode activation. event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16); @@ -652,7 +689,7 @@ TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); ConfigMetricsReportList reports; vector<uint8_t> buffer; @@ -790,8 +827,10 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventDeactivationMap.size(), 2u); EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3].size(), 1u); + EXPECT_EQ(eventDeactivationMap[4].size(), 1u); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); std::unique_ptr<LogEvent> event; @@ -815,8 +854,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, 0); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // First processed event. event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); @@ -834,8 +873,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // 2nd processed event. // The activation by screen_on event expires, but the one by battery save mode is still active. @@ -849,8 +888,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // No new broadcast since the config should still be active. EXPECT_EQ(broadcastCount, 1); @@ -872,8 +911,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // Re-activate metric via screen on. event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, @@ -890,8 +929,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // 4th processed event. event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); @@ -911,8 +950,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // 5th processed event. event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); @@ -932,8 +971,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // Screen-on activation expired. event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); @@ -948,8 +987,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); processor.OnLogEvent(event.get()); @@ -968,8 +1007,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); // Cancel battery saver mode and screen on activation. event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16); @@ -984,8 +1023,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); ConfigMetricsReportList reports; vector<uint8_t> buffer; @@ -1066,6 +1105,203 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { data.bucket_info(0).end_bucket_elapsed_nanos()); } +TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation) { + auto config = CreateStatsdConfigWithSameDeactivations(); + + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp<UidMap> m = new UidMap(); + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> subscriberAlarmMonitor; + vector<int64_t> activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, + bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector<int64_t>& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), + activeConfigs.begin(), activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); + + EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // Two activations: one is triggered by battery saver mode (tracker index 0), the other is + // triggered by screen on event (tracker index 2). + EXPECT_EQ(eventActivationMap.size(), 2u); + EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap.size(), 1u); + EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); + EXPECT_EQ(eventDeactivationMap[3].size(), 2u); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[3][1], eventActivationMap[2]); + EXPECT_EQ(broadcastCount, 0); + + std::unique_ptr<LogEvent> event; + + // Event that should be ignored. + event = CreateAppCrashEvent(111, bucketStartTimeNs + 1); + processor.OnLogEvent(event.get()); + + // Activate metric via screen on for 2 minutes. + event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + 10); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10); + + // 1st processed event. + event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); + processor.OnLogEvent(event.get()); + + // Enable battery saver mode activation for 5 minutes. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 + 10); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 + 10); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 10); + + // 2nd processed event. + event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 + 40); + processor.OnLogEvent(event.get()); + + // Cancel battery saver mode and screen on activation. + int64_t firstDeactivation = bucketStartTimeNs + NS_PER_SEC * 61; + event = CreateScreenBrightnessChangedEvent(64, firstDeactivation); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 2); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + + // Should be ignored + event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 61 + 80); + processor.OnLogEvent(event.get()); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 15); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + + // 3rd processed event. + event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 80); + processor.OnLogEvent(event.get()); + + // Cancel battery saver mode activation. + int64_t secondDeactivation = bucketStartTimeNs + NS_PER_SEC * 60 * 13; + event = CreateScreenBrightnessChangedEvent(140, secondDeactivation); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 4); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + + // Should be ignored. + event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 13 + 80); + processor.OnLogEvent(event.get()); + + ConfigMetricsReportList reports; + vector<uint8_t> buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(3, reports.reports(0).metrics(0).count_metrics().data_size()); + + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue( + reports.reports(0).metrics(0).count_metrics(), &countMetrics); + EXPECT_EQ(3, countMetrics.data_size()); + + auto data = countMetrics.data(0); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(firstDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(555, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(secondDeactivation, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { auto config = CreateStatsdConfigWithTwoMetricsTwoDeactivations(); @@ -1127,8 +1363,10 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventDeactivationMap.size(), 2u); EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3].size(), 1u); + EXPECT_EQ(eventDeactivationMap[4].size(), 1u); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_EQ(eventActivationMap2.size(), 2u); EXPECT_TRUE(eventActivationMap2.find(0) != eventActivationMap2.end()); @@ -1142,8 +1380,10 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventDeactivationMap2.size(), 2u); EXPECT_TRUE(eventDeactivationMap2.find(3) != eventDeactivationMap2.end()); EXPECT_TRUE(eventDeactivationMap2.find(4) != eventDeactivationMap2.end()); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap[3].size(), 1u); + EXPECT_EQ(eventDeactivationMap[4].size(), 1u); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); std::unique_ptr<LogEvent> event; @@ -1170,8 +1410,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, 0); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_TRUE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); @@ -1179,8 +1419,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, 0); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // First processed event. event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); @@ -1200,8 +1440,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_TRUE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); @@ -1209,8 +1449,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // 2nd processed event. // The activation by screen_on event expires, but the one by battery save mode is still active. @@ -1226,8 +1466,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_TRUE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); @@ -1235,8 +1475,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // No new broadcast since the config should still be active. EXPECT_EQ(broadcastCount, 1); @@ -1262,8 +1502,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_FALSE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); @@ -1271,8 +1511,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + 20); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // Re-activate metric via screen on. event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, @@ -1289,8 +1529,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_TRUE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + 10); @@ -1298,8 +1538,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // 4th processed event. event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); @@ -1321,8 +1561,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_TRUE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); @@ -1330,8 +1570,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // 5th processed event. event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); @@ -1353,8 +1593,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_FALSE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); @@ -1362,8 +1602,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // Screen-on activation expired. event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); @@ -1380,8 +1620,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_FALSE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); @@ -1389,8 +1629,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); processor.OnLogEvent(event.get()); @@ -1411,8 +1651,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_TRUE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); @@ -1420,8 +1660,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); // Cancel battery saver mode and screen on activation. event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16); @@ -1436,8 +1676,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); - EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_EQ(eventDeactivationMap[3][0], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4][0], eventActivationMap[2]); EXPECT_FALSE(metricProducer2->mIsActive); EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[0]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); @@ -1445,8 +1685,8 @@ TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); EXPECT_EQ(eventActivationMap2[2]->start_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); - EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); - EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + EXPECT_EQ(eventDeactivationMap2[3][0], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4][0], eventActivationMap2[2]); ConfigMetricsReportList reports; vector<uint8_t> buffer; diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java index 5ff627f48017..12edc5eb7e33 100644 --- a/core/java/android/hardware/SensorAdditionalInfo.java +++ b/core/java/android/hardware/SensorAdditionalInfo.java @@ -119,12 +119,50 @@ public class SensorAdditionalInfo { public static final int TYPE_VEC3_CALIBRATION = 0x10002; /** - * Sensor placement. Describes location and installation angle of the sensor device. + * Sensor placement. * - * Payload: - * floatValues[0..11]: First 3 rows of homogeneous matrix in row major order that describes - * the location and orientation of the sensor. Origin of reference will be the mobile device - * geometric sensor. Reference frame is defined as the same as Android sensor frame. + * Provides the orientation and location of the sensor element in terms of the + * Android coordinate system. This data is given as a 3x4 matrix consisting of a 3x3 rotation + * matrix (R) concatenated with a 3x1 location vector (t). The rotation matrix provides the + * orientation of the Android device coordinate frame relative to the local coordinate frame of + * the sensor. Note that assuming the axes conventions of the sensor are the same as Android, + * this is the inverse of the matrix applied to raw samples read from the sensor to convert them + * into the Android representation. The location vector represents the translation from the + * origin of the Android sensor coordinate system to the geometric center of the sensor, + * specified in millimeters (mm). + * <p> + * <b>Payload</b>: + * <code>floatValues[0..11]</code>: 3x4 matrix in row major order [R; t] + * </p> + * <p> + * <b>Example</b>: + * This raw buffer: <code>{0, 1, 0, 0, -1, 0, 0, 10, 0, 0, 1, -2.5}</code><br> + * Corresponds to this 3x4 matrix: + * <table> + * <thead> + * <tr><td colspan="3">Orientation</td><td>Location</tr> + * </thead> + * <tbody> + * <tr><td>0</td><td>1</td><td>0</td><td>0</td></tr> + * <tr><td>-1</td><td>0</td><td>0</td><td>10</td></tr> + * <tr><td>0</td><td>0</td><td>1</td><td>-2.5</td></tr> + * </tbody> + * </table> + * The sensor is oriented such that: + * <ul> + * <li>The device X axis corresponds to the sensor's local -Y axis + * <li>The device Y axis corresponds to the sensor's local X axis + * <li>The device Z axis and sensor's local Z axis are equivalent + * </ul> + * In other words, if viewing the origin of the Android coordinate system from the positive + * Z direction, the device coordinate frame is to be rotated 90° counter-clockwise about the + * Z axis to align with the sensor's local coordinate frame. Equivalently, a vector in the + * Android coordinate frame may be multiplied with R to rotate it 90° clockwise (270° + * counter-clockwise), yielding its representation in the sensor's coordinate frame. + * Relative to the origin of the Android coordinate system, the physical center of the + * sensor is located 10mm in the positive Y direction, and 2.5mm in the negative Z + * direction. + * </p> */ public static final int TYPE_SENSOR_PLACEMENT = 0x10003; diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 3716b3edaed4..0ee9a1192a5f 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -16,6 +16,7 @@ package android.os; +import android.Manifest; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -25,6 +26,7 @@ import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.provider.MediaStore; @@ -1159,11 +1161,36 @@ public class Environment { */ public static boolean isExternalStorageLegacy(@NonNull File path) { final Context context = AppGlobals.getInitialApplication(); + final int uid = context.getApplicationInfo().uid; + if (Process.isIsolated(uid)) { + return false; + } + + final PackageManager packageManager = context.getPackageManager(); + if (packageManager.isInstantApp()) { + return false; + } + + if (packageManager.checkPermission(Manifest.permission.WRITE_MEDIA_STORAGE, + context.getPackageName()) == PackageManager.PERMISSION_GRANTED) { + return true; + } + + if (packageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES, + context.getPackageName()) == PackageManager.PERMISSION_GRANTED) { + return true; + } final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); + final String[] packagesForUid = packageManager.getPackagesForUid(uid); + for (String packageName : packagesForUid) { + if (appOps.checkOpNoThrow(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, + uid, packageName) == AppOpsManager.MODE_ALLOWED) { + return true; + } + } return appOps.checkOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE, - context.getApplicationInfo().uid, - context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED; + uid, context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED; } static File getDirectory(String variableName, String defaultPath) { diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java index 9c268f2d18a8..3ed48f655d47 100644 --- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java @@ -19,7 +19,9 @@ package android.view.textclassifier; import android.annotation.Nullable; import android.app.Person; import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Pair; @@ -200,10 +202,12 @@ public final class ActionsSuggestionsHelper { if (remoteAction == null) { return null; } + Intent actionIntent = ExtrasUtils.getActionIntent(conversationAction.getExtras()); + ComponentName componentName = actionIntent.getComponent(); + // Action without a component name will be considered as from the same app. + String packageName = componentName == null ? null : componentName.getPackageName(); return new Pair<>( - conversationAction.getAction().getTitle().toString(), - ExtrasUtils.getActionIntent( - conversationAction.getExtras()).getComponent().getPackageName()); + conversationAction.getAction().getTitle().toString(), packageName); } private static final class PersonEncoder { diff --git a/core/java/android/view/textclassifier/intent/LabeledIntent.java b/core/java/android/view/textclassifier/intent/LabeledIntent.java index b4bc8d39d562..30fc20ea86a1 100644 --- a/core/java/android/view/textclassifier/intent/LabeledIntent.java +++ b/core/java/android/view/textclassifier/intent/LabeledIntent.java @@ -118,14 +118,16 @@ public final class LabeledIntent { return null; } Intent resolvedIntent = new Intent(intent); - resolvedIntent.setComponent(new ComponentName(packageName, className)); resolvedIntent.putExtra( TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, getFromTextClassifierExtra(textLanguagesBundle)); - boolean shouldShowIcon = false; Icon icon = null; if (!"android".equals(packageName)) { + // We only set the component name when the package name is not resolved to "android" + // to workaround a bug that explicit intent with component name == ResolverActivity + // can't be launched on keyguard. + resolvedIntent.setComponent(new ComponentName(packageName, className)); if (resolveInfo.activityInfo.getIconResource() != 0) { icon = Icon.createWithResource( packageName, resolveInfo.activityInfo.getIconResource()); diff --git a/core/java/com/android/internal/os/BinderDeathDispatcher.java b/core/java/com/android/internal/os/BinderDeathDispatcher.java new file mode 100644 index 000000000000..0c93f7f160e4 --- /dev/null +++ b/core/java/com/android/internal/os/BinderDeathDispatcher.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.IInterface; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; + +/** + * Multiplexes multiple binder death recipients on the same binder objects, so that at the native + * level, we only need to keep track of one death recipient reference. This will help reduce the + * number of JNI strong references. + * + * test with: atest FrameworksCoreTests:BinderDeathDispatcherTest + */ +public class BinderDeathDispatcher<T extends IInterface> { + private static final String TAG = "BinderDeathDispatcher"; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final ArrayMap<IBinder, RecipientsInfo> mTargets = new ArrayMap<>(); + + @VisibleForTesting + class RecipientsInfo implements DeathRecipient { + final IBinder mTarget; + + /** + * Recipient list. If it's null, {@link #mTarget} has already died, but in that case + * this RecipientsInfo instance is removed from {@link #mTargets}. + */ + @GuardedBy("mLock") + @Nullable + ArraySet<DeathRecipient> mRecipients = new ArraySet<>(); + + private RecipientsInfo(IBinder target) { + mTarget = target; + } + + @Override + public void binderDied() { + final ArraySet<DeathRecipient> copy; + synchronized (mLock) { + copy = mRecipients; + mRecipients = null; + + // Also remove from the targets. + mTargets.remove(mTarget); + } + if (copy == null) { + return; + } + // Let's call it without holding the lock. + final int size = copy.size(); + for (int i = 0; i < size; i++) { + copy.valueAt(i).binderDied(); + } + } + } + + /** + * Add a {@code recipient} to the death recipient list on {@code target}. + * + * @return # of recipients in the recipient list, including {@code recipient}. Or, -1 + * if {@code target} is already dead, in which case recipient's + * {@link DeathRecipient#binderDied} won't be called. + */ + public int linkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) { + final IBinder ib = target.asBinder(); + synchronized (mLock) { + RecipientsInfo info = mTargets.get(ib); + if (info == null) { + info = new RecipientsInfo(ib); + + // First recipient; need to link to death. + try { + ib.linkToDeath(info, 0); + } catch (RemoteException e) { + return -1; // Already dead. + } + mTargets.put(ib, info); + } + info.mRecipients.add(recipient); + return info.mRecipients.size(); + } + } + + public void unlinkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) { + final IBinder ib = target.asBinder(); + + synchronized (mLock) { + final RecipientsInfo info = mTargets.get(ib); + if (info == null) { + return; + } + if (info.mRecipients.remove(recipient) && info.mRecipients.size() == 0) { + info.mTarget.unlinkToDeath(info, 0); + mTargets.remove(info.mTarget); + } + } + } + + public void dump(PrintWriter pw, String indent) { + synchronized (mLock) { + pw.print(indent); + pw.print("# of watched binders: "); + pw.println(mTargets.size()); + + pw.print(indent); + pw.print("# of death recipients: "); + int n = 0; + for (RecipientsInfo info : mTargets.values()) { + n += info.mRecipients.size(); + } + pw.println(n); + } + } + + @VisibleForTesting + public ArrayMap<IBinder, RecipientsInfo> getTargetsForTest() { + return mTargets; + } +} diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index e8691fa5e23e..b73ecd1974aa 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -756,4 +756,8 @@ public class ArrayUtils { return String.valueOf(value); } } + + public static @Nullable <T> T firstOrNull(T[] items) { + return items.length > 0 ? items[0] : null; + } } diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java index db5f82a0a373..80bce264c11b 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java @@ -208,6 +208,36 @@ public class ActionsSuggestionsHelperTest { assertThat(conversationActions.get(2).getAction()).isNull(); } + @Test + public void testDeduplicateActions_nullComponent() { + Bundle phoneExtras = new Bundle(); + Intent phoneIntent = new Intent(Intent.ACTION_DIAL); + ExtrasUtils.putActionIntent(phoneExtras, phoneIntent); + PendingIntent pendingIntent = PendingIntent.getActivity( + InstrumentationRegistry.getTargetContext(), + 0, + phoneIntent, + 0); + Icon icon = Icon.createWithData(new byte[0], 0, 0); + ConversationAction action = + new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) + .setAction(new RemoteAction(icon, "label", "1", pendingIntent)) + .setExtras(phoneExtras) + .build(); + ConversationAction actionWithSameLabel = + new ConversationAction.Builder(ConversationAction.TYPE_CALL_PHONE) + .setAction(new RemoteAction( + icon, "label", "2", pendingIntent)) + .setExtras(phoneExtras) + .build(); + + List<ConversationAction> conversationActions = + ActionsSuggestionsHelper.removeActionsWithDuplicates( + Arrays.asList(action, actionWithSameLabel)); + + assertThat(conversationActions).isEmpty(); + } + public void createLabeledIntentResult_null() { ActionsSuggestionsModel.ActionSuggestion nativeSuggestion = new ActionsSuggestionsModel.ActionSuggestion( diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java new file mode 100644 index 000000000000..59148870fa3c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.os; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.os.DeadObjectException; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileDescriptor; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BinderDeathDispatcherTest { + private static class MyTarget implements IInterface, IBinder { + public boolean isAlive = true; + public DeathRecipient mRecipient; + + @Override + public String getInterfaceDescriptor() throws RemoteException { + return null; + } + + @Override + public boolean pingBinder() { + return false; + } + + @Override + public boolean isBinderAlive() { + return isAlive; + } + + @Override + public IInterface queryLocalInterface(String descriptor) { + return null; + } + + @Override + public void dump(FileDescriptor fd, String[] args) throws RemoteException { + + } + + @Override + public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException { + + } + + @Override + public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) + throws RemoteException { + + } + + @Override + public boolean transact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + return false; + } + + @Override + public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException { + // In any situation, a single binder object should only have at most one death + // recipient. + assertThat(mRecipient).isNull(); + + if (!isAlive) { + throw new DeadObjectException(); + } + + mRecipient = recipient; + } + + @Override + public boolean unlinkToDeath(DeathRecipient recipient, int flags) { + if (!isAlive) { + return false; + } + assertThat(mRecipient).isSameAs(recipient); + mRecipient = null; + return true; + } + + @Override + public IBinder asBinder() { + return this; + } + + public void die() { + isAlive = false; + if (mRecipient != null) { + mRecipient.binderDied(); + } + mRecipient = null; + } + + public boolean hasDeathRecipient() { + return mRecipient != null; + } + } + + @Test + public void testRegisterAndUnregister() { + BinderDeathDispatcher<MyTarget> d = new BinderDeathDispatcher<>(); + + MyTarget t1 = new MyTarget(); + MyTarget t2 = new MyTarget(); + MyTarget t3 = new MyTarget(); + + DeathRecipient r1 = mock(DeathRecipient.class); + DeathRecipient r2 = mock(DeathRecipient.class); + DeathRecipient r3 = mock(DeathRecipient.class); + DeathRecipient r4 = mock(DeathRecipient.class); + DeathRecipient r5 = mock(DeathRecipient.class); + + // Start hooking up. + + // Link 3 recipients to t1 -- only one real recipient will be set. + assertThat(d.linkToDeath(t1, r1)).isEqualTo(1); + assertThat(d.getTargetsForTest().size()).isEqualTo(1); + + assertThat(d.linkToDeath(t1, r2)).isEqualTo(2); + assertThat(d.linkToDeath(t1, r3)).isEqualTo(3); + assertThat(d.getTargetsForTest().size()).isEqualTo(1); + + // Unlink two -- the real recipient is still set. + d.unlinkToDeath(t1, r1); + d.unlinkToDeath(t1, r2); + + assertThat(t1.hasDeathRecipient()).isTrue(); + assertThat(d.getTargetsForTest().size()).isEqualTo(1); + + // Unlink the last one. The real recipient is also unlinked. + d.unlinkToDeath(t1, r3); + assertThat(t1.hasDeathRecipient()).isFalse(); + assertThat(d.getTargetsForTest().size()).isEqualTo(0); + + // Set recipients to t1, t2 and t3. t3 has two. + assertThat(d.linkToDeath(t1, r1)).isEqualTo(1); + assertThat(d.linkToDeath(t2, r1)).isEqualTo(1); + assertThat(d.linkToDeath(t3, r1)).isEqualTo(1); + assertThat(d.linkToDeath(t3, r2)).isEqualTo(2); + + + // They should all have a real recipient. + assertThat(t1.hasDeathRecipient()).isTrue(); + assertThat(t2.hasDeathRecipient()).isTrue(); + assertThat(t3.hasDeathRecipient()).isTrue(); + + assertThat(d.getTargetsForTest().size()).isEqualTo(3); + + // Unlink r1 from t3. t3 still has r2, so it should still have a real recipient. + d.unlinkToDeath(t3, r1); + assertThat(t1.hasDeathRecipient()).isTrue(); + assertThat(t2.hasDeathRecipient()).isTrue(); + assertThat(t3.hasDeathRecipient()).isTrue(); + assertThat(d.getTargetsForTest().size()).isEqualTo(3); + + // Unlink r2 from t3. Now t3 has no real recipient. + d.unlinkToDeath(t3, r2); + assertThat(t3.hasDeathRecipient()).isFalse(); + assertThat(d.getTargetsForTest().size()).isEqualTo(2); + } + + @Test + public void testRegisterAndKill() { + BinderDeathDispatcher<MyTarget> d = new BinderDeathDispatcher<>(); + + MyTarget t1 = new MyTarget(); + MyTarget t2 = new MyTarget(); + MyTarget t3 = new MyTarget(); + + DeathRecipient r1 = mock(DeathRecipient.class); + DeathRecipient r2 = mock(DeathRecipient.class); + DeathRecipient r3 = mock(DeathRecipient.class); + DeathRecipient r4 = mock(DeathRecipient.class); + DeathRecipient r5 = mock(DeathRecipient.class); + + // Hook them up. + + d.linkToDeath(t1, r1); + d.linkToDeath(t1, r2); + d.linkToDeath(t1, r3); + + // r4 is linked then unlinked. It shouldn't be notified. + d.linkToDeath(t1, r4); + d.unlinkToDeath(t1, r4); + + d.linkToDeath(t2, r1); + + d.linkToDeath(t3, r3); + d.linkToDeath(t3, r5); + + assertThat(d.getTargetsForTest().size()).isEqualTo(3); + + // Kill the targets. + + t1.die(); + verify(r1, times(1)).binderDied(); + verify(r2, times(1)).binderDied(); + verify(r3, times(1)).binderDied(); + verify(r4, times(0)).binderDied(); + verify(r5, times(0)).binderDied(); + + assertThat(d.getTargetsForTest().size()).isEqualTo(2); + + reset(r1, r2, r3, r4, r5); + + t2.die(); + verify(r1, times(1)).binderDied(); + verify(r2, times(0)).binderDied(); + verify(r3, times(0)).binderDied(); + verify(r4, times(0)).binderDied(); + verify(r5, times(0)).binderDied(); + + assertThat(d.getTargetsForTest().size()).isEqualTo(1); + + reset(r1, r2, r3, r4, r5); + + t3.die(); + verify(r1, times(0)).binderDied(); + verify(r2, times(0)).binderDied(); + verify(r3, times(1)).binderDied(); + verify(r4, times(0)).binderDied(); + verify(r5, times(1)).binderDied(); + + assertThat(d.getTargetsForTest().size()).isEqualTo(0); + + // Try to register to a dead object -> should return -1. + assertThat(d.linkToDeath(t1, r1)).isEqualTo(-1); + + assertThat(d.getTargetsForTest().size()).isEqualTo(0); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 5e192193a87a..305558fd9efa 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -25,6 +25,9 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static com.android.systemui.tuner.TunablePadding.FLAG_END; import static com.android.systemui.tuner.TunablePadding.FLAG_START; +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.annotation.Dimension; import android.app.ActivityManager; import android.app.Fragment; @@ -49,6 +52,7 @@ import android.os.SystemProperties; import android.provider.Settings.Secure; import android.util.DisplayMetrics; import android.util.Log; +import android.util.MathUtils; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; @@ -60,12 +64,8 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.WindowManager; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.OvershootInterpolator; +import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; -import android.view.animation.ScaleAnimation; -import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import android.widget.ImageView; @@ -77,8 +77,10 @@ import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.SecureSetting; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.NavigationBarTransitions; +import com.android.systemui.statusbar.phone.NavigationModeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.tuner.TunablePadding; import com.android.systemui.tuner.TunerService; @@ -125,6 +127,7 @@ public class ScreenDecorations extends SystemUI implements Tunable, private Handler mHandler; private boolean mAssistHintBlocked = false; private boolean mIsReceivingNavBarColor = false; + private boolean mInGesturalMode; /** * Converts a set of {@link Rect}s into a {@link Region} @@ -149,6 +152,58 @@ public class ScreenDecorations extends SystemUI implements Tunable, mHandler.post(this::startOnScreenDecorationsThread); setupStatusBarPaddingIfNeeded(); putComponent(ScreenDecorations.class, this); + mInGesturalMode = QuickStepContract.isGesturalMode( + Dependency.get(NavigationModeController.class) + .addListener(this::handleNavigationModeChange)); + } + + @VisibleForTesting + void handleNavigationModeChange(int navigationMode) { + if (!mHandler.getLooper().isCurrentThread()) { + mHandler.post(() -> handleNavigationModeChange(navigationMode)); + return; + } + boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode); + if (mInGesturalMode != inGesturalMode) { + mInGesturalMode = inGesturalMode; + + if (mInGesturalMode && mOverlay == null) { + setupDecorations(); + if (mOverlay != null) { + updateLayoutParams(); + } + } + } + } + + /** + * Returns an animator that animates the given view from start to end over durationMs. Start and + * end represent total animation progress: 0 is the start, 1 is the end, 1.1 would be an + * overshoot. + */ + Animator getHandleAnimator(View view, float start, float end, boolean isLeft, long durationMs, + Interpolator interpolator) { + // Note that lerp does allow overshoot, in cases where start and end are outside of [0,1]. + float scaleStart = MathUtils.lerp(2f, 1f, start); + float scaleEnd = MathUtils.lerp(2f, 1f, end); + Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, scaleStart, scaleEnd); + Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, scaleStart, scaleEnd); + float translationStart = MathUtils.lerp(0.2f, 0f, start); + float translationEnd = MathUtils.lerp(0.2f, 0f, end); + int xDirection = isLeft ? -1 : 1; + Animator translateX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, + xDirection * translationStart * view.getWidth(), + xDirection * translationEnd * view.getWidth()); + Animator translateY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, + translationStart * view.getHeight(), translationEnd * view.getHeight()); + + AnimatorSet set = new AnimatorSet(); + set.play(scaleX).with(scaleY); + set.play(scaleX).with(translateX); + set.play(scaleX).with(translateY); + set.setDuration(durationMs); + set.setInterpolator(interpolator); + return set; } private void fade(View view, boolean fadeIn, boolean isLeft) { @@ -157,23 +212,23 @@ public class ScreenDecorations extends SystemUI implements Tunable, view.setAlpha(1f); view.setVisibility(View.VISIBLE); - AnimationSet anim = new AnimationSet(true); - anim.setDuration(900); - - Animation scaleAnimation = new ScaleAnimation(2f, 1f, 2f, 1f, - ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f); - anim.addAnimation(scaleAnimation); - anim.setInterpolator(new PathInterpolator(0.02f, 0.44f, 0.67f, 1.00f)); - - Animation translateAnimation = new TranslateAnimation( - TranslateAnimation.RELATIVE_TO_SELF, isLeft ? -0.2f : 0.2f, - TranslateAnimation.RELATIVE_TO_SELF, - 0f, - TranslateAnimation.RELATIVE_TO_SELF, 0.2f, TranslateAnimation.RELATIVE_TO_SELF, - 0f); - anim.addAnimation(translateAnimation); - anim.setInterpolator(new OvershootInterpolator()); - view.startAnimation(anim); + // A piecewise spring-like interpolation. + // End value in one animator call must match the start value in the next, otherwise + // there will be a discontinuity. + AnimatorSet anim = new AnimatorSet(); + Animator first = getHandleAnimator(view, 0, 1.1f, isLeft, 750, + new PathInterpolator(0, 0.45f, .67f, 1f)); + Interpolator secondInterpolator = new PathInterpolator(0.33f, 0, 0.67f, 1f); + Animator second = getHandleAnimator(view, 1.1f, 0.97f, isLeft, 400, + secondInterpolator); + Animator third = getHandleAnimator(view, 0.97f, 1.02f, isLeft, 400, + secondInterpolator); + Animator fourth = getHandleAnimator(view, 1.02f, 1f, isLeft, 400, + secondInterpolator); + anim.play(first).before(second); + anim.play(second).before(third); + anim.play(third).before(fourth); + anim.start(); } else { view.animate().cancel(); view.animate().setDuration(400).alpha(0f); @@ -232,6 +287,7 @@ public class ScreenDecorations extends SystemUI implements Tunable, break; } } + updateWindowVisibilities(); } /** @@ -256,11 +312,15 @@ public class ScreenDecorations extends SystemUI implements Tunable, return thread.getThreadHandler(); } + private boolean shouldHostHandles() { + return mInGesturalMode; + } + private void startOnScreenDecorationsThread() { mRotation = RotationUtils.getExactRotation(mContext); mWindowManager = mContext.getSystemService(WindowManager.class); updateRoundedCornerRadii(); - if (hasRoundedCorners() || shouldDrawCutout()) { + if (hasRoundedCorners() || shouldDrawCutout() || shouldHostHandles()) { setupDecorations(); } @@ -565,7 +625,10 @@ public class ScreenDecorations extends SystemUI implements Tunable, boolean visibleForCutout = shouldDrawCutout() && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE; boolean visibleForRoundedCorners = hasRoundedCorners(); - overlay.setVisibility(visibleForCutout || visibleForRoundedCorners + boolean visibleForHandles = overlay.findViewById(R.id.assist_hint_left).getVisibility() + == View.VISIBLE || overlay.findViewById(R.id.assist_hint_right).getVisibility() + == View.VISIBLE; + overlay.setVisibility(visibleForCutout || visibleForRoundedCorners || visibleForHandles ? View.VISIBLE : View.GONE); } @@ -688,10 +751,6 @@ public class ScreenDecorations extends SystemUI implements Tunable, setSize(mOverlay.findViewById(R.id.right), sizeTop); setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom); setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom); - setSize(mOverlay.findViewById(R.id.assist_hint_left), sizeTop * 2); - setSize(mOverlay.findViewById(R.id.assist_hint_right), sizeTop * 2); - setSize(mBottomOverlay.findViewById(R.id.assist_hint_left), sizeBottom * 2); - setSize(mBottomOverlay.findViewById(R.id.assist_hint_right), sizeBottom * 2); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index 5717a54fd8a0..18b8a9c859b1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -228,6 +228,10 @@ public abstract class BiometricDialogView extends LinearLayout { showTryAgainButton(false /* show */); mCallback.onTryAgainPressed(); }); + + // Must set these in order for the back button events to be received. + mLayout.setFocusableInTouchMode(true); + mLayout.requestFocus(); } public void onSaveState(Bundle bundle) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 3ec6cb78ecc1..b05058a92650 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -125,6 +125,7 @@ public class PipTouchHandler { private int mImeOffset; private boolean mIsShelfShowing; private int mShelfHeight; + private int mMovementBoundsExtraOffsets; private float mSavedSnapFraction = -1f; private boolean mSendingHoverAccessibilityEvents; private boolean mMovementWithinMinimize; @@ -262,7 +263,7 @@ public class PipTouchHandler { mShelfHeight = shelfHeight; } - public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect animatingBounds, + public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) { final int bottomOffset = mIsImeShowing ? mImeHeight : 0; @@ -283,6 +284,12 @@ public class PipTouchHandler { mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds, bottomOffset); + // The extra offset does not really affect the movement bounds, but are applied based on the + // current state (ime showing, or shelf offset) when we need to actually shift + int extraOffset = Math.max( + mIsImeShowing ? mImeOffset : 0, + !mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0); + // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not // occluded by the IME or shelf. if (fromImeAdjustment || fromShelfAdjustment) { @@ -290,41 +297,19 @@ public class PipTouchHandler { // Defer the update of the current movement bounds until after the user finishes // touching the screen } else { - final int adjustedOffset = Math.max(mIsImeShowing ? mImeHeight + mImeOffset : 0, - mIsShelfShowing ? mShelfHeight : 0); - Rect normalAdjustedBounds = new Rect(); - mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalAdjustedBounds, - adjustedOffset); - Rect expandedAdjustedBounds = new Rect(); - mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, - expandedAdjustedBounds, adjustedOffset); - final Rect toAdjustedBounds = mMenuState == MENU_STATE_FULL - ? expandedAdjustedBounds - : normalAdjustedBounds; - final Rect toMovementBounds = mMenuState == MENU_STATE_FULL - ? expandedMovementBounds - : normalMovementBounds; - - // If the PIP window needs to shift to right above shelf/IME and it's already above - // that, don't move the PIP window. - if (toAdjustedBounds.bottom < mMovementBounds.bottom - && animatingBounds.top < toAdjustedBounds.bottom) { - return; - } - - // If the PIP window needs to shift down due to dismissal of shelf/IME but it's way - // above the position as if shelf/IME shows, don't move the PIP window. - int movementBoundsAdjustment = toMovementBounds.bottom - mMovementBounds.bottom; - int offsetAdjustment = fromImeAdjustment ? mImeOffset : mShelfHeight; - final float bottomOffsetBufferInPx = BOTTOM_OFFSET_BUFFER_DP + final float offsetBufferPx = BOTTOM_OFFSET_BUFFER_DP * mContext.getResources().getDisplayMetrics().density; - if (toAdjustedBounds.bottom >= mMovementBounds.bottom - && animatingBounds.top + Math.round(bottomOffsetBufferInPx) - < toAdjustedBounds.bottom - movementBoundsAdjustment - offsetAdjustment) { - return; + final Rect toMovementBounds = mMenuState == MENU_STATE_FULL + ? new Rect(expandedMovementBounds) + : new Rect(normalMovementBounds); + final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets; + final int toBottom = toMovementBounds.bottom < toMovementBounds.top + ? toMovementBounds.bottom + : toMovementBounds.bottom - extraOffset; + if ((Math.min(prevBottom, toBottom) - offsetBufferPx) <= curBounds.top + && curBounds.top <= (Math.max(prevBottom, toBottom) + offsetBufferPx)) { + mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top); } - - animateToOffset(animatingBounds, toAdjustedBounds); } } @@ -335,6 +320,7 @@ public class PipTouchHandler { mDisplayRotation = displayRotation; mInsetBounds.set(insetBounds); updateMovementBounds(mMenuState); + mMovementBoundsExtraOffsets = extraOffset; // If we have a deferred resize, apply it now if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) { @@ -346,14 +332,6 @@ public class PipTouchHandler { } } - private void animateToOffset(Rect animatingBounds, Rect toAdjustedBounds) { - int offset = toAdjustedBounds.bottom - animatingBounds.top; - // In landscape mode, PIP window can go offset while launching IME. We want to align the - // the top of the PIP window with the top of the movement bounds in that case. - offset += Math.max(0, mMovementBounds.top - animatingBounds.top); - mMotionHelper.animateToOffset(animatingBounds, offset); - } - private void onRegistrationChanged(boolean isRegistered) { mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered ? new PipAccessibilityInteractionConnection(mMotionHelper, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index 0f6740d44d04..7c6c556b5241 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -569,16 +569,21 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G switch (behavior) { case BEHAVIOR_ALERTING: - alert.setSelected(true); - silence.setSelected(false); mPriorityDescriptionView.setVisibility(VISIBLE); mSilentDescriptionView.setVisibility(GONE); + post(() -> { + alert.setSelected(true); + silence.setSelected(false); + }); break; case BEHAVIOR_SILENT: - alert.setSelected(false); - silence.setSelected(true); + mSilentDescriptionView.setVisibility(VISIBLE); mPriorityDescriptionView.setVisibility(GONE); + post(() -> { + alert.setSelected(false); + silence.setSelected(true); + }); break; default: throw new IllegalArgumentException("Unrecognized alerting behavior: " + behavior); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 05a86fa9d7ea..38ff468304b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -152,6 +152,8 @@ public class EdgeBackGestureHandler implements DisplayListener { private WindowManager.LayoutParams mEdgePanelLp; private final Rect mSamplingRect = new Rect(); private RegionSamplingHelper mRegionSamplingHelper; + private int mLeftInset; + private int mRightInset; public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService) { final Resources res = context.getResources(); @@ -299,7 +301,7 @@ public class EdgeBackGestureHandler implements DisplayListener { return false; } - if (x > mEdgeWidth && x < (mDisplaySize.x - mEdgeWidth)) { + if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) { return false; } boolean isInExcludedRegion = mExcludeRegion.contains(x, y); @@ -325,7 +327,7 @@ public class EdgeBackGestureHandler implements DisplayListener { // Verify if this is in within the touch region and we aren't in immersive mode, and // either the bouncer is showing or the notification panel is hidden int stateFlags = mOverviewProxyService.getSystemUiStateFlags(); - mIsOnLeftEdge = ev.getX() <= mEdgeWidth; + mIsOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset; mAllowGesture = !QuickStepContract.isBackGestureDisabled(stateFlags) && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); if (mAllowGesture) { @@ -400,7 +402,7 @@ public class EdgeBackGestureHandler implements DisplayListener { private void updateSamplingRect() { int top = mEdgePanelLp.y; - int left = mIsOnLeftEdge ? 0 : mDisplaySize.x - mEdgePanelLp.width; + int left = mIsOnLeftEdge ? mLeftInset : mDisplaySize.x - mRightInset - mEdgePanelLp.width; int right = left + mEdgePanelLp.width; int bottom = top + mEdgePanelLp.height; mSamplingRect.set(left, top, right, bottom); @@ -442,6 +444,11 @@ public class EdgeBackGestureHandler implements DisplayListener { InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } + public void setInsets(int leftInset, int rightInset) { + mLeftInset = leftInset; + mRightInset = rightInset; + } + class SysUiInputEventReceiver extends InputEventReceiver { SysUiInputEventReceiver(InputChannel channel, Looper looper) { super(channel, looper); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 3ec16090ea87..90bce391c68d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -238,7 +238,19 @@ public class NavigationBarView extends FrameLayout implements info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); return; } + info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + ButtonDispatcher imeSwitchButton = getImeSwitchButton(); + if (imeSwitchButton.getVisibility() == VISIBLE) { + // If the IME is not up, but the ime switch button is visible, then make sure that + // button is touchable + int[] loc = new int[2]; + View buttonView = imeSwitchButton.getCurrentView(); + buttonView.getLocationInWindow(loc); + info.touchableRegion.set(loc[0], loc[1], loc[0] + buttonView.getWidth(), + loc[1] + buttonView.getHeight()); + return; + } info.touchableRegion.setEmpty(); }; @@ -1095,8 +1107,13 @@ public class NavigationBarView extends FrameLayout implements @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - setPadding(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), - insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); + int leftInset = insets.getSystemWindowInsetLeft(); + int rightInset = insets.getSystemWindowInsetRight(); + setPadding(leftInset, insets.getSystemWindowInsetTop(), rightInset, + insets.getSystemWindowInsetBottom()); + // we're passing the insets onto the gesture handler since the back arrow is only + // conditionally added and doesn't always get all the insets. + mEdgeBackGestureHandler.setInsets(leftInset, rightInset); return super.onApplyWindowInsets(insets); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index e79aa454aa62..5fff054b3bc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1520,6 +1520,8 @@ public class StatusBar extends SystemUI implements DemoMode, @Override // UnlockMethodCache.OnUnlockMethodChangedListener public void onUnlockMethodStateChanged() { + // Unlock method state changed. Notify KeguardMonitor + updateKeyguardState(); logStateToEventlog(); } @@ -3416,9 +3418,7 @@ public class StatusBar extends SystemUI implements DemoMode, checkBarModes(); updateScrimController(); mPresenter.updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); - mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), - mUnlockMethodCache.isMethodSecure(), - mStatusBarKeyguardViewManager.isOccluded()); + updateKeyguardState(); Trace.endSection(); } @@ -3457,6 +3457,12 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarStateController.setIsDozing(dozing); } + private void updateKeyguardState() { + mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), + mUnlockMethodCache.isMethodSecure(), + mStatusBarKeyguardViewManager.isOccluded()); + } + public void onActivationReset() { mKeyguardIndicationController.hideTransientIndication(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 64ab060e14a2..3b5e12c4ef96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -45,6 +45,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.view.Display; import android.view.View; import android.view.WindowManager; +import android.view.WindowManagerPolicyConstants; import androidx.test.filters.SmallTest; @@ -52,6 +53,7 @@ import com.android.systemui.R.dimen; import com.android.systemui.ScreenDecorations.TunablePaddingTagListener; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; +import com.android.systemui.statusbar.phone.NavigationModeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.tuner.TunablePadding; @@ -78,6 +80,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { private TunerService mTunerService; private StatusBarWindowView mView; private TunablePaddingService mTunablePaddingService; + private NavigationModeController mNavigationModeController; @Before public void setup() { @@ -87,6 +90,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { mTunablePaddingService = mDependency.injectMockDependency(TunablePaddingService.class); mTunerService = mDependency.injectMockDependency(TunerService.class); mFragmentService = mDependency.injectMockDependency(FragmentService.class); + mNavigationModeController = mDependency.injectMockDependency( + NavigationModeController.class); mStatusBar = mock(StatusBar.class); mWindowManager = mock(WindowManager.class); @@ -208,6 +213,54 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Test + public void testAssistHandles() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius, 0); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius_top, 0); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius_bottom, 0); + mContext.getOrCreateTestableResources() + .addOverride(dimen.rounded_corner_content_padding, 0); + when(mNavigationModeController.addListener(any())).thenReturn( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL); + + mScreenDecorations.start(); + + // Add 2 windows for rounded corners (top and bottom). + verify(mWindowManager, times(2)).addView(any(), any()); + } + + @Test + public void testDelayedAssistHandles() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius, 0); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius_top, 0); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius_bottom, 0); + mContext.getOrCreateTestableResources() + .addOverride(dimen.rounded_corner_content_padding, 0); + when(mNavigationModeController.addListener(any())).thenReturn( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON); + + mScreenDecorations.start(); + + // No handles and no corners + verify(mWindowManager, never()).addView(any(), any()); + + mScreenDecorations.handleNavigationModeChange( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL); + + // Add 2 windows for rounded corners (top and bottom). + verify(mWindowManager, times(2)).addView(any(), any()); + } + + @Test public void hasRoundedCornerOverlayFlagSet() { assertThat(mScreenDecorations.getWindowLayoutParams().privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index d0edaaaf7619..b9679133b739 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -210,6 +210,7 @@ class AlarmManagerService extends SystemService { IAlarmListener mTimeTickTrigger; PendingIntent mDateChangeSender; Random mRandom; + PendingIntent.CancelListener mOperationCancelListener; boolean mInteractive = true; long mNonInteractiveStartTime; long mNonInteractiveTime; @@ -1497,6 +1498,7 @@ class AlarmManagerService extends SystemService { synchronized (mLock) { mHandler = new AlarmHandler(); + mOperationCancelListener = (intent) -> removeImpl(intent, null); mConstants = new Constants(mHandler); mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW); @@ -1748,7 +1750,9 @@ class AlarmManagerService extends SystemService { } else { maxElapsed = triggerElapsed + windowLength; } - + if (operation != null) { + operation.registerCancelListener(mOperationCancelListener); + } synchronized (mLock) { if (DEBUG_BATCH) { Slog.v(TAG, "set(" + operation + ") : type=" + type @@ -1761,6 +1765,8 @@ class AlarmManagerService extends SystemService { "Maximum limit of concurrent alarms " + mConstants.MAX_ALARMS_PER_UID + " reached for uid: " + UserHandle.formatUid(callingUid) + ", callingPackage: " + callingPackage; + mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER, + operation).sendToTarget(); // STOPSHIP (b/128866264): Just to catch breakages. Remove before final release. Slog.wtf(TAG, errorMsg); throw new IllegalStateException(errorMsg); @@ -1782,6 +1788,8 @@ class AlarmManagerService extends SystemService { if (ActivityManager.getService().isAppStartModeDisabled(callingUid, callingPackage)) { Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a + " -- package not allowed to start"); + mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER, + operation).sendToTarget(); return; } } catch (RemoteException e) { @@ -2136,10 +2144,11 @@ class AlarmManagerService extends SystemService { Slog.w(TAG, "remove() with no intent or listener"); return; } - synchronized (mLock) { removeLocked(operation, listener); } + mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER, + operation).sendToTarget(); } @Override @@ -4179,6 +4188,7 @@ class AlarmManagerService extends SystemService { public static final int APP_STANDBY_BUCKET_CHANGED = 5; public static final int APP_STANDBY_PAROLE_CHANGED = 6; public static final int REMOVE_FOR_STOPPED = 7; + public static final int UNREGISTER_CANCEL_LISTENER = 8; AlarmHandler() { super(Looper.myLooper()); @@ -4261,6 +4271,13 @@ class AlarmManagerService extends SystemService { } break; + case UNREGISTER_CANCEL_LISTENER: + final PendingIntent pi = (PendingIntent) msg.obj; + if (pi != null) { + pi.unregisterCancelListener(mOperationCancelListener); + } + break; + default: // nope, just ignore it break; @@ -4716,6 +4733,11 @@ class AlarmManagerService extends SystemService { Intent.EXTRA_ALARM_COUNT, alarm.count), mDeliveryTracker, mHandler, null, allowWhileIdle ? mIdleOptions : null); + if (alarm.repeatInterval == 0) { + // Keep the listener for repeating alarms until they get cancelled + mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER, + alarm.operation).sendToTarget(); + } } catch (PendingIntent.CanceledException e) { if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 998ee1ed9ebe..7824a0ac3194 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.job.JobInfo; import android.content.BroadcastReceiver; @@ -56,6 +57,7 @@ import android.os.ShellCallback; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -63,6 +65,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BinderDeathDispatcher; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; @@ -83,6 +86,9 @@ public final class ContentService extends IContentService.Stub { static final String TAG = "ContentService"; static final boolean DEBUG = false; + /** Do a WTF if a single observer is registered more than this times. */ + private static final int TOO_MANY_OBSERVERS_THRESHOLD = 1000; + public static class Lifecycle extends SystemService { private ContentService mService; @@ -135,6 +141,12 @@ public final class ContentService extends IContentService.Stub { private SyncManager mSyncManager = null; private final Object mSyncManagerLock = new Object(); + private static final BinderDeathDispatcher<IContentObserver> sObserverDeathDispatcher = + new BinderDeathDispatcher<>(); + + @GuardedBy("sObserverLeakDetectedUid") + private static final ArraySet<Integer> sObserverLeakDetectedUid = new ArraySet<>(0); + /** * Map from userId to providerPackageName to [clientPackageName, uri] to * value. This structure is carefully optimized to keep invalidation logic @@ -236,6 +248,13 @@ public final class ContentService extends IContentService.Stub { pw.println(); pw.print(" Total number of nodes: "); pw.println(counts[0]); pw.print(" Total number of observers: "); pw.println(counts[1]); + + sObserverDeathDispatcher.dump(pw, " "); + } + synchronized (sObserverLeakDetectedUid) { + pw.println(); + pw.print("Observer leaking UIDs: "); + pw.println(sObserverLeakDetectedUid.toString()); } synchronized (mCache) { @@ -1345,18 +1364,40 @@ public final class ContentService extends IContentService.Stub { private final Object observersLock; public ObserverEntry(IContentObserver o, boolean n, Object observersLock, - int _uid, int _pid, int _userHandle) { + int _uid, int _pid, int _userHandle, Uri uri) { this.observersLock = observersLock; observer = o; uid = _uid; pid = _pid; userHandle = _userHandle; notifyForDescendants = n; - try { - observer.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { + + final int entries = sObserverDeathDispatcher.linkToDeath(observer, this); + if (entries == -1) { binderDied(); + } else if (entries == TOO_MANY_OBSERVERS_THRESHOLD) { + boolean alreadyDetected; + + synchronized (sObserverLeakDetectedUid) { + alreadyDetected = sObserverLeakDetectedUid.contains(uid); + if (!alreadyDetected) { + sObserverLeakDetectedUid.add(uid); + } + } + if (!alreadyDetected) { + String caller = null; + try { + caller = ArrayUtils.firstOrNull(AppGlobals.getPackageManager() + .getPackagesForUid(uid)); + } catch (RemoteException ignore) { + } + Slog.wtf(TAG, "Observer registered too many times. Leak? cpid=" + pid + + " cuid=" + uid + + " cpkg=" + caller + + " url=" + uri); + } } + } @Override @@ -1454,7 +1495,7 @@ public final class ContentService extends IContentService.Stub { // If this is the leaf node add the observer if (index == countUriSegments(uri)) { mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock, - uid, pid, userHandle)); + uid, pid, userHandle, uri)); return; } @@ -1498,7 +1539,7 @@ public final class ContentService extends IContentService.Stub { if (entry.observer.asBinder() == observerBinder) { mObservers.remove(i); // We no longer need to listen for death notifications. Remove it. - observerBinder.unlinkToDeath(entry, 0); + sObserverDeathDispatcher.unlinkToDeath(observer, entry); break; } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index bd8859401856..0032e9a8ea51 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -617,7 +617,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mStagingManager.createSession(session); } - mCallbacks.notifySessionCreated(session.sessionId, session.userId); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionCreated(session.sessionId, session.userId); + } writeSessionsAsync(); return sessionId; } @@ -1210,16 +1212,25 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements class InternalCallback { public void onSessionBadgingChanged(PackageInstallerSession session) { - mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId); + } + writeSessionsAsync(); } public void onSessionActiveChanged(PackageInstallerSession session, boolean active) { - mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, active); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, + active); + } } public void onSessionProgressChanged(PackageInstallerSession session, float progress) { - mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, + progress); + } } public void onStagedSessionChanged(PackageInstallerSession session) { @@ -1232,7 +1243,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } public void onSessionFinished(final PackageInstallerSession session, boolean success) { - mCallbacks.notifySessionFinished(session.sessionId, session.userId, success); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionFinished(session.sessionId, session.userId, success); + } mInstallHandler.post(new Runnable() { @Override diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 8e05f7405be7..5ba47d8433cc 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -2639,7 +2639,7 @@ class ActivityStack extends ConfigurationContainer { if (!hasRunningActivity) { // There are no activities left in the stack, let's look somewhere else. - return resumeTopActivityInNextFocusableStack(prev, options, "noMoreActivities"); + return resumeNextFocusableActivityWhenStackIsEmpty(prev, options); } next.delayedResume = false; @@ -3040,21 +3040,33 @@ class ActivityStack extends ConfigurationContainer { return true; } - private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev, - ActivityOptions options, String reason) { - final ActivityStack nextFocusedStack = adjustFocusToNextFocusableStack(reason); - if (nextFocusedStack != null) { - // Try to move focus to the next visible stack with a running activity if this - // stack is not covering the entire screen or is on a secondary display (with no home - // stack). - return mRootActivityContainer.resumeFocusedStacksTopActivities(nextFocusedStack, prev, - null /* targetOptions */); + /** + * Resume the next eligible activity in a focusable stack when this one does not have any + * running activities left. The focus will be adjusted to the next focusable stack and + * top running activities will be resumed in all focusable stacks. However, if the current stack + * is a home stack - we have to keep it focused, start and resume a home activity on the current + * display instead to make sure that the display is not empty. + */ + private boolean resumeNextFocusableActivityWhenStackIsEmpty(ActivityRecord prev, + ActivityOptions options) { + final String reason = "noMoreActivities"; + + if (!isActivityTypeHome()) { + final ActivityStack nextFocusedStack = adjustFocusToNextFocusableStack(reason); + if (nextFocusedStack != null) { + // Try to move focus to the next visible stack with a running activity if this + // stack is not covering the entire screen or is on a secondary display with no home + // stack. + return mRootActivityContainer.resumeFocusedStacksTopActivities(nextFocusedStack, + prev, null /* targetOptions */); + } } - // Let's just start up the Launcher... + // If the current stack is a home stack, or if focus didn't switch to a different stack - + // just start up the Launcher... ActivityOptions.abort(options); if (DEBUG_STATES) Slog.d(TAG_STATES, - "resumeTopActivityInNextFocusableStack: " + reason + ", go home"); + "resumeNextFocusableActivityWhenStackIsEmpty: " + reason + ", go home"); return mRootActivityContainer.resumeHomeActivity(prev, reason, mDisplayId); } diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java index cdcdf9195ad5..1e29ed6ba5f3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -1032,6 +1032,16 @@ public class AlarmManagerServiceTest { assertEquals(-1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, -1)); } + @Test + public void alarmCountOnPendingIntentCancel() { + final PendingIntent pi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 123, pi); + verify(pi).registerCancelListener(mService.mOperationCancelListener); + assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + mService.mOperationCancelListener.onCancelled(pi); + assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + } + @After public void tearDown() { if (mMockingSession != null) { diff --git a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java index 62b0ca805c1b..891ca74a545f 100644 --- a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java +++ b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java @@ -30,7 +30,7 @@ import com.android.server.content.ContentService.ObserverCall; import com.android.server.content.ContentService.ObserverNode; /** - * bit FrameworksServicesTests:com.android.server.content.ObserverNodeTest + * atest FrameworksServicesTests:com.android.server.content.ObserverNodeTest */ @SmallTest public class ObserverNodeTest extends AndroidTestCase { diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index c77e25fabf19..8d2c3dd80538 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -35,6 +36,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityDisplay.POSITION_TOP; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; +import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.RootActivityContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; import static org.junit.Assert.assertEquals; @@ -397,6 +399,58 @@ public class RootActivityContainerTests extends ActivityTestsBase { } /** + * Verify that home activity will be started on a display even if another display has a + * focusable activity. + */ + @Test + public void testResumeFocusedStacksStartsHomeActivity_NoActivities() { + mFullscreenStack.remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY) + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + + doReturn(true).when(mRootActivityContainer).resumeHomeActivity(any(), any(), anyInt()); + + mService.setBooted(true); + + // Trigger resume on all displays + mRootActivityContainer.resumeFocusedStacksTopActivities(); + + // Verify that home activity was started on the default display + verify(mRootActivityContainer).resumeHomeActivity(any(), any(), eq(DEFAULT_DISPLAY)); + } + + /** + * Verify that home activity will be started on a display even if another display has a + * focusable activity. + */ + @Test + public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() { + mFullscreenStack.remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY) + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + + // Create an activity on secondary display. + final TestActivityDisplay secondDisplay = addNewActivityDisplayAt( + ActivityDisplay.POSITION_TOP); + final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build(); + new ActivityBuilder(mService).setTask(task).build(); + + doReturn(true).when(mRootActivityContainer).resumeHomeActivity(any(), any(), anyInt()); + + mService.setBooted(true); + + // Trigger resume on all displays + mRootActivityContainer.resumeFocusedStacksTopActivities(); + + // Verify that home activity was started on the default display + verify(mRootActivityContainer).resumeHomeActivity(any(), any(), eq(DEFAULT_DISPLAY)); + } + + /** * Verify that a lingering transition is being executed in case the activity to be resumed is * already resumed */ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index d6011b946139..b7b511e5eb4b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1498,6 +1498,48 @@ public class TelephonyManager { */ public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL = 4; + /** + * Integer intent extra to be used with {@link #ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED} + * to indicate if the SIM combination in DSDS has limitation or compatible issue. + * e.g. two CDMA SIMs may disrupt each other's voice call in certain scenarios. + * + * @hide + */ + public static final String EXTRA_SIM_COMBINATION_WARNING_TYPE = + "android.telephony.extra.SIM_COMBINATION_WARNING_TYPE"; + + /** @hide */ + @IntDef({ + EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE, + EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SimCombinationWarningType{} + + /** + * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE} + * to indicate there's no SIM combination warning. + * @hide + */ + public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE = 0; + + /** + * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE} + * to indicate two active SIMs are both CDMA hence there might be functional limitation. + * @hide + */ + public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA = 1; + + /** + * String intent extra to be used with {@link #ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED} + * to indicate what's the name of SIM combination it has limitation or compatible issue. + * e.g. two CDMA SIMs may disrupt each other's voice call in certain scenarios, and the + * name will be "operator1 & operator2". + * + * @hide + */ + public static final String EXTRA_SIM_COMBINATION_NAMES = + "android.telephony.extra.SIM_COMBINATION_NAMES"; // // // Device Info |