diff options
91 files changed, 1816 insertions, 937 deletions
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 20f9e76b4436..d3d7e1d483e8 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -56,7 +56,6 @@ cc_defaults { "src/condition/condition_util.cpp", "src/condition/ConditionWizard.cpp", "src/condition/SimpleConditionTracker.cpp", - "src/condition/StateConditionTracker.cpp", "src/config/ConfigKey.cpp", "src/config/ConfigListener.cpp", "src/config/ConfigManager.cpp", @@ -315,7 +314,6 @@ cc_test { "tests/condition/CombinationConditionTracker_test.cpp", "tests/condition/ConditionTimer_test.cpp", "tests/condition/SimpleConditionTracker_test.cpp", - "tests/condition/StateConditionTracker_test.cpp", "tests/ConfigManager_test.cpp", "tests/e2e/Alarm_e2e_test.cpp", "tests/e2e/Anomaly_count_e2e_test.cpp", diff --git a/cmds/statsd/src/condition/StateConditionTracker.cpp b/cmds/statsd/src/condition/StateConditionTracker.cpp deleted file mode 100644 index d19a1761ac00..000000000000 --- a/cmds/statsd/src/condition/StateConditionTracker.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#define DEBUG false // STOPSHIP if true -#include "Log.h" - -#include "StateConditionTracker.h" -#include "guardrail/StatsdStats.h" - -namespace android { -namespace os { -namespace statsd { - -using std::vector; - -StateConditionTracker::StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index, - const SimplePredicate& simplePredicate, - const unordered_map<int64_t, int>& trackerNameIndexMap, - const vector<Matcher> primaryKeys) - : ConditionTracker(id, index), mConfigKey(key), mPrimaryKeys(primaryKeys) { - if (simplePredicate.has_start()) { - auto pair = trackerNameIndexMap.find(simplePredicate.start()); - if (pair == trackerNameIndexMap.end()) { - ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start()); - return; - } - mStartLogMatcherIndex = pair->second; - mTrackerIndex.insert(mStartLogMatcherIndex); - } else { - ALOGW("Condition %lld must have a start matcher", (long long)id); - return; - } - - if (simplePredicate.has_dimensions()) { - translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions); - if (mOutputDimensions.size() > 0) { - mSliced = true; - mDimensionTag = mOutputDimensions[0].mMatcher.getTag(); - } else { - ALOGW("Condition %lld has invalid dimensions", (long long)id); - return; - } - } else { - ALOGW("Condition %lld being a state tracker, but has no dimension", (long long)id); - return; - } - - if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) { - mInitialValue = ConditionState::kFalse; - } else { - mInitialValue = ConditionState::kUnknown; - } - - mNonSlicedConditionState = mInitialValue; - mInitialized = true; -} - -StateConditionTracker::~StateConditionTracker() { - VLOG("~StateConditionTracker()"); -} - -bool StateConditionTracker::init(const vector<Predicate>& allConditionConfig, - const vector<sp<ConditionTracker>>& allConditionTrackers, - const unordered_map<int64_t, int>& conditionIdIndexMap, - vector<bool>& stack) { - return mInitialized; -} - -void StateConditionTracker::dumpState() { - VLOG("StateConditionTracker %lld DUMP:", (long long)mConditionId); - for (const auto& value : mSlicedState) { - VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str()); - } - VLOG("Last Changed to True: "); - for (const auto& value : mLastChangedToTrueDimensions) { - VLOG("%s", value.toString().c_str()); - } - VLOG("Last Changed to False: "); - for (const auto& value : mLastChangedToFalseDimensions) { - VLOG("%s", value.toString().c_str()); - } -} - -bool StateConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { - if (mSlicedState.find(newKey) != mSlicedState.end()) { - // if the condition is not sliced or the key is not new, we are good! - return false; - } - // 1. Report the tuple count if the tuple count > soft limit - if (mSlicedState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = mSlicedState.size() + 1; - StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("Predicate %lld dropping data for dimension key %s", - (long long)mConditionId, newKey.toString().c_str()); - return true; - } - } - return false; -} - -void StateConditionTracker::evaluateCondition(const LogEvent& event, - const vector<MatchingState>& eventMatcherValues, - const vector<sp<ConditionTracker>>& mAllConditions, - vector<ConditionState>& conditionCache, - vector<bool>& conditionChangedCache) { - mLastChangedToTrueDimensions.clear(); - mLastChangedToFalseDimensions.clear(); - if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { - // it has been evaluated. - VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]); - return; - } - - if (mStartLogMatcherIndex >= 0 && - eventMatcherValues[mStartLogMatcherIndex] != MatchingState::kMatched) { - conditionCache[mIndex] = - mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionChangedCache[mIndex] = false; - return; - } - - VLOG("StateConditionTracker evaluate event %s", event.ToString().c_str()); - - // Primary key can exclusive fields must be simple fields. so there won't be more than - // one keys matched. - HashableDimensionKey primaryKey; - HashableDimensionKey state; - if ((mPrimaryKeys.size() > 0 && !filterValues(mPrimaryKeys, event.getValues(), &primaryKey)) || - !filterValues(mOutputDimensions, event.getValues(), &state)) { - ALOGE("Failed to filter fields in the event?? panic now!"); - conditionCache[mIndex] = - mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse; - conditionChangedCache[mIndex] = false; - return; - } - hitGuardRail(primaryKey); - - VLOG("StateConditionTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str()); - - auto it = mSlicedState.find(primaryKey); - if (it == mSlicedState.end()) { - mSlicedState[primaryKey] = state; - conditionCache[mIndex] = ConditionState::kTrue; - mLastChangedToTrueDimensions.insert(state); - conditionChangedCache[mIndex] = true; - } else if (!(it->second == state)) { - mLastChangedToFalseDimensions.insert(it->second); - mLastChangedToTrueDimensions.insert(state); - mSlicedState[primaryKey] = state; - conditionCache[mIndex] = ConditionState::kTrue; - conditionChangedCache[mIndex] = true; - } else { - conditionCache[mIndex] = ConditionState::kTrue; - conditionChangedCache[mIndex] = false; - } - - if (DEBUG) { - dumpState(); - } - return; -} - -void StateConditionTracker::isConditionMet( - const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions, - const bool isPartialLink, - vector<ConditionState>& conditionCache) const { - if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { - // it has been evaluated. - VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]); - return; - } - - const auto pair = conditionParameters.find(mConditionId); - if (pair == conditionParameters.end()) { - if (mSlicedState.size() > 0) { - conditionCache[mIndex] = ConditionState::kTrue; - } else { - conditionCache[mIndex] = ConditionState::kUnknown; - } - return; - } - - const auto& primaryKey = pair->second; - conditionCache[mIndex] = mInitialValue; - auto it = mSlicedState.find(primaryKey); - if (it != mSlicedState.end()) { - conditionCache[mIndex] = ConditionState::kTrue; - } -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/condition/StateConditionTracker.h b/cmds/statsd/src/condition/StateConditionTracker.h deleted file mode 100644 index 0efe1fb3fcb2..000000000000 --- a/cmds/statsd/src/condition/StateConditionTracker.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include <gtest/gtest_prod.h> -#include "ConditionTracker.h" -#include "config/ConfigKey.h" -#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" -#include "stats_util.h" - -namespace android { -namespace os { -namespace statsd { - -class StateConditionTracker : public virtual ConditionTracker { -public: - StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index, - const SimplePredicate& simplePredicate, - const std::unordered_map<int64_t, int>& trackerNameIndexMap, - const vector<Matcher> primaryKeys); - - ~StateConditionTracker(); - - bool init(const std::vector<Predicate>& allConditionConfig, - const std::vector<sp<ConditionTracker>>& allConditionTrackers, - const std::unordered_map<int64_t, int>& conditionIdIndexMap, - std::vector<bool>& stack) override; - - void evaluateCondition(const LogEvent& event, - const std::vector<MatchingState>& eventMatcherValues, - const std::vector<sp<ConditionTracker>>& mAllConditions, - std::vector<ConditionState>& conditionCache, - std::vector<bool>& changedCache) override; - - /** - * Note: dimensionFields will be ignored in StateConditionTracker, because we demand metrics - * must take the entire dimension fields from StateConditionTracker. This is to make implementation - * simple and efficient. - * - * For example: wakelock duration by uid process states: - * dimension in condition must be {uid, process state}. - */ - void isConditionMet(const ConditionKey& conditionParameters, - const std::vector<sp<ConditionTracker>>& allConditions, - const bool isPartialLink, - std::vector<ConditionState>& conditionCache) const override; - - virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions( - const std::vector<sp<ConditionTracker>>& allConditions) const { - return &mLastChangedToTrueDimensions; - } - - virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions( - const std::vector<sp<ConditionTracker>>& allConditions) const { - return &mLastChangedToFalseDimensions; - } - - bool IsChangedDimensionTrackable() const override { return true; } - - bool IsSimpleCondition() const override { return true; } - - bool equalOutputDimensions( - const std::vector<sp<ConditionTracker>>& allConditions, - const vector<Matcher>& dimensions) const override { - return equalDimensions(mOutputDimensions, dimensions); - } - - void getTrueSlicedDimensions( - const std::vector<sp<ConditionTracker>>& allConditions, - std::set<HashableDimensionKey>* dimensions) const override { - for (const auto& itr : mSlicedState) { - dimensions->insert(itr.second); - } - } - -private: - const ConfigKey mConfigKey; - - // The index of the LogEventMatcher which defines the start. - int mStartLogMatcherIndex; - - std::set<HashableDimensionKey> mLastChangedToTrueDimensions; - std::set<HashableDimensionKey> mLastChangedToFalseDimensions; - - std::vector<Matcher> mOutputDimensions; - std::vector<Matcher> mPrimaryKeys; - - ConditionState mInitialValue; - - int mDimensionTag; - - void dumpState(); - - bool hitGuardRail(const HashableDimensionKey& newKey); - - // maps from [primary_key] to [primary_key, exclusive_state]. - std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState; - - FRIEND_TEST(StateConditionTrackerTest, TestStateChange); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 0d0788e05e0a..2fcb13b709f9 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -26,7 +26,6 @@ #include "MetricProducer.h" #include "condition/CombinationConditionTracker.h" #include "condition/SimpleConditionTracker.h" -#include "condition/StateConditionTracker.h" #include "external/StatsPullerManager.h" #include "matchers/CombinationLogMatchingTracker.h" #include "matchers/EventMatcherWizard.h" @@ -283,49 +282,6 @@ bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap, return true; } -/** - * A StateConditionTracker is built from a SimplePredicate which has only "start", and no "stop" - * or "stop_all". The start must be an atom matcher that matches a state atom. It must - * have dimension, the dimension must be the state atom's primary fields plus exclusive state - * field. For example, the StateConditionTracker is used in tracking UidProcessState and ScreenState. - * - */ -bool isStateConditionTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) { - // 1. must not have "stop". must have "dimension" - if (!simplePredicate.has_stop() && simplePredicate.has_dimensions()) { - auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find( - simplePredicate.dimensions().field()); - // 2. must be based on a state atom. - if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) { - // 3. dimension must be primary fields + state field IN ORDER - size_t expectedDimensionCount = it->second.primaryFields.size() + 1; - vector<Matcher> dimensions; - translateFieldMatcher(simplePredicate.dimensions(), &dimensions); - if (dimensions.size() != expectedDimensionCount) { - return false; - } - // 3.1 check the primary fields first. - size_t index = 0; - for (const auto& field : it->second.primaryFields) { - Matcher matcher = getSimpleMatcher(it->first, field); - if (!(matcher == dimensions[index])) { - return false; - } - primaryKeys->push_back(matcher); - index++; - } - Matcher stateFieldMatcher = - getSimpleMatcher(it->first, it->second.exclusiveField); - // 3.2 last dimension should be the exclusive field. - if (!(dimensions.back() == stateFieldMatcher)) { - return false; - } - return true; - } - } - return false; -} // namespace statsd - bool initConditions(const ConfigKey& key, const StatsdConfig& config, const unordered_map<int64_t, int>& logTrackerMap, unordered_map<int64_t, int>& conditionTrackerMap, @@ -341,16 +297,8 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, int index = allConditionTrackers.size(); switch (condition.contents_case()) { case Predicate::ContentsCase::kSimplePredicate: { - vector<Matcher> primaryKeys; - if (isStateConditionTracker(condition.simple_predicate(), &primaryKeys)) { - allConditionTrackers.push_back(new StateConditionTracker(key, condition.id(), index, - condition.simple_predicate(), - logTrackerMap, primaryKeys)); - } else { - allConditionTrackers.push_back(new SimpleConditionTracker( - key, condition.id(), index, condition.simple_predicate(), - logTrackerMap)); - } + allConditionTrackers.push_back(new SimpleConditionTracker( + key, condition.id(), index, condition.simple_predicate(), logTrackerMap)); break; } case Predicate::ContentsCase::kCombination: { diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h index a8ccc6289b9a..6af7a9adca20 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/metrics_manager_util.h @@ -132,8 +132,6 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds); -bool isStateConditionTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys); - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/condition/StateConditionTracker_test.cpp b/cmds/statsd/tests/condition/StateConditionTracker_test.cpp deleted file mode 100644 index 86b50ae82ff4..000000000000 --- a/cmds/statsd/tests/condition/StateConditionTracker_test.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (C) 2017 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "src/condition/StateConditionTracker.h" -#include "tests/statsd_test_util.h" - -#include <gmock/gmock.h> -#include <gtest/gtest.h> -#include <stdio.h> -#include <numeric> -#include <vector> - -using std::map; -using std::unordered_map; -using std::vector; - -#ifdef __ANDROID__ -namespace android { -namespace os { -namespace statsd { - -const int kUidProcTag = 27; - -SimplePredicate getUidProcStatePredicate() { - SimplePredicate simplePredicate; - simplePredicate.set_start(StringToId("UidProcState")); - - simplePredicate.mutable_dimensions()->set_field(kUidProcTag); - simplePredicate.mutable_dimensions()->add_child()->set_field(1); - simplePredicate.mutable_dimensions()->add_child()->set_field(2); - - simplePredicate.set_count_nesting(false); - return simplePredicate; -} - -// TODO(b/149590301): Update these tests to use new socket schema. -//void makeUidProcStateEvent(int32_t uid, int32_t state, LogEvent* event) { -// event->write(uid); -// event->write(state); -// event->init(); -//} -// -//TEST(StateConditionTrackerTest, TestStateChange) { -// int uid1 = 111; -// int uid2 = 222; -// -// int state1 = 1001; -// int state2 = 1002; -// unordered_map<int64_t, int> trackerNameIndexMap; -// trackerNameIndexMap[StringToId("UidProcState")] = 0; -// vector<Matcher> primaryFields; -// primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1)); -// StateConditionTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(), -// trackerNameIndexMap, primaryFields); -// -// LogEvent event(kUidProcTag, 0 /*timestamp*/); -// makeUidProcStateEvent(uid1, state1, &event); -// -// vector<MatchingState> matcherState; -// matcherState.push_back(MatchingState::kMatched); -// vector<sp<ConditionTracker>> allPredicates; -// vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated); -// vector<bool> changedCache(1, false); -// -// tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache); -// EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); -// EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); -// EXPECT_TRUE(changedCache[0]); -// -// changedCache[0] = false; -// conditionCache[0] = ConditionState::kNotEvaluated; -// tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache); -// EXPECT_EQ(0ULL, tracker.mLastChangedToTrueDimensions.size()); -// EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); -// EXPECT_FALSE(changedCache[0]); -// -// LogEvent event2(kUidProcTag, 0 /*timestamp*/); -// makeUidProcStateEvent(uid1, state2, &event2); -// -// changedCache[0] = false; -// conditionCache[0] = ConditionState::kNotEvaluated; -// tracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache); -// EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); -// EXPECT_EQ(1ULL, tracker.mLastChangedToFalseDimensions.size()); -// EXPECT_TRUE(changedCache[0]); -// -// LogEvent event3(kUidProcTag, 0 /*timestamp*/); -// makeUidProcStateEvent(uid2, state1, &event3); -// changedCache[0] = false; -// conditionCache[0] = ConditionState::kNotEvaluated; -// tracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache); -// EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size()); -// EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size()); -// EXPECT_TRUE(changedCache[0]); -//} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java index a381f9c4560a..2909048da7ea 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java @@ -17,16 +17,23 @@ package com.android.statsd.shelltools; import com.android.os.StatsLog.ConfigMetricsReportList; +import com.google.common.io.Files; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; import java.util.logging.ConsoleHandler; import java.util.logging.Formatter; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Utilities for local use of statsd. @@ -80,7 +87,8 @@ public class Utils { * @throws InterruptedException */ public static ConfigMetricsReportList getReportList(long configId, boolean clearData, - boolean useShellUid, Logger logger) throws IOException, InterruptedException { + boolean useShellUid, Logger logger, String deviceSerial) + throws IOException, InterruptedException { try { File outputFile = File.createTempFile("statsdret", ".bin"); outputFile.deleteOnExit(); @@ -88,6 +96,8 @@ public class Utils { outputFile, logger, "adb", + "-s", + deviceSerial, "shell", CMD_DUMP_REPORT, useShellUid ? SHELL_UID : "", @@ -117,12 +127,14 @@ public class Utils { * @throws IOException * @throws InterruptedException */ - public static void logAppBreadcrumb(int label, int state, Logger logger) + public static void logAppBreadcrumb(int label, int state, Logger logger, String deviceSerial) throws IOException, InterruptedException { runCommand( null, logger, "adb", + "-s", + deviceSerial, "shell", CMD_LOG_APP_BREADCRUMB, String.valueOf(label), @@ -145,13 +157,14 @@ public class Utils { * Algorithm: true if (sdk >= minSdk) || (sdk == minSdk-1 && codeName.startsWith(minCodeName)) * If all else fails, assume it will work (letting future commands deal with any errors). */ - public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename) { + public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename, + String deviceSerial) { BufferedReader in = null; try { File outFileSdk = File.createTempFile("shelltools_sdk", "tmp"); outFileSdk.deleteOnExit(); runCommand(outFileSdk, logger, - "adb", "shell", "getprop", "ro.build.version.sdk"); + "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.sdk"); in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileSdk))); // If NullPointerException/NumberFormatException/etc., just catch and return true. int sdk = Integer.parseInt(in.readLine().trim()); @@ -162,7 +175,7 @@ public class Utils { File outFileCode = File.createTempFile("shelltools_codename", "tmp"); outFileCode.deleteOnExit(); runCommand(outFileCode, logger, - "adb", "shell", "getprop", "ro.build.version.codename"); + "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.codename"); in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileCode))); return in.readLine().startsWith(minCodename); } else { @@ -190,4 +203,30 @@ public class Utils { return record.getMessage() + "\n"; } } + + /** + * Parse the result of "adb devices" to return the list of connected devices. + * @param logger Logger to log error messages + * @return List of the serial numbers of the connected devices. + */ + public static List<String> getDeviceSerials(Logger logger) { + try { + ArrayList<String> devices = new ArrayList<>(); + File outFile = File.createTempFile("device_serial", "tmp"); + outFile.deleteOnExit(); + Utils.runCommand(outFile, logger, "adb", "devices"); + List<String> outputLines = Files.readLines(outFile, Charset.defaultCharset()); + Pattern regex = Pattern.compile("^(.*)\tdevice$"); + for (String line : outputLines) { + Matcher m = regex.matcher(line); + if (m.find()) { + devices.add(m.group(1)); + } + } + return devices; + } catch (Exception ex) { + logger.log(Level.SEVERE, "Failed to list connected devices: " + ex.getMessage()); + } + return null; + } } diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java index 2eb46605b28d..7db514180b9a 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java @@ -26,6 +26,8 @@ import com.google.protobuf.TextFormat; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -49,7 +51,7 @@ public class LocalDrive { public static final String HELP_STRING = "Usage:\n\n" + - "statsd_localdrive upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] upload CONFIG_FILE [CONFIG_ID] [--binary]\n" + " Uploads the given statsd config file (in binary or human-readable-text format).\n" + " If a config with this id already exists, removes it first.\n" + " CONFIG_FILE Location of config file on host.\n" + @@ -59,12 +61,12 @@ public class LocalDrive { // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID "\n" + - "statsd_localdrive update CONFIG_FILE [CONFIG_ID] [--binary]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] update CONFIG_FILE [CONFIG_ID] [--binary]\n" + " Same as upload, but does not remove the old config first (if it already exists).\n" + // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID "\n" + - "statsd_localdrive get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" + " Prints the output statslog data (in binary or human-readable-text format).\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + " --binary Output should be in binary, instead of default human-readable text.\n" + @@ -75,13 +77,13 @@ public class LocalDrive { // --include_current_bucket --proto "\n" + - "statsd_localdrive remove [CONFIG_ID]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] remove [CONFIG_ID]\n" + " Removes the config.\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID "\n" + - "statsd_localdrive clear [CONFIG_ID]\n" + + "statsd_localdrive [-s DEVICE_SERIAL] clear [CONFIG_ID]\n" + " Clears the data associated with the config.\n" + " CONFIG_ID Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" + // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID @@ -94,29 +96,59 @@ public class LocalDrive { /** Usage: make statsd_localdrive && statsd_localdrive */ public static void main(String[] args) { Utils.setUpLogger(sLogger, DEBUG); + if (args.length == 0) { + printHelp(); + return; + } + + int remainingArgsLength = args.length; + String deviceSerial = null; + if (args[0].equals("-s")) { + if (args.length == 1) { + printHelp(); + } + deviceSerial = args[1]; + remainingArgsLength -= 2; + } + + List<String> connectedDevices = Utils.getDeviceSerials(sLogger); + if (connectedDevices == null || connectedDevices.size() == 0) { + sLogger.log(Level.SEVERE, "No device connected."); + return; + } + if (connectedDevices.size() == 1 && deviceSerial == null) { + deviceSerial = connectedDevices.get(0); + } + + if (deviceSerial == null) { + sLogger.log(Level.SEVERE, "More than one devices connected. Please specify" + + " with -s DEVICE_SERIAL"); + return; + } - if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME)) { + if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME, deviceSerial)) { sLogger.severe("LocalDrive only works with statsd versions for Android " + MIN_CODENAME + " or higher."); return; } - if (args.length > 0) { - switch (args[0]) { + int idx = args.length - remainingArgsLength; + if (remainingArgsLength > 0) { + switch (args[idx]) { case "clear": - cmdClear(args); + cmdClear(args, idx, deviceSerial); return; case "get-data": - cmdGetData(args); + cmdGetData(args, idx, deviceSerial); return; case "remove": - cmdRemove(args); + cmdRemove(args, idx); return; case "update": - cmdUpdate(args); + cmdUpdate(args, idx, deviceSerial); return; case "upload": - cmdUpload(args); + cmdUpload(args, idx, deviceSerial); return; } } @@ -128,17 +160,18 @@ public class LocalDrive { } // upload CONFIG_FILE [CONFIG_ID] [--binary] - private static boolean cmdUpload(String[] args) { - return updateConfig(args, true); + private static boolean cmdUpload(String[] args, int idx, String deviceSerial) { + return updateConfig(args, idx, true, deviceSerial); } // update CONFIG_FILE [CONFIG_ID] [--binary] - private static boolean cmdUpdate(String[] args) { - return updateConfig(args, false); + private static boolean cmdUpdate(String[] args, int idx, String deviceSerial) { + return updateConfig(args, idx, false, deviceSerial); } - private static boolean updateConfig(String[] args, boolean removeOldConfig) { - int argCount = args.length - 1; // Used up one for upload/update. + private static boolean updateConfig(String[] args, int idx, boolean removeOldConfig, + String deviceSerial) { + int argCount = args.length - 1 - idx; // Used up one for upload/update. // Get CONFIG_FILE if (argCount < 1) { @@ -146,7 +179,7 @@ public class LocalDrive { printHelp(); return false; } - final String origConfigLocation = args[1]; + final String origConfigLocation = args[idx + 1]; if (!new File(origConfigLocation).exists()) { sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation); return false; @@ -154,13 +187,13 @@ public class LocalDrive { argCount--; // Get --binary - boolean binary = contains(args, 2, BINARY_FLAG); + boolean binary = contains(args, idx + 2, BINARY_FLAG); if (binary) argCount --; // Get CONFIG_ID long configId; try { - configId = getConfigId(argCount < 1, args, 2); + configId = getConfigId(argCount < 1, args, idx + 2); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); @@ -174,7 +207,8 @@ public class LocalDrive { try { Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG, Utils.SHELL_UID, String.valueOf(configId)); - Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger); + Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, + deviceSerial); } catch (InterruptedException | IOException e) { sLogger.severe("Failed to remove config: " + e.getMessage()); return false; @@ -218,19 +252,19 @@ public class LocalDrive { } // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map] - private static boolean cmdGetData(String[] args) { - boolean binary = contains(args, 1, BINARY_FLAG); - boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG); - boolean clearData = contains(args, 1, CLEAR_DATA); + private static boolean cmdGetData(String[] args, int idx, String deviceSerial) { + boolean binary = contains(args, idx + 1, BINARY_FLAG); + boolean noUidMap = contains(args, idx + 1, NO_UID_MAP_FLAG); + boolean clearData = contains(args, idx + 1, CLEAR_DATA); // Get CONFIG_ID - int argCount = args.length - 1; // Used up one for get-data. + int argCount = args.length - 1 - idx; // Used up one for get-data. if (binary) argCount--; if (noUidMap) argCount--; if (clearData) argCount--; long configId; try { - configId = getConfigId(argCount < 1, args, 1); + configId = getConfigId(argCount < 1, args, idx + 1); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); @@ -243,7 +277,8 @@ public class LocalDrive { // Even if the args request no modifications, we still parse it to make sure it's valid. ConfigMetricsReportList reportList; try { - reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger); + reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger, + deviceSerial); } catch (IOException | InterruptedException e) { sLogger.severe("Failed to get report list: " + e.getMessage()); return false; @@ -274,11 +309,11 @@ public class LocalDrive { } // clear [CONFIG_ID] - private static boolean cmdClear(String[] args) { + private static boolean cmdClear(String[] args, int idx, String deviceSerial) { // Get CONFIG_ID long configId; try { - configId = getConfigId(false, args, 1); + configId = getConfigId(false, args, idx + 1); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); @@ -287,7 +322,8 @@ public class LocalDrive { sLogger.fine(String.format("cmdClear with %d", configId)); try { - Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger); + Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger, + deviceSerial); } catch (IOException | InterruptedException e) { sLogger.severe("Failed to get report list: " + e.getMessage()); return false; @@ -296,11 +332,11 @@ public class LocalDrive { } // remove [CONFIG_ID] - private static boolean cmdRemove(String[] args) { + private static boolean cmdRemove(String[] args, int idx) { // Get CONFIG_ID long configId; try { - configId = getConfigId(false, args, 1); + configId = getConfigId(false, args, idx + 1); } catch (NumberFormatException e) { sLogger.severe("Invalid config id provided."); printHelp(); diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index 75518a3ea56f..2a7cfd306174 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -71,6 +72,7 @@ public class TestDrive { private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); private String mAdditionalAllowedPackage; + private String mDeviceSerial; private final Set<Long> mTrackedMetrics = new HashSet<>(); public static void main(String[] args) { @@ -81,15 +83,41 @@ public class TestDrive { if (args.length < 1) { LOGGER.log(Level.SEVERE, "Usage: ./test_drive [-p additional_allowed_package] " + + "[-s DEVICE_SERIAL_NUMBER]" + "<atomId1> <atomId2> ... <atomIdN>"); return; } - if (args.length >= 3 && args[0].equals("-p")) { - testDrive.mAdditionalAllowedPackage = args[1]; + List<String> connectedDevices = Utils.getDeviceSerials(LOGGER); + if (connectedDevices == null || connectedDevices.size() == 0) { + LOGGER.log(Level.SEVERE, "No device connected."); + return; + } + + int arg_index = 0; + while (arg_index < args.length) { + String arg = args[arg_index]; + if (arg.equals("-p")) { + testDrive.mAdditionalAllowedPackage = args[++arg_index]; + } else if (arg.equals("-s")) { + testDrive.mDeviceSerial = args[++arg_index]; + } else { + break; + } + arg_index++; + } + + if (connectedDevices.size() == 1 && testDrive.mDeviceSerial == null) { + testDrive.mDeviceSerial = connectedDevices.get(0); + } + + if (testDrive.mDeviceSerial == null) { + LOGGER.log(Level.SEVERE, "More than one devices connected. Please specify" + + " with -s DEVICE_SERIAL"); + return; } - for (int i = testDrive.mAdditionalAllowedPackage == null ? 0 : 2; i < args.length; i++) { + for (int i = arg_index; i < args.length; i++) { try { int atomId = Integer.valueOf(args[i]); if (Atom.getDescriptor().findFieldByNumber(atomId) == null) { @@ -109,7 +137,7 @@ public class TestDrive { LOGGER.log(Level.SEVERE, "Failed to create valid config."); return; } - remoteConfigPath = testDrive.pushConfig(config); + remoteConfigPath = testDrive.pushConfig(config, testDrive.mDeviceSerial); LOGGER.info("Pushed the following config to statsd:"); LOGGER.info(config.toString()); if (!hasPulledAtom(trackedAtoms)) { @@ -120,17 +148,18 @@ public class TestDrive { } else { LOGGER.info("Now wait for 1.5 minutes ..."); Thread.sleep(15_000); - Utils.logAppBreadcrumb(0, 0, LOGGER); + Utils.logAppBreadcrumb(0, 0, LOGGER, testDrive.mDeviceSerial); Thread.sleep(75_000); } testDrive.dumpMetrics(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e); } finally { - testDrive.removeConfig(); + testDrive.removeConfig(testDrive.mDeviceSerial); if (remoteConfigPath != null) { try { - Utils.runCommand(null, LOGGER, "adb", "shell", "rm", remoteConfigPath); + Utils.runCommand(null, LOGGER, + "adb", "-s", testDrive.mDeviceSerial, "shell", "rm", remoteConfigPath); } catch (Exception e) { LOGGER.log(Level.WARNING, "Unable to remove remote config file: " + remoteConfigPath, e); @@ -140,7 +169,8 @@ public class TestDrive { } private void dumpMetrics() throws Exception { - ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER); + ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER, + mDeviceSerial); // We may get multiple reports. Take the last one. ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1); for (StatsLogReport statsLog : report.getMetricsList()) { @@ -216,22 +246,24 @@ public class TestDrive { return atomMatcherBuilder.build(); } - private static String pushConfig(StatsdConfig config) throws IOException, InterruptedException { + private static String pushConfig(StatsdConfig config, String deviceSerial) + throws IOException, InterruptedException { File configFile = File.createTempFile("statsdconfig", ".config"); configFile.deleteOnExit(); Files.write(config.toByteArray(), configFile); String remotePath = "/data/local/tmp/" + configFile.getName(); - Utils.runCommand(null, LOGGER, "adb", "push", configFile.getAbsolutePath(), remotePath); - Utils.runCommand(null, LOGGER, - "adb", "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG, + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "push", configFile.getAbsolutePath(), remotePath); + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG, String.valueOf(CONFIG_ID)); return remotePath; } - private static void removeConfig() { + private static void removeConfig(String deviceSerial) { try { - Utils.runCommand(null, LOGGER, - "adb", "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID)); + Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial, + "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID)); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to remove config: " + e.getMessage()); } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 65f45d895027..ea5cc7f2e8bc 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -634,17 +634,39 @@ public final class DisplayManager { public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface, int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { - return createVirtualDisplay(null /* projection */, name, width, height, densityDpi, surface, - flags, callback, handler, null /* uniqueId */); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, + height, densityDpi); + builder.setFlags(flags); + if (surface != null) { + builder.setSurface(surface); + } + return createVirtualDisplay(null /* projection */, builder.build(), callback, handler); } + // TODO : Remove this hidden API after remove all callers. (Refer to MultiDisplayService) /** @hide */ public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection, @NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface, int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler, @Nullable String uniqueId) { - return mGlobal.createVirtualDisplay(mContext, projection, - name, width, height, densityDpi, surface, flags, callback, handler, uniqueId); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, + height, densityDpi); + builder.setFlags(flags); + if (uniqueId != null) { + builder.setUniqueId(uniqueId); + } + if (surface != null) { + builder.setSurface(surface); + } + return createVirtualDisplay(projection, builder.build(), callback, handler); + } + + /** @hide */ + public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection, + @NonNull VirtualDisplayConfig virtualDisplayConfig, + @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { + return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback, + handler); } /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 526db85b47d4..4d645e6052a7 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -451,35 +451,26 @@ public final class DisplayManagerGlobal { } } - public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection, - String name, int width, int height, int densityDpi, Surface surface, int flags, - VirtualDisplay.Callback callback, Handler handler, String uniqueId) { - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("name must be non-null and non-empty"); - } - if (width <= 0 || height <= 0 || densityDpi <= 0) { - throw new IllegalArgumentException("width, height, and densityDpi must be " - + "greater than 0"); - } - + public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection, + @NonNull VirtualDisplayConfig virtualDisplayConfig, VirtualDisplay.Callback callback, + Handler handler) { VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler); IMediaProjection projectionToken = projection != null ? projection.getProjection() : null; int displayId; try { - displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken, - context.getPackageName(), name, width, height, densityDpi, surface, flags, - uniqueId); + displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper, + projectionToken, context.getPackageName()); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } if (displayId < 0) { - Log.e(TAG, "Could not create virtual display: " + name); + Log.e(TAG, "Could not create virtual display: " + virtualDisplayConfig.getName()); return null; } Display display = getRealDisplay(displayId); if (display == null) { Log.wtf(TAG, "Could not obtain display info for newly created " - + "virtual display: " + name); + + "virtual display: " + virtualDisplayConfig.getName()); try { mDm.releaseVirtualDisplay(callbackWrapper); } catch (RemoteException ex) { @@ -487,7 +478,8 @@ public final class DisplayManagerGlobal { } return null; } - return new VirtualDisplay(this, display, callbackWrapper, surface); + return new VirtualDisplay(this, display, callbackWrapper, + virtualDisplayConfig.getSurface()); } public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) { diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index d22188ec5d7f..c697106d0c17 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -22,6 +22,7 @@ import android.hardware.display.BrightnessConfiguration; import android.hardware.display.Curve; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.hardware.display.WifiDisplay; import android.hardware.display.WifiDisplayStatus; import android.media.projection.IMediaProjection; @@ -71,9 +72,9 @@ interface IDisplayManager { // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate // MediaProjection token for certain combinations of flags. - int createVirtualDisplay(in IVirtualDisplayCallback callback, - in IMediaProjection projectionToken, String packageName, String name, - int width, int height, int densityDpi, in Surface surface, int flags, String uniqueId); + int createVirtualDisplay(in VirtualDisplayConfig virtualDisplayConfig, + in IVirtualDisplayCallback callback, in IMediaProjection projectionToken, + String packageName); // No permissions required, but must be same Uid as the creator. void resizeVirtualDisplay(in IVirtualDisplayCallback token, diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.aidl b/core/java/android/hardware/display/VirtualDisplayConfig.aidl new file mode 100644 index 000000000000..c28f1dfb9806 --- /dev/null +++ b/core/java/android/hardware/display/VirtualDisplayConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +parcelable VirtualDisplayConfig; diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java new file mode 100644 index 000000000000..10e1c7c2e0df --- /dev/null +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.projection.MediaProjection; +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.Surface; + +import com.android.internal.util.DataClass; + +/** + * Holds configuration used to create {@link VirtualDisplay} instances. See + * {@link MediaProjection#createVirtualDisplay(VirtualDisplayConfig, VirtualDisplay.Callback, Handler)}. + * + * @hide + */ +@DataClass(genParcelable = true, genAidl = true, genBuilder = true) +public final class VirtualDisplayConfig implements Parcelable { + /** + * The name of the virtual display, must be non-empty. + */ + @NonNull + private String mName; + + /** + * The width of the virtual display in pixels. Must be greater than 0. + */ + @IntRange(from = 1) + private int mWidth; + + /** + * The height of the virtual display in pixels. Must be greater than 0. + */ + @IntRange(from = 1) + private int mHeight; + + /** + * The density of the virtual display in dpi. Must be greater than 0. + */ + @IntRange(from = 1) + private int mDensityDpi; + + /** + * A combination of virtual display flags. + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, + * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. + */ + private int mFlags = 0; + + /** + * The surface to which the content of the virtual display should be rendered, or null if + * there is none initially. + */ + @Nullable + private Surface mSurface = null; + + /** + * The unique identifier for the display. Shouldn't be displayed to the user. + * @hide + */ + @Nullable + private String mUniqueId = null; + + /** + * The id of the display that the virtual display should mirror, or + * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially. + */ + private int mDisplayIdToMirror = DEFAULT_DISPLAY; + + + + // Code below generated by codegen v1.0.15. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ VirtualDisplayConfig( + @NonNull String name, + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @IntRange(from = 1) int densityDpi, + int flags, + @Nullable Surface surface, + @Nullable String uniqueId, + int displayIdToMirror) { + this.mName = name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mName); + this.mWidth = width; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mWidth, + "from", 1); + this.mHeight = height; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mHeight, + "from", 1); + this.mDensityDpi = densityDpi; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mDensityDpi, + "from", 1); + this.mFlags = flags; + this.mSurface = surface; + this.mUniqueId = uniqueId; + this.mDisplayIdToMirror = displayIdToMirror; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * The name of the virtual display, must be non-empty. + */ + @DataClass.Generated.Member + public @NonNull String getName() { + return mName; + } + + /** + * The width of the virtual display in pixels. Must be greater than 0. + */ + @DataClass.Generated.Member + public @IntRange(from = 1) int getWidth() { + return mWidth; + } + + /** + * The height of the virtual display in pixels. Must be greater than 0. + */ + @DataClass.Generated.Member + public @IntRange(from = 1) int getHeight() { + return mHeight; + } + + /** + * The density of the virtual display in dpi. Must be greater than 0. + */ + @DataClass.Generated.Member + public @IntRange(from = 1) int getDensityDpi() { + return mDensityDpi; + } + + /** + * A combination of virtual display flags. + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, + * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. + */ + @DataClass.Generated.Member + public int getFlags() { + return mFlags; + } + + /** + * The surface to which the content of the virtual display should be rendered, or null if + * there is none initially. + */ + @DataClass.Generated.Member + public @Nullable Surface getSurface() { + return mSurface; + } + + /** + * The unique identifier for the display. Shouldn't be displayed to the user. + * + * @hide + */ + @DataClass.Generated.Member + public @Nullable String getUniqueId() { + return mUniqueId; + } + + /** + * The id of the display that the virtual display should mirror, or + * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially. + */ + @DataClass.Generated.Member + public int getDisplayIdToMirror() { + return mDisplayIdToMirror; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + int flg = 0; + if (mSurface != null) flg |= 0x20; + if (mUniqueId != null) flg |= 0x40; + dest.writeInt(flg); + dest.writeString(mName); + dest.writeInt(mWidth); + dest.writeInt(mHeight); + dest.writeInt(mDensityDpi); + dest.writeInt(mFlags); + if (mSurface != null) dest.writeTypedObject(mSurface, flags); + if (mUniqueId != null) dest.writeString(mUniqueId); + dest.writeInt(mDisplayIdToMirror); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ VirtualDisplayConfig(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int flg = in.readInt(); + String name = in.readString(); + int width = in.readInt(); + int height = in.readInt(); + int densityDpi = in.readInt(); + int flags = in.readInt(); + Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR); + String uniqueId = (flg & 0x40) == 0 ? null : in.readString(); + int displayIdToMirror = in.readInt(); + + this.mName = name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mName); + this.mWidth = width; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mWidth, + "from", 1); + this.mHeight = height; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mHeight, + "from", 1); + this.mDensityDpi = densityDpi; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mDensityDpi, + "from", 1); + this.mFlags = flags; + this.mSurface = surface; + this.mUniqueId = uniqueId; + this.mDisplayIdToMirror = displayIdToMirror; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<VirtualDisplayConfig> CREATOR + = new Parcelable.Creator<VirtualDisplayConfig>() { + @Override + public VirtualDisplayConfig[] newArray(int size) { + return new VirtualDisplayConfig[size]; + } + + @Override + public VirtualDisplayConfig createFromParcel(@NonNull Parcel in) { + return new VirtualDisplayConfig(in); + } + }; + + /** + * A builder for {@link VirtualDisplayConfig} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @NonNull String mName; + private @IntRange(from = 1) int mWidth; + private @IntRange(from = 1) int mHeight; + private @IntRange(from = 1) int mDensityDpi; + private int mFlags; + private @Nullable Surface mSurface; + private @Nullable String mUniqueId; + private int mDisplayIdToMirror; + + private long mBuilderFieldsSet = 0L; + + /** + * Creates a new Builder. + * + * @param name + * The name of the virtual display, must be non-empty. + * @param width + * The width of the virtual display in pixels. Must be greater than 0. + * @param height + * The height of the virtual display in pixels. Must be greater than 0. + * @param densityDpi + * The density of the virtual display in dpi. Must be greater than 0. + */ + public Builder( + @NonNull String name, + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @IntRange(from = 1) int densityDpi) { + mName = name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mName); + mWidth = width; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mWidth, + "from", 1); + mHeight = height; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mHeight, + "from", 1); + mDensityDpi = densityDpi; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mDensityDpi, + "from", 1); + } + + /** + * The name of the virtual display, must be non-empty. + */ + @DataClass.Generated.Member + public @NonNull Builder setName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mName = value; + return this; + } + + /** + * The width of the virtual display in pixels. Must be greater than 0. + */ + @DataClass.Generated.Member + public @NonNull Builder setWidth(@IntRange(from = 1) int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mWidth = value; + return this; + } + + /** + * The height of the virtual display in pixels. Must be greater than 0. + */ + @DataClass.Generated.Member + public @NonNull Builder setHeight(@IntRange(from = 1) int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mHeight = value; + return this; + } + + /** + * The density of the virtual display in dpi. Must be greater than 0. + */ + @DataClass.Generated.Member + public @NonNull Builder setDensityDpi(@IntRange(from = 1) int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mDensityDpi = value; + return this; + } + + /** + * A combination of virtual display flags. + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE}, + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, + * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. + */ + @DataClass.Generated.Member + public @NonNull Builder setFlags(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mFlags = value; + return this; + } + + /** + * The surface to which the content of the virtual display should be rendered, or null if + * there is none initially. + */ + @DataClass.Generated.Member + public @NonNull Builder setSurface(@NonNull Surface value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mSurface = value; + return this; + } + + /** + * The unique identifier for the display. Shouldn't be displayed to the user. + * + * @hide + */ + @DataClass.Generated.Member + public @NonNull Builder setUniqueId(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; + mUniqueId = value; + return this; + } + + /** + * The id of the display that the virtual display should mirror, or + * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially. + */ + @DataClass.Generated.Member + public @NonNull Builder setDisplayIdToMirror(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x80; + mDisplayIdToMirror = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull VirtualDisplayConfig build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x100; // Mark builder used + + if ((mBuilderFieldsSet & 0x10) == 0) { + mFlags = 0; + } + if ((mBuilderFieldsSet & 0x20) == 0) { + mSurface = null; + } + if ((mBuilderFieldsSet & 0x40) == 0) { + mUniqueId = null; + } + if ((mBuilderFieldsSet & 0x80) == 0) { + mDisplayIdToMirror = DEFAULT_DISPLAY; + } + VirtualDisplayConfig o = new VirtualDisplayConfig( + mName, + mWidth, + mHeight, + mDensityDpi, + mFlags, + mSurface, + mUniqueId, + mDisplayIdToMirror); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x100) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1585179350902L, + codegenVersion = "1.0.15", + sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java", + inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange(from=1L) int mWidth\nprivate @android.annotation.IntRange(from=1L) int mHeight\nprivate @android.annotation.IntRange(from=1L) int mDensityDpi\nprivate int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 35f955f7e78b..b5e8dd8212e3 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1747,16 +1747,13 @@ public final class ViewRootImpl implements ViewParent, || !mBlastSurfaceControl.isValid()) { return null; } + if (mBlastBufferQueue == null) { mBlastBufferQueue = new BLASTBufferQueue( mBlastSurfaceControl, width, height); } mBlastBufferQueue.update(mBlastSurfaceControl, width, height); - mTransaction.show(mBlastSurfaceControl) - .reparent(mBlastSurfaceControl, mSurfaceControl) - .apply(); - return mBlastBufferQueue.getSurface(); } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 561ee604aa7f..316a5f2c88d2 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -36,7 +36,6 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import java.util.List; @@ -70,8 +69,7 @@ import java.util.List; public final class WindowManagerImpl implements WindowManager { @UnsupportedAppUsage private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); - @VisibleForTesting - public final Context mContext; + private final Context mContext; private final Window mParentWindow; private IBinder mDefaultToken; diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java index f00776897f2c..99b4b5fb7707 100644 --- a/core/java/com/android/internal/policy/DecorContext.java +++ b/core/java/com/android/internal/policy/DecorContext.java @@ -41,17 +41,17 @@ import java.lang.ref.WeakReference; public class DecorContext extends ContextThemeWrapper { private PhoneWindow mPhoneWindow; private WindowManager mWindowManager; - private Resources mResources; + private Resources mActivityResources; private ContentCaptureManager mContentCaptureManager; - private WeakReference<Context> mContext; + private WeakReference<Context> mActivityContext; // TODO(b/149928768): Non-activity context can be passed. @VisibleForTesting - public DecorContext(Context baseContext, Context context) { - super(baseContext.createDisplayContext(context.getDisplayNoVerify()), null); - mContext = new WeakReference<>(context); - mResources = context.getResources(); + public DecorContext(Context context, Context activityContext) { + super(context.createDisplayContext(activityContext.getDisplayNoVerify()), null); + mActivityContext = new WeakReference<>(activityContext); + mActivityResources = activityContext.getResources(); } void setPhoneWindow(PhoneWindow phoneWindow) { @@ -61,56 +61,58 @@ public class DecorContext extends ContextThemeWrapper { @Override public Object getSystemService(String name) { - final Context context = mContext.get(); if (Context.WINDOW_SERVICE.equals(name)) { - if (context != null && mWindowManager == null) { - WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(name); + if (mWindowManager == null) { + WindowManagerImpl wm = + (WindowManagerImpl) super.getSystemService(Context.WINDOW_SERVICE); mWindowManager = wm.createLocalWindowManager(mPhoneWindow); } return mWindowManager; } if (Context.CONTENT_CAPTURE_MANAGER_SERVICE.equals(name)) { - if (context != null && mContentCaptureManager == null) { - mContentCaptureManager = (ContentCaptureManager) context.getSystemService(name); + if (mContentCaptureManager == null) { + Context activityContext = mActivityContext.get(); + if (activityContext != null) { + mContentCaptureManager = (ContentCaptureManager) activityContext + .getSystemService(name); + } } return mContentCaptureManager; } - // LayoutInflater and WallpaperManagerService should also be obtained from context - // instead of application context. - return (context != null) ? context.getSystemService(name) : super.getSystemService(name); + return super.getSystemService(name); } @Override public Resources getResources() { - Context context = mContext.get(); + Context activityContext = mActivityContext.get(); // Attempt to update the local cached Resources from the activity context. If the activity // is no longer around, return the old cached values. - if (context != null) { - mResources = context.getResources(); + if (activityContext != null) { + mActivityResources = activityContext.getResources(); } - return mResources; + return mActivityResources; } @Override public AssetManager getAssets() { - return mResources.getAssets(); + return mActivityResources.getAssets(); } @Override public AutofillOptions getAutofillOptions() { - Context context = mContext.get(); - if (context != null) { - return context.getAutofillOptions(); + Context activityContext = mActivityContext.get(); + if (activityContext != null) { + return activityContext.getAutofillOptions(); } return null; } @Override public ContentCaptureOptions getContentCaptureOptions() { - Context context = mContext.get(); - if (context != null) { - return context.getContentCaptureOptions(); + Context activityContext = mActivityContext.get(); + if (activityContext != null) { + return activityContext.getContentCaptureOptions(); } return null; } diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java index 2779be6f9753..575a5320bbd3 100644 --- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java @@ -69,6 +69,7 @@ public class DividerSnapAlgorithm { private final ArrayList<SnapTarget> mTargets = new ArrayList<>(); private final Rect mInsets = new Rect(); private final int mSnapMode; + private final boolean mFreeSnapMode; private final int mMinimalSizeResizableTask; private final int mTaskHeightInMinimizedMode; private final float mFixedRatio; @@ -125,6 +126,8 @@ public class DividerSnapAlgorithm { mInsets.set(insets); mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED : res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode); + mFreeSnapMode = res.getBoolean( + com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode); mFixedRatio = res.getFraction( com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); mMinimalSizeResizableTask = res.getDimensionPixelSize( @@ -247,7 +250,20 @@ public class DividerSnapAlgorithm { } } + private boolean shouldApplyFreeSnapMode(int position) { + if (!mFreeSnapMode) { + return false; + } + if (!isFirstSplitTargetAvailable() || !isLastSplitTargetAvailable()) { + return false; + } + return mFirstSplitTarget.position < position && position < mLastSplitTarget.position; + } + private SnapTarget snap(int position, boolean hardDismiss) { + if (shouldApplyFreeSnapMode(position)) { + return new SnapTarget(position, position, SnapTarget.FLAG_NONE); + } int minIndex = -1; float minDistance = Float.MAX_VALUE; int size = mTargets.size(); diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index ab68c440483e..523c7493420b 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -331,7 +331,8 @@ public class ConversationLayout extends FrameLayout @RemotableViewMethod public void setIsImportantConversation(boolean isImportantConversation) { mImportantConversation = isImportantConversation; - mImportanceRingView.setVisibility(isImportantConversation ? VISIBLE : GONE); + mImportanceRingView.setVisibility(isImportantConversation + && mIcon.getVisibility() != GONE ? VISIBLE : GONE); } public boolean isImportantConversation() { diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b1bba53bd7ab..340dd4d7d89a 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3346,6 +3346,10 @@ This should only be set when the device has gestural navigation enabled by default. --> <bool name="config_showGesturalNavigationHints">false</bool> + <!-- Controls the free snap mode for the docked stack divider. In this mode, the divider can be + snapped to any position between the first target and the last target. --> + <bool name="config_dockedStackDividerFreeSnapMode">false</bool> + <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows. These values are in DPs and will be converted to pixel sizes internally. --> <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ec8058235912..11dda41d0b57 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1667,6 +1667,7 @@ <java-symbol type="bool" name="config_perDisplayFocusEnabled" /> <java-symbol type="bool" name="config_showNavigationBar" /> <java-symbol type="bool" name="config_supportAutoRotation" /> + <java-symbol type="bool" name="config_dockedStackDividerFreeSnapMode" /> <java-symbol type="dimen" name="docked_stack_divider_thickness" /> <java-symbol type="dimen" name="docked_stack_divider_insets" /> <java-symbol type="dimen" name="docked_stack_minimize_thickness" /> diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 74ca2036d764..bc0cdc1e029b 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -34,6 +34,8 @@ import static com.android.internal.app.ChooserListAdapter.SHORTCUT_TARGET_SCORE_ import static com.android.internal.app.ChooserWrapperActivity.sOverrides; import static com.android.internal.app.MatcherUtils.first; +import static junit.framework.Assert.assertTrue; + import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; @@ -1682,6 +1684,54 @@ public class ChooserActivityTest { .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId())); } + @Test + public void testAutolaunch_singleTarget_wifthWorkProfileAndTabbedViewOff_noAutolaunch() { + ResolverActivity.ENABLE_TABBED_VIEW = false; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + waitForIdle(); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + + assertTrue(chosen[0] == null); + } + + @Test + public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() { + ResolverActivity.ENABLE_TABBED_VIEW = false; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(1); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + waitForIdle(); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + + assertThat(chosen[0], is(personalResolvedComponentInfos.get(0).getResolveInfoAt(0))); + } + private Intent createSendTextIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 5a3aff937b79..0bf8663c7a85 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -30,6 +30,8 @@ import static com.android.internal.app.MatcherUtils.first; import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo; import static com.android.internal.app.ResolverWrapperActivity.sOverrides; +import static junit.framework.Assert.assertTrue; + import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -710,6 +712,54 @@ public class ResolverActivityTest { .check(matches(isDisplayed())); } + @Test + public void testAutolaunch_singleTarget_withWorkProfileAndTabbedViewOff_noAutolaunch() { + ResolverActivity.ENABLE_TABBED_VIEW = false; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + waitForIdle(); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + + assertTrue(chosen[0] == null); + } + + @Test + public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() { + ResolverActivity.ENABLE_TABBED_VIEW = false; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(1); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + waitForIdle(); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + + assertThat(chosen[0], is(personalResolvedComponentInfos.get(0).getResolveInfoAt(0))); + } + private Intent createSendImageIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); diff --git a/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java index d019704fb684..3e40466e4b64 100644 --- a/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java +++ b/core/tests/coretests/src/com/android/internal/policy/DecorContextTest.java @@ -20,24 +20,19 @@ import static android.view.Display.DEFAULT_DISPLAY; import static org.junit.Assert.assertEquals; -import android.app.Activity; -import android.app.EmptyActivity; import android.content.Context; import android.hardware.display.DisplayManagerGlobal; import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.DisplayAdjustments; import android.view.DisplayInfo; -import android.view.WindowManager; -import android.view.WindowManagerImpl; -import androidx.test.core.app.ApplicationProvider; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; + import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,13 +46,9 @@ public final class DecorContextTest { private Context mContext; private static final int EXTERNAL_DISPLAY = DEFAULT_DISPLAY + 1; - @Rule - public ActivityTestRule<EmptyActivity> mActivityRule = - new ActivityTestRule<>(EmptyActivity.class); - @Before - public void setUp() { - mContext = ApplicationProvider.getApplicationContext(); + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); } @Test @@ -85,19 +76,4 @@ public final class DecorContextTest { Display associatedDisplay = decorContext.getDisplay(); assertEquals(expectedDisplayId, associatedDisplay.getDisplayId()); } - - @Test - public void testGetWindowManagerFromVisualDecorContext() throws Throwable { - mActivityRule.runOnUiThread(() -> { - Activity activity = mActivityRule.getActivity(); - final DecorContext decorContext = new DecorContext(mContext.getApplicationContext(), - activity); - WindowManagerImpl actualWm = (WindowManagerImpl) - decorContext.getSystemService(WindowManager.class); - WindowManagerImpl expectedWm = (WindowManagerImpl) - activity.getSystemService(WindowManager.class); - // Verify that window manager is from activity not application context. - assertEquals(expectedWm.mContext, actualWm.mContext); - }); - } } diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index 632cfb0f1e30..37e141537c79 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; +import android.hardware.display.VirtualDisplayConfig; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.os.Handler; @@ -100,11 +101,18 @@ public final class MediaProjection { int width, int height, int dpi, boolean isSecure, @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); - int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0; - return dm.createVirtualDisplay(this, name, width, height, dpi, surface, - flags | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR | - DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, callback, handler, - null /* uniqueId */); + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR + | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; + if (isSecure) { + flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; + } + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, + height, dpi); + builder.setFlags(flags); + if (surface != null) { + builder.setSurface(surface); + } + return dm.createVirtualDisplay(this, builder.build(), callback, handler); } /** @@ -133,9 +141,35 @@ public final class MediaProjection { public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height, int dpi, int flags, @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { - DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); - return dm.createVirtualDisplay(this, name, width, height, dpi, surface, flags, callback, - handler, null /* uniqueId */); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, + height, dpi); + builder.setFlags(flags); + if (surface != null) { + builder.setSurface(surface); + } + return createVirtualDisplay(builder.build(), callback, handler); + } + + /** + * Creates a {@link android.hardware.display.VirtualDisplay} to capture the + * contents of the screen. + * + * @param virtualDisplayConfig The arguments for the virtual display configuration. See + * {@link VirtualDisplayConfig} for using it. + * @param callback Callback to call when the virtual display's state + * changes, or null if none. + * @param handler The {@link android.os.Handler} on which the callback should be + * invoked, or null if the callback should be invoked on the calling + * thread's main {@link android.os.Looper}. + * + * @see android.hardware.display.VirtualDisplay + * @hide + */ + @Nullable + public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig, + @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { + DisplayManager dm = mContext.getSystemService(DisplayManager.class); + return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler); } /** diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml index 65b04fd8fd99..b3c7cf74941a 100644 --- a/packages/SystemUI/res/layout/bubble_overflow_activity.xml +++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml @@ -20,6 +20,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="@dimen/bubble_overflow_padding" + android:paddingLeft="@dimen/bubble_overflow_padding" + android:paddingRight="@dimen/bubble_overflow_padding" android:orientation="vertical" android:layout_gravity="center_horizontal"> diff --git a/packages/SystemUI/res/layout/bubble_overflow_view.xml b/packages/SystemUI/res/layout/bubble_overflow_view.xml index d67c81d67ada..88a05ec5824a 100644 --- a/packages/SystemUI/res/layout/bubble_overflow_view.xml +++ b/packages/SystemUI/res/layout/bubble_overflow_view.xml @@ -37,5 +37,6 @@ android:layout_height="wrap_content" android:maxLines="1" android:layout_gravity="center" + android:paddingTop="@dimen/bubble_overflow_text_padding" android:gravity="center"/> </LinearLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a9e5fa9cf4ae..9074dad608ce 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1148,11 +1148,13 @@ <!-- Default (and minimum) height of the expanded view shown when the bubble is expanded --> <dimen name="bubble_expanded_default_height">180dp</dimen> <!-- Default height of bubble overflow --> - <dimen name="bubble_overflow_height">380dp</dimen> + <dimen name="bubble_overflow_height">460dp</dimen> <!-- Bubble overflow padding when there are no bubbles --> <dimen name="bubble_overflow_empty_state_padding">16dp</dimen> <!-- Padding of container for overflow bubbles --> - <dimen name="bubble_overflow_padding">5dp</dimen> + <dimen name="bubble_overflow_padding">15dp</dimen> + <!-- Padding of label for bubble overflow view --> + <dimen name="bubble_overflow_text_padding">7dp</dimen> <!-- Height of the triangle that points to the expanded bubble --> <dimen name="bubble_pointer_height">4dp</dimen> <!-- Width of the triangle that points to the expanded bubble --> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index 2231d11b7bc2..3f985ef37746 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -81,11 +81,15 @@ public class BubbleOverflowActivity extends Activity { DisplayMetrics displayMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); - final int viewWidth = displayMetrics.widthPixels / columns; + final int recyclerViewWidth = (displayMetrics.widthPixels + - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding)); + final int viewWidth = recyclerViewWidth / columns; final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow); final int rows = (int) Math.ceil((double) maxOverflowBubbles / columns); - final int viewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height) / rows; + final int recyclerViewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height) + - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding); + final int viewHeight = recyclerViewHeight / rows; mAdapter = new BubbleOverflowAdapter(mOverflowBubbles, mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java index 059d6ffa9180..148cdea92052 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java @@ -95,8 +95,8 @@ public class DynamicChildBindController { private void freeChildContent(NotificationEntry entry) { RowContentBindParams params = mStage.getStageParams(entry); - params.freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED); - params.freeContentViews(FLAG_CONTENT_VIEW_EXPANDED); + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED); + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED); mStage.requestRebind(entry, null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index d2f781d2e19c..77376e595819 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -539,7 +539,8 @@ public class NotificationEntryManager implements } } - private void addNotificationInternal(StatusBarNotification notification, + private void addNotificationInternal( + StatusBarNotification notification, RankingMap rankingMap) throws InflationException { String key = notification.getKey(); if (DEBUG) { @@ -579,6 +580,9 @@ public class NotificationEntryManager implements for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onEntryAdded(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingApplied(); + } } public void addNotification(StatusBarNotification notification, RankingMap ranking) { @@ -635,6 +639,9 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPostEntryUpdated(entry); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingApplied(); + } } public void updateNotification(StatusBarNotification notification, RankingMap ranking) { @@ -693,6 +700,9 @@ public class NotificationEntryManager implements for (NotifCollectionListener listener : mNotifCollectionListeners) { listener.onRankingUpdate(rankingMap); } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingApplied(); + } } private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) { @@ -799,6 +809,9 @@ public class NotificationEntryManager implements */ public void updateRanking(RankingMap rankingMap, String reason) { updateRankingAndSort(rankingMap, reason); + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingApplied(); + } } /** Resorts / filters the current notification set with the current RankingMap */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 0377c0900d63..d22564b2a811 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -67,7 +67,6 @@ import com.android.systemui.statusbar.notification.icon.IconPack; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotificationGuts; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; import java.util.ArrayList; @@ -579,10 +578,6 @@ public final class NotificationEntry extends ListEntry { if (row != null) row.resetUserExpansion(); } - public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { - if (row != null) row.freeContentViewWhenSafe(inflationFlag); - } - public boolean rowExists() { return row != null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java index 573c129f199a..2a3b2b7d815d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; +import static com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager.alertAgain; import android.annotation.Nullable; @@ -28,6 +29,8 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; +import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -55,6 +58,8 @@ public class HeadsUpCoordinator implements Coordinator { private static final String TAG = "HeadsUpCoordinator"; private final HeadsUpManager mHeadsUpManager; + private final HeadsUpViewBinder mHeadsUpViewBinder; + private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final NotificationRemoteInputManager mRemoteInputManager; // tracks the current HeadUpNotification reported by HeadsUpManager @@ -66,8 +71,12 @@ public class HeadsUpCoordinator implements Coordinator { @Inject public HeadsUpCoordinator( HeadsUpManager headsUpManager, + HeadsUpViewBinder headsUpViewBinder, + NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationRemoteInputManager remoteInputManager) { mHeadsUpManager = headsUpManager; + mHeadsUpViewBinder = headsUpViewBinder; + mNotificationInterruptStateProvider = notificationInterruptStateProvider; mRemoteInputManager = remoteInputManager; } @@ -84,8 +93,51 @@ public class HeadsUpCoordinator implements Coordinator { return mNotifSection; } + private void onHeadsUpViewBound(NotificationEntry entry) { + mHeadsUpManager.showNotification(entry); + } + private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { /** + * Notification was just added and if it should heads up, bind the view and then show it. + */ + @Override + public void onEntryAdded(NotificationEntry entry) { + if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView( + entry, + HeadsUpCoordinator.this::onHeadsUpViewBound); + } + } + + /** + * Notification could've updated to be heads up or not heads up. Even if it did update to + * heads up, if the notification specified that it only wants to alert once, don't heads + * up again. + */ + @Override + public void onEntryUpdated(NotificationEntry entry) { + boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification()); + // includes check for whether this notification should be filtered: + boolean shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry); + final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey()); + if (wasHeadsUp) { + if (shouldHeadsUp) { + mHeadsUpManager.updateNotification(entry.getKey(), hunAgain); + } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) { + // We don't want this to be interrupting anymore, let's remove it + mHeadsUpManager.removeNotification( + entry.getKey(), false /* removeImmediately */); + } + } else if (shouldHeadsUp && hunAgain) { + // This notification was updated to be heads up, show it! + mHeadsUpViewBinder.bindHeadsUpView( + entry, + HeadsUpCoordinator.this::onHeadsUpViewBound); + } + } + + /** * Stop alerting HUNs that are removed from the notification collection */ @Override @@ -98,6 +150,11 @@ public class HeadsUpCoordinator implements Coordinator { mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput); } } + + @Override + public void onEntryCleanUp(NotificationEntry entry) { + mHeadsUpViewBinder.abortBindCallback(entry); + } }; private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() { @@ -153,6 +210,9 @@ public class HeadsUpCoordinator implements Coordinator { mNotifPromoter.invalidateList(); mNotifSection.invalidateList(); } + if (!isHeadsUp) { + mHeadsUpViewBinder.unbindHeadsUpView(entry); + } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 742615c7fd0f..9973ef9ae14e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -33,9 +33,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.NotifInf import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager; -import com.android.systemui.statusbar.policy.HeadsUpManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -63,8 +61,6 @@ public class PreparationCoordinator implements Coordinator { private final NotifViewBarn mViewBarn; private final Map<NotificationEntry, Integer> mInflationStates = new ArrayMap<>(); private final IStatusBarService mStatusBarService; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; - private final HeadsUpManager mHeadsUpManager; @Inject public PreparationCoordinator( @@ -72,9 +68,7 @@ public class PreparationCoordinator implements Coordinator { NotifInflaterImpl notifInflater, NotifInflationErrorManager errorManager, NotifViewBarn viewBarn, - IStatusBarService service, - NotificationInterruptStateProvider notificationInterruptStateProvider, - HeadsUpManager headsUpManager + IStatusBarService service ) { mLogger = logger; mNotifInflater = notifInflater; @@ -83,8 +77,6 @@ public class PreparationCoordinator implements Coordinator { mNotifErrorManager.addInflationErrorListener(mInflationErrorListener); mViewBarn = viewBarn; mStatusBarService = service; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; - mHeadsUpManager = headsUpManager; } @Override @@ -158,11 +150,6 @@ public class PreparationCoordinator implements Coordinator { mLogger.logNotifInflated(entry.getKey()); mViewBarn.registerViewForEntry(entry, entry.getRow()); mInflationStates.put(entry, STATE_INFLATED); - - // TODO: should eventually be moved to HeadsUpCoordinator - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpManager.showNotification(entry); - } mNotifInflatingFilter.invalidateList(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 723728488826..32f1822804f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.collection.inflation; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; - import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -227,24 +225,17 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { final boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, entry.getImportance()); - final boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight - && !mPresenter.isPresenterFullyCollapsed(); final boolean isLowPriority = entry.isAmbient(); RowContentBindParams params = mRowContentBindStage.getStageParams(entry); params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); params.setUseLowPriority(entry.isAmbient()); - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); - } //TODO: Replace this API with RowContentBindParams directly row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry)); params.rebindAllContentViews(); mRowContentBindStage.requestRebind(entry, en -> { row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setUsesIncreasedHeadsUpHeight(useIncreasedHeadsUp); row.setIsLowPriority(isLowPriority); mInflationCallback.onAsyncInflationFinished(en); }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 0c0cded32db2..41ca52d5a626 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -74,7 +74,9 @@ public interface NotifCollectionListener { * non-lifetime-extended notification entries will have their ranking object updated. * * Ranking updates occur whenever a notification is added, updated, or removed, or when a - * standalone ranking is sent from the server. + * standalone ranking is sent from the server. If a non-standalone ranking is applied, the event + * that accompanied the ranking is emitted first (e.g. {@link #onEntryAdded}), followed by the + * ranking event. */ default void onRankingApplied() { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java new file mode 100644 index 000000000000..a7b1f37edf0e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.headsup; + +import androidx.annotation.NonNull; + +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controller class for old pipeline heads up view binding. It listens to + * {@link NotificationEntryManager} entry events and appropriately binds or unbinds the heads up + * view. + * + * This has a subtle contract with {@link NotificationAlertingManager} where this controller handles + * the heads up binding, but {@link NotificationAlertingManager} listens for general inflation + * events to actually mark it heads up/update. In the new pipeline, we combine the classes. + * See {@link HeadsUpCoordinator}. + */ +@Singleton +public class HeadsUpBindController { + private final HeadsUpViewBinder mHeadsUpViewBinder; + private final NotificationInterruptStateProvider mInterruptStateProvider; + + @Inject + HeadsUpBindController( + HeadsUpViewBinder headsUpViewBinder, + NotificationInterruptStateProvider notificationInterruptStateProvider) { + mInterruptStateProvider = notificationInterruptStateProvider; + mHeadsUpViewBinder = headsUpViewBinder; + } + + /** + * Attach this controller and add its listeners. + */ + public void attach( + NotificationEntryManager entryManager, + HeadsUpManager headsUpManager) { + entryManager.addCollectionListener(mCollectionListener); + headsUpManager.addListener(mOnHeadsUpChangedListener); + } + + private NotifCollectionListener mCollectionListener = new NotifCollectionListener() { + @Override + public void onEntryAdded(NotificationEntry entry) { + if (mInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView(entry, null); + } + } + + @Override + public void onEntryUpdated(NotificationEntry entry) { + if (mInterruptStateProvider.shouldHeadsUp(entry)) { + mHeadsUpViewBinder.bindHeadsUpView(entry, null); + } + } + + @Override + public void onEntryCleanUp(NotificationEntry entry) { + mHeadsUpViewBinder.abortBindCallback(entry); + } + }; + + private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() { + @Override + public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) { + if (!isHeadsUp) { + mHeadsUpViewBinder.unbindHeadsUpView(entry); + } + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java new file mode 100644 index 000000000000..37acfa8dc0a4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.headsup; + +import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; + +import android.util.ArrayMap; + +import androidx.annotation.Nullable; +import androidx.core.os.CancellationSignal; + +import com.android.internal.util.NotificationMessagingUtil; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; +import com.android.systemui.statusbar.notification.row.RowContentBindParams; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; + +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Wrapper around heads up view binding logic. {@link HeadsUpViewBinder} is responsible for + * figuring out the right heads up inflation parameters and inflating/freeing the heads up + * content view. + * + * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated + * (i.e. when {@link HeadsUpBindController} is removed). + */ +@Singleton +public class HeadsUpViewBinder { + private final RowContentBindStage mStage; + private final NotificationMessagingUtil mNotificationMessagingUtil; + private final Map<NotificationEntry, CancellationSignal> mOngoingBindCallbacks = + new ArrayMap<>(); + + private NotificationPresenter mNotificationPresenter; + + @Inject + HeadsUpViewBinder( + NotificationMessagingUtil notificationMessagingUtil, + RowContentBindStage bindStage) { + mNotificationMessagingUtil = notificationMessagingUtil; + mStage = bindStage; + } + + /** + * Set notification presenter to determine parameters for heads up view inflation. + */ + public void setPresenter(NotificationPresenter presenter) { + mNotificationPresenter = presenter; + } + + /** + * Bind heads up view to the notification row. + * @param callback callback after heads up view is bound + */ + public void bindHeadsUpView(NotificationEntry entry, @Nullable BindCallback callback) { + RowContentBindParams params = mStage.getStageParams(entry); + final boolean isImportantMessage = mNotificationMessagingUtil.isImportantMessaging( + entry.getSbn(), entry.getImportance()); + final boolean useIncreasedHeadsUp = isImportantMessage + && !mNotificationPresenter.isPresenterFullyCollapsed(); + params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); + params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); + CancellationSignal signal = mStage.requestRebind(entry, en -> { + en.getRow().setUsesIncreasedHeadsUpHeight(params.useIncreasedHeadsUpHeight()); + if (callback != null) { + callback.onBindFinished(en); + } + }); + abortBindCallback(entry); + mOngoingBindCallbacks.put(entry, signal); + } + + /** + * Abort any callbacks waiting for heads up view binding to finish for a given notification. + * @param entry notification with bind in progress + */ + public void abortBindCallback(NotificationEntry entry) { + CancellationSignal ongoingBindCallback = mOngoingBindCallbacks.remove(entry); + if (ongoingBindCallback != null) { + ongoingBindCallback.cancel(); + } + } + + /** + * Unbind the heads up view from the notification row. + */ + public void unbindHeadsUpView(NotificationEntry entry) { + abortBindCallback(entry); + mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP); + mStage.requestRebind(entry, null); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 7a7178c3464a..d1cceaeb6dd5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.NotificationListController import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer +import com.android.systemui.statusbar.notification.headsup.HeadsUpBindController import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper @@ -35,6 +36,7 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder import com.android.systemui.statusbar.policy.RemoteInputUriController import dagger.Lazy import java.io.FileDescriptor @@ -63,7 +65,9 @@ class NotificationsControllerImpl @Inject constructor( private val bubbleController: BubbleController, private val groupManager: NotificationGroupManager, private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper, - private val headsUpManager: HeadsUpManager + private val headsUpManager: HeadsUpManager, + private val headsUpBindController: HeadsUpBindController, + private val headsUpViewBinder: HeadsUpViewBinder ) : NotificationsController { override fun initialize( @@ -91,6 +95,7 @@ class NotificationsControllerImpl @Inject constructor( presenter, listContainer, bindRowCallback) + headsUpViewBinder.setPresenter(presenter) notifBindPipelineInitializer.initialize() if (featureFlags.isNewNotifPipelineEnabled) { @@ -109,6 +114,7 @@ class NotificationsControllerImpl @Inject constructor( groupAlertTransferHelper.bind(entryManager, groupManager) headsUpManager.addListener(groupManager) headsUpManager.addListener(groupAlertTransferHelper) + headsUpBindController.attach(entryManager, headsUpManager) groupManager.setHeadsUpManager(headsUpManager) groupAlertTransferHelper.setHeadsUpManager(headsUpManager) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java index b5725029450d..5d070981f81b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.interruption; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import android.app.Notification; import android.service.notification.StatusBarNotification; @@ -95,16 +94,10 @@ public class NotificationAlertingManager { // TODO: Instead of this back and forth, we should listen to changes in heads up and // cancel on-going heads up view inflation using the bind pipeline. if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) { - // Possible for shouldHeadsUp to change between the inflation starting and ending. - // If it does and we no longer need to heads up, we should free the view. - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpManager.showNotification(entry); - if (!mStatusBarStateController.isDozing()) { - // Mark as seen immediately - setNotificationShown(entry.getSbn()); - } - } else { - entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP); + mHeadsUpManager.showNotification(entry); + if (!mStatusBarStateController.isDozing()) { + // Mark as seen immediately + setNotificationShown(entry.getSbn()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 255c2ea808e0..2917346153d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -17,12 +17,7 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; -import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; import android.animation.Animator; @@ -463,41 +458,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Marks a content view as freeable, setting it so that future inflations do not reinflate * and ensuring that the view is freed when it is safe to remove. * - * TODO: This should be moved to the respective coordinator and call - * {@link RowContentBindParams#freeContentViews} directly after disappear animation - * finishes instead of depending on binding API to know when it's "safe". - * * @param inflationFlag flag corresponding to the content view to be freed + * @deprecated By default, {@link NotificationContentInflater#unbindContent} will tell the + * view hierarchy to only free when the view is safe to remove so this method is no longer + * needed. Will remove when all uses are gone. */ + @Deprecated public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) { - // View should not be reinflated in the future - Runnable freeViewRunnable = () -> { - // Possible for notification to be removed after free request. - if (!isRemoved()) { - RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); - params.freeContentViews(inflationFlag); - mRowContentBindStage.requestRebind(mEntry, null /* callback */); - } - }; - switch (inflationFlag) { - case FLAG_CONTENT_VIEW_CONTRACTED: - getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, - freeViewRunnable); - break; - case FLAG_CONTENT_VIEW_EXPANDED: - getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED, - freeViewRunnable); - break; - case FLAG_CONTENT_VIEW_HEADS_UP: - getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, - freeViewRunnable); - break; - case FLAG_CONTENT_VIEW_PUBLIC: - getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, - freeViewRunnable); - default: - break; - } + RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); + params.markContentViewsFreeable(inflationFlag); + mRowContentBindStage.requestRebind(mEntry, null /* callback */); } /** @@ -1571,7 +1541,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (needsRedaction) { params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC); } else { - params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC); + params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC); } mRowContentBindStage.requestRebind(mEntry, null /* callback */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java index d744fc398d7a..893e8490eb90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.notification.row; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.ArrayMap; import android.util.ArraySet; import android.widget.FrameLayout; @@ -25,12 +28,15 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.os.CancellationSignal; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; @@ -75,14 +81,18 @@ import javax.inject.Singleton; public final class NotifBindPipeline { private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>(); private final NotifBindPipelineLogger mLogger; + private final List<BindCallback> mScratchCallbacksList = new ArrayList<>(); + private final Handler mMainHandler; private BindStage mStage; @Inject NotifBindPipeline( CommonNotifCollection collection, - NotifBindPipelineLogger logger) { + NotifBindPipelineLogger logger, + @Main Looper mainLooper) { collection.addCollectionListener(mCollectionListener); mLogger = logger; + mMainHandler = new NotifBindPipelineHandler(mainLooper); } /** @@ -107,7 +117,7 @@ public final class NotifBindPipeline { final BindEntry bindEntry = getBindEntry(entry); bindEntry.row = row; if (bindEntry.invalidated) { - startPipeline(entry); + requestPipelineRun(entry); } } @@ -130,7 +140,28 @@ public final class NotifBindPipeline { signal.setOnCancelListener(() -> callbacks.remove(callback)); } - startPipeline(entry); + requestPipelineRun(entry); + } + + /** + * Request pipeline to start. + * + * We avoid starting the pipeline immediately as multiple clients may request rebinds + * back-to-back due to a single change (e.g. notification update), and it's better to start + * the real work once rather than repeatedly start and cancel it. + */ + private void requestPipelineRun(NotificationEntry entry) { + mLogger.logRequestPipelineRun(entry.getKey()); + + final BindEntry bindEntry = getBindEntry(entry); + + // Abort any existing pipeline run + mStage.abortStage(entry, bindEntry.row); + + if (!mMainHandler.hasMessages(START_PIPELINE_MSG, entry)) { + Message msg = Message.obtain(mMainHandler, START_PIPELINE_MSG, entry); + mMainHandler.sendMessage(msg); + } } /** @@ -151,7 +182,6 @@ public final class NotifBindPipeline { return; } - mStage.abortStage(entry, row); mStage.executeStage(entry, row, (en) -> onPipelineComplete(en)); } @@ -162,10 +192,15 @@ public final class NotifBindPipeline { mLogger.logFinishedPipeline(entry.getKey(), callbacks.size()); bindEntry.invalidated = false; - for (BindCallback cb : callbacks) { - cb.onBindFinished(entry); - } + // Move all callbacks to separate list as callbacks may themselves add/remove callbacks. + // TODO: Throw an exception for this re-entrant behavior once we deprecate + // NotificationGroupAlertTransferHelper + mScratchCallbacksList.addAll(callbacks); callbacks.clear(); + for (int i = 0; i < mScratchCallbacksList.size(); i++) { + mScratchCallbacksList.get(i).onBindFinished(entry); + } + mScratchCallbacksList.clear(); } private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() { @@ -183,6 +218,7 @@ public final class NotifBindPipeline { mStage.abortStage(entry, row); } mStage.deleteStageParams(entry); + mMainHandler.removeMessages(START_PIPELINE_MSG, entry); } }; @@ -211,4 +247,25 @@ public final class NotifBindPipeline { public final Set<BindCallback> callbacks = new ArraySet<>(); public boolean invalidated; } + + private static final int START_PIPELINE_MSG = 1; + + private class NotifBindPipelineHandler extends Handler { + + NotifBindPipelineHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case START_PIPELINE_MSG: + NotificationEntry entry = (NotificationEntry) msg.obj; + startPipeline(entry); + break; + default: + throw new IllegalArgumentException("Unknown message type: " + msg.what); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt index 2717d7ad143b..199730427aec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt @@ -40,6 +40,14 @@ class NotifBindPipelineLogger @Inject constructor( }) } + fun logRequestPipelineRun(notifKey: String) { + buffer.log(TAG, INFO, { + str1 = notifKey + }, { + "Request pipeline run for notif: $str1" + }) + } + fun logStartPipeline(notifKey: String) { buffer.log(TAG, INFO, { str1 = notifKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 719f74fdcde2..9d5443729d45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -118,6 +118,9 @@ public class NotificationContentInflater implements NotificationRowContentBinder mRemoteViewCache.clearCache(entry); } + // Cancel any pending frees on any view we're trying to bind since we should be bound after. + cancelContentViewFrees(row, contentToBind); + AsyncInflationTask task = new AsyncInflationTask( mBgExecutor, mInflateSynchronously, @@ -198,44 +201,69 @@ public class NotificationContentInflater implements NotificationRowContentBinder } /** - * Frees the content view associated with the inflation flag. Will only succeed if the - * view is safe to remove. + * Frees the content view associated with the inflation flag as soon as the view is not showing. * * @param inflateFlag the flag corresponding to the content view which should be freed */ - private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row, + private void freeNotificationView( + NotificationEntry entry, + ExpandableNotificationRow row, @InflationFlag int inflateFlag) { switch (inflateFlag) { case FLAG_CONTENT_VIEW_CONTRACTED: - if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) { + row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> { row.getPrivateLayout().setContractedChild(null); mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED); - } + }); break; case FLAG_CONTENT_VIEW_EXPANDED: - if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_EXPANDED)) { + row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED, () -> { row.getPrivateLayout().setExpandedChild(null); mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); - } + }); break; case FLAG_CONTENT_VIEW_HEADS_UP: - if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) { + row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, () -> { row.getPrivateLayout().setHeadsUpChild(null); mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null); - } + }); break; case FLAG_CONTENT_VIEW_PUBLIC: - if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) { + row.getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> { row.getPublicLayout().setContractedChild(null); mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC); - } + }); break; default: break; } } + /** + * Cancel any pending content view frees from {@link #freeNotificationView} for the provided + * content views. + * + * @param row top level notification row containing the content views + * @param contentViews content views to cancel pending frees on + */ + private void cancelContentViewFrees( + ExpandableNotificationRow row, + @InflationFlag int contentViews) { + if ((contentViews & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { + row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED); + } + if ((contentViews & FLAG_CONTENT_VIEW_EXPANDED) != 0) { + row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_EXPANDED); + } + if ((contentViews & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { + row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_HEADSUP); + } + if ((contentViews & FLAG_CONTENT_VIEW_PUBLIC) != 0) { + row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED); + } + } + private static InflationProgress inflateSmartReplyViews(InflationProgress result, @InflationFlag int reInflateFlags, NotificationEntry entry, Context context, Context packageContext, HeadsUpManager headsUpManager, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 8efdc1b56e8e..b18bf01ea91f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -385,6 +385,7 @@ public class NotificationContentView extends FrameLayout { */ public void setContractedChild(@Nullable View child) { if (mContractedChild != null) { + mOnContentViewInactiveListeners.remove(mContractedChild); mContractedChild.animate().cancel(); removeView(mContractedChild); } @@ -432,6 +433,7 @@ public class NotificationContentView extends FrameLayout { ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput); } } + mOnContentViewInactiveListeners.remove(mExpandedChild); mExpandedChild.animate().cancel(); removeView(mExpandedChild); mExpandedRemoteInput = null; @@ -470,6 +472,7 @@ public class NotificationContentView extends FrameLayout { ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput); } } + mOnContentViewInactiveListeners.remove(mHeadsUpChild); mHeadsUpChild.animate().cancel(); removeView(mHeadsUpChild); mHeadsUpRemoteInput = null; @@ -1108,7 +1111,6 @@ public class NotificationContentView extends FrameLayout { public void onNotificationUpdated(NotificationEntry entry) { mStatusBarNotification = entry.getSbn(); - mOnContentViewInactiveListeners.clear(); mBeforeN = entry.targetSdk < Build.VERSION_CODES.N; updateAllSingleLineViews(); ExpandableNotificationRow row = entry.getRow(); @@ -1623,7 +1625,7 @@ public class NotificationContentView extends FrameLayout { * @param visibleType visible type corresponding to the content view to listen * @param listener runnable to run once when the content view becomes inactive */ - public void performWhenContentInactive(int visibleType, Runnable listener) { + void performWhenContentInactive(int visibleType, Runnable listener) { View view = getViewForVisibleType(visibleType); // View is already inactive if (view == null || isContentViewInactive(visibleType)) { @@ -1634,6 +1636,22 @@ public class NotificationContentView extends FrameLayout { } /** + * Remove content inactive listeners for a given content view . See + * {@link #performWhenContentInactive}. + * + * @param visibleType visible type corresponding to the content type + */ + void removeContentInactiveRunnable(int visibleType) { + View view = getViewForVisibleType(visibleType); + // View is already inactive + if (view == null) { + return; + } + + mOnContentViewInactiveListeners.remove(view); + } + + /** * Whether or not the content view is inactive. This means it should not be visible * or the showing content as removing it would cause visual jank. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index 88ed0bb37d0d..d3fec695f012 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -109,11 +109,14 @@ public final class RowContentBindParams { } /** - * Free the content view so that it will no longer be bound after the rebind request. + * Mark the content view to be freed. The view may not be immediately freeable since it may + * be visible and animating out but this lets the binder know to free the view when safe. + * Note that the callback passed into {@link RowContentBindStage#requestRebind} + * may return before the view is actually freed since the view is considered up-to-date. * * @see InflationFlag */ - public void freeContentViews(@InflationFlag int contentViews) { + public void markContentViewsFreeable(@InflationFlag int contentViews) { mContentViews &= ~contentViews; mDirtyContentViews &= ~contentViews; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 0c311b403c48..5205bab8fea3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -130,10 +130,18 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { if (mNotificationHeader != null) { mNotificationHeader.setAppOpsOnClickListener(listener); } - mAppOps.setOnClickListener(listener); - mCameraIcon.setOnClickListener(listener); - mMicIcon.setOnClickListener(listener); - mOverlayIcon.setOnClickListener(listener); + if (mAppOps != null) { + mAppOps.setOnClickListener(listener); + } + if (mCameraIcon != null) { + mCameraIcon.setOnClickListener(listener); + } + if (mMicIcon != null) { + mMicIcon.setOnClickListener(listener); + } + if (mOverlayIcon != null) { + mOverlayIcon.setOnClickListener(listener); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 8c31a372a756..dd9c8207af06 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -391,7 +391,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis alertNotificationWhenPossible(entry, mHeadsUpManager); } else { // The transfer is no longer valid. Free the content. - entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag()); + mRowContentBindStage.getStageParams(entry).markContentViewsFreeable( + contentFlag); + mRowContentBindStage.requestRebind(entry, null); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 66a1d3ff756a..0d5a14960850 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -161,7 +161,6 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } - entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP); } protected void updatePinnedMode() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java index 94aa39142926..86998ab2fdd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.systemui.statusbar.policy; diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index 471149c936e4..6decb88ee148 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import android.animation.ObjectAnimator; import android.content.Context; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.annotation.UiThreadTest; @@ -52,7 +53,11 @@ public class ExpandHelperTest extends SysuiTestCase { mDependency.injectMockDependency(NotificationMediaManager.class); allowTestableLooperAsMainThread(); Context context = getContext(); - mRow = new NotificationTestHelper(context, mDependency).createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = helper.createRow(); mCallback = mock(ExpandHelper.Callback.class); mExpandHelper = new ExpandHelper(context, mCallback, 10, 100); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 6f3fbb9cbd2c..037f04ec1d7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -223,7 +223,10 @@ public class BubbleControllerTest extends SysuiTestCase { mNotificationShadeWindowController.attach(); // Need notifications for bubbles - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mRow = mNotificationTestHelper.createBubble(mDeleteIntent); mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); mNonBubbleNotifRow = mNotificationTestHelper.createRow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index 7df39838b167..d2f912770577 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -109,7 +109,10 @@ public class BubbleDataTest extends SysuiTestCase { @Before public void setUp() throws Exception { - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); MockitoAnnotations.initMocks(this); mEntryA1 = createBubbleEntry(1, "a1", "package.a"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index a31e3f8d7cc9..545de210d5b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -210,7 +210,10 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mNotificationShadeWindowController.attach(); // Need notifications for bubbles - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mRow = mNotificationTestHelper.createBubble(mDeleteIntent); mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); mNonBubbleNotifRow = mNotificationTestHelper.createRow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 83877f2e72f1..ea68516e639c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -99,7 +99,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); - mHelper = new NotificationTestHelper(mContext, mDependency); + mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); mViewHierarchyManager = new NotificationViewHierarchyManager(mContext, mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java index 0a38f163cfba..2b9456160122 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.verify; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.widget.FrameLayout; @@ -46,7 +47,10 @@ public class AboveShelfObserverTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - mNotificationTestHelper = new NotificationTestHelper(getContext(), mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mHostLayout = new FrameLayout(getContext()); mObserver = new AboveShelfObserver(mHostLayout); ExpandableNotificationRow row = mNotificationTestHelper.createRow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java index bf2d59880ffb..29040147d1ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java @@ -83,8 +83,8 @@ public class DynamicChildBindControllerTest extends SysuiTestCase { mDynamicChildBindController.updateChildContentViews(mGroupNotifs); // THEN we free content views - verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED); - verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_EXPANDED); + verify(bindParams).markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED); + verify(bindParams).markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED); verify(mBindStage).requestRebind(eq(lastChild), any()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java index 97e0a3199e17..277ac244cec5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java @@ -32,6 +32,7 @@ import android.content.pm.PackageManager; import android.os.Bundle; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.annotation.UiThreadTest; @@ -98,7 +99,11 @@ public class NotificationFilterTest extends SysuiTestCase { mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment); when(mEnvironment.isDeviceProvisioned()).thenReturn(true); when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true); - mRow = new NotificationTestHelper(getContext(), mDependency).createRow(); + NotificationTestHelper testHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = testHelper.createRow(); mNotificationFilter = new NotificationFilter(mock(StatusBarStateController.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java index 0c109c498dd7..f3038ce051cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,6 +39,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; +import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; @@ -63,6 +68,8 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { @Mock private NotifPipeline mNotifPipeline; @Mock private HeadsUpManager mHeadsUpManager; + @Mock private HeadsUpViewBinder mHeadsUpViewBinder; + @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider; @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private RemoteInputController mRemoteInputController; @Mock private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; @@ -76,6 +83,8 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { mCoordinator = new HeadsUpCoordinator( mHeadsUpManager, + mHeadsUpViewBinder, + mNotificationInterruptStateProvider, mRemoteInputManager ); @@ -168,6 +177,36 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { } @Test + public void testShowHUNOnInflationFinished() { + // WHEN a notification should HUN and its inflation is finished + when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true); + + ArgumentCaptor<BindCallback> bindCallbackCaptor = + ArgumentCaptor.forClass(BindCallback.class); + mCollectionListener.onEntryAdded(mEntry); + verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), bindCallbackCaptor.capture()); + + bindCallbackCaptor.getValue().onBindFinished(mEntry); + + // THEN we tell the HeadsUpManager to show the notification + verify(mHeadsUpManager).showNotification(mEntry); + } + + @Test + public void testNoHUNOnInflationFinished() { + // WHEN a notification shouldn't HUN and its inflation is finished + when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false); + ArgumentCaptor<BindCallback> bindCallbackCaptor = + ArgumentCaptor.forClass(BindCallback.class); + mCollectionListener.onEntryAdded(mEntry); + + // THEN we never bind the heads up view or tell HeadsUpManager to show the notification + verify(mHeadsUpViewBinder, never()).bindHeadsUpView( + eq(mEntry), bindCallbackCaptor.capture()); + verify(mHeadsUpManager, never()).showNotification(mEntry); + } + + @Test public void testOnEntryRemovedRemovesHeadsUpNotification() { // GIVEN the current HUN is mEntry setCurrentHUN(mEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index 8143cf5a4673..6b9e43bcb290 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -20,10 +20,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.os.RemoteException; import android.testing.AndroidTestingRunner; @@ -41,9 +39,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager; -import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; import org.junit.Test; @@ -78,8 +74,6 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Mock private NotifPipeline mNotifPipeline; @Mock private IStatusBarService mService; @Mock private NotifInflaterImpl mNotifInflater; - @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider; - @Mock private HeadsUpManager mHeadsUpManager; @Before public void setUp() { @@ -94,9 +88,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mNotifInflater, mErrorManager, mock(NotifViewBarn.class), - mService, - mNotificationInterruptStateProvider, - mHeadsUpManager); + mService); ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); mCoordinator.attach(mNotifPipeline); @@ -180,24 +172,4 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // THEN it isn't filtered from shade list assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); } - - @Test - public void testShowHUNOnInflationFinished() { - // WHEN a notification should HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true); - mCallback.onInflationFinished(mEntry); - - // THEN we tell the HeadsUpManager to show the notification - verify(mHeadsUpManager).showNotification(mEntry); - } - - @Test - public void testNoHUNOnInflationFinished() { - // WHEN a notification shouldn't HUN and its inflation is finished - when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false); - mCallback.onInflationFinished(mEntry); - - // THEN we never tell the HeadsUpManager to show the notification - verify(mHeadsUpManager, never()).showNotification(mEntry); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index cb379208eb94..43dcbe30f3c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -20,7 +20,6 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -39,6 +38,7 @@ import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.app.NotificationChannel; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.ArraySet; import android.view.NotificationHeaderView; @@ -79,7 +79,10 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mGroupRow = mNotificationTestHelper.createGroup(); mGroupRow.setHeadsUpAnimatingAwayListener( animatingAway -> mHeadsUpAnimatingAway = animatingAway); @@ -135,22 +138,13 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void testFreeContentViewWhenSafe() throws Exception { - ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); - - row.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP); - - assertNull(row.getPrivateLayout().getHeadsUpChild()); - } - - @Test public void setNeedsRedactionFreesViewWhenFalse() throws Exception { ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL); row.setNeedsRedaction(true); row.getPublicLayout().setVisibility(View.GONE); row.setNeedsRedaction(false); - + TestableLooper.get(this).processAllMessages(); assertNull(row.getPublicLayout().getContractedChild()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java index 6408f7a38133..bdd82fd98e0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -59,7 +59,10 @@ public class NotifBindPipelineTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); CommonNotifCollection collection = mock(CommonNotifCollection.class); - mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class)); + mBindPipeline = new NotifBindPipeline( + collection, + mock(NotifBindPipelineLogger.class), + TestableLooper.get(this).getLooper()); mBindPipeline.setStage(mStage); ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor = @@ -78,6 +81,7 @@ public class NotifBindPipelineTest extends SysuiTestCase { // WHEN content is invalidated BindCallback callback = mock(BindCallback.class); mStage.requestRebind(mEntry, callback); + TestableLooper.get(this).processAllMessages(); // WHEN stage finishes its work mStage.doWorkSynchronously(); @@ -94,6 +98,7 @@ public class NotifBindPipelineTest extends SysuiTestCase { // GIVEN an in-progress pipeline run BindCallback callback = mock(BindCallback.class); CancellationSignal signal = mStage.requestRebind(mEntry, callback); + TestableLooper.get(this).processAllMessages(); // WHEN the callback is cancelled. signal.cancel(); @@ -113,10 +118,12 @@ public class NotifBindPipelineTest extends SysuiTestCase { // WHEN the pipeline is invalidated. BindCallback callback = mock(BindCallback.class); mStage.requestRebind(mEntry, callback); + TestableLooper.get(this).processAllMessages(); // WHEN the pipeline is invalidated again before the work completes. BindCallback callback2 = mock(BindCallback.class); mStage.requestRebind(mEntry, callback2); + TestableLooper.get(this).processAllMessages(); // WHEN the stage finishes all work. mStage.doWorkSynchronously(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java index 481bac2c19c6..7c8328d59a51 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java @@ -86,7 +86,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem); mDependency.injectMockDependency(BubbleController.class); - mHelper = new NotificationTestHelper(mContext, mDependency); + mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); mBlockingHelperManager = new NotificationBlockingHelperManager( mContext, mGutsManager, mEntryManager, mock(MetricsLogger.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index 6a65269c34b0..25da74137a90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -40,6 +40,7 @@ import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.view.ViewGroup; @@ -94,8 +95,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { .setContentTitle("Title") .setContentText("Text") .setStyle(new Notification.BigTextStyle().bigText("big text")); - ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow( - mBuilder.build()); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + ExpandableNotificationRow row = helper.createRow(mBuilder.build()); mRow = spy(row); final SmartReplyConstants smartReplyConstants = mock(SmartReplyConstants.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java index 0f268984a996..b018b59e4389 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java @@ -76,13 +76,13 @@ public class NotificationContentViewTest extends SysuiTestCase { @Test @UiThreadTest public void testShowAppOpsIcons() { - View mockContracted = mock(View.class); + View mockContracted = mock(NotificationHeaderView.class); when(mockContracted.findViewById(com.android.internal.R.id.mic)) .thenReturn(mockContracted); - View mockExpanded = mock(View.class); + View mockExpanded = mock(NotificationHeaderView.class); when(mockExpanded.findViewById(com.android.internal.R.id.mic)) .thenReturn(mockExpanded); - View mockHeadsUp = mock(View.class); + View mockHeadsUp = mock(NotificationHeaderView.class); when(mockHeadsUp.findViewById(com.android.internal.R.id.mic)) .thenReturn(mockHeadsUp); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index a5d8a84e3e08..be026f7884c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -182,7 +182,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager); NotifBindPipeline pipeline = new NotifBindPipeline( mEntryManager, - mock(NotifBindPipelineLogger.class)); + mock(NotifBindPipelineLogger.class), + TestableLooper.get(this).getLooper()); mBgExecutor = new FakeExecutor(new FakeSystemClock()); NotificationContentInflater binder = new NotificationContentInflater( cache, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 462da935e0c3..ed4642344dba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -131,7 +131,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); mDependency.injectMockDependency(NotificationLockscreenUserManager.class); mHandler = Handler.createAsync(mTestableLooper.getLooper()); - mHelper = new NotificationTestHelper(mContext, mDependency); + mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 0e67feb1d4ee..07f2085a1b76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -38,6 +38,7 @@ import android.content.pm.LauncherApps; import android.graphics.drawable.Icon; import android.os.UserHandle; import android.service.notification.StatusBarNotification; +import android.testing.TestableLooper; import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; @@ -92,6 +93,7 @@ public class NotificationTestHelper { private static final String APP_NAME = "appName"; private final Context mContext; + private final TestableLooper mTestLooper; private int mId; private final NotificationGroupManager mGroupManager; private ExpandableNotificationRow mRow; @@ -103,8 +105,12 @@ public class NotificationTestHelper { private StatusBarStateController mStatusBarStateController; private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; - public NotificationTestHelper(Context context, TestableDependency dependency) { + public NotificationTestHelper( + Context context, + TestableDependency dependency, + TestableLooper testLooper) { mContext = context; + mTestLooper = testLooper; dependency.injectMockDependency(NotificationMediaManager.class); dependency.injectMockDependency(BubbleController.class); dependency.injectMockDependency(NotificationShadeWindowController.class); @@ -133,7 +139,10 @@ public class NotificationTestHelper { CommonNotifCollection collection = mock(CommonNotifCollection.class); - mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class)); + mBindPipeline = new NotifBindPipeline( + collection, + mock(NotifBindPipelineLogger.class), + mTestLooper.getLooper()); mBindPipeline.setStage(mBindStage); ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor = @@ -414,7 +423,7 @@ public class NotificationTestHelper { mPeopleNotificationIdentifier); row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); - inflateAndWait(entry, mBindStage); + inflateAndWait(entry); // This would be done as part of onAsyncInflationFinished, but we skip large amounts of // the callback chain, so we need to make up for not adding it to the group manager @@ -423,10 +432,10 @@ public class NotificationTestHelper { return row; } - private static void inflateAndWait(NotificationEntry entry, RowContentBindStage stage) - throws Exception { + private void inflateAndWait(NotificationEntry entry) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); - stage.requestRebind(entry, en -> countDownLatch.countDown()); + mBindStage.requestRebind(entry, en -> countDownLatch.countDown()); + mTestLooper.processAllMessages(); assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index 0f2482ce9c4e..96a58e27ed0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -93,7 +93,7 @@ public class RowContentBindStageTest extends SysuiTestCase { // WHEN inflation flags are cleared and stage executed. final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; - params.freeContentViews(flags); + params.markContentViewsFreeable(flags); mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { }); // THEN binder unbinds flags. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index b661b28c42fc..45f7c5a6fdc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row.wrapper; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.RemoteViews; @@ -43,7 +44,11 @@ public class NotificationCustomViewWrapperTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - mRow = new NotificationTestHelper(mContext, mDependency).createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = helper.createRow(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java index 18ea774e56f0..fbe4d7315baa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java @@ -27,6 +27,7 @@ import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.RemoteViews; @@ -97,7 +98,11 @@ public class NotificationMediaTemplateViewWrapperTest extends SysuiTestCase { mNotif = builder.build(); assertTrue(mNotif.hasMediaSession()); - mRow = new NotificationTestHelper(mContext, mDependency).createRow(mNotif); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = helper.createRow(mNotif); RemoteViews views = new RemoteViews(mContext.getPackageName(), com.android.internal.R.layout.notification_template_material_big_media); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java index 830e8d93196c..085bd900debc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.LinearLayout; @@ -48,7 +49,11 @@ public class NotificationViewWrapperTest extends SysuiTestCase { public void setup() throws Exception { allowTestableLooperAsMainThread(); mView = mock(View.class); - mRow = new NotificationTestHelper(getContext(), mDependency).createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mRow = helper.createRow(); mNotificationViewWrapper = new TestableNotificationViewWrapper(mContext, mView, mRow); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index a2029c76bb55..703789151895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.stack; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.NotificationHeaderView; import android.view.View; @@ -44,7 +45,10 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mGroup = mNotificationTestHelper.createGroup(); mChildrenContainer = mGroup.getChildrenContainer(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index ba2b94677814..d795cbac700f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; @@ -66,7 +67,10 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { mBypassController, new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext)); allowTestableLooperAsMainThread(); - NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + NotificationTestHelper testHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mFirst = testHelper.createRow(); mFirst.setHeadsUpAnimatingAwayListener(animatingAway -> mRoundnessManager.onHeadsupAnimatingAwayChanged(mFirst, animatingAway)); @@ -146,7 +150,10 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mSecond), createSection(null, null) }); - NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + NotificationTestHelper testHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); ExpandableNotificationRow row = testHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.getRow()).thenReturn(row); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index a74657e561aa..e546dff8abf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.TextView; @@ -69,7 +70,10 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + NotificationTestHelper testHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); mFirst = testHelper.createRow(); mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher); mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index f6a099da3282..67f941301e5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -26,7 +26,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -236,8 +235,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture()); callbackCaptor.getValue().onBindFinished(childEntry); - verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager - .getContentFlag()); + assertTrue((params.getContentViews() & FLAG_CONTENT_VIEW_HEADS_UP) == 0); assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey())); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index b9c5b7c02b1c..dd28687e749c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -152,7 +152,10 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1)); when(mContentIntent.getIntent()).thenReturn(mContentIntentInner); - mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); + mNotificationTestHelper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); // Create standard notification with contentIntent mNotificationRow = mNotificationTestHelper.createRow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 86add98ab929..e88b514ef238 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -105,8 +105,11 @@ public class RemoteInputViewTest extends SysuiTestCase { @Test public void testSendRemoteInput_intentContainsResultsAndSource() throws Exception { - ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency) - .createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + ExpandableNotificationRow row = helper.createRow(); RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); setTestPendingIntent(view); @@ -127,7 +130,11 @@ public class RemoteInputViewTest extends SysuiTestCase { private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser) throws Exception { - ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow( + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + ExpandableNotificationRow row = helper.createRow( DUMMY_MESSAGE_APP_PKG, UserHandle.getUid(fromUser.getIdentifier(), DUMMY_MESSAGE_APP_ID), toUser); @@ -169,8 +176,11 @@ public class RemoteInputViewTest extends SysuiTestCase { @Test public void testNoCrashWithoutVisibilityListener() throws Exception { - ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency) - .createRow(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + ExpandableNotificationRow row = helper.createRow(); RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); view.setOnVisibilityChangedListener(null); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index e942b274dc53..55a92966eb29 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -645,33 +645,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Returns whether inline suggestions are enabled for Autofill. + * Returns whether inline suggestions are supported by Autofill provider (not augmented + * Autofill provider). */ - private boolean isInlineSuggestionsEnabledLocked() { - return mService.isInlineSuggestionsEnabled() - || mService.getRemoteInlineSuggestionRenderServiceLocked() != null; + private boolean isInlineSuggestionsEnabledByAutofillProviderLocked() { + return mService.isInlineSuggestionsEnabled(); } - /** - * Ask the IME to make an inline suggestions request if enabled. - */ - private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState, - int newState, int flags) { - if (isInlineSuggestionsEnabledLocked()) { - Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = - mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true); - if (inlineSuggestionsRequestConsumer != null) { - mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, - inlineSuggestionsRequestConsumer); - } - } else { - mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false); - } - requestNewFillResponseLocked(viewState, newState, flags); + private boolean isInlineSuggestionRenderServiceAvailable() { + return mService.getRemoteInlineSuggestionRenderServiceLocked() != null; } /** * Reads a new structure and then request a new fill response from the fill service. + * + * <p> Also asks the IME to make an inline suggestions request if it's enabled. */ @GuardedBy("mLock") private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState, @@ -717,6 +705,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // structure is taken. This causes only one fill request per bust of focus changes. cancelCurrentRequestLocked(); + // Only ask IME to create inline suggestions request if Autofill provider supports it and + // the render service is available. + if (isInlineSuggestionsEnabledByAutofillProviderLocked() + && isInlineSuggestionRenderServiceAvailable()) { + Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = + mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true); + if (inlineSuggestionsRequestConsumer != null) { + mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, + inlineSuggestionsRequestConsumer); + } + } else { + mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false); + } + + // Now request the assist structure data. try { final Bundle receiverExtras = new Bundle(); receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); @@ -2322,8 +2325,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if ((flags & FLAG_MANUAL_REQUEST) != 0) { mForAugmentedAutofillOnly = false; if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags); - maybeRequestInlineSuggestionsRequestThenFillLocked(viewState, - ViewState.STATE_RESTARTED_SESSION, flags); + requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags); return true; } @@ -2333,8 +2335,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": " + viewState.getStateAsString()); } - maybeRequestInlineSuggestionsRequestThenFillLocked(viewState, - ViewState.STATE_STARTED_PARTITION, flags); + requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags); return true; } else { if (sVerbose) { @@ -2458,8 +2459,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // View is triggering autofill. mCurrentViewId = viewState.id; viewState.update(value, virtualBounds, flags); - maybeRequestInlineSuggestionsRequestThenFillLocked(viewState, - ViewState.STATE_STARTED_SESSION, flags); + requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags); break; case ACTION_VALUE_CHANGED: if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { @@ -3107,12 +3107,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState }, mService.getRemoteInlineSuggestionRenderServiceLocked()); }; - // There are 3 cases when augmented autofill should ask IME for a new request: - // 1. standard autofill provider is None - // 2. standard autofill provider doesn't support inline (and returns null response) - // 3. standard autofill provider supports inline, but isn't called because the field - // doesn't want autofill - if (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabledLocked()) { + // When the inline suggestion render service is available, there are 2 cases when + // augmented autofill should ask IME for inline suggestion request, because standard + // autofill flow didn't: + // 1. the field is augmented autofill only (when standard autofill provider is None or + // when it returns null response) + // 2. standard autofill provider doesn't support inline suggestion + if (isInlineSuggestionRenderServiceAvailable() + && (mForAugmentedAutofillOnly + || !isInlineSuggestionsEnabledByAutofillProviderLocked())) { if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, /*requestConsumer=*/ requestAugmentedAutofill); diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 63a8d7c92441..696daca79092 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -19,6 +19,7 @@ package com.android.server.display; import android.graphics.Rect; import android.hardware.display.DisplayViewport; import android.os.IBinder; +import android.view.Display; import android.view.DisplayAddress; import android.view.Surface; import android.view.SurfaceControl; @@ -78,6 +79,13 @@ abstract class DisplayDevice { } /** + * Gets the id of the display to mirror. + */ + public int getDisplayIdToMirrorLocked() { + return Display.DEFAULT_DISPLAY; + } + + /** * Gets the name of the display device. * * @return The display device name. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index a23205124f74..3afbf661f97e 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -57,6 +57,7 @@ import android.hardware.display.DisplayedContentSamplingAttributes; import android.hardware.display.IDisplayManager; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.hardware.display.WifiDisplayStatus; import android.hardware.input.InputManagerInternal; import android.media.projection.IMediaProjection; @@ -794,8 +795,8 @@ public final class DisplayManagerService extends SystemService { } private int createVirtualDisplayInternal(IVirtualDisplayCallback callback, - IMediaProjection projection, int callingUid, String packageName, String name, int width, - int height, int densityDpi, Surface surface, int flags, String uniqueId) { + IMediaProjection projection, int callingUid, String packageName, Surface surface, + int flags, VirtualDisplayConfig virtualDisplayConfig) { synchronized (mSyncRoot) { if (mVirtualDisplayAdapter == null) { Slog.w(TAG, "Rejecting request to create private virtual display " @@ -804,8 +805,8 @@ public final class DisplayManagerService extends SystemService { } DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked( - callback, projection, callingUid, packageName, name, width, height, densityDpi, - surface, flags, uniqueId); + callback, projection, callingUid, packageName, surface, flags, + virtualDisplayConfig); if (device == null) { return -1; } @@ -1480,8 +1481,8 @@ public final class DisplayManagerService extends SystemService { if (!ownContent) { if (display != null && !display.hasContentLocked()) { // If the display does not have any content of its own, then - // automatically mirror the default logical display contents. - display = null; + // automatically mirror the requested logical display contents if possible. + display = mLogicalDisplays.get(device.getDisplayIdToMirrorLocked()); } if (display == null) { display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY); @@ -1729,6 +1730,28 @@ public final class DisplayManagerService extends SystemService { } } + @VisibleForTesting + int getDisplayIdToMirrorInternal(int displayId) { + synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked(); + return displayDevice.getDisplayIdToMirrorLocked(); + } + return Display.INVALID_DISPLAY; + } + } + + @VisibleForTesting + Surface getVirtualDisplaySurfaceInternal(IBinder appToken) { + synchronized (mSyncRoot) { + if (mVirtualDisplayAdapter == null) { + return null; + } + return mVirtualDisplayAdapter.getVirtualDisplaySurfaceLocked(appToken); + } + } + private final class DisplayManagerHandler extends Handler { public DisplayManagerHandler(Looper looper) { super(looper, null, true /*async*/); @@ -2050,10 +2073,8 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call - public int createVirtualDisplay(IVirtualDisplayCallback callback, - IMediaProjection projection, String packageName, String name, - int width, int height, int densityDpi, Surface surface, int flags, - String uniqueId) { + public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, + IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) { final int callingUid = Binder.getCallingUid(); if (!validatePackageName(callingUid, packageName)) { throw new SecurityException("packageName must match the calling uid"); @@ -2061,13 +2082,12 @@ public final class DisplayManagerService extends SystemService { if (callback == null) { throw new IllegalArgumentException("appToken must not be null"); } - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("name must be non-null and non-empty"); - } - if (width <= 0 || height <= 0 || densityDpi <= 0) { - throw new IllegalArgumentException("width, height, and densityDpi must be " - + "greater than 0"); + if (virtualDisplayConfig == null) { + throw new IllegalArgumentException("virtualDisplayConfig must not be null"); } + final Surface surface = virtualDisplayConfig.getSurface(); + int flags = virtualDisplayConfig.getFlags(); + if (surface != null && surface.isSingleBuffered()) { throw new IllegalArgumentException("Surface can't be single-buffered"); } @@ -2128,7 +2148,7 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { return createVirtualDisplayInternal(callback, projection, callingUid, packageName, - name, width, height, densityDpi, surface, flags, uniqueId); + surface, flags, virtualDisplayConfig); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index f4f2eadfaa8e..ccd88483593a 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -28,6 +28,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPO import android.content.Context; import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.os.Handler; @@ -84,22 +85,24 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback, - IMediaProjection projection, int ownerUid, String ownerPackageName, String name, - int width, int height, int densityDpi, Surface surface, int flags, String uniqueId) { + IMediaProjection projection, int ownerUid, String ownerPackageName, Surface surface, + int flags, VirtualDisplayConfig virtualDisplayConfig) { + String name = virtualDisplayConfig.getName(); boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; IBinder appToken = callback.asBinder(); IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure); final String baseUniqueId = UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ","; final int uniqueIndex = getNextUniqueIndex(baseUniqueId); + String uniqueId = virtualDisplayConfig.getUniqueId(); if (uniqueId == null) { uniqueId = baseUniqueId + uniqueIndex; } else { uniqueId = UNIQUE_ID_PREFIX + ownerPackageName + ":" + uniqueId; } VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken, - ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags, - new Callback(callback, mHandler), uniqueId, uniqueIndex); + ownerUid, ownerPackageName, surface, flags, new Callback(callback, mHandler), + uniqueId, uniqueIndex, virtualDisplayConfig); mVirtualDisplayDevices.put(appToken, device); @@ -127,6 +130,14 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } } + @VisibleForTesting + Surface getVirtualDisplaySurfaceLocked(IBinder appToken) { + VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken); + if (device != null) { + return device.getSurfaceLocked(); + } + return null; + } public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) { VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken); @@ -214,20 +225,21 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private int mUniqueIndex; private Display.Mode mMode; private boolean mIsDisplayOn; + private int mDisplayIdToMirror; public VirtualDisplayDevice(IBinder displayToken, IBinder appToken, - int ownerUid, String ownerPackageName, - String name, int width, int height, int densityDpi, Surface surface, int flags, - Callback callback, String uniqueId, int uniqueIndex) { + int ownerUid, String ownerPackageName, Surface surface, int flags, + Callback callback, String uniqueId, int uniqueIndex, + VirtualDisplayConfig virtualDisplayConfig) { super(VirtualDisplayAdapter.this, displayToken, uniqueId); mAppToken = appToken; mOwnerUid = ownerUid; mOwnerPackageName = ownerPackageName; - mName = name; - mWidth = width; - mHeight = height; - mMode = createMode(width, height, REFRESH_RATE); - mDensityDpi = densityDpi; + mName = virtualDisplayConfig.getName(); + mWidth = virtualDisplayConfig.getWidth(); + mHeight = virtualDisplayConfig.getHeight(); + mMode = createMode(mWidth, mHeight, REFRESH_RATE); + mDensityDpi = virtualDisplayConfig.getDensityDpi(); mSurface = surface; mFlags = flags; mCallback = callback; @@ -235,6 +247,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mPendingChanges |= PENDING_SURFACE_CHANGE; mUniqueIndex = uniqueIndex; mIsDisplayOn = surface != null; + mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror(); } @Override @@ -260,6 +273,16 @@ public class VirtualDisplayAdapter extends DisplayAdapter { } @Override + public int getDisplayIdToMirrorLocked() { + return mDisplayIdToMirror; + } + + @VisibleForTesting + Surface getSurfaceLocked() { + return mSurface; + } + + @Override public boolean hasStableUniqueId() { return false; } @@ -332,6 +355,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { pw.println("mFlags=" + mFlags); pw.println("mDisplayState=" + Display.stateToString(mDisplayState)); pw.println("mStopped=" + mStopped); + pw.println("mDisplayIdToMirror=" + mDisplayIdToMirror); } diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java index a16dbb726613..3f2b5c231dca 100644 --- a/services/core/java/com/android/server/vr/Vr2dDisplay.java +++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java @@ -11,6 +11,7 @@ import android.content.IntentFilter; import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; +import android.hardware.display.VirtualDisplayConfig; import android.media.ImageReader; import android.os.Handler; import android.os.RemoteException; @@ -295,10 +296,12 @@ class Vr2dDisplay { flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi); + builder.setUniqueId(UNIQUE_DISPLAY_ID); + builder.setFlags(flags); mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */, - DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi, - null /* surface */, flags, null /* callback */, null /* handler */, - UNIQUE_DISPLAY_ID); + builder.build(), null /* callback */, null /* handler */); if (mVirtualDisplay != null) { updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId()); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 9356ec200f20..77ef01134292 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -732,11 +732,11 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { /** * Returns an existing stack compatible with the windowing mode and activity type or creates one * if a compatible stack doesn't exist. - * @see #getOrCreateStack(int, int, boolean, Intent, Task, boolean) + * @see #getOrCreateStack(int, int, boolean, Intent, Task) */ ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop) { return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */, - null /* candidateTask */, false /* createdByOrganizer */); + null /* candidateTask */); } /** @@ -748,7 +748,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { * @see #createStack(int, int, boolean) */ ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop, - Intent intent, Task candidateTask, boolean createdByOrganizer) { + Intent intent, Task candidateTask) { if (!alwaysCreateStack(windowingMode, activityType)) { ActivityStack stack = getStack(windowingMode, activityType); if (stack != null) { @@ -757,13 +757,13 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } else if (candidateTask != null) { final ActivityStack stack = (ActivityStack) candidateTask; final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; - if (isSplitScreenModeActivated()) { - final Task splitRootSecondary = getTask(t -> t.mCreatedByOrganizer && t.isRootTask() - && t.inSplitScreenSecondaryWindowingMode()); + Task launchRootTask = updateLaunchRootTask(windowingMode); + + if (launchRootTask != null) { if (stack.getParent() == null) { - splitRootSecondary.addChild(stack, position); - } else if (stack.getParent() != splitRootSecondary) { - stack.reparent(splitRootSecondary, position); + launchRootTask.addChild(stack, position); + } else if (stack.getParent() != launchRootTask) { + stack.reparent(launchRootTask, position); } } else if (stack.getDisplay() != mDisplayContent || !stack.isRootTask()) { if (stack.getParent() == null) { @@ -779,7 +779,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { return stack; } return createStack(windowingMode, activityType, onTop, null /*info*/, intent, - createdByOrganizer); + false /* createdByOrganizer */); } /** @@ -798,7 +798,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { // it's display's windowing mode. windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType); return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */, - candidateTask, false /* createdByOrganizer */); + candidateTask); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 872f2543edb8..b641e4c391ce 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -277,9 +277,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return null; } - final Task task = display.mTaskContainers.getOrCreateStack(windowingMode, - ACTIVITY_TYPE_UNDEFINED, false /* onTop */, new Intent(), - null /* candidateTask */, true /* createdByOrganizer */); + final Task task = display.mTaskContainers.createStack(windowingMode, + ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(), + true /* createdByOrganizer */); RunningTaskInfo out = task.getTaskInfo(); mLastSentTaskInfos.put(task, out); return out; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index b25383b15421..d4470f8334ea 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1066,14 +1066,15 @@ class WindowStateAnimator { if (!w.mSeamlesslyRotated) { + // Wallpaper is already updated above when calling setWallpaperPositionAndScale so + // we only need to consider the non-wallpaper case here. if (!mIsWallpaper) { applyCrop(clipRect, recoveringMemory); - mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale, + mSurfaceController.setMatrixInTransaction( + mDsDx * w.mHScale * mExtraHScale, mDtDx * w.mVScale * mExtraVScale, mDtDy * w.mHScale * mExtraHScale, mDsDy * w.mVScale * mExtraVScale, recoveringMemory); - } else { - setWallpaperPositionAndScale(mXOffset, mYOffset, mWallpaperScale, recoveringMemory); } } @@ -1152,13 +1153,20 @@ class WindowStateAnimator { mSurfaceController, mShownAlpha, mDsDx, w.mHScale, mDtDx, w.mVScale, mDtDy, w.mHScale, mDsDy, w.mVScale, w); - boolean prepared = - mSurfaceController.prepareToShowInTransaction(mShownAlpha, + boolean prepared = true; + + if (mIsWallpaper) { + setWallpaperPositionAndScale( + mXOffset, mYOffset, mWallpaperScale, recoveringMemory); + } else { + prepared = + mSurfaceController.prepareToShowInTransaction(mShownAlpha, mDsDx * w.mHScale * mExtraHScale, mDtDx * w.mVScale * mExtraVScale, mDtDy * w.mHScale * mExtraHScale, mDsDy * w.mVScale * mExtraVScale, recoveringMemory); + } if (prepared && mDrawState == HAS_DRAWN) { if (mLastHidden) { diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index d7c97b922d2d..f7ea953e5a01 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -127,6 +127,7 @@ class WindowSurfaceController { mBLASTSurfaceControl = win.makeSurface() .setParent(mSurfaceControl) .setName("BLAST Adapter Layer") + .setHidden(false) .setBLASTLayer() .build(); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 702718569871..7ad39b440e14 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -34,14 +34,17 @@ import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.hardware.input.InputManagerInternal; import android.os.Handler; import android.os.IBinder; import android.view.Display; import android.view.DisplayInfo; +import android.view.Surface; import android.view.SurfaceControl; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -67,6 +70,8 @@ import java.util.stream.LongStream; public class DisplayManagerServiceTest { private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1; private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10; + private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; + private static final String PACKAGE_NAME = "com.android.frameworks.servicestests"; private Context mContext; @@ -97,6 +102,7 @@ public class DisplayManagerServiceTest { @Mock InputManagerInternal mMockInputManagerInternal; @Mock IVirtualDisplayCallback.Stub mMockAppToken; + @Mock IVirtualDisplayCallback.Stub mMockAppToken2; @Mock WindowManagerInternal mMockWindowManagerInternal; @Mock LightsManager mMockLightsManager; @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter; @@ -135,10 +141,12 @@ public class DisplayManagerServiceTest { int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); - int displayId = bs.createVirtualDisplay(mMockAppToken /* callback */, - null /* projection */, "com.android.frameworks.servicestests", - "Test Virtual Display", width, height, dpi, null /* surface */, flags /* flags */, - uniqueId); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setUniqueId(uniqueId); + builder.setFlags(flags); + int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, + null /* projection */, PACKAGE_NAME); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -241,10 +249,12 @@ public class DisplayManagerServiceTest { int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); - int displayId = bs.createVirtualDisplay(mMockAppToken /* callback */, - null /* projection */, "com.android.frameworks.servicestests", - "Test Virtual Display", width, height, dpi, null /* surface */, flags /* flags */, - uniqueId); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setFlags(flags); + builder.setUniqueId(uniqueId); + int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */, + null /* projection */, PACKAGE_NAME); displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); @@ -409,6 +419,87 @@ public class DisplayManagerServiceTest { assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); } + /** + * Tests that the virtual display is created with + * {@link VirtualDisplayConfig.Builder#setDisplayIdToMirror(int)} + */ + @Test + @FlakyTest(bugId = 127687569) + public void testCreateVirtualDisplay_displayIdToMirror() throws Exception { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService binderService = displayManager.new BinderService(); + + final String uniqueId = "uniqueId --- displayIdToMirrorTest"; + final int width = 600; + final int height = 800; + final int dpi = 320; + + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setUniqueId(uniqueId); + final int firstDisplayId = binderService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + + // The second virtual display requests to mirror the first virtual display. + final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2"; + when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2); + final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi).setUniqueId(uniqueId2); + builder2.setUniqueId(uniqueId2); + builder2.setDisplayIdToMirror(firstDisplayId); + final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(), + mMockAppToken2 /* callback */, null /* projection */, PACKAGE_NAME); + displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); + + // The displayId to mirror should be a default display if there is none initially. + assertEquals(displayManager.getDisplayIdToMirrorInternal(firstDisplayId), + Display.DEFAULT_DISPLAY); + assertEquals(displayManager.getDisplayIdToMirrorInternal(secondDisplayId), + firstDisplayId); + } + + /** + * Tests that the virtual display is created with + * {@link VirtualDisplayConfig.Builder#setSurface(Surface)} + */ + @Test + @FlakyTest(bugId = 127687569) + public void testCreateVirtualDisplay_setSurface() throws Exception { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService binderService = displayManager.new BinderService(); + + final String uniqueId = "uniqueId --- setSurface"; + final int width = 600; + final int height = 800; + final int dpi = 320; + final Surface surface = new Surface(); + + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setSurface(surface); + builder.setUniqueId(uniqueId); + final int displayId = binderService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + + displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); + + assertEquals(displayManager.getVirtualDisplaySurfaceInternal(mMockAppToken), surface); + } + private void registerDefaultDisplays(DisplayManagerService displayManager) { Handler handler = displayManager.getDisplayHandler(); // Would prefer to call displayManager.onStart() directly here but it performs binderService diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index c37735c729a2..13a8273d64d4 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -153,8 +153,10 @@ public final class CellIdentityLte extends CellIdentity { cid.bands.stream().mapToInt(Integer::intValue).toArray(), cid.base.bandwidth, cid.base.base.mcc, cid.base.base.mnc, cid.base.operatorNames.alphaLong, cid.base.operatorNames.alphaShort, cid.additionalPlmns, - cid.optionalCsgInfo.csgInfo() != null - ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null); + cid.optionalCsgInfo.getDiscriminator() + == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo + ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) + : null); } private CellIdentityLte(@NonNull CellIdentityLte cid) { diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java index 3f95596a076e..6dffe922ffd1 100644 --- a/telephony/java/android/telephony/CellIdentityTdscdma.java +++ b/telephony/java/android/telephony/CellIdentityTdscdma.java @@ -128,8 +128,11 @@ public final class CellIdentityTdscdma extends CellIdentity { this(cid.base.base.mcc, cid.base.base.mnc, cid.base.base.lac, cid.base.base.cid, cid.base.base.cpid, cid.base.uarfcn, cid.base.operatorNames.alphaLong, cid.base.operatorNames.alphaShort, - cid.additionalPlmns, cid.optionalCsgInfo.csgInfo() != null - ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null); + cid.additionalPlmns, + cid.optionalCsgInfo.getDiscriminator() + == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo + ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) + : null); } /** @hide */ diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java index 38c4ed482e14..eab174ade3b7 100644 --- a/telephony/java/android/telephony/CellIdentityWcdma.java +++ b/telephony/java/android/telephony/CellIdentityWcdma.java @@ -123,8 +123,10 @@ public final class CellIdentityWcdma extends CellIdentity { this(cid.base.base.lac, cid.base.base.cid, cid.base.base.psc, cid.base.base.uarfcn, cid.base.base.mcc, cid.base.base.mnc, cid.base.operatorNames.alphaLong, cid.base.operatorNames.alphaShort, cid.additionalPlmns, - cid.optionalCsgInfo.csgInfo() != null - ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) : null); + cid.optionalCsgInfo.getDiscriminator() + == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo + ? new ClosedSubscriberGroupInfo(cid.optionalCsgInfo.csgInfo()) + : null); } private CellIdentityWcdma(@NonNull CellIdentityWcdma cid) { |