diff options
215 files changed, 6080 insertions, 1433 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 0ca8b2dfb0d0..4eeaaf87ea0d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1513,6 +1513,10 @@ package android.media { field public static final String SAMPLE_RATE = "android.media.audiotrack.sampleRate"; } + public final class MediaCas implements java.lang.AutoCloseable { + method public void forceResourceLost(); + } + public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint { ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size); ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size); diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp index 4bce89fd7f9c..482d66fc7556 100644 --- a/cmds/statsd/benchmark/metric_util.cpp +++ b/cmds/statsd/benchmark/metric_util.cpp @@ -370,13 +370,6 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const Stat return processor; } -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) { - AttributionNodeInternal attribution; - attribution.set_uid(uid); - attribution.set_tag(tag); - return attribution; -} - void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) { std::sort(events->begin(), events->end(), [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) { diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h index 6199fa9dc7a1..c5fcf7c27440 100644 --- a/cmds/statsd/benchmark/metric_util.h +++ b/cmds/statsd/benchmark/metric_util.h @@ -120,9 +120,6 @@ std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs, const vector<string>& attributionTags, const string& name); -// Helper function to create an AttributionNodeInternal proto. -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag); - // Create a statsd log event processor upon the start time in seconds, config and key. sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, const ConfigKey& key); diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index a6ae3899e4de..0ec11f926815 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -81,74 +81,6 @@ LogEvent::LogEvent(int32_t uid, int32_t pid) mLogPid(pid) { } -LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs) { - mLogdTimestampNs = wallClockTimestampNs; - mElapsedTimestampNs = elapsedTimestampNs; - mTagId = tagId; - mLogUid = 0; - mContext = create_android_logger(1937006964); // the event tag shared by all stats logs - if (mContext) { - android_log_write_int64(mContext, elapsedTimestampNs); - android_log_write_int32(mContext, tagId); - } -} - -LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map) { - mLogdTimestampNs = wallClockTimestampNs; - mElapsedTimestampNs = elapsedTimestampNs; - mTagId = util::KEY_VALUE_PAIRS_ATOM; - mLogUid = uid; - - int pos[] = {1, 1, 1}; - - mValues.push_back(FieldValue(Field(mTagId, pos, 0 /* depth */), Value(uid))); - pos[0]++; - for (const auto&itr : int_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 2; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - - for (const auto&itr : long_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 3; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - - for (const auto&itr : string_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 4; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - - for (const auto&itr : float_map) { - pos[2] = 1; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); - pos[2] = 5; - mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); - mValues.back().mField.decorateLastPos(2); - pos[1]++; - } - if (!mValues.empty()) { - mValues.back().mField.decorateLastPos(1); - mValues.at(mValues.size() - 2).mField.decorateLastPos(1); - } -} - LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging, bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, const std::vector<uint8_t>& experimentIds, int32_t userId) { @@ -184,17 +116,6 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status))); } -LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) { - mLogdTimestampNs = timestampNs; - mTagId = tagId; - mLogUid = uid; - mContext = create_android_logger(1937006964); // the event tag shared by all stats logs - if (mContext) { - android_log_write_int64(mContext, timestampNs); - android_log_write_int32(mContext, tagId); - } -} - LogEvent::~LogEvent() { if (mContext) { // This is for the case when LogEvent is created using the test interface @@ -203,154 +124,6 @@ LogEvent::~LogEvent() { } } -bool LogEvent::write(int32_t value) { - if (mContext) { - return android_log_write_int32(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::write(uint32_t value) { - if (mContext) { - return android_log_write_int32(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::write(int64_t value) { - if (mContext) { - return android_log_write_int64(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::write(uint64_t value) { - if (mContext) { - return android_log_write_int64(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::write(const string& value) { - if (mContext) { - return android_log_write_string8_len(mContext, value.c_str(), value.length()) >= 0; - } - return false; -} - -bool LogEvent::write(float value) { - if (mContext) { - return android_log_write_float32(mContext, value) >= 0; - } - return false; -} - -bool LogEvent::writeBytes(const string& value) { - /* if (mContext) { - return android_log_write_char_array(mContext, value.c_str(), value.length()) >= 0; - }*/ - return false; -} - -bool LogEvent::writeKeyValuePairs(int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map) { - if (mContext) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(uid); - for (const auto& itr : int_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } - - for (const auto& itr : long_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } - - for (const auto& itr : string_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second.c_str()); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } - - for (const auto& itr : float_map) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - write(itr.first); - write(itr.second); - if (android_log_write_list_end(mContext) < 0) { - return false; - } - } - - if (android_log_write_list_end(mContext) < 0) { - return false; - } - return true; - } - return false; -} - -bool LogEvent::write(const std::vector<AttributionNodeInternal>& nodes) { - if (mContext) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - for (size_t i = 0; i < nodes.size(); ++i) { - if (!write(nodes[i])) { - return false; - } - } - if (android_log_write_list_end(mContext) < 0) { - return false; - } - return true; - } - return false; -} - -bool LogEvent::write(const AttributionNodeInternal& node) { - if (mContext) { - if (android_log_write_list_begin(mContext) < 0) { - return false; - } - if (android_log_write_int32(mContext, node.uid()) < 0) { - return false; - } - if (android_log_write_string8(mContext, node.tag().c_str()) < 0) { - return false; - } - if (android_log_write_list_end(mContext) < 0) { - return false; - } - return true; - } - return false; -} - void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t value = readNextValue<int32_t>(); addToValues(pos, depth, value, last); diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 0a89be4ce335..6c6aab1c5ada 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -28,27 +28,6 @@ namespace android { namespace os { namespace statsd { -struct AttributionNodeInternal { - void set_uid(int32_t id) { - mUid = id; - } - - void set_tag(const std::string& value) { - mTag = value; - } - - int32_t uid() const { - return mUid; - } - - const std::string& tag() const { - return mTag; - } - - int32_t mUid; - std::string mTag; -}; - struct InstallTrainInfo { int64_t trainVersionCode; std::string trainName; @@ -83,28 +62,6 @@ public: */ bool parseBuffer(uint8_t* buf, size_t len); - // TODO(b/149590301): delete unused functions below once LogEvent uses the - // new socket schema within test code. Really we would like the only entry - // points into LogEvent to be the above constructor and parseBuffer functions. - - /** - * Constructs a LogEvent with synthetic data for testing. Must call init() before reading. - */ - explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs); - - // For testing. The timestamp is used as both elapsed real time and logd timestamp. - explicit LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid); - - /** - * Constructs a KeyValuePairsAtom LogEvent from value maps. - */ - explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, - int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map); - // Constructs a BinaryPushStateChanged LogEvent from API call. explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging, bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, @@ -152,25 +109,6 @@ public: std::vector<uint8_t> GetStorage(size_t key, status_t* err) const; /** - * Write test data to the LogEvent. This can only be used when the LogEvent is constructed - * using LogEvent(tagId, timestampNs). You need to call init() before you can read from it. - */ - bool write(uint32_t value); - bool write(int32_t value); - bool write(uint64_t value); - bool write(int64_t value); - bool write(const std::string& value); - bool write(float value); - bool write(const std::vector<AttributionNodeInternal>& nodes); - bool write(const AttributionNodeInternal& node); - bool writeBytes(const std::string& value); - bool writeKeyValuePairs(int32_t uid, - const std::map<int32_t, int32_t>& int_map, - const std::map<int32_t, int64_t>& long_map, - const std::map<int32_t, std::string>& string_map, - const std::map<int32_t, float>& float_map); - - /** * Return a string representation of this event. */ std::string ToString() const; diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 2f81c2ded8b0..7d765d3fbbf5 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -1060,13 +1060,6 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const in return processor; } -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) { - AttributionNodeInternal attribution; - attribution.set_uid(uid); - attribution.set_tag(tag); - return attribution; -} - void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) { std::sort(events->begin(), events->end(), [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) { diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index 715ba2b73169..f24705a0c89f 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -291,9 +291,6 @@ std::unique_ptr<LogEvent> CreateOverlayStateChangedEvent(int64_t timestampNs, co const bool usingAlertWindow, const OverlayStateChanged::State state); -// Helper function to create an AttributionNodeInternal proto. -AttributionNodeInternal CreateAttribution(const int& uid, const string& tag); - // Create a statsd log event processor upon the start time in seconds, config and key. sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs, const StatsdConfig& config, const ConfigKey& key, diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 27c9cfcdd05b..aa290404c001 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -25,6 +25,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IOnAppsChangedListener; import android.content.pm.LauncherApps; +import android.content.pm.ShortcutQueryWrapper; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IShortcutChangeCallback; import android.content.pm.PackageInstaller; @@ -67,9 +68,8 @@ interface ILauncherApps { LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, String packageName, in UserHandle user); - ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName, - in List shortcutIds, in List<LocusId> locusIds, in ComponentName componentName, - int flags, in UserHandle user); + ParceledListSlice getShortcuts(String callingPackage, in ShortcutQueryWrapper query, + in UserHandle user); void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, in UserHandle user); boolean startShortcut(String callingPackage, String packageName, String featureId, String id, @@ -93,9 +93,8 @@ interface ILauncherApps { in IPackageInstallerCallback callback); ParceledListSlice getAllSessions(String callingPackage); - void registerShortcutChangeCallback(String callingPackage, long changedSince, - String packageName, in List shortcutIds, in List<LocusId> locusIds, - in ComponentName componentName, int flags, in IShortcutChangeCallback callback); + void registerShortcutChangeCallback(String callingPackage, in ShortcutQueryWrapper query, + in IShortcutChangeCallback callback); void unregisterShortcutChangeCallback(String callingPackage, in IShortcutChangeCallback callback); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 6c161fcb8646..87dc0a17f41c 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -1046,8 +1046,7 @@ public class LauncherApps { // changed callback, but that only returns shortcuts with the "key" information, so // that won't return disabled message. return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(), - query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds, - query.mActivity, query.mQueryFlags, user) + new ShortcutQueryWrapper(query), user) .getList()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1826,8 +1825,7 @@ public class LauncherApps { mShortcutChangeCallbacks.put(callback, new Pair<>(executor, proxy)); try { mService.registerShortcutChangeCallback(mContext.getPackageName(), - query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds, - query.mActivity, query.mQueryFlags, proxy); + new ShortcutQueryWrapper(query), proxy); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.aidl b/core/java/android/content/pm/ShortcutQueryWrapper.aidl new file mode 100644 index 000000000000..d02600acf232 --- /dev/null +++ b/core/java/android/content/pm/ShortcutQueryWrapper.aidl @@ -0,0 +1,20 @@ +/** + * 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.content.pm; + +parcelable ShortcutQueryWrapper; + diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.java b/core/java/android/content/pm/ShortcutQueryWrapper.java new file mode 100644 index 000000000000..c6134416adbc --- /dev/null +++ b/core/java/android/content/pm/ShortcutQueryWrapper.java @@ -0,0 +1,190 @@ +/* + * 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.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.LocusId; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.ArrayList; +import java.util.List; + +/** + * @hide + */ +@DataClass(genParcelable = true, genToString = true) +public final class ShortcutQueryWrapper extends LauncherApps.ShortcutQuery implements Parcelable { + + public ShortcutQueryWrapper(LauncherApps.ShortcutQuery query) { + this(); + mChangedSince = query.mChangedSince; + mPackage = query.mPackage; + mLocusIds = query.mLocusIds; + mShortcutIds = query.mShortcutIds; + mActivity = query.mActivity; + mQueryFlags = query.mQueryFlags; + } + + public long getChangedSince() { + return mChangedSince; + } + + @Nullable + public String getPackage() { + return mPackage; + } + + @Nullable + public List<LocusId> getLocusIds() { + return mLocusIds; + } + + @Nullable + public List<String> getShortcutIds() { + return mShortcutIds; + } + + @Nullable + public ComponentName getActivity() { + return mActivity; + } + + public int getQueryFlags() { + return mQueryFlags; + } + + // Code below generated by codegen v1.0.14. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ShortcutQueryWrapper.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public ShortcutQueryWrapper() { + + // onConstructed(); // You can define this method to get a callback + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ShortcutQueryWrapper { " + + " }"; + } + + @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) { ... } + + byte flg = 0; + if (mPackage != null) flg |= 0x2; + if (mShortcutIds != null) flg |= 0x4; + if (mLocusIds != null) flg |= 0x8; + if (mActivity != null) flg |= 0x10; + dest.writeByte(flg); + dest.writeLong(mChangedSince); + if (mPackage != null) dest.writeString(mPackage); + if (mShortcutIds != null) dest.writeStringList(mShortcutIds); + if (mLocusIds != null) dest.writeParcelableList(mLocusIds, flags); + if (mActivity != null) dest.writeTypedObject(mActivity, flags); + dest.writeInt(mQueryFlags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ ShortcutQueryWrapper(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + long changedSince = in.readLong(); + String pkg = (flg & 0x2) == 0 ? null : in.readString(); + List<String> shortcutIds = null; + if ((flg & 0x4) != 0) { + shortcutIds = new ArrayList<>(); + in.readStringList(shortcutIds); + } + List<LocusId> locusIds = null; + if ((flg & 0x8) != 0) { + locusIds = new ArrayList<>(); + in.readParcelableList(locusIds, LocusId.class.getClassLoader()); + } + ComponentName activity = (flg & 0x10) == 0 ? null + : (ComponentName) in.readTypedObject(ComponentName.CREATOR); + int queryFlags = in.readInt(); + + this.mChangedSince = changedSince; + this.mPackage = pkg; + this.mShortcutIds = shortcutIds; + this.mLocusIds = locusIds; + this.mActivity = activity; + this.mQueryFlags = queryFlags; + com.android.internal.util.AnnotationValidations.validate( + QueryFlags.class, null, mQueryFlags); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ShortcutQueryWrapper> CREATOR + = new Parcelable.Creator<ShortcutQueryWrapper>() { + @Override + public ShortcutQueryWrapper[] newArray(int size) { + return new ShortcutQueryWrapper[size]; + } + + @Override + public ShortcutQueryWrapper createFromParcel(@NonNull Parcel in) { + return new ShortcutQueryWrapper(in); + } + }; + + @DataClass.Generated( + time = 1582049937960L, + codegenVersion = "1.0.14", + sourceFile = "frameworks/base/core/java/android/content/pm/ShortcutQueryWrapper.java", + inputSignatures = "public long getChangedSince()\npublic @android.annotation.Nullable java.lang.String getPackage()\npublic @android.annotation.Nullable java.util.List<android.content.LocusId> getLocusIds()\npublic @android.annotation.Nullable java.util.List<java.lang.String> getShortcutIds()\npublic @android.annotation.Nullable android.content.ComponentName getActivity()\npublic int getQueryFlags()\nclass ShortcutQueryWrapper extends android.content.pm.LauncherApps.ShortcutQuery implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} 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/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 7ff954bdc1d2..651494d1c99c 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -1074,6 +1074,21 @@ public final class LinkProperties implements Parcelable { } /** + * Returns true if this link has an IPv4 unreachable default route. + * + * @return {@code true} if there is an IPv4 unreachable default route, {@code false} otherwise. + * @hide + */ + public boolean hasIpv4UnreachableDefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv4UnreachableDefault()) { + return true; + } + } + return false; + } + + /** * For backward compatibility. * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely * just yet. @@ -1102,6 +1117,21 @@ public final class LinkProperties implements Parcelable { } /** + * Returns true if this link has an IPv6 unreachable default route. + * + * @return {@code true} if there is an IPv6 unreachable default route, {@code false} otherwise. + * @hide + */ + public boolean hasIpv6UnreachableDefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv6UnreachableDefault()) { + return true; + } + } + return false; + } + + /** * For backward compatibility. * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely * just yet. diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index dbdaa4c2da67..e550f85e6b9a 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -426,6 +426,16 @@ public final class RouteInfo implements Parcelable { } /** + * Indicates if this route is an unreachable default route. + * + * @return {@code true} if it's an unreachable route with prefix length of 0. + * @hide + */ + private boolean isUnreachableDefaultRoute() { + return mType == RTN_UNREACHABLE && mDestination.getPrefixLength() == 0; + } + + /** * Indicates if this route is an IPv4 default route. * @hide */ @@ -434,6 +444,14 @@ public final class RouteInfo implements Parcelable { } /** + * Indicates if this route is an IPv4 unreachable default route. + * @hide + */ + public boolean isIPv4UnreachableDefault() { + return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet4Address; + } + + /** * Indicates if this route is an IPv6 default route. * @hide */ @@ -442,6 +460,14 @@ public final class RouteInfo implements Parcelable { } /** + * Indicates if this route is an IPv6 unreachable default route. + * @hide + */ + public boolean isIPv6UnreachableDefault() { + return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet6Address; + } + + /** * Indicates if this route is a host route (ie, matches only a single host address). * * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6, diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 20e5f243163f..683993f762c0 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -251,6 +251,11 @@ public final class BinderProxy implements IBinder { } } } + // For gathering this debug output, we're making synchronous binder calls + // out of system_server to all processes hosting binder objects it holds a reference to; + // since some of those processes might be frozen, we don't want to block here + // forever. Disable the freezer. + Process.enableFreezer(false); for (WeakReference<BinderProxy> weakRef : proxiesToQuery) { BinderProxy bp = weakRef.get(); String key; @@ -273,6 +278,7 @@ public final class BinderProxy implements IBinder { counts.put(key, i + 1); } } + Process.enableFreezer(true); Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray( new Map.Entry[counts.size()]); diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index fb81d675939c..e86aa62d00bc 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -266,6 +266,13 @@ public final class DeviceConfig { public static final String NAMESPACE_SCHEDULER = "scheduler"; /** + * Namespace for settings statistics features. + * + * @hide + */ + public static final String NAMESPACE_SETTINGS_STATS = "settings_stats"; + + /** * Namespace for storage-related features. * * @deprecated Replace storage namespace with storage_native_boot. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 35f955f7e78b..3534bb0f763f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1747,17 +1747,18 @@ public final class ViewRootImpl implements ViewParent, || !mBlastSurfaceControl.isValid()) { return null; } + + Surface ret = null; if (mBlastBufferQueue == null) { mBlastBufferQueue = new BLASTBufferQueue( mBlastSurfaceControl, width, height); + // We only return the Surface the first time, as otherwise + // it hasn't changed and there is no need to update. + ret = mBlastBufferQueue.getSurface(); } mBlastBufferQueue.update(mBlastSurfaceControl, width, height); - mTransaction.show(mBlastSurfaceControl) - .reparent(mBlastSurfaceControl, mSurfaceControl) - .apply(); - - return mBlastBufferQueue.getSurface(); + return ret; } private void setBoundsLayerCrop() { @@ -7352,8 +7353,14 @@ public final class ViewRootImpl implements ViewParent, if (!mUseBLASTAdapter) { mSurface.copyFrom(mSurfaceControl); } else { - mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x, - mSurfaceSize.y)); + final Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, + mSurfaceSize.y); + // If blastSurface == null that means it hasn't changed since the last time we + // called. In this situation, avoid calling transferFrom as we would then + // inc the generation ID and cause EGL resources to be recreated. + if (blastSurface != null) { + mSurface.transferFrom(blastSurface); + } } } else { destroySurface(); 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/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 0c74387c1a2e..7c32ca653114 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -696,7 +696,7 @@ static jlong android_os_Process_getTotalMemory(JNIEnv* env, jobject clazz) return -1; } - return si.totalram; + return static_cast<jlong>(si.totalram) * si.mem_unit; } /* diff --git a/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java new file mode 100644 index 000000000000..8f8488f8b287 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java @@ -0,0 +1,77 @@ +/* + * 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.content.pm; + +import static org.junit.Assert.assertEquals; + +import android.content.ComponentName; +import android.content.LocusId; +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import androidx.test.runner.AndroidJUnit4; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public class ShortcutQueryWrapperTest { + + private static final long CHANGED_SINCE = TimeUnit.SECONDS.toMillis(1); + private static final String PACKAGE_NAME = "com.android.test"; + private static final List<String> SHORTCUT_IDS = Lists.newArrayList("s1", "s2", "s3"); + private static final List<LocusId> LOCUS_IDS = Lists.newArrayList( + new LocusId("id1"), new LocusId("id2"), new LocusId("id3")); + private static final ComponentName COMPONENT_NAME = new ComponentName( + PACKAGE_NAME, "ShortcutQueryTest"); + private static final int QUERY_FLAG = LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS; + + private ShortcutQueryWrapper mShortcutQuery; + + @Before + public void setUp() throws Exception { + mShortcutQuery = new ShortcutQueryWrapper(new LauncherApps.ShortcutQuery() + .setChangedSince(CHANGED_SINCE) + .setPackage(PACKAGE_NAME) + .setShortcutIds(SHORTCUT_IDS) + .setLocusIds(LOCUS_IDS) + .setActivity(COMPONENT_NAME) + .setQueryFlags(QUERY_FLAG)); + } + + @Test + public void testWriteAndReadFromParcel() { + Parcel p = Parcel.obtain(); + mShortcutQuery.writeToParcel(p, 0); + p.setDataPosition(0); + ShortcutQueryWrapper q = ShortcutQueryWrapper.CREATOR.createFromParcel(p); + assertEquals("Changed since doesn't match!", CHANGED_SINCE, q.getChangedSince()); + assertEquals("Package name doesn't match!", PACKAGE_NAME, q.getPackage()); + assertEquals("Shortcut ids doesn't match", SHORTCUT_IDS, q.getShortcutIds()); + assertEquals("Locus ids doesn't match", LOCUS_IDS, q.getLocusIds()); + assertEquals("Component name doesn't match", COMPONENT_NAME, q.getActivity()); + assertEquals("Query flag doesn't match", QUERY_FLAG, q.getQueryFlags()); + p.recycle(); + } +} diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index d649b945492b..61f58b0d27ce 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -645,14 +645,60 @@ public class DeviceConfigTest { Properties modifiedProperties2 = new Properties.Builder(namespaceToBan2).setString(KEY, VALUE) + .setString(KEY3, NULL_VALUE).setString(KEY4, VALUE2).build(); + DeviceConfig.setProperties(modifiedProperties2); + modifiedProperties2 = DeviceConfig.getProperties(namespaceToBan2); + assertThat(modifiedProperties2.getKeyset()).containsExactly(KEY, KEY3, KEY4); + assertThat(modifiedProperties2.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); + assertThat(modifiedProperties2.getString(KEY4, DEFAULT_VALUE)).isEqualTo(VALUE2); + // Since value is null DEFAULT_VALUE should be returned + assertThat(modifiedProperties2.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + } + + @Test + public void allConfigsUnbannedIfAnyUnbannedConfigUpdated() + throws DeviceConfig.BadConfigException { + // Given namespaces will be permanently banned, thus they need to be different every time + final String namespaceToBan1 = NAMESPACE + System.currentTimeMillis(); + final String namespaceToBan2 = NAMESPACE + System.currentTimeMillis() + 1; + + // Set namespaces properties + Properties properties1 = new Properties.Builder(namespaceToBan1).setString(KEY, VALUE) + .setString(KEY4, NULL_VALUE).build(); + DeviceConfig.setProperties(properties1); + Properties properties2 = new Properties.Builder(namespaceToBan2).setString(KEY2, VALUE2) + .setString(KEY4, NULL_VALUE).build(); + DeviceConfig.setProperties(properties2); + + // Ban namespace with related properties + DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan1); + DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan2); + + // Verify given namespace properties are banned + assertThrows(DeviceConfig.BadConfigException.class, + () -> DeviceConfig.setProperties(properties1)); + assertThrows(DeviceConfig.BadConfigException.class, + () -> DeviceConfig.setProperties(properties2)); + + // Modify properties and verify we can set them + Properties modifiedProperties1 = new Properties.Builder(namespaceToBan1).setString(KEY, + VALUE) .setString(KEY4, NULL_VALUE).setString(KEY2, VALUE2).build(); DeviceConfig.setProperties(modifiedProperties1); - modifiedProperties2 = DeviceConfig.getProperties(namespaceToBan1); - assertThat(modifiedProperties2.getKeyset()).containsExactly(KEY, KEY2, KEY4); - assertThat(modifiedProperties2.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); - assertThat(modifiedProperties2.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + modifiedProperties1 = DeviceConfig.getProperties(namespaceToBan1); + assertThat(modifiedProperties1.getKeyset()).containsExactly(KEY, KEY2, KEY4); + assertThat(modifiedProperties1.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); + assertThat(modifiedProperties1.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + // Since value is null DEFAULT_VALUE should be returned + assertThat(modifiedProperties1.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + + // verify that other banned namespaces are unbanned now. + DeviceConfig.setProperties(properties2); + Properties result = DeviceConfig.getProperties(namespaceToBan2); + assertThat(result.getKeyset()).containsExactly(KEY2, KEY4); + assertThat(result.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); // Since value is null DEFAULT_VALUE should be returned - assertThat(modifiedProperties2.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + assertThat(result.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); } // TODO(mpape): resolve b/142727848 and re-enable listener tests 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/MediaCas.java b/media/java/android/media/MediaCas.java index ad9486cc6597..405410a054de 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -19,6 +19,7 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.Context; import android.hardware.cas.V1_0.HidlCasPluginDescriptor; import android.hardware.cas.V1_0.ICas; @@ -1076,6 +1077,17 @@ public final class MediaCas implements AutoCloseable { } } + /** + * Release Cas session. This is primarily used as a test API for CTS. + * @hide + */ + @TestApi + public void forceResourceLost() { + if (mResourceListener != null) { + mResourceListener.onReclaimResources(); + } + } + @Override public void close() { if (mICas != null) { 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/media/packages/BluetoothMidiService/Android.bp b/media/packages/BluetoothMidiService/Android.bp index f45114a277aa..77e6a14e1f00 100644 --- a/media/packages/BluetoothMidiService/Android.bp +++ b/media/packages/BluetoothMidiService/Android.bp @@ -1,6 +1,35 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_library { + name: "BluetoothMidiLib", + srcs: [ + "src/**/*.java", + ], + platform_apis: true, + plugins: ["java_api_finder"], + manifest: "AndroidManifestBase.xml", +} + android_app { name: "BluetoothMidiService", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + ], platform_apis: true, certificate: "platform", + manifest: "AndroidManifest.xml", } diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml index 1cfd55d556a9..4042ce8b93df 100644 --- a/media/packages/BluetoothMidiService/AndroidManifest.xml +++ b/media/packages/BluetoothMidiService/AndroidManifest.xml @@ -1,12 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> <manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" package="com.android.bluetoothmidiservice" + android:versionCode="1" + android:versionName="R-initial" > + <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> <uses-feature android:name="android.software.midi" android:required="true"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <application + tools:replace="android:label" android:label="@string/app_name"> <service android:name=".BluetoothMidiService" android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"> diff --git a/media/packages/BluetoothMidiService/AndroidManifestBase.xml b/media/packages/BluetoothMidiService/AndroidManifestBase.xml new file mode 100644 index 000000000000..ebe62b039434 --- /dev/null +++ b/media/packages/BluetoothMidiService/AndroidManifestBase.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.bluetoothmidiservice" + android:versionCode="1" + android:versionName="R-initial" + > + <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> + <application + android:label="BluetoothMidi" + android:defaultToDeviceProtectedStorage="true" + android:directBootAware="true"> + </application> +</manifest> diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java index c51c8fa73c4e..8d18b77700b5 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java @@ -70,7 +70,9 @@ public class BluetoothPacketDecoder extends PacketDecoder { } byte header = buffer[0]; - if ((header & 0xC0) != 0x80) { + // Check for the header bit 7. + // Ignore the reserved bit 6. + if ((header & 0x80) != 0x80) { Log.e(TAG, "packet does not start with header"); return; } diff --git a/media/packages/BluetoothMidiService/tests/unit/Android.bp b/media/packages/BluetoothMidiService/tests/unit/Android.bp new file mode 100644 index 000000000000..4d4ae9e15532 --- /dev/null +++ b/media/packages/BluetoothMidiService/tests/unit/Android.bp @@ -0,0 +1,38 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test { + name: "BluetoothMidiTests", + srcs: ["src/**/*.java"], + certificate: "platform", + static_libs: [ + //"frameworks-base-testutils", + "android-support-test", + "androidx.test.core", + "androidx.test.ext.truth", + "androidx.test.runner", + "androidx.test.rules", + "platform-test-annotations", + "BluetoothMidiLib", + ], + test_suites: ["device-tests"], + libs: [ + "framework-res", + "android.test.runner", + "android.test.base", + "android.test.mock", + ], +} diff --git a/media/packages/BluetoothMidiService/tests/unit/AndroidManifest.xml b/media/packages/BluetoothMidiService/tests/unit/AndroidManifest.xml new file mode 100644 index 000000000000..4d27e1e7fe04 --- /dev/null +++ b/media/packages/BluetoothMidiService/tests/unit/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.bluetoothmidiservice.tests.unit"> + <uses-sdk + android:minSdkVersion="29" + android:targetSdkVersion="29" /> + + <application android:testOnly="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.bluetoothmidiservice.tests.unit" + android:label="Bluetooth MIDI Service tests"> + </instrumentation> +</manifest> diff --git a/media/packages/BluetoothMidiService/tests/unit/AndroidTest.xml b/media/packages/BluetoothMidiService/tests/unit/AndroidTest.xml new file mode 100644 index 000000000000..02e7f0d55349 --- /dev/null +++ b/media/packages/BluetoothMidiService/tests/unit/AndroidTest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<configuration description="Runs Bluetooth MIDI Service Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="BluetoothMidiTests.apk" /> + </target_preparer> + + <option name="test-tag" value="BLEMidiTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.bluetoothmidiservice.tests.unit" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false" /> + </test> +</configuration> diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/AccumulatingMidiReceiver.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/AccumulatingMidiReceiver.java new file mode 100644 index 000000000000..57859bc114d8 --- /dev/null +++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/AccumulatingMidiReceiver.java @@ -0,0 +1,47 @@ +/* + * 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.bluetoothmidiservice; + +import android.media.midi.MidiReceiver; +import android.util.Log; + +import com.android.internal.midi.MidiFramer; + +import java.util.ArrayList; + +class AccumulatingMidiReceiver extends MidiReceiver { + private static final String TAG = "AccumulatingMidiReceiver"; + ArrayList<byte[]> mBuffers = new ArrayList<byte[]>(); + ArrayList<Long> mTimestamps = new ArrayList<Long>(); + + public void onSend(byte[] buffer, int offset, int count, long timestamp) { + Log.d(TAG, "onSend() passed " + MidiFramer.formatMidiData(buffer, offset, count)); + byte[] actualRow = new byte[count]; + System.arraycopy(buffer, offset, actualRow, 0, count); + mBuffers.add(actualRow); + mTimestamps.add(timestamp); + } + + byte[][] getBuffers() { + return mBuffers.toArray(new byte[mBuffers.size()][]); + } + + Long[] getTimestamps() { + return mTimestamps.toArray(new Long[mTimestamps.size()]); + } +} + diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java new file mode 100644 index 000000000000..3285f59e55c9 --- /dev/null +++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java @@ -0,0 +1,257 @@ +/* + * 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.bluetoothmidiservice; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.util.Log; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.midi.MidiFramer; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * End to end testing of the Bluetooth Encoder and Decoder + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class BluetoothMidiCodecTest { + + private static final String TAG = "BluetoothMidiCodecTest"; + private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final long NANOS_PER_MSEC = 1000000L; + + static class EncoderDecoderChecker implements PacketEncoder.PacketReceiver { + BluetoothPacketEncoder mEncoder; + BluetoothPacketDecoder mDecoder; + AccumulatingMidiReceiver mReceiver; + MidiFramer mFramer; + AccumulatingMidiReceiver mBypassReceiver; + MidiFramer mBypassFramer; + int mMaxPacketsPerConnection; + int mConnectionIntervalMillis; + BlockingQueue<byte[]> mPacketQueue; + ScheduledExecutorService mScheduler; + + EncoderDecoderChecker() { + this(2, 15, 20); + } + + EncoderDecoderChecker( + int maxPacketsPerConnection, + int connectionIntervalMillis, + int maxBytesPerPacket) { + mMaxPacketsPerConnection = maxPacketsPerConnection; + mConnectionIntervalMillis = connectionIntervalMillis; + mEncoder = new BluetoothPacketEncoder(this, maxBytesPerPacket); + mDecoder = new BluetoothPacketDecoder(maxBytesPerPacket); + mReceiver = new AccumulatingMidiReceiver(); + mFramer = new MidiFramer(mReceiver); + mBypassReceiver = new AccumulatingMidiReceiver(); + mBypassFramer = new MidiFramer(mBypassReceiver); + mScheduler = Executors.newSingleThreadScheduledExecutor(); + mPacketQueue = new LinkedBlockingDeque<>(maxPacketsPerConnection); + } + + void processQueue() throws InterruptedException { + for (int i = 0; i < mMaxPacketsPerConnection; i++) { + byte[] packet = mPacketQueue.poll(0, TimeUnit.SECONDS); + if (packet == null) break; + Log.d(TAG, "decode " + MidiFramer.formatMidiData(packet, 0, packet.length)); + mDecoder.decodePacket(packet, mFramer); + } + Log.d(TAG, "call writeComplete()"); + mEncoder.writeComplete(); + } + + public void start() { + mScheduler.scheduleAtFixedRate( + () -> { + Log.d(TAG, "run scheduled task"); + try { + processQueue(); + } catch (Exception e) { + assertEquals(null, e); + } + }, + mConnectionIntervalMillis, // initial delay + mConnectionIntervalMillis, // period + TimeUnit.MILLISECONDS); + } + + public void stop() { + // TODO wait for queue to empty + mScheduler.shutdown(); + } + + // TODO Should this block? + // Store the packets and then write them from a periodic task. + @Override + public void writePacket(byte[] buffer, int count) { + Log.d(TAG, "writePacket() passed " + MidiFramer.formatMidiData(buffer, 0, count)); + byte[] packet = new byte[count]; + System.arraycopy(buffer, 0, packet, 0, count); + try { + mPacketQueue.put(packet); + } catch (Exception e) { + assertEquals(null, e); + } + Log.d(TAG, "writePacket() returns"); + } + + void test(final byte[][] midi) + throws IOException, InterruptedException { + test(midi, 2); + } + + // Send the MIDI messages through the encoder, + // then through the decoder, + // then gather the resulting MIDI and compare the results. + void test(final byte[][] midi, int intervalMillis) + throws IOException, InterruptedException { + start(); + long timestamp = 0; + // Send all of the MIDI messages and gather the response. + for (int i = 0; i < midi.length; i++) { + byte[] outMessage = midi[i]; + Log.d(TAG, "outMessage " + + MidiFramer.formatMidiData(outMessage, 0, outMessage.length)); + mEncoder.send(outMessage, 0, outMessage.length, timestamp); + timestamp += 2 * NANOS_PER_MSEC; + // Also send a copy through a MidiFramer to align the messages. + mBypassFramer.send(outMessage, 0, outMessage.length, timestamp); + } + Thread.sleep(200); + stop(); + + // Compare the gathered rows with the expected rows. + byte[][] expectedMessages = mBypassReceiver.getBuffers(); + byte[][] inMessages = mReceiver.getBuffers(); + Log.d(TAG, "expectedMessage length = " + expectedMessages.length + + ", inMessages length = " + inMessages.length); + assertEquals(expectedMessages.length, inMessages.length); + Long[] actualTimestamps = mReceiver.getTimestamps(); + long previousTime = 0; + for (int i = 0; i < expectedMessages.length; i++) { + byte[] expectedMessage = expectedMessages[i]; + Log.d(TAG, "expectedMessage = " + + MidiFramer.formatMidiData(expectedMessage, + 0, expectedMessage.length)); + byte[] actualMessage = inMessages[i]; + Log.d(TAG, "actualMessage = " + + MidiFramer.formatMidiData(actualMessage, 0, actualMessage.length)); + assertArrayEquals(expectedMessage, actualMessage); + // Are the timestamps monotonic? + long currentTime = actualTimestamps[i]; + Log.d(TAG, "previousTime = " + previousTime + + ", currentTime = " + currentTime); + assertTrue(currentTime >= previousTime); + previousTime = currentTime; + } + } + } + + @Test + public void testOneNoteOn() throws IOException, InterruptedException { + final byte[][] midi = { + {(byte) 0x90, 0x40, 0x64} + }; + EncoderDecoderChecker checker = new EncoderDecoderChecker(); + checker.test(midi); + } + + @Test + public void testTwoNoteOnSameTime() throws IOException, InterruptedException { + final byte[][] midi = { + {(byte) 0x90, 0x40, 0x64, (byte) 0x90, 0x47, 0x70} + }; + EncoderDecoderChecker checker = new EncoderDecoderChecker(); + checker.test(midi); + } + + @Test + public void testTwoNoteOnStaggered() throws IOException, InterruptedException { + final byte[][] midi = { + {(byte) 0x90, 0x40, 0x64}, + {(byte) 0x90, 0x47, 0x70} + }; + EncoderDecoderChecker checker = new EncoderDecoderChecker(); + checker.test(midi); + } + + public void checkNoteBurst(int maxPacketsPerConnection, + int period, + int maxBytesPerPacket) throws IOException, InterruptedException { + final int numNotes = 100; + final byte[][] midi = new byte[numNotes][]; + int channel = 2; + for (int i = 0; i < numNotes; i++) { + byte[] message = {(byte) (0x90 + channel), (byte) (i + 1), 0x64}; + midi[i] = message; + channel ^= 1; + } + EncoderDecoderChecker checker = new EncoderDecoderChecker( + maxPacketsPerConnection, 15, maxBytesPerPacket); + checker.test(midi, period); + } + + @Test + public void testNoteBurstM1P6() throws IOException, InterruptedException { + checkNoteBurst(1, 6, 20); + } + @Test + public void testNoteBurstM1P2() throws IOException, InterruptedException { + checkNoteBurst(1, 2, 20); + } + @Test + public void testNoteBurstM2P6() throws IOException, InterruptedException { + checkNoteBurst(2, 6, 20); + } + @Test + public void testNoteBurstM2P2() throws IOException, InterruptedException { + checkNoteBurst(2, 2, 20); + } + @Test + public void testNoteBurstM2P0() throws IOException, InterruptedException { + checkNoteBurst(2, 0, 20); + } + @Test + public void testNoteBurstM2P6B21() throws IOException, InterruptedException { + checkNoteBurst(2, 6, 21); + } + @Test + public void testNoteBurstM2P2B21() throws IOException, InterruptedException { + checkNoteBurst(2, 2, 21); + } + @Test + public void testNoteBurstM2P0B21() throws IOException, InterruptedException { + checkNoteBurst(2, 0, 21); + } +} diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiDecoderTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiDecoderTest.java new file mode 100644 index 000000000000..6ecc53957eaa --- /dev/null +++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiDecoderTest.java @@ -0,0 +1,247 @@ +/* + * 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.bluetoothmidiservice; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.util.Log; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.midi.MidiFramer; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class BluetoothMidiDecoderTest { + + private static final String TAG = "BluetoothMidiDecoderTest"; + private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final long NANOS_PER_MSEC = 1000000L; + + static class DecoderChecker { + AccumulatingMidiReceiver mReceiver; + BluetoothPacketDecoder mDecoder; + + DecoderChecker() { + mReceiver = new AccumulatingMidiReceiver(); + final int maxBytes = 20; + mDecoder = new BluetoothPacketDecoder(maxBytes); + } + + void compareWithExpected(final byte[][] expectedMessages) { + byte[][] actualRows = mReceiver.getBuffers(); + Long[] actualTimestamps = mReceiver.getTimestamps(); + long previousTime = 0; + // Compare the gathered with the expected. + assertEquals(expectedMessages.length, actualRows.length); + for (int i = 0; i < expectedMessages.length; i++) { + byte[] expectedRow = expectedMessages[i]; + Log.d(TAG, "expectedRow = " + + MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length)); + byte[] actualRow = actualRows[i]; + Log.d(TAG, "actualRow = " + + MidiFramer.formatMidiData(actualRow, 0, actualRow.length)); + assertArrayEquals(expectedRow, actualRow); + // Are the timestamps monotonic? + long currentTime = actualTimestamps[i]; + Log.d(TAG, "previousTime = " + previousTime + ", currentTime = " + currentTime); + assertTrue(currentTime >= previousTime); + previousTime = currentTime; + } + } + + void decodePacket(byte[] packet) throws IOException { + mDecoder.decodePacket(packet, mReceiver); + } + + void decodePackets(byte[][] multiplePackets) throws IOException { + try { + for (int i = 0; i < multiplePackets.length; i++) { + byte[] packet = multiplePackets[i]; + mDecoder.decodePacket(packet, mReceiver); + } + } catch (Exception e) { + assertEquals(null, e); + } + } + + void test(byte[] encoded, byte[][] decoded) throws IOException { + decodePacket(encoded); + compareWithExpected(decoded); + } + + void test(byte[][] encoded, byte[][] decoded) throws IOException { + decodePackets(encoded); + compareWithExpected(decoded); + } + } + + @Test + public void testOneNoteOn() throws IOException { + final byte[] encoded = { + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // high bit of timestamp + (byte) 0x90, 0x40, 0x64 + }; + final byte[][] decoded = { + {(byte) 0x90, 0x40, 0x64} + }; + new DecoderChecker().test(encoded, decoded); + } + + @Test + public void testReservedHeaderBit() throws IOException { + final byte[] encoded = { + // Decoder should ignore the reserved bit. + (byte) (0x80 | 0x40), // set RESERVED bit in header! + (byte) 0x80, // high bit of timestamp + (byte) 0x90, 0x40, 0x64 + }; + final byte[][] decoded = { + {(byte) 0x90, 0x40, 0x64} + }; + new DecoderChecker().test(encoded, decoded); + } + + @Test + public void testTwoNotesOnRunning() throws IOException { + final byte[] encoded = { + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // high bit of timestamp + (byte) 0x90, 0x40, 0x64, + (byte) 0x85, // timestamp + (byte) 0x42, 0x70 + }; + final byte[][] decoded = { + {(byte) 0x90, 0x40, 0x64}, + {(byte) 0x42, 0x70} + }; + new DecoderChecker().test(encoded, decoded); + } + + @Test + public void testTwoNoteOnsTwoChannels() throws IOException { + final byte[] encoded = { + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // high bit of timestamp + (byte) 0x93, 0x40, 0x60, + // two channels so no running status + (byte) 0x80, // high bit of timestamp + (byte) 0x95, 0x47, 0x64 + }; + final byte[][] decoded = { + {(byte) 0x93, 0x40, 0x60, (byte) 0x95, 0x47, 0x64} + }; + new DecoderChecker().test(encoded, decoded); + } + + @Test + public void testTwoNoteOnsOverTime() throws IOException { + final byte[][] encoded = {{ + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // high bit of timestamp + (byte) 0x98, 0x45, 0x60 + }, + { + (byte) 0x80, // high bit of header must be set + (byte) 0x82, // timestamp advanced by 2 msec + (byte) 0x90, 0x40, 0x64, + (byte) 0x84, // timestamp needed because of time delay + // encoder uses running status + 0x47, 0x72 + }}; + final byte[][] decoded = { + {(byte) 0x98, 0x45, 0x60}, + {(byte) 0x90, 0x40, 0x64}, + {(byte) 0x47, 0x72} + }; + new DecoderChecker().test(encoded, decoded); + } + + @Test + public void testSysExBasic() throws IOException { + final byte[][] encoded = {{ + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // timestamp + (byte) 0xF0, 0x7D, // Begin prototyping SysEx + 0x01, 0x02, 0x03, 0x04, 0x05, + (byte) 0x80, // timestamp + (byte) 0xF7 // End SysEx + }}; + final byte[][] decoded = { + {(byte) 0xF0, 0x7D, // experimental SysEx + 0x01, 0x02, 0x03, 0x04, 0x05, (byte) 0xF7} + }; + new DecoderChecker().test(encoded, decoded); + } + + @Test + public void testSysExTwoPackets() throws IOException { + final byte[][] encoded = {{ + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // timestamp + (byte) 0xF0, 0x7D, // Begin prototyping SysEx + 0x01, 0x02 + }, + { + (byte) 0x80, // high bit of header must be set + 0x03, 0x04, 0x05, + (byte) 0x80, // timestamp + (byte) 0xF7 // End SysEx + }}; + final byte[][] decoded = { + {(byte) 0xF0, 0x7D, 0x01, 0x02}, // experimental SysEx + {0x03, 0x04, 0x05, (byte) 0xF7} + }; + new DecoderChecker().test(encoded, decoded); + } + + @Test + public void testSysExThreePackets() throws IOException { + final byte[][] encoded = { + {(byte) 0x80, // high bit of header must be set + (byte) 0x80, // timestamp + (byte) 0xF0, 0x7D, // Begin prototyping SysEx + 0x01, 0x02 + }, + { + (byte) 0x80, // high bit of header must be set + 0x03, 0x04, 0x05, + }, + { + (byte) 0x80, // high bit of header must be set + 0x06, 0x07, 0x08, + (byte) 0x80, // timestamp + (byte) 0xF7 // End SysEx + }}; + final byte[][] decoded = { + {(byte) 0xF0, 0x7D, 0x01, 0x02}, // experimental SysEx + {0x03, 0x04, 0x05}, + {0x06, 0x07, 0x08, (byte) 0xF7} + }; + new DecoderChecker().test(encoded, decoded); + } + +} diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java new file mode 100644 index 000000000000..a169c0d7c7f9 --- /dev/null +++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java @@ -0,0 +1,244 @@ +/* + * 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.bluetoothmidiservice; + +import static org.junit.Assert.assertEquals; + +import android.util.Log; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.midi.MidiFramer; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class BluetoothMidiEncoderTest { + + private static final String TAG = "BluetoothMidiEncoderTest"; + private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final long NANOS_PER_MSEC = 1000000L; + + static class AccumulatingPacketReceiver implements PacketEncoder.PacketReceiver { + ArrayList<byte[]> mBuffers = new ArrayList<byte[]>(); + + public void writePacket(byte[] buffer, int count) { + byte[] actualRow = new byte[count]; + Log.d(TAG, "writePacket() passed " + MidiFramer.formatMidiData(buffer, 0, count)); + System.arraycopy(buffer, 0, actualRow, 0, count); + mBuffers.add(actualRow); + } + + byte[][] getBuffers() { + return mBuffers.toArray(new byte[mBuffers.size()][]); + } + } + + static class EncoderChecker { + AccumulatingPacketReceiver mReceiver; + BluetoothPacketEncoder mEncoder; + + EncoderChecker() { + mReceiver = new AccumulatingPacketReceiver(); + final int maxBytes = 20; + mEncoder = new BluetoothPacketEncoder(mReceiver, maxBytes); + } + + void send(byte[] data) throws IOException { + send(data, 0); + } + + void send(byte[] data, long timestamp) throws IOException { + Log.d(TAG, "send " + MidiFramer.formatMidiData(data, 0, data.length)); + mEncoder.send(data, 0, data.length, timestamp); + } + + void compareWithExpected(final byte[][] expected) { + byte[][] actualRows = mReceiver.getBuffers(); + assertEquals(expected.length, actualRows.length); + // Compare the gathered rows with the expected rows. + for (int i = 0; i < expected.length; i++) { + byte[] expectedRow = expected[i]; + Log.d(TAG, "expectedRow = " + + MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length)); + byte[] actualRow = actualRows[i]; + Log.d(TAG, "actualRow = " + + MidiFramer.formatMidiData(actualRow, 0, actualRow.length)); + assertEquals(expectedRow.length, actualRow.length); + for (int k = 0; k < expectedRow.length; k++) { + assertEquals(expectedRow[k], actualRow[k]); + } + } + } + + void writeComplete() { + mEncoder.writeComplete(); + } + + } + + @Test + public void testOneNoteOn() throws IOException { + final byte[][] encoded = {{ + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // high bit of timestamp + (byte) 0x90, 0x40, 0x64 + }}; + EncoderChecker checker = new EncoderChecker(); + checker.send(new byte[] {(byte) 0x90, 0x40, 0x64}); + checker.compareWithExpected(encoded); + } + + @Test + public void testTwoNoteOnsSameChannel() throws IOException { + final byte[][] encoded = {{ + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // high bit of timestamp + (byte) 0x90, 0x40, 0x64, + // encoder converts to running status + 0x47, 0x72 + }}; + EncoderChecker checker = new EncoderChecker(); + checker.send(new byte[] {(byte) 0x90, 0x40, 0x64, (byte) 0x90, 0x47, 0x72}); + checker.compareWithExpected(encoded); + } + + @Test + public void testTwoNoteOnsTwoChannels() throws IOException { + final byte[][] encoded = {{ + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // high bit of timestamp + (byte) 0x93, 0x40, 0x60, + // two channels so no running status + (byte) 0x80, // high bit of timestamp + (byte) 0x95, 0x47, 0x64 + }}; + EncoderChecker checker = new EncoderChecker(); + checker.send(new byte[] {(byte) 0x93, 0x40, 0x60, (byte) 0x95, 0x47, 0x64}); + checker.compareWithExpected(encoded); + } + + @Test + public void testTwoNoteOnsOverTime() throws IOException { + final byte[][] encoded = { + { + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // high bit of timestamp + (byte) 0x98, 0x45, 0x60 + }, + { + (byte) 0x80, // high bit of header must be set + (byte) 0x82, // timestamp advanced by 2 msec + (byte) 0x90, 0x40, 0x64, + (byte) 0x84, // timestamp needed because of time delay + // encoder converts to running status + 0x47, 0x72 + }}; + EncoderChecker checker = new EncoderChecker(); + long timestamp = 0; + // Send one note. This will cause an immediate packet write + // because we don't know when the next one will arrive. + checker.send(new byte[] {(byte) 0x98, 0x45, 0x60}, timestamp); + + // Send two notes. These should accumulate into the + // same packet because we do not yet have a writeComplete. + timestamp += 2 * NANOS_PER_MSEC; + checker.send(new byte[] {(byte) 0x90, 0x40, 0x64}, timestamp); + timestamp += 2 * NANOS_PER_MSEC; + checker.send(new byte[] {(byte) 0x90, 0x47, 0x72}, timestamp); + // Tell the encoder that the first packet has been written to the + // hardware. So it can flush the two pending notes. + checker.writeComplete(); + checker.compareWithExpected(encoded); + } + + @Test + public void testSysExBasic() throws IOException { + final byte[][] encoded = {{ + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // timestamp + (byte) 0xF0, 0x7D, // Begin prototyping SysEx + 0x01, 0x02, 0x03, 0x04, 0x05, + (byte) 0x80, // timestamp + (byte) 0xF7 // End SysEx + }}; + EncoderChecker checker = new EncoderChecker(); + checker.send(new byte[] {(byte) 0xF0, 0x7D, // experimental SysEx + 0x01, 0x02, 0x03, 0x04, 0x05, (byte) 0xF7}); + checker.compareWithExpected(encoded); + } + + @Test + public void testSysExTwoPackets() throws IOException { + final byte[][] encoded = {{ + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // timestamp + (byte) 0xF0, 0x7D, // Begin prototyping SysEx + 0x01, 0x02 + }, + { + (byte) 0x80, // high bit of header must be set + 0x03, 0x04, 0x05, + (byte) 0x80, // timestamp + (byte) 0xF7 // End SysEx + }}; + EncoderChecker checker = new EncoderChecker(); + // Send in two messages. + checker.send(new byte[] {(byte) 0xF0, 0x7D, // experimental SysEx + 0x01, 0x02}); + checker.send(new byte[] {0x03, 0x04, 0x05, (byte) 0xF7}); + // Tell the encoder that the first packet has been written to the + // hardware. So it can flush the remaining data. + checker.writeComplete(); + checker.compareWithExpected(encoded); + } + + @Test + public void testSysExThreePackets() throws IOException { + final byte[][] encoded = {{ + (byte) 0x80, // high bit of header must be set + (byte) 0x80, // timestamp + (byte) 0xF0, 0x7D, // Begin prototyping SysEx + 0x01, 0x02 + }, + { + (byte) 0x80, // high bit of header must be set + 0x03, 0x04, 0x05, + }, + { + (byte) 0x80, // high bit of header must be set + 0x06, 0x07, 0x08, + (byte) 0x80, // timestamp + (byte) 0xF7 // End SysEx + }}; + EncoderChecker checker = new EncoderChecker(); + // Send in three messages. + checker.send(new byte[] {(byte) 0xF0, 0x7D, // experimental SysEx + 0x01, 0x02}); + checker.send(new byte[] {0x03, 0x04, 0x05}); + checker.writeComplete(); + checker.send(new byte[] {0x06, 0x07, 0x08, (byte) 0xF7}); + checker.writeComplete(); + checker.compareWithExpected(encoded); + } +} diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/MidiFramerTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/MidiFramerTest.java new file mode 100644 index 000000000000..8cda6eb3a3ea --- /dev/null +++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/MidiFramerTest.java @@ -0,0 +1,109 @@ +/* + * 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.bluetoothmidiservice; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import android.util.Log; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.midi.MidiFramer; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class MidiFramerTest { + + private static final String TAG = "MidiFramerTest"; + private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + + // For testing MidiFramer + // TODO move MidiFramer tests to their own file + static class FramerChecker { + AccumulatingMidiReceiver mReceiver; + MidiFramer mFramer; + + FramerChecker() { + mReceiver = new AccumulatingMidiReceiver(); + mFramer = new MidiFramer(mReceiver); + } + + void compareWithExpected(final byte[][] expected) { + byte[][] actualRows = mReceiver.getBuffers(); + assertEquals(expected.length, actualRows.length); + // Compare the gathered rows with the expected rows. + for (int i = 0; i < expected.length; i++) { + byte[] expectedRow = expected[i]; + Log.d(TAG, "expectedRow = " + + MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length)); + byte[] actualRow = actualRows[i]; + Log.d(TAG, "actualRow = " + + MidiFramer.formatMidiData(actualRow, 0, actualRow.length)); + assertArrayEquals(expectedRow, actualRow); + } + } + + void send(byte[] data) throws IOException { + Log.d(TAG, "send " + MidiFramer.formatMidiData(data, 0, data.length)); + mFramer.send(data, 0, data.length, 0); + } + } + + @Test + public void testFramerTwoNoteOns() throws IOException { + final byte[][] expected = { + {(byte) 0x90, 0x40, 0x64}, + {(byte) 0x90, 0x47, 0x50} + }; + FramerChecker checker = new FramerChecker(); + checker.send(new byte[] {(byte) 0x90, 0x40, 0x64, (byte) 0x90, 0x47, 0x50}); + checker.compareWithExpected(expected); + } + + @Test + public void testFramerTwoNoteOnsRunning() throws IOException { + final byte[][] expected = { + {(byte) 0x90, 0x40, 0x64}, + {(byte) 0x90, 0x47, 0x70} + }; + FramerChecker checker = new FramerChecker(); + // Two notes with running status + checker.send(new byte[] {(byte) 0x90, 0x40, 0x64, 0x47, 0x70}); + checker.compareWithExpected(expected); + } + + @Test + public void testFramerPreGarbage() throws IOException { + final byte[][] expected = { + {(byte) 0x90, 0x40, 0x64}, + {(byte) 0x90, 0x47, 0x70} + }; + FramerChecker checker = new FramerChecker(); + // Garbage can come before the first status byte if you connect + // a MIDI cable in the middle of a message. + checker.send(new byte[] {0x01, 0x02, // garbage bytes + (byte) 0x90, 0x40, 0x64, 0x47, 0x70}); + checker.compareWithExpected(expected); + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 2fde87c08ad5..d3d04e5a31d0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2813,6 +2813,7 @@ public class SettingsProvider extends ContentProvider { if (settingsState.isNewConfigBannedLocked(prefix, keyValues)) { return false; } + settingsState.unbanAllConfigIfBannedConfigUpdatedLocked(prefix); List<String> changedSettings = settingsState.setSettingsLocked(prefix, keyValues, packageName); if (!changedSettings.isEmpty()) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index d93c0150410d..6b8219ea9c70 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -459,6 +459,16 @@ final class SettingsState { } @GuardedBy("mLock") + public void unbanAllConfigIfBannedConfigUpdatedLocked(String prefix) { + // If the prefix updated is a banned namespace, clear mNamespaceBannedHashes + // to unban all unbanned namespaces. + if (mNamespaceBannedHashes.get(prefix) != null) { + mNamespaceBannedHashes.clear(); + scheduleWriteIfNeededLocked(); + } + } + + @GuardedBy("mLock") public void banConfigurationLocked(String prefix, Map<String, String> keyValues) { if (prefix == null || keyValues.isEmpty()) { return; diff --git a/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml new file mode 100644 index 000000000000..8f8f1b664692 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml @@ -0,0 +1,162 @@ +<!-- +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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="412dp" + android:height="300dp" + android:viewportWidth="412" + android:viewportHeight="300"> + <group> + <clip-path + android:pathData="M206,150m-150,0a150,150 0,1 1,300 0a150,150 0,1 1,-300 0"/> + <path + android:pathData="M296,105.2h-9.6l-3.1,-2.5l-3.1,2.5H116c-1.7,0 -3,1.3 -3,3v111.7c0,1.7 1.3,3 3,3h180c1.7,0 3,-1.3 3,-3V108.2C299,106.6 297.7,105.2 296,105.2C296,105.2 296,105.2 296,105.2z" + android:fillColor="#3C4043"/> + <path + android:strokeWidth="1" + android:pathData="M252.4,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#D2E3FC" + android:strokeColor="#4285F4"/> + <path + android:pathData="M261.9,95.7m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0" + android:fillColor="#4285F4"/> + <path + android:strokeWidth="1" + android:pathData="M160.6,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#FAD2CF" + android:strokeColor="#EA4335"/> + <path + android:pathData="M170.1,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#EA4335"/> + <path + android:strokeWidth="1" + android:pathData="M192.1,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#FEEFC3" + android:strokeColor="#FBBC04"/> + <path + android:strokeWidth="1" + android:pathData="M221.8,85.4m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#CEEAD6" + android:strokeColor="#34A853"/> + <path + android:pathData="M201.6,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#FBBC04"/> + <path + android:pathData="M231.4,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#34A853"/> + <path + android:pathData="M282.8,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#3C4043"/> + <path + android:pathData="M278.7,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M282.8,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M286.9,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M129.2,72.9h-3.4l0.3,1c1,-0.3 2.1,-0.4 3.2,-0.4v-1V72.9zM122.6,74.8c-0.5,0.3 -1,0.6 -1.4,1l0,0l0,0l0,0l0,0h-0.6l0,0l0,0l0,0l0,0l0,0l0,0c-0.2,0.2 -0.3,0.3 -0.4,0.5l0.8,0.7c0.7,-0.8 1.5,-1.5 2.4,-2.1l-0.5,-0.8L122.6,74.8zM118,80L118,80L118,80L118,80L118,80L118,80L118,80L118,80c-0.5,1 -0.8,2 -1,3l1,0.2c0.2,-1 0.5,-2 1,-3L118,80zM117.8,86.7l-1,0.1c0.1,0.6 0.2,1.1 0.3,1.7l0,0l0,0h0.1l0,0l0,0l0,0l0,0c0.1,0.5 0.3,0.9 0.5,1.4l0.9,-0.4c-0.4,-1 -0.7,-2 -0.8,-3.1L117.8,86.7zM120.2,92.5l-0.8,0.6l0.2,0.3l0,0l0,0l0,0l0,0h0.3l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0.4,0.4l0,0l0,0l0,0l0,0h0.1l0,0l0,0l0,0l0,0l0,0h0.5l0,0l0,0l0,0l0,0l0,0l0,0l0.6,0.4l0.6,-0.8c-0.9,-0.6 -1.7,-1.4 -2.3,-2.2L120.2,92.5zM125.4,96.2l-0.3,0.9c1.1,0.4 2.2,0.6 3.4,0.7l0.1,-1C127.5,96.7 126.4,96.5 125.4,96.2zM134.7,95.4c-0.9,0.5 -2,0.9 -3,1.1l0.2,1h0.4c1,-0.3 2,-0.6 2.9,-1.2l-0.5,-0.9L134.7,95.4zM139.2,90.9c-0.5,0.9 -1.2,1.8 -1.9,2.5l0.7,0.7v-0.1h0.2l0,0l0,0c0.7,-0.7 1.3,-1.6 1.8,-2.4l-0.9,-0.5L139.2,90.9zM141.6,84.7h-1c0,0.2 0,0.4 0,0.6c0,0.9 -0.1,1.7 -0.3,2.6l1,0.2c0.1,-0.4 0.2,-0.8 0.2,-1.2l0,0v-0.1l0,0v-0.1l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0c0,-0.2 0,-0.3 0,-0.5l0,0L141.6,84.7zM139.3,78.2l-0.8,0.6c0.6,0.9 1.1,1.8 1.5,2.8l0.9,-0.3c-0.1,-0.2 -0.2,-0.4 -0.2,-0.7l0,0l0,0h-0.1l0,0l0,0l0,0l0,0l0,0l0,0c-0.3,-0.7 -0.7,-1.4 -1.1,-2l0,0l0,0l0,0l0,0l0,0l0,0l0,0L139.3,78.2zM134,73.9l-0.4,0.9c1,0.4 1.9,1 2.7,1.6l0.6,-0.8l0,0l0,0l0,0l0,0l0,0c-0.3,-0.3 -0.7,-0.5 -1,-0.7l0,0h-0.1h-0.6c-0.4,-0.2 -0.8,-0.4 -1.2,-0.6L134,73.9zM129.2,72.9v1c0.4,0 0.9,0 1.3,0.1l0.1,-1l-0.9,-0.1L129.2,72.9L129.2,72.9z" + android:fillColor="#34A853"/> + <path + android:pathData="M206,252m-11.7,0a11.7,11.7 0,1 1,23.4 0a11.7,11.7 0,1 1,-23.4 0" + android:fillColor="#F1F3F4"/> + <path + android:pathData="M201.7,247.7L210.3,256.3" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#202124" + android:strokeLineCap="round"/> + <path + android:pathData="M210.3,247.7L201.7,256.3" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#202124" + android:strokeLineCap="round"/> + <path + android:strokeWidth="1" + android:pathData="M205.3,221.9m-10.4,0a10.4,10.4 0,1 1,20.8 0a10.4,10.4 0,1 1,-20.8 0" + android:fillColor="#CEEAD6" + android:strokeColor="#34A853"/> + <path + android:pathData="M481.4,292.2c48,58.3 119.8,125.8 58.6,162.9c-38.7,23.5 -53.9,24 -98.3,33.2c-43.8,9.1 -93.6,-89.8 -101.1,-134.5C329.6,288.6 452.6,257.2 481.4,292.2z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M458.2,320.7l-21.1,-71.4L400.5,193c-2.7,-5.1 -1.2,-11.4 3.5,-14.7l0,0c2.8,-2 6.6,-1.5 8.8,1.1c0,0 40.6,38.4 53.2,61.1l81.5,134.8l-69.9,-39.1L458.2,320.7z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M403.8,184.8l5.4,6.9c1.2,1.5 3.3,1.9 4.9,0.9l3,-1.8" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M420.9,325.8l-37.8,-88.6l-58.4,-37.8c-5.7,-5.4 -7.4,-13.8 -4.2,-21l0,0c2,-4.6 7.4,-6.7 12,-4.6c0.2,0.1 0.4,0.2 0.7,0.3c0,0 70.7,36.3 81.5,48.3l59.8,95.5l-49.9,24.9L420.9,325.8z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M324.6,183.9l8,6.2c2.1,1.7 5.2,1.4 7,-0.6l2.9,-3.3" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M392.4,231c3.8,-5.1 9.1,-8.9 15.1,-10.9" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M401.3,283.8L405.8,292.6" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M378.2,346.9l-34.7,-75.6l-60,-61.2c-6.3,-4.7 -9,-12.8 -6.7,-20.4l0,0c1.5,-4.8 6.5,-7.5 11.3,-6c0.2,0.1 0.4,0.1 0.7,0.2c0,0 73.5,48.2 82.6,61.7l64.1,95.7l-40.3,23.5L378.2,346.9z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M280.8,196.6l7.6,4.6c2.6,1.6 5.9,1.1 7.9,-1.1l4.1,-4.5" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M347.5,251c3.8,-5.1 9.1,-8.9 15.1,-10.9" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M207.2,234.1c-8.8,-11 4.7,-31.5 19.8,-19c17.7,14.7 74.7,64.3 74.7,64.3l103.8,101.8c0,0 -36.4,53.8 -44.5,42.3C287.8,319.3 234.4,267.9 207.2,234.1z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M209.6,226.2l9.3,9.5c1,0.8 3,0.4 3.1,-1c0.2,-2.2 4.6,-6.2 7,-6.6c1.1,-0.3 1.7,-1.4 1.4,-2.4c-0.1,-0.2 -0.2,-0.4 -0.3,-0.6l-4.4,-3.9" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M284.1,296.2c3.1,-5.5 7.8,-10 13.5,-12.8" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + </group> + <path + android:pathData="M206,4c80.6,0 146,65.4 146,146c0,38.7 -15.4,75.9 -42.8,103.2c-57,57 -149.5,57 -206.5,0s-57,-149.5 0,-206.5C130.1,19.3 167.3,3.9 206,4M206,0C123.2,0 56,67.2 56,150s67.2,150 150,150s150,-67.2 150,-150S288.8,0 206,0z" + android:fillColor="#D2E3FC"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml new file mode 100644 index 000000000000..5e02f67700e7 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml @@ -0,0 +1,162 @@ +<!-- +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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="412dp" + android:height="300dp" + android:viewportWidth="412" + android:viewportHeight="300"> + <group> + <clip-path + android:pathData="M206,150m-150,0a150,150 0,1 1,300 0a150,150 0,1 1,-300 0"/> + <path + android:pathData="M296,105.2h-9.6l-3.1,-2.5l-3.1,2.5H116c-1.7,0 -3,1.3 -3,3v111.7c0,1.7 1.3,3 3,3h180c1.7,0 3,-1.3 3,-3V108.2C299,106.6 297.7,105.2 296,105.2L296,105.2z" + android:fillColor="#F1F3F4"/> + <path + android:strokeWidth="1" + android:pathData="M252.4,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#D2E3FC" + android:strokeColor="#4285F4"/> + <path + android:pathData="M261.9,95.7m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0" + android:fillColor="#4285F4"/> + <path + android:strokeWidth="1" + android:pathData="M160.6,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#FAD2CF" + android:strokeColor="#EA4335"/> + <path + android:pathData="M170.1,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#EA4335"/> + <path + android:strokeWidth="1" + android:pathData="M192.1,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#FEEFC3" + android:strokeColor="#FBBC04"/> + <path + android:strokeWidth="1" + android:pathData="M221.8,85.4m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#CEEAD6" + android:strokeColor="#34A853"/> + <path + android:pathData="M201.6,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#FBBC04"/> + <path + android:pathData="M231.4,95.7m-4.6,0a4.6,4.6 0,1 1,9.2 0a4.6,4.6 0,1 1,-9.2 0" + android:fillColor="#34A853"/> + <path + android:pathData="M282.8,85.3m-12.4,0a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0" + android:fillColor="#F1F3F4"/> + <path + android:pathData="M278.7,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#3474E0"/> + <path + android:pathData="M282.8,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#3474E0"/> + <path + android:pathData="M286.9,85.7m-1.2,0a1.2,1.2 0,1 1,2.4 0a1.2,1.2 0,1 1,-2.4 0" + android:fillColor="#3474E0"/> + <path + android:pathData="M129.2,72.9h-3.4l0.3,1c1,-0.3 2.1,-0.4 3.2,-0.4v-1v0.4H129.2zM122.6,74.8c-0.5,0.3 -1,0.6 -1.4,1l0,0l0,0l0,0l0,0h-0.6l0,0l0,0l0,0l0,0l0,0l0,0c-0.2,0.2 -0.3,0.3 -0.4,0.5L121,77c0.7,-0.8 1.5,-1.5 2.4,-2.1l-0.5,-0.8L122.6,74.8zM118,80L118,80L118,80L118,80L118,80L118,80L118,80L118,80c-0.5,1 -0.8,2 -1,3l1,0.2c0.2,-1 0.5,-2 1,-3L118,80zM117.8,86.7l-1,0.1c0.1,0.6 0.2,1.1 0.3,1.7l0,0l0,0h0.1l0,0l0,0l0,0l0,0c0.1,0.5 0.3,0.9 0.5,1.4l0.9,-0.4c-0.4,-1 -0.7,-2 -0.8,-3.1V86.7zM120.2,92.5l-0.8,0.6l0.2,0.3l0,0l0,0l0,0l0,0h0.3l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0.4,0.4l0,0l0,0l0,0l0,0h0.1l0,0l0,0l0,0l0,0l0,0h0.5l0,0l0,0l0,0l0,0l0,0l0,0l0.6,0.4l0.6,-0.8c-0.9,-0.6 -1.7,-1.4 -2.3,-2.2L120.2,92.5zM125.4,96.2l-0.3,0.9c1.1,0.4 2.2,0.6 3.4,0.7l0.1,-1C127.5,96.7 126.4,96.5 125.4,96.2zM134.7,95.4c-0.9,0.5 -2,0.9 -3,1.1l0.2,1h0.4c1,-0.3 2,-0.6 2.9,-1.2L134.7,95.4L134.7,95.4zM139.2,90.9c-0.5,0.9 -1.2,1.8 -1.9,2.5l0.7,0.7V94h0.2l0,0l0,0c0.7,-0.7 1.3,-1.6 1.8,-2.4l-0.9,-0.5L139.2,90.9zM141.6,84.7h-1c0,0.2 0,0.4 0,0.6c0,0.9 -0.1,1.7 -0.3,2.6l1,0.2c0.1,-0.4 0.2,-0.8 0.2,-1.2l0,0v-0.1l0,0v-0.1l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0c0,-0.2 0,-0.3 0,-0.5l0,0L141.6,84.7zM139.3,78.2l-0.8,0.6c0.6,0.9 1.1,1.8 1.5,2.8l0.9,-0.3c-0.1,-0.2 -0.2,-0.4 -0.2,-0.7l0,0l0,0h-0.1l0,0l0,0l0,0l0,0l0,0l0,0c-0.3,-0.7 -0.7,-1.4 -1.1,-2l0,0l0,0l0,0l0,0l0,0l0,0l0,0L139.3,78.2zM134,73.9l-0.4,0.9c1,0.4 1.9,1 2.7,1.6l0.6,-0.8l0,0l0,0l0,0l0,0l0,0c-0.3,-0.3 -0.7,-0.5 -1,-0.7l0,0h-0.1h-0.6c-0.4,-0.2 -0.8,-0.4 -1.2,-0.6V73.9zM129.2,72.9v1c0.4,0 0.9,0 1.3,0.1l0.1,-1l-0.9,-0.1H129.2L129.2,72.9z" + android:fillColor="#34A853"/> + <path + android:pathData="M206,252m-11.7,0a11.7,11.7 0,1 1,23.4 0a11.7,11.7 0,1 1,-23.4 0" + android:fillColor="#9AA0A6"/> + <path + android:pathData="M201.7,247.7L210.3,256.3" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#F1F3F4" + android:strokeLineCap="round"/> + <path + android:pathData="M210.3,247.7L201.7,256.3" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="#F1F3F4" + android:strokeLineCap="round"/> + <path + android:strokeWidth="1" + android:pathData="M205.3,221.9m-10.4,0a10.4,10.4 0,1 1,20.8 0a10.4,10.4 0,1 1,-20.8 0" + android:fillColor="#CEEAD6" + android:strokeColor="#34A853"/> + <path + android:pathData="M481.4,292.2c48,58.3 119.8,125.8 58.6,162.9c-38.7,23.5 -53.9,24 -98.3,33.2c-43.8,9.1 -93.6,-89.8 -101.1,-134.5C329.6,288.6 452.6,257.2 481.4,292.2z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M458.2,320.7l-21.1,-71.4L400.5,193c-2.7,-5.1 -1.2,-11.4 3.5,-14.7l0,0c2.8,-2 6.6,-1.5 8.8,1.1c0,0 40.6,38.4 53.2,61.1l81.5,134.8l-69.9,-39.1L458.2,320.7z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M403.8,184.8l5.4,6.9c1.2,1.5 3.3,1.9 4.9,0.9l3,-1.8" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M420.9,325.8l-37.8,-88.6l-58.4,-37.8c-5.7,-5.4 -7.4,-13.8 -4.2,-21l0,0c2,-4.6 7.4,-6.7 12,-4.6c0.2,0.1 0.4,0.2 0.7,0.3c0,0 70.7,36.3 81.5,48.3l59.8,95.5l-49.9,24.9L420.9,325.8z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M324.6,183.9l8,6.2c2.1,1.7 5.2,1.4 7,-0.6l2.9,-3.3" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M392.4,231c3.8,-5.1 9.1,-8.9 15.1,-10.9" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M401.3,283.8L405.8,292.6" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M378.2,346.9l-34.7,-75.6l-60,-61.2c-6.3,-4.7 -9,-12.8 -6.7,-20.4l0,0c1.5,-4.8 6.5,-7.5 11.3,-6c0.2,0.1 0.4,0.1 0.7,0.2c0,0 73.5,48.2 82.6,61.7l64.1,95.7l-40.3,23.5L378.2,346.9z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M280.8,196.6l7.6,4.6c2.6,1.6 5.9,1.1 7.9,-1.1l4.1,-4.5" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M347.5,251c3.8,-5.1 9.1,-8.9 15.1,-10.9" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + <path + android:pathData="M207.2,234.1c-8.8,-11 4.7,-31.5 19.8,-19c17.7,14.7 74.7,64.3 74.7,64.3l103.8,101.8c0,0 -36.4,53.8 -44.5,42.3C287.8,319.3 234.4,267.9 207.2,234.1z" + android:fillColor="#D2E3FC"/> + <path + android:pathData="M209.6,226.2l9.3,9.5c1,0.8 3,0.4 3.1,-1c0.2,-2.2 4.6,-6.2 7,-6.6c1.1,-0.3 1.7,-1.4 1.4,-2.4c-0.1,-0.2 -0.2,-0.4 -0.3,-0.6l-4.4,-3.9" + android:strokeLineJoin="bevel" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9" + android:strokeLineCap="square"/> + <path + android:pathData="M284.1,296.2c3.1,-5.5 7.8,-10 13.5,-12.8" + android:strokeWidth="1.75" + android:fillColor="#00000000" + android:strokeColor="#A0C2F9"/> + </group> + <path + android:pathData="M206,4c80.6,0 146,65.4 146,146c0,38.7 -15.4,75.9 -42.8,103.2c-57,57 -149.5,57 -206.5,0s-57,-149.5 0,-206.5C130.1,19.3 167.3,3.9 206,4M206,0C123.2,0 56,67.2 56,150s67.2,150 150,150s150,-67.2 150,-150S288.8,0 206,0z" + android:fillColor="#D2E3FC"/> +</vector> diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml index 65b04fd8fd99..306061911f8d 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"> @@ -39,6 +41,13 @@ android:orientation="vertical" android:gravity="center"> + <ImageView + android:layout_width="@dimen/bubble_empty_overflow_image_height" + android:layout_height="@dimen/bubble_empty_overflow_image_height" + android:id="@+id/bubble_overflow_empty_state_image" + android:scaleType="fitCenter" + android:layout_gravity="center"/> + <TextView android:id="@+id/bubble_overflow_empty_title" android:text="@string/bubble_overflow_empty_title" @@ -57,6 +66,7 @@ android:text="@string/bubble_overflow_empty_subtitle" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingBottom="@dimen/bubble_empty_overflow_subtitle_padding" android:gravity="center"/> </LinearLayout> </LinearLayout> 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/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml index 7dcc46c3ca09..ec004296ff9d 100644 --- a/packages/SystemUI/res/layout/people_strip.xml +++ b/packages/SystemUI/res/layout/people_strip.xml @@ -43,6 +43,7 @@ android:forceHasOverlappingRendering="false"> <TextView + android:id="@+id/header_label" style="@style/TextAppearance.NotificationSectionHeaderButton" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml index fe8557bfcce4..fc3bf941b27a 100644 --- a/packages/SystemUI/res/layout/qs_media_panel.xml +++ b/packages/SystemUI/res/layout/qs_media_panel.xml @@ -136,6 +136,47 @@ </LinearLayout> </LinearLayout> + <!-- Seek Bar --> + <SeekBar + android:id="@+id/media_progress_bar" + android:clickable="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:maxHeight="3dp" + android:paddingTop="24dp" + android:paddingBottom="24dp" + android:layout_marginBottom="-24dp" + android:layout_marginTop="-24dp" + android:splitTrack="false" + /> + + <FrameLayout + android:id="@+id/notification_media_progress_time" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + > + <!-- width is set to "match_parent" to avoid extra layout calls --> + <TextView + android:id="@+id/media_elapsed_time" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:fontFamily="@*android:string/config_bodyFontFamily" + android:textSize="14sp" + android:gravity="left" + /> + <TextView + android:id="@+id/media_total_time" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="@*android:string/config_bodyFontFamily" + android:layout_alignParentRight="true" + android:textSize="14sp" + android:gravity="right" + /> + </FrameLayout> + <!-- Controls --> <LinearLayout android:id="@+id/media_actions" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a9e5fa9cf4ae..5b213edd5f0f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1148,11 +1148,17 @@ <!-- 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 bubble overflow empty state illustration --> + <dimen name="bubble_empty_overflow_image_height">200dp</dimen> + <!-- Padding of bubble overflow empty state subtitle --> + <dimen name="bubble_empty_overflow_subtitle_padding">50dp</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/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 80fd826f28c6..35ad422c56b8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -26,7 +26,7 @@ import com.android.systemui.shared.recents.IPinnedStackAnimationListener; /** * Temporary callbacks into SystemUI. - * Next id = 25 + * Next id = 26 */ interface ISystemUiProxy { @@ -140,4 +140,10 @@ interface ISystemUiProxy { * Sets listener to get pinned stack animation callbacks. */ void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) = 24; + + /** + * Notifies that quickstep will switch to a new task + * @param rotation indicates which Surface.Rotation the gesture was started in + */ + void onQuickSwitchToNewTask(int rotation) = 25; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java index 29100ef8f70f..8bd7c790682d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java @@ -18,6 +18,7 @@ package com.android.systemui.shared.system; import android.content.Context; import android.graphics.PixelFormat; +import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.IBinder; import android.util.Size; @@ -59,6 +60,7 @@ public class SurfaceViewRequestReceiver { if (mSurfaceControlViewHost != null) { mSurfaceControlViewHost.die(); } + SurfaceControl surfaceControl = SurfaceViewRequestUtils.getSurfaceControl(bundle); if (surfaceControl != null) { if (viewSize == null) { @@ -70,8 +72,10 @@ public class SurfaceViewRequestReceiver { WindowlessWindowManager windowlessWindowManager = new WindowlessWindowManager(context.getResources().getConfiguration(), surfaceControl, hostToken); + DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); mSurfaceControlViewHost = new SurfaceControlViewHost(context, - context.getDisplayNoVerify(), windowlessWindowManager); + dm.getDisplay(SurfaceViewRequestUtils.getDisplayId(bundle)), + windowlessWindowManager); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( viewSize.getWidth(), diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java index 4409276f8c27..6742a4dc06b7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestUtils.java @@ -26,30 +26,38 @@ import android.view.SurfaceView; public class SurfaceViewRequestUtils { private static final String KEY_HOST_TOKEN = "host_token"; private static final String KEY_SURFACE_CONTROL = "surface_control"; + private static final String KEY_DISPLAY_ID = "display_id"; /** Creates a SurfaceView based bundle that stores the input host token and surface control. */ public static Bundle createSurfaceBundle(SurfaceView surfaceView) { Bundle bundle = new Bundle(); bundle.putBinder(KEY_HOST_TOKEN, surfaceView.getHostToken()); bundle.putParcelable(KEY_SURFACE_CONTROL, surfaceView.getSurfaceControl()); + bundle.putInt(KEY_DISPLAY_ID, surfaceView.getDisplay().getDisplayId()); return bundle; } /** * Retrieves the SurfaceControl from a bundle created by * {@link #createSurfaceBundle(SurfaceView)}. - **/ + */ public static SurfaceControl getSurfaceControl(Bundle bundle) { return bundle.getParcelable(KEY_SURFACE_CONTROL); } /** - * Retrieves the input token from a bundle created by - * {@link #createSurfaceBundle(SurfaceView)}. - **/ + * Retrieves the input token from a bundle created by {@link #createSurfaceBundle(SurfaceView)}. + */ public static @Nullable IBinder getHostToken(Bundle bundle) { return bundle.getBinder(KEY_HOST_TOKEN); } + /** + * Retrieves the display id from a bundle created by {@link #createSurfaceBundle(SurfaceView)}. + */ + public static int getDisplayId(Bundle bundle) { + return bundle.getInt(KEY_DISPLAY_ID); + } + private SurfaceViewRequestUtils() {} } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index 2231d11b7bc2..37841f24a3cf 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -21,18 +21,17 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.app.Activity; -import android.app.Notification; -import android.app.Person; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Bundle; -import android.os.Parcelable; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -55,6 +54,7 @@ public class BubbleOverflowActivity extends Activity { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES; private LinearLayout mEmptyState; + private ImageView mEmptyStateImage; private BubbleController mBubbleController; private BubbleOverflowAdapter mAdapter; private RecyclerView mRecyclerView; @@ -73,6 +73,7 @@ public class BubbleOverflowActivity extends Activity { mEmptyState = findViewById(R.id.bubble_overflow_empty_state); mRecyclerView = findViewById(R.id.bubble_overflow_recycler); + mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image); Resources res = getResources(); final int columns = res.getInteger(R.integer.bubbles_overflow_columns); @@ -81,11 +82,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); @@ -94,6 +99,31 @@ public class BubbleOverflowActivity extends Activity { mBubbleController.setOverflowCallback(() -> { onDataChanged(mBubbleController.getOverflowBubbles()); }); + onThemeChanged(); + } + + /** + * Handle theme changes. + */ + void onThemeChanged() { + final int mode = + getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + switch (mode) { + case Configuration.UI_MODE_NIGHT_NO: + if (DEBUG_OVERFLOW) { + Log.d(TAG, "Set overflow UI to light mode"); + } + mEmptyStateImage.setImageDrawable( + getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_light)); + break; + case Configuration.UI_MODE_NIGHT_YES: + if (DEBUG_OVERFLOW) { + Log.d(TAG, "Set overflow UI to dark mode"); + } + mEmptyStateImage.setImageDrawable( + getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_dark)); + break; + } } void setBackgroundColor() { @@ -134,6 +164,7 @@ public class BubbleOverflowActivity extends Activity { @Override public void onResume() { super.onResume(); + onThemeChanged(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 6a7b0da0d8d8..eff693436451 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -713,7 +713,7 @@ public class BubbleStackView extends FrameLayout { } else { mBubbleContainer.removeView(mBubbleOverflow.getBtn()); mBubbleOverflow.updateIcon(mContext, this); - overflowBtnIndex = mBubbleContainer.getChildCount() - 1; + overflowBtnIndex = mBubbleContainer.getChildCount(); } mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 6d99ef1829b2..fdd859373685 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -78,10 +78,13 @@ import android.widget.ImageView.ScaleType; import android.widget.TextView; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.drawable.ScrimDrawable; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.EmergencyAffordanceManager; @@ -171,6 +174,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final IActivityManager mIActivityManager; private final TelecomManager mTelecomManager; private final MetricsLogger mMetricsLogger; + private final UiEventLogger mUiEventLogger; private final NotificationShadeDepthController mDepthController; private final BlurUtils mBlurUtils; @@ -203,6 +207,23 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final ControlsListingController mControlsListingController; private boolean mAnyControlsProviders = false; + @VisibleForTesting + public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The global actions / power menu surface became visible on the screen.") + GA_POWER_MENU_OPEN(337); + + private final int mId; + + GlobalActionsEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + /** * @param context everything needs a context :( */ @@ -223,7 +244,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, ControlsUiController controlsUiController, IWindowManager iWindowManager, @Background Executor backgroundExecutor, ControlsListingController controlsListingController, - ControlsController controlsController) { + ControlsController controlsController, UiEventLogger uiEventLogger) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -240,6 +261,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mIActivityManager = iActivityManager; mTelecomManager = telecomManager; mMetricsLogger = metricsLogger; + mUiEventLogger = uiEventLogger; mDepthController = depthController; mSysuiColorExtractor = colorExtractor; mStatusBarService = statusBarService; @@ -997,6 +1019,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, */ public void onShow(DialogInterface dialog) { mMetricsLogger.visible(MetricsEvent.POWER_MENU); + mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN); } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt new file mode 100644 index 000000000000..aa5ebaa22f2d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -0,0 +1,86 @@ +/* + * 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.media + +import android.content.res.ColorStateList +import android.text.format.DateUtils +import android.view.View +import android.widget.SeekBar +import android.widget.TextView +import androidx.annotation.UiThread +import androidx.lifecycle.Observer + +import com.android.systemui.R + +/** + * Observer for changes from SeekBarViewModel. + * + * <p>Updates the seek bar views in response to changes to the model. + */ +class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> { + + private val seekBarView: SeekBar + private val elapsedTimeView: TextView + private val totalTimeView: TextView + + init { + seekBarView = view.findViewById(R.id.media_progress_bar) + elapsedTimeView = view.findViewById(R.id.media_elapsed_time) + totalTimeView = view.findViewById(R.id.media_total_time) + } + + /** Updates seek bar views when the data model changes. */ + @UiThread + override fun onChanged(data: SeekBarViewModel.Progress) { + if (data.enabled && seekBarView.visibility == View.GONE) { + seekBarView.visibility = View.VISIBLE + elapsedTimeView.visibility = View.VISIBLE + totalTimeView.visibility = View.VISIBLE + } else if (!data.enabled && seekBarView.visibility == View.VISIBLE) { + seekBarView.visibility = View.GONE + elapsedTimeView.visibility = View.GONE + totalTimeView.visibility = View.GONE + return + } + + // TODO: update the style of the disabled progress bar + seekBarView.setEnabled(data.seekAvailable) + + data.color?.let { + var tintList = ColorStateList.valueOf(it) + seekBarView.setThumbTintList(tintList) + tintList = tintList.withAlpha(192) // 75% + seekBarView.setProgressTintList(tintList) + tintList = tintList.withAlpha(128) // 50% + seekBarView.setProgressBackgroundTintList(tintList) + elapsedTimeView.setTextColor(it) + totalTimeView.setTextColor(it) + } + + data.elapsedTime?.let { + seekBarView.setProgress(it) + elapsedTimeView.setText(DateUtils.formatElapsedTime( + it / DateUtils.SECOND_IN_MILLIS)) + } + + data.duration?.let { + seekBarView.setMax(it) + totalTimeView.setText(DateUtils.formatElapsedTime( + it / DateUtils.SECOND_IN_MILLIS)) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt new file mode 100644 index 000000000000..cf8f26841cf9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -0,0 +1,152 @@ +/* + * 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.media + +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.PlaybackState +import android.view.MotionEvent +import android.view.View +import android.widget.SeekBar +import androidx.annotation.AnyThread +import androidx.annotation.WorkerThread +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.LiveData + +import com.android.systemui.util.concurrency.DelayableExecutor + +private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L + +/** ViewModel for seek bar in QS media player. */ +class SeekBarViewModel(val bgExecutor: DelayableExecutor) { + + private val _progress = MutableLiveData<Progress>().apply { + postValue(Progress(false, false, null, null, null)) + } + val progress: LiveData<Progress> + get() = _progress + private var controller: MediaController? = null + private var playbackState: PlaybackState? = null + + /** Listening state (QS open or closed) is used to control polling of progress. */ + var listening = true + set(value) { + if (value) { + checkPlaybackPosition() + } + } + + /** + * Handle request to change the current position in the media track. + * @param position Place to seek to in the track. + */ + @WorkerThread + fun onSeek(position: Long) { + controller?.transportControls?.seekTo(position) + } + + /** + * Updates media information. + * @param mediaController controller for media session + * @param color foreground color for UI elements + */ + @WorkerThread + fun updateController(mediaController: MediaController?, color: Int) { + controller = mediaController + playbackState = controller?.playbackState + val mediaMetadata = controller?.metadata + val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L + val position = playbackState?.position?.toInt() + val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() + val enabled = if (duration != null && duration <= 0) false else true + _progress.postValue(Progress(enabled, seekAvailable, position, duration, color)) + if (shouldPollPlaybackPosition()) { + checkPlaybackPosition() + } + } + + @AnyThread + private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ + val currentPosition = controller?.playbackState?.position?.toInt() + if (currentPosition != null && _progress.value!!.elapsedTime != currentPosition) { + _progress.postValue(_progress.value!!.copy(elapsedTime = currentPosition)) + } + if (shouldPollPlaybackPosition()) { + checkPlaybackPosition() + } + }, POSITION_UPDATE_INTERVAL_MILLIS) + + @WorkerThread + private fun shouldPollPlaybackPosition(): Boolean { + val state = playbackState?.state + val moving = if (state == null) false else + state == PlaybackState.STATE_PLAYING || + state == PlaybackState.STATE_BUFFERING || + state == PlaybackState.STATE_FAST_FORWARDING || + state == PlaybackState.STATE_REWINDING + return moving && listening + } + + /** Gets a listener to attach to the seek bar to handle seeking. */ + val seekBarListener: SeekBar.OnSeekBarChangeListener + get() { + return SeekBarChangeListener(this, bgExecutor) + } + + /** Gets a listener to attach to the seek bar to disable touch intercepting. */ + val seekBarTouchListener: View.OnTouchListener + get() { + return SeekBarTouchListener() + } + + private class SeekBarChangeListener( + val viewModel: SeekBarViewModel, + val bgExecutor: DelayableExecutor + ) : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) { + if (fromUser) { + bgExecutor.execute { + viewModel.onSeek(progress.toLong()) + } + } + } + override fun onStartTrackingTouch(bar: SeekBar) { + } + override fun onStopTrackingTouch(bar: SeekBar) { + val pos = bar.progress.toLong() + bgExecutor.execute { + viewModel.onSeek(pos) + } + } + } + + private class SeekBarTouchListener : View.OnTouchListener { + override fun onTouch(view: View, event: MotionEvent): Boolean { + view.parent.requestDisallowInterceptTouchEvent(true) + return view.onTouchEvent(event) + } + } + + /** State seen by seek bar UI. */ + data class Progress( + val enabled: Boolean, + val seekAvailable: Boolean, + val elapsedTime: Int?, + val duration: Int?, + val color: Int? + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 15c9dbad1680..8cff20ac31f7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -37,7 +37,6 @@ import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.RemoteException; import android.util.Log; import android.util.Size; import android.view.SurfaceControl; @@ -541,7 +540,12 @@ public class PipTaskOrganizer extends TaskOrganizer { return null; } final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout; - return new Size(windowLayout.minWidth, windowLayout.minHeight); + // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout> + // without minWidth/minHeight + if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) { + return new Size(windowLayout.minWidth, windowLayout.minHeight); + } + return null; } private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index 8922e146cc50..339a408d501a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -16,11 +16,14 @@ package com.android.systemui.qs; +import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle; + import android.app.Notification; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.media.session.MediaController; import android.media.session.MediaSession; import android.util.Log; import android.view.View; @@ -28,12 +31,16 @@ import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.SeekBar; import android.widget.TextView; import com.android.settingslib.media.MediaDevice; import com.android.systemui.R; import com.android.systemui.media.MediaControlPanel; +import com.android.systemui.media.SeekBarObserver; +import com.android.systemui.media.SeekBarViewModel; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.concurrent.Executor; @@ -54,6 +61,9 @@ public class QSMediaPlayer extends MediaControlPanel { }; private final QSPanel mParent; + private final DelayableExecutor mBackgroundExecutor; + private final SeekBarViewModel mSeekBarViewModel; + private final SeekBarObserver mSeekBarObserver; /** * Initialize quick shade version of player @@ -64,10 +74,20 @@ public class QSMediaPlayer extends MediaControlPanel { * @param backgroundExecutor */ public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager, - Executor foregroundExecutor, Executor backgroundExecutor) { + Executor foregroundExecutor, DelayableExecutor backgroundExecutor) { super(context, parent, manager, R.layout.qs_media_panel, QS_ACTION_IDS, foregroundExecutor, backgroundExecutor); mParent = (QSPanel) parent; + mBackgroundExecutor = backgroundExecutor; + mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor); + mSeekBarObserver = new SeekBarObserver(getView()); + // Can't use the viewAttachLifecycle of media player because remove/add is used to adjust + // priority of players. As soon as it is removed, the lifecycle will end and the seek bar + // will stop updating. So, use the lifecycle of the parent instead. + mSeekBarViewModel.getProgress().observe(viewAttachLifecycle(parent), mSeekBarObserver); + SeekBar bar = getView().findViewById(R.id.media_progress_bar); + bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener()); + bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener()); } /** @@ -115,6 +135,11 @@ public class QSMediaPlayer extends MediaControlPanel { thisBtn.setVisibility(View.GONE); } + // Seek Bar + final MediaController controller = new MediaController(getContext(), token); + mBackgroundExecutor.execute( + () -> mSeekBarViewModel.updateController(controller, iconColor)); + // Set up long press menu View guts = mMediaNotifView.findViewById(R.id.media_guts); View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options); @@ -155,4 +180,16 @@ public class QSMediaPlayer extends MediaControlPanel { return true; // consumed click }); } + + /** + * Sets the listening state of the player. + * + * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid + * unnecessary work when the QS panel is closed. + * + * @param listening True when player should be active. Otherwise, false. + */ + public void setListening(boolean listening) { + mSeekBarViewModel.setListening(listening); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 33cc086a8d9f..c8412fffd143 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -69,6 +69,7 @@ import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -103,7 +104,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private final NotificationMediaManager mNotificationMediaManager; private final LocalBluetoothManager mLocalBluetoothManager; private final Executor mForegroundExecutor; - private final Executor mBackgroundExecutor; + private final DelayableExecutor mBackgroundExecutor; private LocalMediaManager mLocalMediaManager; private MediaDevice mDevice; private boolean mUpdateCarousel = false; @@ -166,7 +167,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne QSLogger qsLogger, NotificationMediaManager notificationMediaManager, @Main Executor foregroundExecutor, - @Background Executor backgroundExecutor, + @Background DelayableExecutor backgroundExecutor, @Nullable LocalBluetoothManager localBluetoothManager ) { super(context, attrs); @@ -278,7 +279,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne Log.d(TAG, "creating new player"); player = new QSMediaPlayer(mContext, this, mNotificationMediaManager, mForegroundExecutor, mBackgroundExecutor); - + player.setListening(mListening); if (player.isPlaying()) { mMediaCarousel.addView(player.getView(), 0, lp); // add in front } else { @@ -584,6 +585,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne if (mListening) { refreshAllTiles(); } + for (QSMediaPlayer player : mMediaPlayers) { + player.setListening(mListening); + } } private String getTilesSpecs() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index be01d75552de..8fa64d3aaf0c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.Utils; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.ArrayList; import java.util.Collection; @@ -81,7 +82,7 @@ public class QuickQSPanel extends QSPanel { QSLogger qsLogger, NotificationMediaManager notificationMediaManager, @Main Executor foregroundExecutor, - @Background Executor backgroundExecutor, + @Background DelayableExecutor backgroundExecutor, @Nullable LocalBluetoothManager localBluetoothManager ) { super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, notificationMediaManager, diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index df85ed524536..66bc177da81d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -55,6 +55,7 @@ import android.os.UserHandle; import android.util.Log; import android.view.InputMonitor; import android.view.MotionEvent; +import android.view.Surface; import android.view.accessibility.AccessibilityManager; import com.android.internal.policy.ScreenDecorationsUtils; @@ -416,6 +417,19 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + @Override + public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { + if (!verifyCaller("onQuickSwitchToNewTask")) { + return; + } + long token = Binder.clearCallingIdentity(); + try { + mHandler.post(() -> notifyQuickSwitchToNewTask(rotation)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -785,6 +799,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + private void notifyQuickSwitchToNewTask(@Surface.Rotation int rotation) { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).onQuickSwitchToNewTask(rotation); + } + } + public void notifyQuickScrubStarted() { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onQuickScrubStarted(); @@ -850,6 +870,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public interface OverviewProxyListener { default void onConnectionChanged(boolean isConnected) {} default void onQuickStepStarted() {} + default void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {} default void onOverviewShown(boolean fromHome) {} default void onQuickScrubStarted() {} /** Notify changes in the nav bar button alpha */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index fe2f1f3eefc5..1297f996b743 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -177,12 +177,32 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle currentUserId); ent.setSensitive(sensitive, deviceSensitive); ent.getRow().setNeedsRedaction(needsRedaction); - if (mGroupManager.isChildInGroupWithSummary(ent.getSbn())) { - NotificationEntry summary = mGroupManager.getGroupSummary(ent.getSbn()); - List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(summary); + boolean isChildInGroup = mGroupManager.isChildInGroupWithSummary(ent.getSbn()); + + boolean groupChangesAllowed = mVisualStabilityManager.areGroupChangesAllowed() + || !ent.hasFinishedInitialization(); + NotificationEntry parent = mGroupManager.getGroupSummary(ent.getSbn()); + if (!groupChangesAllowed) { + // We don't to change groups while the user is looking at them + boolean wasChildInGroup = ent.isChildInGroup(); + if (isChildInGroup && !wasChildInGroup) { + isChildInGroup = wasChildInGroup; + mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager); + } else if (!isChildInGroup && wasChildInGroup) { + // We allow grouping changes if the group was collapsed + if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) { + isChildInGroup = wasChildInGroup; + parent = ent.getRow().getNotificationParent().getEntry(); + mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager); + } + } + } + + if (isChildInGroup) { + List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent); if (orderedChildren == null) { orderedChildren = new ArrayList<>(); - mTmpChildOrderMap.put(summary, orderedChildren); + mTmpChildOrderMap.put(parent, orderedChildren); } orderedChildren.add(ent); } else { @@ -205,7 +225,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle } for (ExpandableNotificationRow viewToRemove : viewsToRemove) { - if (mGroupManager.isChildInGroupWithSummary(viewToRemove.getEntry().getSbn())) { + if (mEntryManager.getPendingOrActiveNotif(viewToRemove.getEntry().getKey()) != null) { // we are only transferring this notification to its parent, don't generate an // animation mListContainer.setChildTransferInProgress(true); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 7ef1d0eba3f1..1696f0715865 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -26,6 +26,7 @@ import com.android.internal.widget.ConversationLayout import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationContentView +import com.android.systemui.statusbar.phone.NotificationGroupManager import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import javax.inject.Singleton @@ -60,6 +61,7 @@ class ConversationNotificationProcessor @Inject constructor( @Singleton class ConversationNotificationManager @Inject constructor( private val notificationEntryManager: NotificationEntryManager, + private val notificationGroupManager: NotificationGroupManager, private val context: Context ) { // Need this state to be thread safe, since it's accessed from the ui thread @@ -81,10 +83,19 @@ class ConversationNotificationManager @Inject constructor( if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { val important = ranking.channel.isImportantConversation + var changed = false entry.row?.layouts?.asSequence() ?.flatMap(::getLayouts) ?.mapNotNull { it as? ConversationLayout } - ?.forEach { it.setIsImportantConversation(important) } + ?.forEach { + if (important != it.isImportantConversation) { + it.setIsImportantConversation(important) + changed = true + } + } + if (changed) { + notificationGroupManager.updateIsolation(entry) + } } } } 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..295adae9c9ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -252,7 +252,7 @@ public class NotificationEntryManager implements } @Override - public void onReorderingAllowed() { + public void onChangeAllowed() { updateNotifications("reordering is now allowed"); } @@ -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/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java index b357ada7bcf1..7ac59954cb57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java @@ -42,12 +42,14 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000; - private final ArrayList<Callback> mCallbacks = new ArrayList<>(); + private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>(); + private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>(); private final Handler mHandler; private boolean mPanelExpanded; private boolean mScreenOn; private boolean mReorderingAllowed; + private boolean mGroupChangedAllowed; private boolean mIsTemporaryReorderingAllowed; private long mTemporaryReorderingStart; private VisibilityLocationProvider mVisibilityLocationProvider; @@ -83,13 +85,22 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl /** * Add a callback to invoke when reordering is allowed again. - * @param callback */ public void addReorderingAllowedCallback(Callback callback) { - if (mCallbacks.contains(callback)) { + if (mReorderingAllowedCallbacks.contains(callback)) { return; } - mCallbacks.add(callback); + mReorderingAllowedCallbacks.add(callback); + } + + /** + * Add a callback to invoke when group changes are allowed again. + */ + public void addGroupChangesAllowedCallback(Callback callback) { + if (mGroupChangesAllowedCallbacks.contains(callback)) { + return; + } + mGroupChangesAllowedCallbacks.add(callback); } /** @@ -97,7 +108,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl */ public void setPanelExpanded(boolean expanded) { mPanelExpanded = expanded; - updateReorderingAllowed(); + updateAllowedStates(); } /** @@ -105,7 +116,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl */ public void setScreenOn(boolean screenOn) { mScreenOn = screenOn; - updateReorderingAllowed(); + updateAllowedStates(); } /** @@ -116,25 +127,30 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl return; } mPulsing = pulsing; - updateReorderingAllowed(); + updateAllowedStates(); } - private void updateReorderingAllowed() { + private void updateAllowedStates() { boolean reorderingAllowed = (!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing; boolean changedToTrue = reorderingAllowed && !mReorderingAllowed; mReorderingAllowed = reorderingAllowed; if (changedToTrue) { - notifyCallbacks(); + notifyChangeAllowed(mReorderingAllowedCallbacks); + } + boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing; + changedToTrue = groupChangesAllowed && !mGroupChangedAllowed; + mGroupChangedAllowed = groupChangesAllowed; + if (changedToTrue) { + notifyChangeAllowed(mGroupChangesAllowedCallbacks); } } - private void notifyCallbacks() { - for (int i = 0; i < mCallbacks.size(); i++) { - Callback callback = mCallbacks.get(i); - callback.onReorderingAllowed(); + private void notifyChangeAllowed(ArrayList<Callback> callbacks) { + for (int i = 0; i < callbacks.size(); i++) { + callbacks.get(i).onChangeAllowed(); } - mCallbacks.clear(); + callbacks.clear(); } /** @@ -145,6 +161,13 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl } /** + * @return whether changes in the grouping should be allowed right now. + */ + public boolean areGroupChangesAllowed() { + return mGroupChangedAllowed; + } + + /** * @return whether a specific notification is allowed to reorder. Certain notifications are * allowed to reorder even if {@link #isReorderingAllowed()} returns false, like newly added * notifications or heads-up notifications that are out of view. @@ -197,12 +220,12 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl mTemporaryReorderingStart = SystemClock.elapsedRealtime(); } mIsTemporaryReorderingAllowed = true; - updateReorderingAllowed(); + updateAllowedStates(); } private final Runnable mOnTemporaryReorderingExpired = () -> { mIsTemporaryReorderingAllowed = false; - updateReorderingAllowed(); + updateAllowedStates(); }; /** @@ -229,9 +252,9 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl public interface Callback { /** - * Called when reordering is allowed again. + * Called when changing is allowed again. */ - void onReorderingAllowed(); + void onChangeAllowed(); } } 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..dd7be2775209 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; @@ -137,7 +136,6 @@ public final class NotificationEntry extends ListEntry { */ public EditedSuggestionInfo editedSuggestionInfo; - private NotificationEntry parent; // our parent (if we're in a group) private ExpandableNotificationRow row; // the outer expanded view private ExpandableNotificationRowController mRowController; @@ -579,10 +577,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; } @@ -715,7 +709,7 @@ public final class NotificationEntry extends ListEntry { } public boolean isChildInGroup() { - return parent == null; + return row != null && row.isChildInGroup(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt new file mode 100644 index 000000000000..1bac938a9fca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -0,0 +1,44 @@ +/* + * 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.collection.coordinator + +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import javax.inject.Inject +import javax.inject.Singleton + +/** + * A coordinator that elevates important conversation notifications + */ +@Singleton +class ConversationCoordinator @Inject constructor() : Coordinator { + + private val notificationPromoter = object : NotifPromoter(TAG) { + override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean { + return entry.channel?.isImportantConversation == true + } + } + + override fun attach(pipeline: NotifPipeline) { + pipeline.addPromoter(notificationPromoter) + } + + companion object { + private const val TAG = "ConversationCoordinator" + } +} 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/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 03c0ae6fde50..2b279bbd553a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -56,6 +56,7 @@ public class NotifCoordinators implements Dumpable { DeviceProvisionedCoordinator deviceProvisionedCoordinator, BubbleCoordinator bubbleCoordinator, HeadsUpCoordinator headsUpCoordinator, + ConversationCoordinator conversationCoordinator, PreparationCoordinator preparationCoordinator) { dumpManager.registerDumpable(TAG, this); mCoordinators.add(new HideLocallyDismissedNotifsCoordinator()); @@ -66,6 +67,7 @@ public class NotifCoordinators implements Dumpable { mCoordinators.add(deviceProvisionedCoordinator); mCoordinators.add(bubbleCoordinator); if (featureFlags.isNewNotifPipelineRenderingEnabled()) { + mCoordinators.add(conversationCoordinator); mCoordinators.add(headsUpCoordinator); mCoordinators.add(preparationCoordinator); } 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/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt index be3873a5fd77..88cca43fd1a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt @@ -96,7 +96,7 @@ class PeopleNotificationIdentifierImpl @Inject constructor( return TYPE_NON_PERSON } - val childTypes = groupManager.getLogicalChildren(statusBarNotification) + val childTypes = groupManager.getChildren(statusBarNotification) ?.asSequence() ?.map { getPeopleNotificationType(it.sbn, it.ranking) } ?: return TYPE_NON_PERSON 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/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 8e2bfb84e2dd..6fc1264d69e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -20,7 +20,6 @@ import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; -import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS; import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; @@ -44,7 +43,6 @@ import android.os.Handler; import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.transition.ChangeBounds; @@ -56,15 +54,12 @@ import android.util.Log; import android.util.Slog; import android.view.View; import android.view.accessibility.AccessibilityEvent; -import android.widget.Button; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.notification.ConversationIconFactory; -import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.notification.VisualStabilityManager; @@ -517,7 +512,6 @@ public class NotificationConversationInfo extends LinearLayout implements bgHandler.post( new UpdateChannelRunnable(mINotificationManager, mPackageName, mAppUid, mSelectedAction, mNotificationChannel)); - mVisualStabilityManager.temporarilyAllowReordering(); } /** 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/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java index d38bc9f7a84d..d02037cf61fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java @@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.LayoutRes; import android.annotation.NonNull; @@ -555,6 +556,12 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section updateSectionBoundaries(); } + void setHeaderForegroundColor(@ColorInt int color) { + mPeopleHubView.setTextColor(color); + mGentleHeader.setForegroundColor(color); + mAlertingHeader.setForegroundColor(color); + } + /** * For now, declare the available notification buckets (sections) here so that other * presentation code can decide what to do based on an entry's buckets diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 823b18660bed..6054b507185e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -33,6 +33,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeAnimator; import android.animation.ValueAnimator; +import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -3865,9 +3866,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); - final int action = ev.getAction(); + final int action = ev.getActionMasked(); + if (ev.findPointerIndex(mActivePointerId) == -1 && action != MotionEvent.ACTION_DOWN) { + // Incomplete gesture, possibly due to window swap mid-gesture. Ignore until a new + // one starts. + Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent " + + MotionEvent.actionToString(ev.getActionMasked())); + return true; + } - switch (action & MotionEvent.ACTION_MASK) { + switch (action) { case MotionEvent.ACTION_DOWN: { if (getChildCount() == 0 || !isInContentBounds(ev)) { return false; @@ -4806,7 +4814,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mUsingLightTheme = lightTheme; Context context = new ContextThemeWrapper(mContext, lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI); - final int textColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor); + final @ColorInt int textColor = + Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor); + mSectionsManager.setHeaderForegroundColor(textColor); mFooterView.setTextColor(textColor); mEmptyShadeView.setTextColor(textColor); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt index bc25c71e4fe5..a1d898fb84b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt @@ -16,11 +16,13 @@ package com.android.systemui.statusbar.notification.stack +import android.annotation.ColorInt import android.content.Context import android.util.AttributeSet import android.view.View import android.view.ViewGroup import android.widget.ImageView +import android.widget.TextView import com.android.systemui.R import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin import com.android.systemui.statusbar.notification.people.DataListener @@ -31,12 +33,14 @@ class PeopleHubView(context: Context, attrs: AttributeSet) : StackScrollerDecorView(context, attrs), SwipeableView { private lateinit var contents: ViewGroup + private lateinit var label: TextView lateinit var personViewAdapters: Sequence<DataListener<PersonViewModel?>> private set override fun onFinishInflate() { contents = requireViewById(R.id.people_list) + label = requireViewById(R.id.header_label) personViewAdapters = (0 until contents.childCount) .asSequence() // so we can map .mapNotNull { idx -> @@ -49,6 +53,8 @@ class PeopleHubView(context: Context, attrs: AttributeSet) : setVisible(true /* nowVisible */, false /* animate */) } + fun setTextColor(@ColorInt color: Int) = label.setTextColor(color) + override fun findContentView(): View = contents override fun findSecondaryView(): View? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java index a3d8eecdfd68..5777ba120ef0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java @@ -16,9 +16,11 @@ package com.android.systemui.statusbar.notification.stack; +import android.annotation.ColorInt; import android.annotation.Nullable; import android.annotation.StringRes; import android.content.Context; +import android.content.res.ColorStateList; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; @@ -124,4 +126,9 @@ public class SectionHeaderView extends StackScrollerDecorView { mLabelTextId = resId; mLabelView.setText(resId); } + + void setForegroundColor(@ColorInt int color) { + mLabelView.setTextColor(color); + mClearAllButton.setImageTintList(ColorStateList.valueOf(color)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 8efda213d659..f103bd01fc3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -19,6 +19,7 @@ import static android.view.Display.INVALID_DISPLAY; import android.content.Context; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PointF; @@ -26,10 +27,14 @@ import android.graphics.Region; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.input.InputManager; +import android.net.Uri; +import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.view.ISystemGestureExclusionListener; import android.view.InputChannel; @@ -40,6 +45,7 @@ import android.view.InputMonitor; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.Surface; import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -53,8 +59,10 @@ import com.android.systemui.plugins.NavigationEdgeBackPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; @@ -72,6 +80,8 @@ public class EdgeBackGestureHandler implements DisplayListener, private static final String TAG = "EdgeBackGestureHandler"; private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( "gestures.back_timeout", 250); + private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform"; + private ISystemGestureExclusionListener mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { @@ -88,6 +98,33 @@ public class EdgeBackGestureHandler implements DisplayListener, } }; + private OverviewProxyService.OverviewProxyListener mQuickSwitchListener = + new OverviewProxyService.OverviewProxyListener() { + @Override + public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { + mStartingQuickstepRotation = rotation; + updateDisabledForQuickstep(); + } + }; + + private TaskStackChangeListener mTaskStackChangeListener = new TaskStackChangeListener() { + @Override + public void onRecentTaskListFrozenChanged(boolean frozen) { + if (!frozen) { + mStartingQuickstepRotation = -1; + mDisabledForQuickstep = false; + } + } + }; + + private final ContentObserver mFixedRotationObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + updatedFixedRotation(); + } + }; + private final Context mContext; private final OverviewProxyService mOverviewProxyService; private PluginManager mPluginManager; @@ -110,6 +147,11 @@ public class EdgeBackGestureHandler implements DisplayListener, private final float mTouchSlop; // Duration after which we consider the event as longpress. private final int mLongPressTimeout; + private int mStartingQuickstepRotation = -1; + // We temporarily disable back gesture when user is quickswitching + // between apps of different orientations + private boolean mDisabledForQuickstep; + private boolean mFixedRotationFlagEnabled; private final PointF mDownPoint = new PointF(); private final PointF mEndPoint = new PointF(); @@ -193,6 +235,13 @@ public class EdgeBackGestureHandler implements DisplayListener, */ public void onNavBarAttached() { mIsAttached = true; + updatedFixedRotation(); + if (mFixedRotationFlagEnabled) { + setRotationCallbacks(true); + } + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME), + false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL); updateIsEnabled(); } @@ -201,9 +250,25 @@ public class EdgeBackGestureHandler implements DisplayListener, */ public void onNavBarDetached() { mIsAttached = false; + if (mFixedRotationFlagEnabled) { + setRotationCallbacks(false); + } + mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver); updateIsEnabled(); } + private void setRotationCallbacks(boolean enable) { + if (enable) { + ActivityManagerWrapper.getInstance().registerTaskStackListener( + mTaskStackChangeListener); + mOverviewProxyService.addCallback(mQuickSwitchListener); + } else { + ActivityManagerWrapper.getInstance().unregisterTaskStackListener( + mTaskStackChangeListener); + mOverviewProxyService.removeCallback(mQuickSwitchListener); + } + } + public void onNavigationModeChanged(int mode, Context currentUserContext) { mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode); updateIsEnabled(); @@ -405,7 +470,8 @@ public class EdgeBackGestureHandler implements DisplayListener, mLogGesture = false; mInRejectedExclusion = false; mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags) - && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); + && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()) + && !mDisabledForQuickstep; if (mAllowGesture) { mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); mEdgeBackPlugin.onMotionEvent(ev); @@ -466,6 +532,11 @@ public class EdgeBackGestureHandler implements DisplayListener, Dependency.get(ProtoTracer.class).update(); } + private void updateDisabledForQuickstep() { + int rotation = mContext.getResources().getConfiguration().windowConfiguration.getRotation(); + mDisabledForQuickstep = mStartingQuickstepRotation != rotation; + } + @Override public void onDisplayAdded(int displayId) { } @@ -474,6 +545,10 @@ public class EdgeBackGestureHandler implements DisplayListener, @Override public void onDisplayChanged(int displayId) { + if (mStartingQuickstepRotation > -1) { + updateDisabledForQuickstep(); + } + if (displayId == mDisplayId) { updateDisplaySize(); } @@ -502,6 +577,17 @@ public class EdgeBackGestureHandler implements DisplayListener, InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } + private void updatedFixedRotation() { + boolean oldFlag = mFixedRotationFlagEnabled; + mFixedRotationFlagEnabled = Settings.Global.getInt(mContext.getContentResolver(), + FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0; + if (oldFlag == mFixedRotationFlagEnabled) { + return; + } + + setRotationCallbacks(mFixedRotationFlagEnabled); + } + public void setInsets(int leftInset, int rightInset) { mLeftInset = leftInset; mRightInset = rightInset; @@ -514,6 +600,7 @@ public class EdgeBackGestureHandler implements DisplayListener, pw.println("EdgeBackGestureHandler:"); pw.println(" mIsEnabled=" + mIsEnabled); pw.println(" mAllowGesture=" + mAllowGesture); + pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); pw.println(" mInRejectedExclusion" + mInRejectedExclusion); pw.println(" mExcludeRegion=" + mExcludeRegion); pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 63fe7005e703..6b0df95f54dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -337,7 +337,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, // VisualStabilityManager.Callback overrides: @Override - public void onReorderingAllowed() { + public void onChangeAllowed() { mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false); for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) { if (isAlerting(entry.getKey())) { 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/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index ccf670708e44..84dd48b6eb6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import android.annotation.Nullable; +import android.app.NotificationChannel; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.Log; @@ -85,6 +86,17 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State return group.expanded; } + /** + * @return if the group that this notification is associated with logically is expanded + */ + public boolean isLogicalGroupExpanded(StatusBarNotification sbn) { + NotificationGroup group = mGroupMap.get(sbn.getGroupKey()); + if (group == null) { + return false; + } + return group.expanded; + } + public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) { NotificationGroup group = mGroupMap.get(getGroupKey(sbn)); if (group == null) { @@ -147,7 +159,15 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State } } + /** + * Notify the group manager that a new entry was added + */ public void onEntryAdded(final NotificationEntry added) { + updateIsolation(added); + onEntryAddedInternal(added); + } + + private void onEntryAddedInternal(final NotificationEntry added) { if (added.isRowRemoved()) { added.setDebugThrowable(new Throwable()); } @@ -193,9 +213,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State } private void onEntryBecomingChild(NotificationEntry entry) { - if (shouldIsolate(entry)) { - isolateNotification(entry); - } + updateIsolation(entry); } private void updateSuppression(NotificationGroup group) { @@ -242,15 +260,6 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State return count; } - private NotificationEntry getIsolatedChild(String groupKey) { - for (StatusBarNotification sbn : mIsolatedEntries.values()) { - if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) { - return mGroupMap.get(sbn.getKey()).summary; - } - } - return null; - } - /** * Update an entry's group information * @param entry notification entry to update @@ -278,7 +287,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) { onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary); } - onEntryAdded(entry); + onEntryAddedInternal(entry); mIsUpdatingUnchangedGroup = false; if (isIsolated(entry.getSbn().getKey())) { mIsolatedEntries.put(entry.getKey(), entry.getSbn()); @@ -413,14 +422,29 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State return null; } ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values()); - NotificationEntry isolatedChild = getIsolatedChild(summary.getGroupKey()); - if (isolatedChild != null) { - children.add(isolatedChild); + for (StatusBarNotification sbn : mIsolatedEntries.values()) { + if (sbn.getGroupKey().equals(summary.getGroupKey())) { + children.add(mGroupMap.get(sbn.getKey()).summary); + } } return children; } /** + * Get the children that are in the summary's group, not including those isolated. + * + * @param summary summary of a group + * @return list of the children + */ + public @Nullable ArrayList<NotificationEntry> getChildren(StatusBarNotification summary) { + NotificationGroup group = mGroupMap.get(summary.getGroupKey()); + if (group == null) { + return null; + } + return new ArrayList<>(group.children.values()); + } + + /** * If there is a {@link NotificationGroup} associated with the provided entry, this method * will update the suppression of that group. */ @@ -495,17 +519,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - onAlertStateChanged(entry, isHeadsUp); - } - - private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting) { - if (isAlerting) { - if (shouldIsolate(entry)) { - isolateNotification(entry); - } - } else { - stopIsolatingNotification(entry); - } + updateIsolation(entry); } /** @@ -519,13 +533,17 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State private boolean shouldIsolate(NotificationEntry entry) { StatusBarNotification sbn = entry.getSbn(); - NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) { return false; } + NotificationChannel channel = entry.getChannel(); + if (channel != null && channel.isImportantConversation()) { + return true; + } if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) { return false; } + NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); return (sbn.getNotification().fullScreenIntent != null || notificationGroup == null || !notificationGroup.expanded @@ -545,7 +563,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State mIsolatedEntries.put(sbn.getKey(), sbn); - onEntryAdded(entry); + onEntryAddedInternal(entry); // We also need to update the suppression of the old group, because this call comes // even before the groupManager knows about the notification at all. // When the notification gets added afterwards it is already isolated and therefore @@ -557,17 +575,31 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State } /** + * Update the isolation of an entry, splitting it from the group. + */ + public void updateIsolation(NotificationEntry entry) { + boolean isIsolated = isIsolated(entry.getSbn().getKey()); + if (shouldIsolate(entry)) { + if (!isIsolated) { + isolateNotification(entry); + } + } else if (isIsolated) { + stopIsolatingNotification(entry); + } + } + + /** * Stop isolating a notification and re-group it with its original logical group. * * @param entry the notification to un-isolate */ private void stopIsolatingNotification(NotificationEntry entry) { StatusBarNotification sbn = entry.getSbn(); - if (mIsolatedEntries.containsKey(sbn.getKey())) { + if (isIsolated(sbn.getKey())) { // not isolated anymore, we need to update the groups onEntryRemovedInternal(entry, entry.getSbn()); mIsolatedEntries.remove(sbn.getKey()); - onEntryAdded(entry); + onEntryAddedInternal(entry); for (OnGroupChangeListener listener : mListeners) { listener.onGroupsChanged(); } 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/src/com/android/systemui/util/SysuiLifecycle.java b/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java index 711a0dfe1931..d73175310802 100644 --- a/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java @@ -48,6 +48,9 @@ public class SysuiLifecycle { ViewLifecycle(View v) { v.addOnAttachStateChangeListener(this); + if (v.isAttachedToWindow()) { + mLifecycle.markState(RESUMED); + } } @NonNull 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/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java new file mode 100644 index 000000000000..137a126f539d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -0,0 +1,145 @@ +/* + * 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.globalactions; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.IActivityManager; +import android.app.admin.DevicePolicyManager; +import android.app.trust.TrustManager; +import android.content.ContentResolver; +import android.content.res.Resources; +import android.media.AudioManager; +import android.net.ConnectivityManager; +import android.os.UserManager; +import android.service.dreams.IDreamManager; +import android.telephony.TelephonyManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.IWindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.controls.controller.ControlsController; +import com.android.systemui.controls.management.ControlsListingController; +import com.android.systemui.controls.ui.ControlsUiController; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.GlobalActions; +import com.android.systemui.statusbar.BlurUtils; +import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class GlobalActionsDialogTest extends SysuiTestCase { + private GlobalActionsDialog mGlobalActionsDialog; + + @Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs; + @Mock private AudioManager mAudioManager; + @Mock private IDreamManager mDreamManager; + @Mock private DevicePolicyManager mDevicePolicyManager; + @Mock private LockPatternUtils mLockPatternUtils; + @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock private ConnectivityManager mConnectivityManager; + @Mock private TelephonyManager mTelephonyManager; + @Mock private ContentResolver mContentResolver; + @Mock private Resources mResources; + @Mock private ConfigurationController mConfigurationController; + @Mock private ActivityStarter mActivityStarter; + @Mock private KeyguardStateController mKeyguardStateController; + @Mock private UserManager mUserManager; + @Mock private TrustManager mTrustManager; + @Mock private IActivityManager mActivityManager; + @Mock private MetricsLogger mMetricsLogger; + @Mock private NotificationShadeDepthController mDepthController; + @Mock private SysuiColorExtractor mColorExtractor; + @Mock private IStatusBarService mStatusBarService; + @Mock private BlurUtils mBlurUtils; + @Mock private NotificationShadeWindowController mNotificationShadeWindowController; + @Mock private ControlsUiController mControlsUiController; + @Mock private IWindowManager mWindowManager; + @Mock private Executor mBackgroundExecutor; + @Mock private ControlsListingController mControlsListingController; + @Mock private ControlsController mControlsController; + @Mock private UiEventLogger mUiEventLogger; + + private TestableLooper mTestableLooper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTestableLooper = TestableLooper.get(this); + allowTestableLooperAsMainThread(); + mGlobalActionsDialog = new GlobalActionsDialog(mContext, + mWindowManagerFuncs, + mAudioManager, + mDreamManager, + mDevicePolicyManager, + mLockPatternUtils, + mBroadcastDispatcher, + mConnectivityManager, + mTelephonyManager, + mContentResolver, + null, + mResources, + mConfigurationController, + mActivityStarter, + mKeyguardStateController, + mUserManager, + mTrustManager, + mActivityManager, + null, + mMetricsLogger, + mDepthController, + mColorExtractor, + mStatusBarService, + mBlurUtils, + mNotificationShadeWindowController, + mControlsUiController, + mWindowManager, + mBackgroundExecutor, + mControlsListingController, + mControlsController, + mUiEventLogger + ); + } + @Test + public void testShouldLogVisibility() { + mGlobalActionsDialog.onShow(null); + verify(mUiEventLogger, times(1)) + .log(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt new file mode 100644 index 000000000000..260f52070a70 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt @@ -0,0 +1,130 @@ +/* + * 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.media + +import android.graphics.Color +import android.content.res.ColorStateList +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.widget.SeekBar +import android.widget.TextView +import androidx.test.filters.SmallTest + +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +public class SeekBarObserverTest : SysuiTestCase() { + + private lateinit var observer: SeekBarObserver + @Mock private lateinit var mockView: View + private lateinit var seekBarView: SeekBar + private lateinit var elapsedTimeView: TextView + private lateinit var totalTimeView: TextView + + @Before + fun setUp() { + mockView = mock(View::class.java) + seekBarView = SeekBar(context) + elapsedTimeView = TextView(context) + totalTimeView = TextView(context) + whenever<SeekBar>( + mockView.findViewById(R.id.media_progress_bar)).thenReturn(seekBarView) + whenever<TextView>( + mockView.findViewById(R.id.media_elapsed_time)).thenReturn(elapsedTimeView) + whenever<TextView>(mockView.findViewById(R.id.media_total_time)).thenReturn(totalTimeView) + observer = SeekBarObserver(mockView) + } + + @Test + fun seekBarGone() { + // WHEN seek bar is disabled + val isEnabled = false + val data = SeekBarViewModel.Progress(isEnabled, false, null, null, null) + observer.onChanged(data) + // THEN seek bar visibility is set to GONE + assertThat(seekBarView.getVisibility()).isEqualTo(View.GONE) + assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.GONE) + assertThat(totalTimeView.getVisibility()).isEqualTo(View.GONE) + } + + @Test + fun seekBarVisible() { + // WHEN seek bar is enabled + val isEnabled = true + val data = SeekBarViewModel.Progress(isEnabled, true, 3000, 12000, -1) + observer.onChanged(data) + // THEN seek bar is visible + assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE) + assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.VISIBLE) + assertThat(totalTimeView.getVisibility()).isEqualTo(View.VISIBLE) + } + + @Test + fun seekBarProgress() { + // WHEN seek bar progress is about half + val data = SeekBarViewModel.Progress(true, true, 3000, 120000, -1) + observer.onChanged(data) + // THEN seek bar is visible + assertThat(seekBarView.progress).isEqualTo(100) + assertThat(seekBarView.max).isEqualTo(120000) + assertThat(elapsedTimeView.getText()).isEqualTo("00:03") + assertThat(totalTimeView.getText()).isEqualTo("02:00") + } + + @Test + fun seekBarDisabledWhenSeekNotAvailable() { + // WHEN seek is not available + val isSeekAvailable = false + val data = SeekBarViewModel.Progress(true, isSeekAvailable, 3000, 120000, -1) + observer.onChanged(data) + // THEN seek bar is not enabled + assertThat(seekBarView.isEnabled()).isFalse() + } + + @Test + fun seekBarEnabledWhenSeekNotAvailable() { + // WHEN seek is available + val isSeekAvailable = true + val data = SeekBarViewModel.Progress(true, isSeekAvailable, 3000, 120000, -1) + observer.onChanged(data) + // THEN seek bar is not enabled + assertThat(seekBarView.isEnabled()).isTrue() + } + + @Test + fun seekBarColor() { + // WHEN data included color + val data = SeekBarViewModel.Progress(true, true, 3000, 120000, Color.RED) + observer.onChanged(data) + // THEN seek bar is colored + val red = ColorStateList.valueOf(Color.RED) + assertThat(elapsedTimeView.getTextColors()).isEqualTo(red) + assertThat(totalTimeView.getTextColors()).isEqualTo(red) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt new file mode 100644 index 000000000000..f316d0480fac --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt @@ -0,0 +1,375 @@ +/* + * 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.media + +import android.graphics.Color +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.PlaybackState +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.widget.SeekBar +import androidx.arch.core.executor.ArchTaskExecutor +import androidx.arch.core.executor.TaskExecutor +import androidx.test.filters.SmallTest + +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat + +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +public class SeekBarViewModelTest : SysuiTestCase() { + + private lateinit var viewModel: SeekBarViewModel + private lateinit var fakeExecutor: FakeExecutor + private val taskExecutor: TaskExecutor = object : TaskExecutor() { + override fun executeOnDiskIO(runnable: Runnable) { + runnable.run() + } + override fun postToMainThread(runnable: Runnable) { + runnable.run() + } + override fun isMainThread(): Boolean { + return true + } + } + @Mock private lateinit var mockController: MediaController + @Mock private lateinit var mockTransport: MediaController.TransportControls + + @Before + fun setUp() { + fakeExecutor = FakeExecutor(FakeSystemClock()) + viewModel = SeekBarViewModel(fakeExecutor) + mockController = mock(MediaController::class.java) + mockTransport = mock(MediaController.TransportControls::class.java) + + // LiveData to run synchronously + ArchTaskExecutor.getInstance().setDelegate(taskExecutor) + } + + @After + fun tearDown() { + ArchTaskExecutor.getInstance().setDelegate(null) + } + + @Test + fun updateColor() { + viewModel.updateController(mockController, Color.RED) + assertThat(viewModel.progress.value!!.color).isEqualTo(Color.RED) + } + + @Test + fun updateDuration() { + // GIVEN that the duration is contained within the metadata + val duration = 12000L + val metadata = MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, duration) + build() + } + whenever(mockController.getMetadata()).thenReturn(metadata) + // WHEN the controller is updated + viewModel.updateController(mockController, Color.RED) + // THEN the duration is extracted + assertThat(viewModel.progress.value!!.duration).isEqualTo(duration) + assertThat(viewModel.progress.value!!.enabled).isTrue() + } + + @Test + fun updateDurationNegative() { + // GIVEN that the duration is negative + val duration = -1L + val metadata = MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, duration) + build() + } + whenever(mockController.getMetadata()).thenReturn(metadata) + // WHEN the controller is updated + viewModel.updateController(mockController, Color.RED) + // THEN the seek bar is disabled + assertThat(viewModel.progress.value!!.enabled).isFalse() + } + + @Test + fun updateDurationZero() { + // GIVEN that the duration is zero + val duration = 0L + val metadata = MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, duration) + build() + } + whenever(mockController.getMetadata()).thenReturn(metadata) + // WHEN the controller is updated + viewModel.updateController(mockController, Color.RED) + // THEN the seek bar is disabled + assertThat(viewModel.progress.value!!.enabled).isFalse() + } + + @Test + fun updateElapsedTime() { + // GIVEN that the PlaybackState contins the current position + val position = 200L + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, position, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + // WHEN the controller is updated + viewModel.updateController(mockController, Color.RED) + // THEN elapsed time is captured + assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(200.toInt()) + } + + @Test + fun updateSeekAvailable() { + // GIVEN that seek is included in actions + val state = PlaybackState.Builder().run { + setActions(PlaybackState.ACTION_SEEK_TO) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + // WHEN the controller is updated + viewModel.updateController(mockController, Color.RED) + // THEN seek is available + assertThat(viewModel.progress.value!!.seekAvailable).isTrue() + } + + @Test + fun updateSeekNotAvailable() { + // GIVEN that seek is not included in actions + val state = PlaybackState.Builder().run { + setActions(PlaybackState.ACTION_PLAY) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + // WHEN the controller is updated + viewModel.updateController(mockController, Color.RED) + // THEN seek is not available + assertThat(viewModel.progress.value!!.seekAvailable).isFalse() + } + + @Test + fun handleSeek() { + whenever(mockController.getTransportControls()).thenReturn(mockTransport) + viewModel.updateController(mockController, Color.RED) + // WHEN user input is dispatched + val pos = 42L + viewModel.onSeek(pos) + fakeExecutor.runAllReady() + // THEN transport controls should be used + verify(mockTransport).seekTo(pos) + } + + @Test + fun handleProgressChangedUser() { + whenever(mockController.getTransportControls()).thenReturn(mockTransport) + viewModel.updateController(mockController, Color.RED) + // WHEN user starts dragging the seek bar + val pos = 42 + viewModel.seekBarListener.onProgressChanged(SeekBar(context), pos, true) + fakeExecutor.runAllReady() + // THEN transport controls should be used + verify(mockTransport).seekTo(pos.toLong()) + } + + @Test + fun handleProgressChangedOther() { + whenever(mockController.getTransportControls()).thenReturn(mockTransport) + viewModel.updateController(mockController, Color.RED) + // WHEN user starts dragging the seek bar + val pos = 42 + viewModel.seekBarListener.onProgressChanged(SeekBar(context), pos, false) + fakeExecutor.runAllReady() + // THEN transport controls should be used + verify(mockTransport, never()).seekTo(pos.toLong()) + } + + @Test + fun handleStartTrackingTouch() { + whenever(mockController.getTransportControls()).thenReturn(mockTransport) + viewModel.updateController(mockController, Color.RED) + // WHEN user starts dragging the seek bar + val pos = 42 + val bar = SeekBar(context).apply { + progress = pos + } + viewModel.seekBarListener.onStartTrackingTouch(bar) + fakeExecutor.runAllReady() + // THEN transport controls should be used + verify(mockTransport, never()).seekTo(pos.toLong()) + } + + @Test + fun handleStopTrackingTouch() { + whenever(mockController.getTransportControls()).thenReturn(mockTransport) + viewModel.updateController(mockController, Color.RED) + // WHEN user ends drag + val pos = 42 + val bar = SeekBar(context).apply { + progress = pos + } + viewModel.seekBarListener.onStopTrackingTouch(bar) + fakeExecutor.runAllReady() + // THEN transport controls should be used + verify(mockTransport).seekTo(pos.toLong()) + } + + @Test + fun queuePollTaskWhenPlaying() { + // GIVEN that the track is playing + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 100L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + // WHEN the controller is updated + viewModel.updateController(mockController, Color.RED) + // THEN a task is queued + assertThat(fakeExecutor.numPending()).isEqualTo(1) + } + + @Test + fun noQueuePollTaskWhenStopped() { + // GIVEN that the playback state is stopped + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_STOPPED, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + // WHEN updated + viewModel.updateController(mockController, Color.RED) + // THEN an update task is not queued + assertThat(fakeExecutor.numPending()).isEqualTo(0) + } + + @Test + fun queuePollTaskWhenListening() { + // GIVEN listening + viewModel.listening = true + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // AND the playback state is playing + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + // WHEN updated + viewModel.updateController(mockController, Color.RED) + // THEN an update task is queued + assertThat(fakeExecutor.numPending()).isEqualTo(1) + } + + @Test + fun noQueuePollTaskWhenNotListening() { + // GIVEN not listening + viewModel.listening = false + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // AND the playback state is playing + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_STOPPED, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + // WHEN updated + viewModel.updateController(mockController, Color.RED) + // THEN an update task is not queued + assertThat(fakeExecutor.numPending()).isEqualTo(0) + } + + @Test + fun pollTaskQueuesAnotherPollTaskWhenPlaying() { + // GIVEN that the track is playing + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 100L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + viewModel.updateController(mockController, Color.RED) + // WHEN the next task runs + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // THEN another task is queued + assertThat(fakeExecutor.numPending()).isEqualTo(1) + } + + @Test + fun taskUpdatesProgress() { + // GIVEN that the PlaybackState contins the current position + val position = 200L + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, position, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + viewModel.updateController(mockController, Color.RED) + // AND the playback state advances + val nextPosition = 300L + val nextState = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, nextPosition, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(nextState) + // WHEN the task runs + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // THEN elapsed time is captured + assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(nextPosition.toInt()) + } + + @Test + fun startListeningQueuesPollTask() { + // GIVEN not listening + viewModel.listening = false + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // AND the playback state is playing + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_STOPPED, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + viewModel.updateController(mockController, Color.RED) + // WHEN start listening + viewModel.listening = true + // THEN an update task is queued + assertThat(fakeExecutor.numPending()).isEqualTo(1) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index dbbbaac66554..862ebe13bd93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java @@ -44,6 +44,7 @@ import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.util.concurrency.DelayableExecutor; import org.junit.Before; import org.junit.Test; @@ -87,7 +88,7 @@ public class QSPanelTest extends SysuiTestCase { @Mock private Executor mForegroundExecutor; @Mock - private Executor mBackgroundExecutor; + private DelayableExecutor mBackgroundExecutor; @Mock private LocalBluetoothManager mLocalBluetoothManager; 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..e55ea41d94e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -98,8 +98,10 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mLockscreenUserManager); mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager); + when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true); + when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true); - 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/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java index 9079223649ff..3d06c57cac37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java @@ -110,7 +110,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.setScreenOn(true); mVisualStabilityManager.addReorderingAllowedCallback(mCallback); mVisualStabilityManager.setScreenOn(false); - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); } @Test @@ -119,7 +119,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.setScreenOn(true); mVisualStabilityManager.addReorderingAllowedCallback(mCallback); mVisualStabilityManager.setPanelExpanded(false); - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); } @Test @@ -130,7 +130,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.setScreenOn(false); mVisualStabilityManager.setScreenOn(true); mVisualStabilityManager.setScreenOn(false); - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); } @Test @@ -190,7 +190,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.setPulsing(true); mVisualStabilityManager.addReorderingAllowedCallback(mCallback); mVisualStabilityManager.setPulsing(false); - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); } @Test @@ -204,7 +204,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.temporarilyAllowReordering(); // THEN callbacks are notified that reordering is allowed - verify(mCallback).onReorderingAllowed(); + verify(mCallback).onChangeAllowed(); assertTrue(mVisualStabilityManager.isReorderingAllowed()); } @@ -218,7 +218,7 @@ public class VisualStabilityManagerTest extends SysuiTestCase { mVisualStabilityManager.temporarilyAllowReordering(); // THEN reordering is still not allowed - verify(mCallback, never()).onReorderingAllowed(); + verify(mCallback, never()).onChangeAllowed(); assertFalse(mVisualStabilityManager.isReorderingAllowed()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt new file mode 100644 index 000000000000..dfc627e14d8c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -0,0 +1,77 @@ +/* + * 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.collection.coordinator + +import android.app.NotificationChannel +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class ConversationCoordinatorTest : SysuiTestCase() { + + private var coordinator: ConversationCoordinator = ConversationCoordinator() + + // captured listeners and pluggables: + private var promoter: NotifPromoter? = null + + @Mock + private val pipeline: NotifPipeline? = null + @Mock + private val channel: NotificationChannel? = null + private var entry: NotificationEntry? = null + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(channel!!.isImportantConversation).thenReturn(true) + + coordinator.attach(pipeline!!) + + // capture arguments: + val notifPromoterCaptor = ArgumentCaptor.forClass(NotifPromoter::class.java) + verify(pipeline).addPromoter(notifPromoterCaptor.capture()) + promoter = notifPromoterCaptor.value + + entry = NotificationEntryBuilder().setChannel(channel).build() + } + + @Test + fun testPromotesCurrentHUN() { + + // only promote important conversations + assertTrue(promoter!!.shouldPromoteToTopLevel(entry)) + assertFalse(promoter!!.shouldPromoteToTopLevel(NotificationEntryBuilder().build())) + } +} 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/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index 6998edda3127..b6bd5e213dd4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -816,29 +816,4 @@ public class NotificationConversationInfoTest extends SysuiTestCase { verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage( anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID)); } - - @Test - public void testAdjustImportanceTemporarilyAllowsReordering() { - mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); - mConversationChannel.setImportance(IMPORTANCE_DEFAULT); - mNotificationInfo.bindNotification( - mShortcutManager, - mMockPackageManager, - mMockINotificationManager, - mVisualStabilityManager, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - mIconFactory, - true); - - mNotificationInfo.findViewById(R.id.silence).performClick(); - mNotificationInfo.findViewById(R.id.done).performClick(); - - mTestableLooper.processAllMessages(); - - verify(mVisualStabilityManager).temporarilyAllowReordering(); - } } 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/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java index ce8085aa4862..486939d1f08e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java @@ -25,6 +25,8 @@ import static androidx.lifecycle.Lifecycle.Event.ON_STOP; import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -35,12 +37,15 @@ import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; import android.view.View; +import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleEventObserver; import androidx.lifecycle.LifecycleOwner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,39 +54,122 @@ import org.junit.runner.RunWith; @SmallTest public class SysuiLifecycleTest extends SysuiTestCase { + private View mView; + + @Before + public void setUp() { + mView = new View(mContext); + } + + @After + public void tearDown() { + if (mView.isAttachedToWindow()) { + ViewUtils.detachView(mView); + TestableLooper.get(this).processAllMessages(); + } + } + @Test public void testAttach() { - View v = new View(mContext); LifecycleEventObserver observer = mock(LifecycleEventObserver.class); - LifecycleOwner lifecycle = viewAttachLifecycle(v); + LifecycleOwner lifecycle = viewAttachLifecycle(mView); lifecycle.getLifecycle().addObserver(observer); - ViewUtils.attachView(v); + ViewUtils.attachView(mView); TestableLooper.get(this).processAllMessages(); verify(observer).onStateChanged(eq(lifecycle), eq(ON_CREATE)); verify(observer).onStateChanged(eq(lifecycle), eq(ON_START)); verify(observer).onStateChanged(eq(lifecycle), eq(ON_RESUME)); - - ViewUtils.detachView(v); - TestableLooper.get(this).processAllMessages(); } @Test public void testDetach() { - View v = new View(mContext); LifecycleEventObserver observer = mock(LifecycleEventObserver.class); - LifecycleOwner lifecycle = viewAttachLifecycle(v); + LifecycleOwner lifecycle = viewAttachLifecycle(mView); lifecycle.getLifecycle().addObserver(observer); - ViewUtils.attachView(v); + ViewUtils.attachView(mView); TestableLooper.get(this).processAllMessages(); - ViewUtils.detachView(v); + ViewUtils.detachView(mView); TestableLooper.get(this).processAllMessages(); verify(observer).onStateChanged(eq(lifecycle), eq(ON_PAUSE)); verify(observer).onStateChanged(eq(lifecycle), eq(ON_STOP)); verify(observer).onStateChanged(eq(lifecycle), eq(ON_DESTROY)); } + + @Test + public void testStateBeforeAttach() { + // WHEN a lifecycle is obtained from a view + LifecycleOwner lifecycle = viewAttachLifecycle(mView); + // THEN the lifecycle state should be INITIAZED + assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo( + Lifecycle.State.INITIALIZED); + } + + @Test + public void testStateAfterAttach() { + // WHEN a lifecycle is obtained from a view + LifecycleOwner lifecycle = viewAttachLifecycle(mView); + // AND the view is attached + ViewUtils.attachView(mView); + TestableLooper.get(this).processAllMessages(); + // THEN the lifecycle state should be RESUMED + assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED); + } + + @Test + public void testStateAfterDetach() { + // WHEN a lifecycle is obtained from a view + LifecycleOwner lifecycle = viewAttachLifecycle(mView); + // AND the view is detached + ViewUtils.attachView(mView); + TestableLooper.get(this).processAllMessages(); + ViewUtils.detachView(mView); + TestableLooper.get(this).processAllMessages(); + // THEN the lifecycle state should be DESTROYED + assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.DESTROYED); + } + + @Test + public void testStateAfterReattach() { + // WHEN a lifecycle is obtained from a view + LifecycleOwner lifecycle = viewAttachLifecycle(mView); + // AND the view is re-attached + ViewUtils.attachView(mView); + TestableLooper.get(this).processAllMessages(); + ViewUtils.detachView(mView); + TestableLooper.get(this).processAllMessages(); + ViewUtils.attachView(mView); + TestableLooper.get(this).processAllMessages(); + // THEN the lifecycle state should still be DESTROYED, err RESUMED? + assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED); + } + + @Test + public void testStateWhenViewAlreadyAttached() { + // GIVEN that a view is already attached + ViewUtils.attachView(mView); + TestableLooper.get(this).processAllMessages(); + // WHEN a lifecycle is obtained from a view + LifecycleOwner lifecycle = viewAttachLifecycle(mView); + // THEN the lifecycle state should be RESUMED + assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED); + } + + @Test + public void testStateWhenViewAlreadyDetached() { + // GIVEN that a view is already detached + ViewUtils.attachView(mView); + TestableLooper.get(this).processAllMessages(); + ViewUtils.detachView(mView); + TestableLooper.get(this).processAllMessages(); + // WHEN a lifecycle is obtained from a view + LifecycleOwner lifecycle = viewAttachLifecycle(mView); + // THEN the lifecycle state should be INITIALIZED + assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo( + Lifecycle.State.INITIALIZED); + } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 4ab035e7f44f..2eaa766ad32d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -6313,7 +6313,8 @@ public class ConnectivityService extends IConnectivityManager.Stub && !nai.networkAgentConfig.allowBypass && nc.getOwnerUid() != Process.SYSTEM_UID && lp.getInterfaceName() != null - && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()); + && (lp.hasIPv4DefaultRoute() || lp.hasIpv4UnreachableDefaultRoute()) + && (lp.hasIPv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute()); } private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc, diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index 7aaf9be1fdd2..e1f9a7a150d8 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -137,12 +137,12 @@ public class AdbService extends IAdbManager.Stub { @Override public File getAdbKeysFile() { - return mDebuggingManager.getUserKeyFile(); + return mDebuggingManager == null ? null : mDebuggingManager.getUserKeyFile(); } @Override public File getAdbTempKeysFile() { - return mDebuggingManager.getAdbTempKeysFile(); + return mDebuggingManager == null ? null : mDebuggingManager.getAdbTempKeysFile(); } @Override diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 85d288317b6a..62d7eb1d0448 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -18376,7 +18376,18 @@ public class ActivityManagerService extends IActivityManager.Stub } } - proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd, finishCallback); + Process.enableFreezer(false); + + final RemoteCallback intermediateCallback = new RemoteCallback( + new RemoteCallback.OnResultListener() { + @Override + public void onResult(Bundle result) { + finishCallback.sendResult(result); + Process.enableFreezer(true); + } + }, null); + + proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback); fd = null; return true; } diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 45b93834c1e2..4431abe43136 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -17,6 +17,7 @@ package com.android.server.biometrics; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; +import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -43,6 +44,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IHwBinder; import android.os.IRemoteCallback; +import android.os.Looper; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -52,6 +54,8 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.FrameworkStatsLog; @@ -93,7 +97,22 @@ public abstract class BiometricServiceBase extends SystemService protected final Map<Integer, Long> mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>()); protected final AppOpsManager mAppOps; - protected final H mHandler = new H(); + + /** + * Handler which all subclasses should post events to. + */ + protected final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case MSG_USER_SWITCHING: + handleUserSwitching(msg.arg1); + break; + default: + Slog.w(getTag(), "Unknown message:" + msg.what); + } + } + }; private final IBinder mToken = new Binder(); // Used for internal enumeration private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>(); @@ -483,23 +502,6 @@ public abstract class BiometricServiceBase extends SystemService void resetLockout(byte[] token) throws RemoteException; } - /** - * Handler which all subclasses should post events to. - */ - protected final class H extends Handler { - @Override - public void handleMessage(android.os.Message msg) { - switch (msg.what) { - case MSG_USER_SWITCHING: - handleUserSwitching(msg.arg1); - break; - - default: - Slog.w(getTag(), "Unknown message:" + msg.what); - } - } - } - private final Runnable mOnTaskStackChangedRunnable = new Runnable() { @Override public void run() { @@ -647,8 +649,9 @@ public abstract class BiometricServiceBase extends SystemService mContext = context; mStatusBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - mKeyguardPackage = ComponentName.unflattenFromString(context.getResources().getString( - com.android.internal.R.string.config_keyguardComponent)).getPackageName(); + final ComponentName keyguardComponent = ComponentName.unflattenFromString( + context.getResources().getString(R.string.config_keyguardComponent)); + mKeyguardPackage = keyguardComponent != null ? keyguardComponent.getPackageName() : null; mAppOps = context.getSystemService(AppOpsManager.class); mActivityTaskManager = ((ActivityTaskManager) context.getSystemService( Context.ACTIVITY_TASK_SERVICE)).getService(); @@ -671,8 +674,8 @@ public abstract class BiometricServiceBase extends SystemService // All client lifecycle must be managed on the handler. mHandler.post(() -> { - handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, - 0 /*vendorCode */); + Slog.e(getTag(), "Sending BIOMETRIC_ERROR_HW_UNAVAILABLE after HAL crash"); + handleError(getHalDeviceId(), BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); }); FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED, @@ -798,9 +801,10 @@ public abstract class BiometricServiceBase extends SystemService } protected void handleEnumerate(BiometricAuthenticator.Identifier identifier, int remaining) { - ClientMonitor client = getCurrentClient(); - - client.onEnumerationResult(identifier, remaining); + ClientMonitor client = mCurrentClient; + if (client != null) { + client.onEnumerationResult(identifier, remaining); + } // All templates in the HAL for this user were enumerated if (remaining == 0) { @@ -818,7 +822,7 @@ public abstract class BiometricServiceBase extends SystemService } removeClient(client); startCleanupUnknownHALTemplates(); - } else { + } else if (client != null) { removeClient(client); } } @@ -898,12 +902,16 @@ public abstract class BiometricServiceBase extends SystemService protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName, int callingUid, int callingPid, int callingUserId, boolean fromClient) { + + if (DEBUG) Slog.v(getTag(), "cancelAuthentication(" + opPackageName + ")"); if (fromClient) { // Only check this if cancel was called from the client (app). If cancel was called // from BiometricService, it means the dialog was dismissed due to user interaction. if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid, callingUserId)) { - if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName); + if (DEBUG) { + Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName); + } return; } } @@ -1059,7 +1067,8 @@ public abstract class BiometricServiceBase extends SystemService * @param newClient the new client that wants to connect * @param initiatedByClient true for authenticate, remove and enroll */ - private void startClient(ClientMonitor newClient, boolean initiatedByClient) { + @VisibleForTesting + void startClient(ClientMonitor newClient, boolean initiatedByClient) { ClientMonitor currentClient = mCurrentClient; if (currentClient != null) { if (DEBUG) Slog.v(getTag(), "request stop current client " + @@ -1122,18 +1131,27 @@ public abstract class BiometricServiceBase extends SystemService Slog.e(getTag(), "Trying to start null client!"); return; } + if (DEBUG) Slog.v(getTag(), "starting client " + mCurrentClient.getClass().getSuperclass().getSimpleName() + "(" + mCurrentClient.getOwnerString() + ")" + " targetUserId: " + mCurrentClient.getTargetUserId() + " currentUserId: " + mCurrentUserId + " cookie: " + cookie + "/" + mCurrentClient.getCookie()); + if (cookie != mCurrentClient.getCookie()) { Slog.e(getTag(), "Mismatched cookie"); return; } - notifyClientActiveCallbacks(true); - mCurrentClient.start(); + + int status = mCurrentClient.start(); + if (status == 0) { + notifyClientActiveCallbacks(true); + } else { + mCurrentClient.onError(getHalDeviceId(), BIOMETRIC_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + removeClient(mCurrentClient); + } } protected void removeClient(ClientMonitor client) { @@ -1145,7 +1163,7 @@ public abstract class BiometricServiceBase extends SystemService } } if (mCurrentClient != null) { - if (DEBUG) Slog.v(getTag(), "Done with client: " + client.getOwnerString()); + if (DEBUG) Slog.v(getTag(), "Done with client: " + mCurrentClient.getOwnerString()); mCurrentClient = null; } if (mPendingClient == null) { 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e8d8ed7a462d..ed3b9f1fc265 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6227,7 +6227,7 @@ public class NotificationManagerService extends SystemService { cancelNotificationLocked( r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName); cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName, - mSendDelete, childrenFlagChecker); + mSendDelete, childrenFlagChecker, mReason); updateLightsLocked(); if (mShortcutHelper != null) { mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, @@ -6687,7 +6687,7 @@ public class NotificationManagerService extends SystemService { // notification was a summary and its group key changed. if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) { cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */, - null); + null, REASON_APP_CANCEL); } } @@ -7892,7 +7892,7 @@ public class NotificationManagerService extends SystemService { final int M = canceledNotifications.size(); for (int i = 0; i < M; i++) { cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid, - listenerName, false /* sendDelete */, flagChecker); + listenerName, false /* sendDelete */, flagChecker, reason); } updateLightsLocked(); } @@ -7963,7 +7963,7 @@ public class NotificationManagerService extends SystemService { // Warning: The caller is responsible for invoking updateLightsLocked(). @GuardedBy("mNotificationLock") private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid, - String listenerName, boolean sendDelete, FlagChecker flagChecker) { + String listenerName, boolean sendDelete, FlagChecker flagChecker, int reason) { Notification n = r.getNotification(); if (!n.isGroupSummary()) { return; @@ -7977,30 +7977,33 @@ public class NotificationManagerService extends SystemService { } cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName, - sendDelete, true, flagChecker); + sendDelete, true, flagChecker, reason); cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid, - listenerName, sendDelete, false, flagChecker); + listenerName, sendDelete, false, flagChecker, reason); } @GuardedBy("mNotificationLock") private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList, NotificationRecord parentNotification, int callingUid, int callingPid, - String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker) { + String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker, + int reason) { final String pkg = parentNotification.getSbn().getPackageName(); final int userId = parentNotification.getUserId(); - final int reason = REASON_GROUP_SUMMARY_CANCELED; + final int childReason = REASON_GROUP_SUMMARY_CANCELED; for (int i = notificationList.size() - 1; i >= 0; i--) { final NotificationRecord childR = notificationList.get(i); final StatusBarNotification childSbn = childR.getSbn(); if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) && childR.getGroupKey().equals(parentNotification.getGroupKey()) && (childR.getFlags() & FLAG_FOREGROUND_SERVICE) == 0 - && (flagChecker == null || flagChecker.apply(childR.getFlags()))) { + && (flagChecker == null || flagChecker.apply(childR.getFlags())) + && (!childR.getChannel().isImportantConversation() + || reason != REASON_CANCEL)) { EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(), - childSbn.getTag(), userId, 0, 0, reason, listenerName); + childSbn.getTag(), userId, 0, 0, childReason, listenerName); notificationList.remove(i); mNotificationsByKey.remove(childR.getKey()); - cancelNotificationLocked(childR, sendDelete, reason, wasPosted, listenerName); + cancelNotificationLocked(childR, sendDelete, childReason, wasPosted, listenerName); } } } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 1b271a739777..1d5c30438870 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -47,6 +47,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutQueryWrapper; import android.content.pm.ShortcutServiceInternal; import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; import android.content.pm.UserInfo; @@ -698,13 +699,19 @@ public class LauncherAppsService extends SystemService { } @Override - public ParceledListSlice getShortcuts(String callingPackage, long changedSince, - String packageName, List shortcutIds, List<LocusId> locusIds, - ComponentName componentName, int flags, UserHandle targetUser) { + public ParceledListSlice getShortcuts(@NonNull final String callingPackage, + @NonNull final ShortcutQueryWrapper query, @NonNull final UserHandle targetUser) { ensureShortcutPermission(callingPackage); if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) { return new ParceledListSlice<>(Collections.EMPTY_LIST); } + + final long changedSince = query.getChangedSince(); + final String packageName = query.getPackage(); + final List<String> shortcutIds = query.getShortcutIds(); + final List<LocusId> locusIds = query.getLocusIds(); + final ComponentName componentName = query.getActivity(); + final int flags = query.getQueryFlags(); if (shortcutIds != null && packageName == null) { throw new IllegalArgumentException( "To query by shortcut ID, package name must also be set"); @@ -723,16 +730,17 @@ public class LauncherAppsService extends SystemService { } @Override - public void registerShortcutChangeCallback(String callingPackage, long changedSince, - String packageName, List shortcutIds, List<LocusId> locusIds, - ComponentName componentName, int flags, IShortcutChangeCallback callback) { + public void registerShortcutChangeCallback(@NonNull final String callingPackage, + @NonNull final ShortcutQueryWrapper query, + @NonNull final IShortcutChangeCallback callback) { + ensureShortcutPermission(callingPackage); - if (shortcutIds != null && packageName == null) { + if (query.getShortcutIds() != null && query.getPackage() == null) { throw new IllegalArgumentException( "To query by shortcut ID, package name must also be set"); } - if (locusIds != null && packageName == null) { + if (query.getLocusIds() != null && query.getPackage() == null) { throw new IllegalArgumentException( "To query by locus ID, package name must also be set"); } @@ -744,10 +752,7 @@ public class LauncherAppsService extends SystemService { user = null; } - // TODO: When ShortcutQueryWrapper (ag/10323729) is available, pass that directly. - ShortcutChangeHandler.QueryInfo query = new ShortcutChangeHandler.QueryInfo( - changedSince, packageName, shortcutIds, locusIds, componentName, flags, user); - mShortcutChangeHandler.addShortcutChangeCallback(callback, query); + mShortcutChangeHandler.addShortcutChangeCallback(callback, query, user); } @Override @@ -1081,9 +1086,11 @@ public class LauncherAppsService extends SystemService { new RemoteCallbackList<>(); public synchronized void addShortcutChangeCallback(IShortcutChangeCallback callback, - QueryInfo query) { + ShortcutQueryWrapper query, UserHandle user) { mCallbacks.unregister(callback); - mCallbacks.register(callback, query); + mCallbacks.register(callback, new QueryInfo(query.getChangedSince(), + query.getPackage(), query.getShortcutIds(), query.getLocusIds(), + query.getActivity(), query.getQueryFlags(), user)); } public synchronized void removeShortcutChangeCallback( diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java index 778683369de5..c2ecd41e7dbf 100644 --- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -176,8 +176,12 @@ public class TestHarnessModeService extends SystemService { private void setUpAdbFiles(PersistentData persistentData) { AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class); - writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath()); - writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath()); + if (adbManager.getAdbKeysFile() != null) { + writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath()); + } + if (adbManager.getAdbTempKeysFile() != null) { + writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath()); + } } private void configureUser() { @@ -310,12 +314,6 @@ public class TestHarnessModeService extends SystemService { AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class); File adbKeys = adbManager.getAdbKeysFile(); File adbTempKeys = adbManager.getAdbTempKeysFile(); - if (adbKeys == null && adbTempKeys == null) { - // This should only be accessible on eng builds that haven't yet set up ADB keys - getErrPrintWriter() - .println("No ADB keys stored; not enabling test harness mode"); - return 1; - } try { byte[] adbKeysBytes = getBytesFromFile(adbKeys); 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/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 10b335e583b0..a446720c1c39 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -3553,9 +3553,8 @@ class ActivityStack extends Task { } } - void reparent(DisplayContent newParent, boolean onTop) { - // Real parent of stack is within display object, so we have to delegate re-parenting there. - newParent.moveStackToDisplay(this, onTop); + void reparent(TaskDisplayArea newParent, boolean onTop) { + reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM); } private void updateSurfaceBounds() { diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 2c7ce9104c3c..8af862473386 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -412,7 +412,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final ActivityStack stack = (ActivityStack) task; stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); if (mToDisplay.getDisplayId() != stack.getDisplayId()) { - mToDisplay.moveStackToDisplay(stack, mOnTop); + stack.reparent(mToDisplay.getDefaultTaskDisplayArea(), mOnTop); } else if (mOnTop) { mToDisplay.mTaskContainers.positionStackAtTop(stack, false /* includingParents */); @@ -566,8 +566,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } void moveRecentsStackToFront(String reason) { - final ActivityStack recentsStack = mRootWindowContainer.getDefaultDisplay().getStack( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); + final ActivityStack recentsStack = mRootWindowContainer.getDefaultTaskDisplayArea() + .getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); if (recentsStack != null) { recentsStack.moveToFront(reason); } @@ -2613,7 +2613,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // from whatever is started from the recents activity, so move the home stack // forward. // TODO (b/115289124): Multi-display supports for recents. - mRootWindowContainer.getDefaultDisplay().mTaskContainers.moveHomeStackToFront( + mRootWindowContainer.getDefaultTaskDisplayArea().moveHomeStackToFront( "startActivityFromRecents"); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7803c7370dac..0b1968765300 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4283,9 +4283,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { long ident = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - final DisplayContent dc = mRootWindowContainer.getDefaultDisplay(); - final Task primary = dc.getRootSplitScreenPrimaryTask(); - final Task secondary = dc.getTask(t -> t.mCreatedByOrganizer && t.isRootTask() + final TaskDisplayArea tc = mRootWindowContainer.getDefaultTaskDisplayArea(); + final Task primary = tc.getRootSplitScreenPrimaryTask(); + final Task secondary = tc.getTask(t -> t.mCreatedByOrganizer && t.isRootTask() && t.inSplitScreenSecondaryWindowingMode()); if (primary == null || secondary == null) { return; @@ -4301,7 +4301,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (otherRect == null) { // Temporary estimation... again this is just for tests. otherRect = new Rect(secondary.getBounds()); - if (dc.getBounds().width() > dc.getBounds().height()) { + if (tc.getBounds().width() > tc.getBounds().height()) { otherRect.left = primaryRect.right + 6; } else { otherRect.top = primaryRect.bottom + 6; diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index 682a14220dff..e8becfa27fac 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -54,7 +54,7 @@ import java.util.Map; * - DisplayArea.Root * - Magnification * - DisplayArea.Tokens (Wallpapers are attached here) - * - TaskContainers + * - TaskDisplayArea * - DisplayArea.Tokens (windows above Tasks up to IME are attached here) * - ImeContainers * - DisplayArea.Tokens (windows above IME up to TYPE_ACCESSIBILITY_OVERLAY attached here) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 55b7be779690..85517a44d656 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -575,13 +575,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private RootWindowContainer mRootWindowContainer; - /** - * All of the stacks on this display. Order matters, topmost stack is in front of all other - * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls - * changing the list should also call {@link #onStackOrderChanged()}. - */ - private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>(); - /** Array of all UIDs that are present on the display. */ private IntArray mDisplayAccessUIDs = new IntArray(); @@ -2062,23 +2055,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return (mDisplay.getFlags() & FLAG_PRIVATE) != 0; } - ActivityStack getRootHomeTask() { - return mTaskContainers.getRootHomeTask(); - } - - /** @return The primary split-screen task, and {@code null} otherwise. */ - @Nullable ActivityStack getRootSplitScreenPrimaryTask() { - return mTaskContainers.getRootSplitScreenPrimaryTask(); - } - - ActivityStack getRootPinnedTask() { - return mTaskContainers.getRootPinnedTask(); - } - - boolean hasPinnedTask() { - return mTaskContainers.getRootPinnedTask() != null; - } - /** * Returns the topmost stack on the display that is compatible with the input windowing mode and * activity type. Null is no compatible stack on the display. @@ -2095,32 +2071,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTaskContainers.mChildren.get(index); } - int getIndexOf(ActivityStack stack) { - return mTaskContainers.getIndexOf(stack); - } - - void removeStack(ActivityStack stack) { - mTaskContainers.removeChild(stack); - } - - @VisibleForTesting - WindowList<ActivityStack> getStacks() { - return mTaskContainers.mChildren; - } - @VisibleForTesting ActivityStack getTopStack() { return mTaskContainers.getTopStack(); } - ArrayList<Task> getVisibleTasks() { - return mTaskContainers.getVisibleTasks(); - } - - SurfaceControl getSplitScreenDividerAnchor() { - return mTaskContainers.getSplitScreenDividerAnchor(); - } - /** * The value is only valid in the scope {@link #onRequestedOverrideConfigurationChanged} of the * changing hierarchy and the {@link #onConfigurationChanged} of its children. @@ -2409,8 +2364,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo out.set(mDisplayFrames.mStable); } - void moveStackToDisplay(ActivityStack stack, boolean onTop) { - stack.reparent(mTaskContainers, onTop ? POSITION_TOP: POSITION_BOTTOM); + /** + * Get the default display area on the display dedicated to app windows. This one should be used + * only as a fallback location for activity launches when no target display area is specified, + * or for cases when multi-instance is not supported yet (like Split-screen, PiP or Recents). + */ + TaskDisplayArea getDefaultTaskDisplayArea() { + return mTaskContainers; } @Override @@ -2473,7 +2433,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ Task findTaskForResizePoint(int x, int y) { final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); - return mTmpTaskForResizePointSearchResult.process(mTaskContainers, x, y, delta); + return mTmpTaskForResizePointSearchResult.process(getDefaultTaskDisplayArea(), x, y, delta); } void updateTouchExcludeRegion() { @@ -2512,8 +2472,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION); } amendWindowTapExcludeRegion(mTouchExcludeRegion); - // TODO(multi-display): Support docked stacks on secondary displays. - if (mDisplayId == DEFAULT_DISPLAY && mTaskContainers.isSplitScreenModeActivated()) { + // TODO(multi-display): Support docked stacks on secondary displays & task containers. + if (mDisplayId == DEFAULT_DISPLAY + && getDefaultTaskDisplayArea().isSplitScreenModeActivated()) { mDividerControllerLocked.getTouchRegion(mTmpRect); mTmpRegion.set(mTmpRect); mTouchExcludeRegion.op(mTmpRegion, Op.UNION); @@ -2908,20 +2869,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.println(); // Dump stack references - final ActivityStack homeStack = getRootHomeTask(); + final ActivityStack homeStack = getDefaultTaskDisplayArea().getRootHomeTask(); if (homeStack != null) { pw.println(prefix + "homeStack=" + homeStack.getName()); } - final ActivityStack pinnedStack = getRootPinnedTask(); + final ActivityStack pinnedStack = getDefaultTaskDisplayArea().getRootPinnedTask(); if (pinnedStack != null) { pw.println(prefix + "pinnedStack=" + pinnedStack.getName()); } - final ActivityStack splitScreenPrimaryStack = getRootSplitScreenPrimaryTask(); + final ActivityStack splitScreenPrimaryStack = getDefaultTaskDisplayArea() + .getRootSplitScreenPrimaryTask(); if (splitScreenPrimaryStack != null) { pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName()); } - final ActivityStack recentsStack = - getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); + // TODO: Support recents on non-default task containers + final ActivityStack recentsStack = getDefaultTaskDisplayArea().getStack( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); if (recentsStack != null) { pw.println(prefix + "recentsStack=" + recentsStack.getName()); } @@ -2955,12 +2918,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\""; } - /** Returns true if the stack in the windowing mode is visible. */ - boolean isStackVisible(int windowingMode) { - final ActivityStack stack = mTaskContainers.getTopStackInWindowingMode(windowingMode); - return stack != null && stack.isVisible(); - } - /** Find the visible, touch-deliverable window under the given point */ WindowState getTouchableWinAtPointLocked(float xf, float yf) { final int x = (int) xf; @@ -4367,7 +4324,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // We skip IME windows so they're processed just above their target, except // in split-screen mode where we process the IME containers above the docked divider. return dc.mInputMethodTarget != null - && !dc.mTaskContainers.isSplitScreenModeActivated(); + && !dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated(); } /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */ @@ -5262,7 +5219,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // released (no more ActivityStack). But, we cannot release it at that moment or the // related WindowContainer will also be removed. So, we set display as removed after // reparenting stack finished. - final DisplayContent toDisplay = mRootWindowContainer.getDefaultDisplay(); + final TaskDisplayArea toTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); mRootWindowContainer.mStackSupervisor.beginDeferResume(); try { int numStacks = getStackCount(); @@ -5276,10 +5233,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // If default display is in split-window mode, set windowing mode of the stack // to split-screen secondary. Otherwise, set the windowing mode to undefined by // default to let stack inherited the windowing mode from the new display. - final int windowingMode = toDisplay.mTaskContainers.isSplitScreenModeActivated() + final int windowingMode = toTaskDisplayArea.isSplitScreenModeActivated() ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_UNDEFINED; - stack.reparent(toDisplay, true /* onTop */); + stack.reparent(toTaskDisplayArea, true /* onTop */); stack.setWindowingMode(windowingMode); lastReparentedStack = stack; } @@ -5392,34 +5349,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mSleeping = asleep; } - /** - * Adds a listener to be notified whenever the stack order in the display changes. Currently - * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the - * current animation when the system state changes. - */ - void registerStackOrderChangedListener(OnStackOrderChangedListener listener) { - if (!mStackOrderChangedCallbacks.contains(listener)) { - mStackOrderChangedCallbacks.add(listener); - } - } - - /** - * Removes a previously registered stack order change listener. - */ - void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) { - mStackOrderChangedCallbacks.remove(listener); - } - - /** - * Notifies of a stack order change - * @param stack The stack which triggered the order change - */ - void onStackOrderChanged(ActivityStack stack) { - for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) { - mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack); - } - } - void setDisplayToSingleTaskInstance() { final int childCount = getStackCount(); if (childCount > 1) { @@ -5448,22 +5377,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } /** - * Callback for when the order of the stacks in the display changes. - */ - interface OnStackOrderChangedListener { - void onStackOrderChanged(ActivityStack stack); - } - - public void dumpStacks(PrintWriter pw) { - for (int i = getStackCount() - 1; i >= 0; --i) { - pw.print(getStackAt(i).getRootTaskId()); - if (i > 0) { - pw.print(","); - } - } - } - - /** * Similar to {@link RootWindowContainer#isAnyNonToastWindowVisibleForUid(int)}, but * used for pid. */ diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 221258e94cb2..264da9fc681b 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2653,8 +2653,8 @@ public class DisplayPolicy { if (mStatusBarController.setBarShowingLw(true)) { changes |= FINISH_LAYOUT_REDO_LAYOUT; } - } else if (topIsFullscreen - && !mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { + } else if (topIsFullscreen && !mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar"); if (mStatusBarController.setBarShowingLw(false)) { changes |= FINISH_LAYOUT_REDO_LAYOUT; @@ -3462,10 +3462,10 @@ public class DisplayPolicy { } private Pair<Integer, WindowState> updateSystemBarsLw(WindowState win, int oldVis, int vis) { - final boolean dockedStackVisible = - mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - final boolean freeformStackVisible = - mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM); + final boolean dockedStackVisible = mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + final boolean freeformStackVisible = mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_FREEFORM); final boolean resizing = mDisplayContent.getDockedDividerController().isResizing(); // We need to force system bars when the docked stack is visible, when the freeform stack diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index af89a05bfa62..bef80f0a230a 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -594,7 +594,8 @@ public class DisplayRotation { // In the presence of the PINNED stack or System Alert windows we unfortunately can not // seamlessly rotate. - if (mDisplayContent.hasPinnedTask() || mDisplayContent.hasAlertWindowSurfaces()) { + if (mDisplayContent.getDefaultTaskDisplayArea().hasPinnedTask() + || mDisplayContent.hasAlertWindowSurfaces()) { return false; } diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java index a9a9df8458e2..a6066684d850 100644 --- a/services/core/java/com/android/server/wm/InputConsumerImpl.java +++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java @@ -153,10 +153,11 @@ class InputConsumerImpl implements IBinder.DeathRecipient { t.reparent(mInputSurface, wc.getSurfaceControl()); } - void disposeChannelsLw() { + void disposeChannelsLw(SurfaceControl.Transaction t) { mService.mInputManager.unregisterInputChannel(mServerChannel); mClientChannel.dispose(); mServerChannel.dispose(); + t.remove(mInputSurface); unlinkFromDeathRecipient(); } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 18332b9484c0..8b34b9b8dd8f 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -112,8 +112,9 @@ final class InputMonitor { @Override public void dispose() { synchronized (mService.mGlobalLock) { - disposeChannelsLw(); + disposeChannelsLw(mInputMonitor.mInputTransaction); mInputEventReceiver.dispose(); + mInputMonitor.updateInputWindowsLw(true /* force */); } } } @@ -195,8 +196,7 @@ final class InputMonitor { private boolean disposeInputConsumer(InputConsumerImpl consumer) { if (consumer != null) { - consumer.disposeChannelsLw(); - consumer.hide(mInputTransaction); + consumer.disposeChannelsLw(mInputTransaction); return true; } return false; diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 007af249c580..e4e57168efe7 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -295,10 +295,10 @@ class InsetsPolicy { } private boolean forceShowsSystemBarsForWindowingMode() { - final boolean isDockedStackVisible = - mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - final boolean isFreeformStackVisible = - mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM); + final boolean isDockedStackVisible = mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + final boolean isFreeformStackVisible = mDisplayContent.getDefaultTaskDisplayArea() + .isStackVisible(WINDOWING_MODE_FREEFORM); final boolean isResizing = mDisplayContent.getDockedDividerController().isResizing(); // We need to force system bars when the docked stack is visible, when the freeform stack diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index a936e74bf694..57a54d0e84ef 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -406,11 +406,12 @@ class KeyguardController { // show on top of the lock screen. In this can we want to dismiss the docked // stack since it will be complicated/risky to try to put the activity on top // of the lock screen in the right fullscreen configuration. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - if (!display.mTaskContainers.isSplitScreenModeActivated()) { + final TaskDisplayArea taskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + if (!taskDisplayArea.isSplitScreenModeActivated()) { return; } - display.mTaskContainers.onSplitScreenModeDismissed(); + taskDisplayArea.onSplitScreenModeDismissed(); } } diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index 66dbfd5f2a84..02a27410dc38 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -286,7 +286,8 @@ class PinnedStackController { } try { final Rect animatingBounds = new Rect(); - final ActivityStack pinnedStack = mDisplayContent.getRootPinnedTask(); + final ActivityStack pinnedStack = mDisplayContent.getDefaultTaskDisplayArea() + .getRootPinnedTask(); if (pinnedStack != null) { pinnedStack.getAnimationOrCurrentBounds(animatingBounds); } diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 08b0abf5e9be..a031fe82d48b 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -52,14 +52,14 @@ import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallback * cleanup. See {@link com.android.server.wm.RecentsAnimationController}. */ class RecentsAnimation implements RecentsAnimationCallbacks, - DisplayContent.OnStackOrderChangedListener { + TaskDisplayArea.OnStackOrderChangedListener { private static final String TAG = RecentsAnimation.class.getSimpleName(); private final ActivityTaskManagerService mService; private final ActivityStackSupervisor mStackSupervisor; private final ActivityStartController mActivityStartController; private final WindowManagerService mWindowManager; - private final DisplayContent mDefaultDisplay; + private final TaskDisplayArea mDefaultTaskDisplayArea; private final Intent mTargetIntent; private final ComponentName mRecentsComponent; private final @Nullable String mRecentsFeatureId; @@ -84,7 +84,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, int recentsUid, @Nullable WindowProcessController caller) { mService = atm; mStackSupervisor = stackSupervisor; - mDefaultDisplay = mService.mRootWindowContainer.getDefaultDisplay(); + mDefaultTaskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea(); mActivityStartController = activityStartController; mWindowManager = wm; mTargetIntent = targetIntent; @@ -107,7 +107,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, void preloadRecentsActivity() { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Preload recents with %s", mTargetIntent); - ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, + ActivityStack targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); ActivityRecord targetActivity = getTargetActivity(targetStack); if (targetActivity != null) { @@ -128,7 +128,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // Create the activity record. Because the activity is invisible, this doesn't really // start the client. startRecentsActivityInBackground("preloadRecents"); - targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); + targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, + mTargetActivityType); targetActivity = getTargetActivity(targetStack); if (targetActivity == null) { Slog.w(TAG, "Cannot start " + mTargetIntent); @@ -176,12 +177,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks, } // If the activity is associated with the recents stack, then try and get that first - ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, + ActivityStack targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); ActivityRecord targetActivity = getTargetActivity(targetStack); final boolean hasExistingActivity = targetActivity != null; if (hasExistingActivity) { - final TaskDisplayArea taskDisplayArea = targetActivity.getDisplayArea(); mRestoreTargetBehindStack = getStackAbove(targetStack); if (mRestoreTargetBehindStack == null) { notifyAnimationCancelBeforeStart(recentsAnimationRunner); @@ -209,7 +209,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, try { if (hasExistingActivity) { // Move the recents activity into place for the animation if it is not top most - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(targetStack); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(targetStack); ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved stack=%s behind stack=%s", targetStack, getStackAbove(targetStack)); @@ -225,10 +225,10 @@ class RecentsAnimation implements RecentsAnimationCallbacks, startRecentsActivityInBackground("startRecentsActivity_noTargetActivity"); // Move the recents activity into place for the animation - targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, + targetStack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); targetActivity = getTargetActivity(targetStack); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(targetStack); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(targetStack); ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved stack=%s behind stack=%s", targetStack, getStackAbove(targetStack)); @@ -251,7 +251,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity"); mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner, - this, mDefaultDisplay.getDisplayId(), + this, mDefaultTaskDisplayArea.getDisplayId(), mStackSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity); // If we updated the launch-behind state, update the visibility of the activities after @@ -262,7 +262,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, START_TASK_TO_FRONT, targetActivity); // Register for stack order changes - mDefaultDisplay.registerStackOrderChangedListener(this); + mDefaultTaskDisplayArea.registerStackOrderChangedListener(this); } catch (Exception e) { Slog.e(TAG, "Failed to start recents activity", e); throw e; @@ -280,7 +280,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, mWindowManager.getRecentsAnimationController(), reorderMode); // Unregister for stack order changes - mDefaultDisplay.unregisterStackOrderChangedListener(this); + mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(this); final RecentsAnimationController controller = mWindowManager.getRecentsAnimationController(); @@ -308,7 +308,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, try { mWindowManager.cleanupRecentsAnimation(reorderMode); - final ActivityStack targetStack = mDefaultDisplay.getStack( + final ActivityStack targetStack = mDefaultTaskDisplayArea.getStack( WINDOWING_MODE_UNDEFINED, mTargetActivityType); // Prefer to use the original target activity instead of top activity because // we may have moved another task to top (starting 3p launcher). @@ -422,7 +422,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, @Override public void onStackOrderChanged(ActivityStack stack) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "onStackOrderChanged(): stack=%s", stack); - if (mDefaultDisplay.getIndexOf(stack) == -1 || !stack.shouldBeVisible(null)) { + if (mDefaultTaskDisplayArea.getIndexOf(stack) == -1 || !stack.shouldBeVisible(null)) { // The stack is not visible, so ignore this change return; } @@ -480,8 +480,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, * @return The top stack that is not always-on-top. */ private ActivityStack getTopNonAlwaysOnTopStack() { - for (int i = mDefaultDisplay.getStackCount() - 1; i >= 0; i--) { - final ActivityStack s = mDefaultDisplay.getStackAt(i); + for (int i = mDefaultTaskDisplayArea.getStackCount() - 1; i >= 0; i--) { + final ActivityStack s = mDefaultTaskDisplayArea.getStackAt(i); if (s.getWindowConfiguration().isAlwaysOnTop()) { continue; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index a30b70de267d..84229f003032 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -340,9 +340,11 @@ public class RecentsAnimationController implements DeathRecipient { // Make leashes for each of the visible/target tasks and add it to the recents animation to // be started - final ArrayList<Task> visibleTasks = mDisplayContent.getVisibleTasks(); - final ActivityStack targetStack = mDisplayContent.getStack(WINDOWING_MODE_UNDEFINED, - targetActivityType); + // TODO(multi-display-area): Support Recents on multiple task display areas + final ArrayList<Task> visibleTasks = mDisplayContent.getDefaultTaskDisplayArea() + .getVisibleTasks(); + final ActivityStack targetStack = mDisplayContent.getDefaultTaskDisplayArea() + .getStack(WINDOWING_MODE_UNDEFINED, targetActivityType); if (targetStack != null) { final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) -> { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class), @@ -385,7 +387,8 @@ public class RecentsAnimationController implements DeathRecipient { } // Save the minimized home height - mMinimizedHomeBounds = mDisplayContent.getRootHomeTask().getBounds(); + mMinimizedHomeBounds = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask() + .getBounds(); mService.mWindowPlacerLocked.performSurfacePlacement(); diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java index 770c08889ab9..32de699eaae9 100644 --- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java +++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java @@ -232,10 +232,11 @@ class ResetTargetTaskHelper { } final ActivityTaskManagerService atmService = mTargetStack.mAtmService; - DisplayContent display = mTargetStack.getDisplay(); - final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance(); + TaskDisplayArea taskDisplayArea = mTargetStack.getDisplayArea(); + final boolean singleTaskInstanceDisplay = + taskDisplayArea.mDisplayContent.isSingleTaskInstance(); if (singleTaskInstanceDisplay) { - display = atmService.mRootWindowContainer.getDefaultDisplay(); + taskDisplayArea = atmService.mRootWindowContainer.getDefaultTaskDisplayArea(); } final int windowingMode = mTargetStack.getWindowingMode(); @@ -246,7 +247,7 @@ class ResetTargetTaskHelper { final boolean alwaysCreateTask = DisplayContent.alwaysCreateStack(windowingMode, activityType); final Task task = alwaysCreateTask - ? display.getBottomMostTask() : mTargetStack.getBottomMostTask(); + ? taskDisplayArea.getBottomMostTask() : mTargetStack.getBottomMostTask(); Task targetTask = null; if (task != null && r.taskAffinity.equals(task.affinity)) { // If the activity currently at the bottom has the same task affinity as @@ -257,7 +258,7 @@ class ResetTargetTaskHelper { } if (targetTask == null) { if (alwaysCreateTask) { - targetTask = display.mTaskContainers.getOrCreateStack(windowingMode, + targetTask = taskDisplayArea.getOrCreateStack(windowingMode, activityType, false /* onTop */); } else { targetTask = mTargetStack.reuseOrCreateTask(r.info, null /*intent*/, diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2764b121cc00..2eeda4dcfeed 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1369,11 +1369,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } calculateDefaultMinimalSizeOfResizeableTasks(); - final DisplayContent defaultDisplay = getDefaultDisplay(); - - defaultDisplay.mTaskContainers.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_HOME, ON_TOP); - positionChildAt(POSITION_TOP, defaultDisplay, false /* includingParents */); + final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea(); + defaultTaskDisplayArea.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, + ON_TOP); + positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent, + false /* includingParents */); } // TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display. @@ -1382,6 +1382,16 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } /** + * Get the default display area on the device dedicated to app windows. This one should be used + * only as a fallback location for activity launches when no target display area is specified, + * or for cases when multi-instance is not supported yet (like Split-screen, Freeform, PiP or + * Recents). + */ + TaskDisplayArea getDefaultTaskDisplayArea() { + return mDefaultDisplay.getDefaultTaskDisplayArea(); + } + + /** * Get an existing instance of {@link DisplayContent} that has the given uniqueId. Unique ID is * defined in {@link DisplayInfo#uniqueId}. * @@ -1436,12 +1446,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return displayContent; } - ActivityRecord getDefaultDisplayHomeActivity() { - return getDefaultDisplayHomeActivityForUser(mCurrentUser); - } - ActivityRecord getDefaultDisplayHomeActivityForUser(int userId) { - return getDisplayContent(DEFAULT_DISPLAY).mTaskContainers.getHomeActivityForUser(userId); + return getDefaultTaskDisplayArea().getHomeActivityForUser(userId); } boolean startHomeOnAllDisplays(int userId, String reason) { @@ -1972,8 +1978,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int focusStackId = topFocusedStack != null ? topFocusedStack.getRootTaskId() : INVALID_TASK_ID; // We dismiss the docked stack whenever we switch users. - if (getDefaultDisplay().mTaskContainers.isSplitScreenModeActivated()) { - getDefaultDisplay().mTaskContainers.onSplitScreenModeDismissed(); + if (getDefaultTaskDisplayArea().isSplitScreenModeActivated()) { + getDefaultTaskDisplayArea().onSplitScreenModeDismissed(); } // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will // also cause all tasks to be moved to the fullscreen stack at a position that is @@ -1995,7 +2001,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int restoreStackId = mUserStackInFront.get(userId); ActivityStack stack = getStack(restoreStackId); if (stack == null) { - stack = getDefaultDisplay().mTaskContainers.getOrCreateRootHomeTask(); + stack = getDefaultTaskDisplayArea().getOrCreateRootHomeTask(); } final boolean homeInFront = stack.isActivityTypeHome(); if (stack.isOnHomeDisplay()) { @@ -2018,7 +2024,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void updateUserStack(int userId, ActivityStack stack) { if (userId != mCurrentUser) { if (stack == null) { - stack = getDefaultDisplay().mTaskContainers.getOrCreateRootHomeTask(); + stack = getDefaultTaskDisplayArea().getOrCreateRootHomeTask(); } mUserStackInFront.put(userId, stack.getRootTaskId()); @@ -2061,7 +2067,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return; } - stack.reparent(displayContent.mDisplayContent, onTop); + stack.reparent(displayContent.getDefaultTaskDisplayArea(), onTop); // TODO(multi-display): resize stacks properly if moved from split-screen. } @@ -2155,8 +2161,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Looking up task on preferred display first final DisplayContent preferredDisplay = getDisplayContent(preferredDisplayId); if (preferredDisplay != null) { - preferredDisplay.mTaskContainers.findTaskLocked(r, true /* isPreferredDisplay */, - mTmpFindTaskResult); + preferredDisplay.getDefaultTaskDisplayArea().findTaskLocked(r, + true /* isPreferredDisplay */, mTmpFindTaskResult); if (mTmpFindTaskResult.mIdealMatch) { return mTmpFindTaskResult.mRecord; } @@ -2168,7 +2174,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> continue; } - display.mTaskContainers.findTaskLocked(r, false /* isPreferredDisplay */, + display.getDefaultTaskDisplayArea().findTaskLocked(r, false /* isPreferredDisplay */, mTmpFindTaskResult); if (mTmpFindTaskResult.mIdealMatch) { return mTmpFindTaskResult.mRecord; @@ -2788,8 +2794,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } final DisplayContent display = getDisplayContentOrCreate(displayId); if (display != null) { - stack = display.mTaskContainers.getOrCreateStack(r, options, candidateTask, - activityType, onTop); + // Falling back to default task container + final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); + stack = taskDisplayArea.getOrCreateStack(r, options, candidateTask, activityType, + onTop); if (stack != null) { return stack; } @@ -2835,7 +2843,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (container == null || !canLaunchOnDisplay(r, container.mDisplayContent.mDisplayId)) { - container = getDefaultDisplay().mTaskContainers; + container = getDefaultTaskDisplayArea(); if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { windowingMode = container.resolveWindowingMode(r, options, candidateTask, activityType); @@ -2887,7 +2895,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // it to the target display. if (candidateTask.isRootTask()) { final ActivityStack stack = candidateTask.getStack(); - displayContent.moveStackToDisplay(stack, true /* onTop */); + stack.reparent(displayContent.getDefaultTaskDisplayArea(), true /* onTop */); return stack; } } @@ -2918,7 +2926,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int activityType = options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED ? options.getLaunchActivityType() : r.getActivityType(); - return displayContent.createStack(windowingMode, activityType, true /*onTop*/); + final TaskDisplayArea taskDisplayArea = displayContent.getDefaultTaskDisplayArea(); + return taskDisplayArea.createStack(windowingMode, activityType, true /*onTop*/); } return null; @@ -2989,7 +2998,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (preferredDisplayArea == null) { // Stack is currently detached because it is being removed. Use the previous display it // was on. - preferredDisplayArea = getDisplayContent(currentFocus.mPrevDisplayId).mTaskContainers; + preferredDisplayArea = getDisplayContent(currentFocus.mPrevDisplayId) + .getDefaultTaskDisplayArea(); } final ActivityStack preferredFocusableStack = preferredDisplayArea.getNextFocusableStack( currentFocus, ignoreCurrent); @@ -3010,7 +3020,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // We've already checked this one continue; } - final ActivityStack nextFocusableStack = display.mTaskContainers + final ActivityStack nextFocusableStack = display.getDefaultTaskDisplayArea() .getNextFocusableStack(currentFocus, ignoreCurrent); if (nextFocusableStack != null) { return nextFocusableStack; @@ -3020,31 +3030,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return null; } - /** - * Get next valid stack for launching provided activity in the system. This will search across - * displays and stacks in last-focused order for a focusable and visible stack, except those - * that are on a currently focused display. - * - * @param r The activity that is being launched. - * @param currentFocus The display that previously had focus and thus needs to be ignored when - * searching for the next candidate. - * @return Next valid {@link ActivityStack}, null if not found. - */ - ActivityStack getNextValidLaunchStack(@NonNull ActivityRecord r, int currentFocus) { - for (int i = getChildCount() - 1; i >= 0; --i) { - final DisplayContent display = getChildAt(i); - if (display.mDisplayId == currentFocus) { - continue; - } - final ActivityStack stack = getValidLaunchStackOnDisplay(display.mDisplayId, r, - null /* options */, null /* launchParams */); - if (stack != null) { - return stack; - } - } - return null; - } - boolean handleAppDied(WindowProcessController app) { boolean hasVisibleActivities = false; for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 77ef01134292..13e4d8b038b1 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -127,6 +127,12 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { * the new stack becomes resumed after which it will be set to current focused stack. */ ActivityStack mLastFocusedStack; + /** + * All of the stacks on this display. Order matters, topmost stack is in front of all other + * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls + * changing the list should also call {@link #onStackOrderChanged()}. + */ + private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>(); TaskDisplayArea(DisplayContent displayContent, WindowManagerService service) { super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER); @@ -336,9 +342,9 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { // Since a stack could be repositioned while being one of the child, return // current index if that's the same stack we are positioning and it is always on // top. - final boolean sameStack = mDisplayContent.getStacks().get(i) == stack; + final boolean sameStack = mChildren.get(i) == stack; if ((sameStack && stack.isAlwaysOnTop()) - || (!sameStack && !mDisplayContent.getStacks().get(i).isAlwaysOnTop())) { + || (!sameStack && !mChildren.get(i).isAlwaysOnTop())) { belowAlwaysOnTopPosition = i; break; } @@ -352,7 +358,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { if (stack.isAlwaysOnTop()) { if (hasPinnedTask()) { // Always-on-top stacks go below the pinned stack. - maxPosition = mDisplayContent.getStacks().indexOf(mRootPinnedTask) - 1; + maxPosition = mChildren.indexOf(mRootPinnedTask) - 1; } // Always-on-top stacks need to be above all other stacks. minPosition = belowAlwaysOnTopPosition @@ -375,7 +381,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { targetPosition = Math.min(targetPosition, maxPosition); targetPosition = Math.max(targetPosition, minPosition); - int prevPosition = mDisplayContent.getStacks().indexOf(stack); + int prevPosition = mChildren.indexOf(stack); // The positions we calculated above (maxPosition, minPosition) do not take into // consideration the following edge cases. // 1) We need to adjust the position depending on the value "adding". @@ -471,7 +477,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { @Override int getOrientation(int candidate) { - if (mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { + if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { // Apps and their containers are not allowed to specify an orientation while using // root tasks...except for the home stack if it is not resizable and currently // visible (top of) its root task. @@ -637,7 +643,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { mPreferredTopFocusableStack = null; } mDisplayContent.releaseSelfIfNeeded(); - mDisplayContent.onStackOrderChanged(stack); + onStackOrderChanged(stack); } void positionStackAt(int position, ActivityStack child, boolean includingParents) { @@ -716,7 +722,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } } - mDisplayContent.onStackOrderChanged(stack); + onStackOrderChanged(stack); } ActivityStack getStack(int rootTaskId) { @@ -765,11 +771,11 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } else if (stack.getParent() != launchRootTask) { stack.reparent(launchRootTask, position); } - } else if (stack.getDisplay() != mDisplayContent || !stack.isRootTask()) { + } else if (stack.getDisplayArea() != this || !stack.isRootTask()) { if (stack.getParent() == null) { addStack(stack, position); } else { - stack.reparent(mDisplayContent, onTop); + stack.reparent(this, onTop); } } // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen. @@ -832,7 +838,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { // Create stack on default display instead since this display can only contain 1 stack. // TODO: Kinda a hack, but better that having the decision at each call point. Hoping // this goes away once ActivityView is no longer using virtual displays. - return mRootWindowContainer.getDefaultDisplay().mTaskContainers.createStack( + return mRootWindowContainer.getDefaultTaskDisplayArea().createStack( windowingMode, activityType, onTop, info, intent, createdByOrganizer); } @@ -1551,6 +1557,16 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { return (index < wc.mChildren.size()) ? (ActivityStack) wc.mChildren.get(index) : null; } + /** Returns true if the stack in the windowing mode is visible. */ + boolean isStackVisible(int windowingMode) { + final ActivityStack stack = getTopStackInWindowingMode(windowingMode); + return stack != null && stack.isVisible(); + } + + void removeStack(ActivityStack stack) { + removeChild(stack); + } + int getDisplayId() { return mDisplayContent.getDisplayId(); } @@ -1558,4 +1574,39 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { boolean isRemoved() { return mDisplayContent.isRemoved(); } + + /** + * Adds a listener to be notified whenever the stack order in the display changes. Currently + * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the + * current animation when the system state changes. + */ + void registerStackOrderChangedListener(OnStackOrderChangedListener listener) { + if (!mStackOrderChangedCallbacks.contains(listener)) { + mStackOrderChangedCallbacks.add(listener); + } + } + + /** + * Removes a previously registered stack order change listener. + */ + void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) { + mStackOrderChangedCallbacks.remove(listener); + } + + /** + * Notifies of a stack order change + * @param stack The stack which triggered the order change + */ + void onStackOrderChanged(ActivityStack stack) { + for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) { + mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack); + } + } + + /** + * Callback for when the order of the stacks in the display changes. + */ + interface OnStackOrderChangedListener { + void onStackOrderChanged(ActivityStack stack); + } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index b641e4c391ce..9ffd8d244c75 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -277,7 +277,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return null; } - final Task task = display.mTaskContainers.createStack(windowingMode, + final Task task = display.getDefaultTaskDisplayArea().createStack(windowingMode, ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(), true /* createdByOrganizer */); RunningTaskInfo out = task.getTaskInfo(); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index e43f4b485349..b9b6c0858031 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -510,7 +510,7 @@ class WallpaperController { private void findWallpaperTarget() { mFindResults.reset(); - if (mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM)) { + if (mDisplayContent.getDefaultTaskDisplayArea().isStackVisible(WINDOWING_MODE_FREEFORM)) { // In freeform mode we set the wallpaper as its own target, so we don't need an // additional window to make it visible. mFindResults.setUseTopWallpaperAsTarget(true); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index dfaa0ec47155..687af64e9d4f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7318,8 +7318,9 @@ public class WindowManagerService extends IWindowManager.Stub @Override public boolean isStackVisibleLw(int windowingMode) { - final DisplayContent dc = getDefaultDisplayContentLocked(); - return dc.isStackVisible(windowingMode); + // TODO(multi-display-area): Support multiple task display areas & displays + final TaskDisplayArea tc = mRoot.getDefaultTaskDisplayArea(); + return tc.isStackVisible(windowingMode); } @Override diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a332b6966291..3e2e9be24c4f 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -234,7 +234,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (task.getParent() != newParent) { if (newParent == null) { // Re-parent task to display as a root task. - dc.moveStackToDisplay(as, hop.getToTop()); + as.reparent(dc.getDefaultTaskDisplayArea(), hop.getToTop()); } else if (newParent.inMultiWindowMode() && !task.isResizeable() && task.isLeafTask()) { Slog.w(TAG, "Can't support task that doesn't support multi-window mode in" diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b87d18143fc7..8e7585ae4bfa 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1516,7 +1516,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Some system windows (e.g. "Power off" dialog) don't have a task, but we would still // associate them with some stack to enable dimming. final DisplayContent dc = getDisplayContent(); - return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getRootHomeTask() : null; + return mAttrs.type >= FIRST_SYSTEM_WINDOW + && dc != null ? dc.getDefaultTaskDisplayArea().getRootHomeTask() : null; } /** 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/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 3c2b6ec9711d..7457a1d05335 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -397,7 +397,8 @@ class WindowToken extends WindowContainer<WindowState> { void assignLayer(SurfaceControl.Transaction t, int layer) { if (windowType == TYPE_DOCK_DIVIDER) { // See {@link DisplayContent#mSplitScreenDividerAnchor} - super.assignRelativeLayer(t, mDisplayContent.getSplitScreenDividerAnchor(), 1); + super.assignRelativeLayer(t, + mDisplayContent.getDefaultTaskDisplayArea().getSplitScreenDividerAnchor(), 1); } else if (mRoundedCornerOverlay) { super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1); } else { diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp index b13d33054e19..02bb0bc3e49c 100644 --- a/services/incremental/Android.bp +++ b/services/incremental/Android.bp @@ -50,6 +50,7 @@ cc_defaults { "libbinder", "libcrypto", "libcutils", + "libdataloader", "libincfs", "liblog", "libz", diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 5e3c337da11e..0da167303ccd 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -17,6 +17,7 @@ #define LOG_TAG "IncrementalService" #include "IncrementalService.h" +#include "IncrementalServiceValidation.h" #include <android-base/file.h> #include <android-base/logging.h> @@ -35,6 +36,7 @@ #include <uuid/uuid.h> #include <zlib.h> +#include <charconv> #include <ctime> #include <filesystem> #include <iterator> @@ -49,6 +51,9 @@ using namespace std::literals; using namespace android::content::pm; namespace fs = std::filesystem; +constexpr const char* kDataUsageStats = "android.permission.LOADER_USAGE_STATS"; +constexpr const char* kOpUsage = "android:get_usage_stats"; + namespace android::incremental { namespace { @@ -231,6 +236,7 @@ IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_v : mVold(sm.getVoldService()), mDataLoaderManager(sm.getDataLoaderManager()), mIncFs(sm.getIncFs()), + mAppOpsManager(sm.getAppOpsManager()), mIncrementalDir(rootDir) { if (!mVold) { LOG(FATAL) << "Vold service is unavailable"; @@ -238,6 +244,9 @@ IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_v if (!mDataLoaderManager) { LOG(FATAL) << "DataLoaderManagerService is unavailable"; } + if (!mAppOpsManager) { + LOG(FATAL) << "AppOpsManager is unavailable"; + } mountExistingImages(); } @@ -275,12 +284,12 @@ void IncrementalService::onDump(int fd) { const IncFsMount& mnt = *ifs.get(); dprintf(fd, "\t[%d]:\n", id); dprintf(fd, "\t\tmountId: %d\n", mnt.mountId); + dprintf(fd, "\t\troot: %s\n", mnt.root.c_str()); dprintf(fd, "\t\tnextStorageDirNo: %d\n", mnt.nextStorageDirNo.load()); dprintf(fd, "\t\tdataLoaderStatus: %d\n", mnt.dataLoaderStatus.load()); - dprintf(fd, "\t\tconnectionLostTime: %s\n", toString(mnt.connectionLostTime)); - if (mnt.savedDataLoaderParams) { - const auto& params = mnt.savedDataLoaderParams.value(); - dprintf(fd, "\t\tsavedDataLoaderParams:\n"); + { + const auto& params = mnt.dataLoaderParams; + dprintf(fd, "\t\tdataLoaderParams:\n"); dprintf(fd, "\t\t\ttype: %s\n", toString(params.type).c_str()); dprintf(fd, "\t\t\tpackageName: %s\n", params.packageName.c_str()); dprintf(fd, "\t\t\tclassName: %s\n", params.className.c_str()); @@ -328,6 +337,7 @@ std::optional<std::future<void>> IncrementalService::onSystemReady() { } std::thread([this, mounts = std::move(mounts)]() { + /* TODO(b/151241369): restore data loaders on reboot. for (auto&& ifs : mounts) { if (prepareDataLoader(*ifs)) { LOG(INFO) << "Successfully started data loader for mount " << ifs->mountId; @@ -336,6 +346,7 @@ std::optional<std::future<void>> IncrementalService::onSystemReady() { LOG(WARNING) << "Failed to start data loader for mount " << ifs->mountId; } } + */ mPrepareDataLoaders.set_value_at_thread_exit(); }).detach(); return mPrepareDataLoaders.get_future(); @@ -455,13 +466,15 @@ StorageId IncrementalService::createStorage( return kInvalidStorageId; } + ifs->dataLoaderParams = std::move(dataLoaderParams); + { metadata::Mount m; m.mutable_storage()->set_id(ifs->mountId); - m.mutable_loader()->set_type((int)dataLoaderParams.type); - m.mutable_loader()->set_package_name(dataLoaderParams.packageName); - m.mutable_loader()->set_class_name(dataLoaderParams.className); - m.mutable_loader()->set_arguments(dataLoaderParams.arguments); + m.mutable_loader()->set_type((int)ifs->dataLoaderParams.type); + m.mutable_loader()->set_package_name(ifs->dataLoaderParams.packageName); + m.mutable_loader()->set_class_name(ifs->dataLoaderParams.className); + m.mutable_loader()->set_arguments(ifs->dataLoaderParams.arguments); const auto metadata = m.SerializeAsString(); m.mutable_loader()->release_arguments(); m.mutable_loader()->release_class_name(); @@ -489,7 +502,7 @@ StorageId IncrementalService::createStorage( // Done here as well, all data structures are in good state. secondCleanupOnFailure.release(); - if (!prepareDataLoader(*ifs, &dataLoaderParams, &dataLoaderStatusListener)) { + if (!prepareDataLoader(*ifs, &dataLoaderStatusListener)) { LOG(ERROR) << "prepareDataLoader() failed"; deleteStorageLocked(*ifs, std::move(l)); return kInvalidStorageId; @@ -569,11 +582,30 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog return -EINVAL; } + ifs->dataLoaderFilesystemParams.readLogsEnabled = enableReadLogs; + if (enableReadLogs) { + // We never unregister the callbacks, but given a restricted number of data loaders and even fewer asking for read log access, should be ok. + registerAppOpsCallback(ifs->dataLoaderParams.packageName); + } + + return applyStorageParams(*ifs); +} + +int IncrementalService::applyStorageParams(IncFsMount& ifs) { + const bool enableReadLogs = ifs.dataLoaderFilesystemParams.readLogsEnabled; + if (enableReadLogs) { + if (auto status = CheckPermissionForDataDelivery(kDataUsageStats, kOpUsage); + !status.isOk()) { + LOG(ERROR) << "CheckPermissionForDataDelivery failed: " << status.toString8(); + return fromBinderStatus(status); + } + } + using unique_fd = ::android::base::unique_fd; ::android::os::incremental::IncrementalFileSystemControlParcel control; - control.cmd.reset(unique_fd(dup(ifs->control.cmd()))); - control.pendingReads.reset(unique_fd(dup(ifs->control.pendingReads()))); - auto logsFd = ifs->control.logs(); + control.cmd.reset(unique_fd(dup(ifs.control.cmd()))); + control.pendingReads.reset(unique_fd(dup(ifs.control.pendingReads()))); + auto logsFd = ifs.control.logs(); if (logsFd >= 0) { control.log.reset(unique_fd(dup(logsFd))); } @@ -582,12 +614,7 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog const auto status = mVold->setIncFsMountOptions(control, enableReadLogs); if (!status.isOk()) { LOG(ERROR) << "Calling Vold::setIncFsMountOptions() failed: " << status.toString8(); - return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC - ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode() - : status.serviceSpecificErrorCode() == 0 - ? -EFAULT - : status.serviceSpecificErrorCode() - : -EIO; + return fromBinderStatus(status); } return 0; @@ -987,13 +1014,13 @@ void IncrementalService::mountExistingImages() { continue; } const auto root = path::join(mIncrementalDir, name); - if (!mountExistingImage(root, name)) { + if (!mountExistingImage(root)) { IncFsMount::cleanupFilesystem(path); } } } -bool IncrementalService::mountExistingImage(std::string_view root, std::string_view key) { +bool IncrementalService::mountExistingImage(std::string_view root) { auto mountTarget = path::join(root, constants().mount); const auto backing = path::join(root, constants().backing); @@ -1011,16 +1038,26 @@ bool IncrementalService::mountExistingImage(std::string_view root, std::string_v auto ifs = std::make_shared<IncFsMount>(std::string(root), -1, std::move(control), *this); - auto m = parseFromIncfs<metadata::Mount>(mIncFs.get(), ifs->control, - path::join(mountTarget, constants().infoMdName)); - if (!m.has_loader() || !m.has_storage()) { + auto mount = parseFromIncfs<metadata::Mount>(mIncFs.get(), ifs->control, + path::join(mountTarget, constants().infoMdName)); + if (!mount.has_loader() || !mount.has_storage()) { LOG(ERROR) << "Bad mount metadata in mount at " << root; return false; } - ifs->mountId = m.storage().id(); + ifs->mountId = mount.storage().id(); mNextId = std::max(mNextId, ifs->mountId + 1); + // DataLoader params + { + auto& dlp = ifs->dataLoaderParams; + const auto& loader = mount.loader(); + dlp.type = (android::content::pm::DataLoaderType)loader.type(); + dlp.packageName = loader.package_name(); + dlp.className = loader.class_name(); + dlp.arguments = loader.arguments(); + } + std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints; auto d = openDir(path::c_str(mountTarget)); while (auto e = ::readdir(d.get())) { @@ -1044,16 +1081,24 @@ bool IncrementalService::mountExistingImage(std::string_view root, std::string_v } auto name = std::string_view(e->d_name); if (name.starts_with(constants().storagePrefix)) { - auto md = parseFromIncfs<metadata::Storage>(mIncFs.get(), ifs->control, - path::join(mountTarget, name)); - auto [_, inserted] = mMounts.try_emplace(md.id(), ifs); + int storageId; + const auto res = std::from_chars(name.data() + constants().storagePrefix.size() + 1, + name.data() + name.size(), storageId); + if (res.ec != std::errc{} || *res.ptr != '_') { + LOG(WARNING) << "Ignoring storage with invalid name '" << name << "' for mount " + << root; + continue; + } + auto [_, inserted] = mMounts.try_emplace(storageId, ifs); if (!inserted) { - LOG(WARNING) << "Ignoring storage with duplicate id " << md.id() + LOG(WARNING) << "Ignoring storage with duplicate id " << storageId << " for mount " << root; continue; } - ifs->storages.insert_or_assign(md.id(), IncFsMount::Storage{std::string(name)}); - mNextId = std::max(mNextId, md.id() + 1); + ifs->storages.insert_or_assign(storageId, + IncFsMount::Storage{ + path::join(root, constants().mount, name)}); + mNextId = std::max(mNextId, storageId + 1); } } } @@ -1083,23 +1128,9 @@ bool IncrementalService::mountExistingImage(std::string_view root, std::string_v } bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, - DataLoaderParamsParcel* params, const DataLoaderStatusListener* externalListener) { if (!mSystemReady.load(std::memory_order_relaxed)) { std::unique_lock l(ifs.lock); - if (params) { - if (ifs.savedDataLoaderParams) { - LOG(WARNING) << "Trying to pass second set of data loader parameters, ignored it"; - } else { - ifs.savedDataLoaderParams = std::move(*params); - } - } else { - if (!ifs.savedDataLoaderParams) { - LOG(ERROR) << "Mount " << ifs.mountId - << " is broken: no data loader params (system is not ready yet)"; - return false; - } - } return true; // eventually... } @@ -1109,12 +1140,6 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, return true; } - auto* dlp = params ? params - : ifs.savedDataLoaderParams ? &ifs.savedDataLoaderParams.value() : nullptr; - if (!dlp) { - LOG(ERROR) << "Mount " << ifs.mountId << " is broken: no data loader params"; - return false; - } FileSystemControlParcel fsControlParcel; fsControlParcel.incremental = aidl::make_nullable<IncrementalFileSystemControlParcel>(); fsControlParcel.incremental->cmd.reset(base::unique_fd(::dup(ifs.control.cmd()))); @@ -1126,13 +1151,11 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, externalListener ? *externalListener : DataLoaderStatusListener()); bool created = false; - auto status = mDataLoaderManager->initializeDataLoader(ifs.mountId, *dlp, fsControlParcel, - listener, &created); + auto status = mDataLoaderManager->initializeDataLoader(ifs.mountId, ifs.dataLoaderParams, fsControlParcel, listener, &created); if (!status.isOk() || !created) { LOG(ERROR) << "Failed to create a data loader for mount " << ifs.mountId; return false; } - ifs.savedDataLoaderParams.reset(); return true; } @@ -1256,6 +1279,42 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ return success; } +void IncrementalService::registerAppOpsCallback(const std::string& packageName) { + if (packageName.empty()) { + return; + } + + { + std::unique_lock lock{mCallbacksLock}; + if (!mCallbackRegistered.insert(packageName).second) { + return; + } + } + + /* TODO(b/152633648): restore callback after it's not crashing Binder anymore. + sp<AppOpsListener> listener = new AppOpsListener(*this, packageName); + mAppOpsManager->startWatchingMode(AppOpsManager::OP_GET_USAGE_STATS, String16(packageName.c_str()), listener); + */ +} + +void IncrementalService::onAppOppChanged(const std::string& packageName) { + std::vector<IfsMountPtr> affected; + { + std::lock_guard l(mLock); + affected.reserve(mMounts.size()); + for (auto&& [id, ifs] : mMounts) { + if (ifs->dataLoaderFilesystemParams.readLogsEnabled && ifs->dataLoaderParams.packageName == packageName) { + affected.push_back(ifs); + } + } + } + /* TODO(b/152633648): restore callback after it's not crashing Kernel anymore. + for (auto&& ifs : affected) { + applyStorageParams(*ifs); + } + */ +} + binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId, int newStatus) { if (externalListener) { @@ -1319,4 +1378,8 @@ binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChange return binder::Status::ok(); } +void IncrementalService::AppOpsListener::opChanged(int32_t op, const String16&) { + incrementalService.onAppOppChanged(packageName); +} + } // namespace android::incremental diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index 90d58a7adcf0..ff69633e185b 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -40,6 +40,7 @@ #include "ServiceWrappers.h" #include "android/content/pm/BnDataLoaderStatusListener.h" #include "incfs.h" +#include "dataloader_ndk.h" #include "path.h" using namespace android::os::incremental; @@ -132,6 +133,7 @@ public: bool startLoading(StorageId storage) const; bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath, std::string_view libDirRelativePath, std::string_view abi); + class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener { public: IncrementalDataLoaderListener(IncrementalService& incrementalService, @@ -145,6 +147,16 @@ public: DataLoaderStatusListener externalListener; }; + class AppOpsListener : public android::BnAppOpsCallback { + public: + AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {} + void opChanged(int32_t op, const String16& packageName) override; + + private: + IncrementalService& incrementalService; + const std::string packageName; + }; + private: struct IncFsMount { struct Bind { @@ -169,11 +181,11 @@ private: /*const*/ MountId mountId; StorageMap storages; BindMap bindPoints; - std::optional<DataLoaderParamsParcel> savedDataLoaderParams; + DataLoaderParamsParcel dataLoaderParams; + DataLoaderFilesystemParams dataLoaderFilesystemParams; std::atomic<int> nextStorageDirNo{0}; std::atomic<int> dataLoaderStatus = -1; bool dataLoaderStartRequested = false; - TimePoint connectionLostTime = TimePoint(); const IncrementalService& incrementalService; IncFsMount(std::string root, MountId mountId, Control control, @@ -181,7 +193,9 @@ private: : root(std::move(root)), control(std::move(control)), mountId(mountId), - incrementalService(incrementalService) {} + incrementalService(incrementalService) { + dataLoaderFilesystemParams.readLogsEnabled = false; + } IncFsMount(IncFsMount&&) = delete; IncFsMount& operator=(IncFsMount&&) = delete; ~IncFsMount(); @@ -196,7 +210,7 @@ private: using BindPathMap = std::map<std::string, IncFsMount::BindMap::iterator, path::PathLess>; void mountExistingImages(); - bool mountExistingImage(std::string_view root, std::string_view key); + bool mountExistingImage(std::string_view root); IfsMountPtr getIfs(StorageId storage) const; const IfsMountPtr& getIfsLocked(StorageId storage) const; @@ -208,8 +222,7 @@ private: std::string&& source, std::string&& target, BindKind kind, std::unique_lock<std::mutex>& mainLock); - bool prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel* params = nullptr, - const DataLoaderStatusListener* externalListener = nullptr); + bool prepareDataLoader(IncFsMount& ifs, const DataLoaderStatusListener* externalListener = nullptr); bool startDataLoader(MountId mountId) const; BindPathMap::const_iterator findStorageLocked(std::string_view path) const; @@ -221,10 +234,16 @@ private: std::string normalizePathToStorage(const IfsMountPtr incfs, StorageId storage, std::string_view path); + int applyStorageParams(IncFsMount& ifs); + + void registerAppOpsCallback(const std::string& packageName); + void onAppOppChanged(const std::string& packageName); + // Member variables - std::unique_ptr<VoldServiceWrapper> mVold; - std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager; - std::unique_ptr<IncFsWrapper> mIncFs; + std::unique_ptr<VoldServiceWrapper> const mVold; + std::unique_ptr<DataLoaderManagerWrapper> const mDataLoaderManager; + std::unique_ptr<IncFsWrapper> const mIncFs; + std::unique_ptr<AppOpsManagerWrapper> const mAppOpsManager; const std::string mIncrementalDir; mutable std::mutex mLock; @@ -232,6 +251,9 @@ private: MountMap mMounts; BindPathMap mBindsByPath; + std::mutex mCallbacksLock; + std::set<std::string> mCallbackRegistered; + std::atomic_bool mSystemReady = false; StorageId mNextId = 0; std::promise<void> mPrepareDataLoaders; diff --git a/services/incremental/IncrementalServiceValidation.h b/services/incremental/IncrementalServiceValidation.h new file mode 100644 index 000000000000..24f9f7f94dfd --- /dev/null +++ b/services/incremental/IncrementalServiceValidation.h @@ -0,0 +1,75 @@ +/* + * 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. + */ + +#pragma once + +#include <android-base/stringprintf.h> +#include <binder/IPCThreadState.h> +#include <binder/PermissionCache.h> +#include <binder/PermissionController.h> +#include <binder/Status.h> + +namespace android::incremental { + +inline binder::Status Ok() { + return binder::Status::ok(); +} + +inline binder::Status Exception(uint32_t code, const std::string& msg) { + return binder::Status::fromExceptionCode(code, String8(msg.c_str())); +} + +inline int fromBinderStatus(const binder::Status& status) { + return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC + ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode() + : status.serviceSpecificErrorCode() == 0 + ? -EFAULT + : status.serviceSpecificErrorCode() + : -EIO; +} + +inline binder::Status CheckPermissionForDataDelivery(const char* permission, const char* operation) { + using android::base::StringPrintf; + + int32_t pid; + int32_t uid; + + if (!PermissionCache::checkCallingPermission(String16(permission), &pid, &uid)) { + return Exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, permission)); + } + + // Caller must also have op granted. + PermissionController pc; + // Package is a required parameter. Need to obtain one. + Vector<String16> packages; + pc.getPackagesForUid(uid, packages); + if (packages.empty()) { + return Exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d / PID %d has no packages", uid, pid)); + } + switch (auto result = pc.noteOp(String16(operation), uid, packages[0]); result) { + case PermissionController::MODE_ALLOWED: + case PermissionController::MODE_DEFAULT: + return binder::Status::ok(); + default: + return Exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d / PID %d lacks app-op %s, error %d", uid, pid, + operation, result)); + } +} + +} // namespace android::incremental diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index 2e31ef1dcc2f..9f4192fbf531 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -59,4 +59,8 @@ std::unique_ptr<IncFsWrapper> RealServiceManager::getIncFs() { return std::make_unique<RealIncFs>(); } +std::unique_ptr<AppOpsManagerWrapper> RealServiceManager::getAppOpsManager() { + return std::make_unique<RealAppOpsManager>(); +} + } // namespace android::os::incremental diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index c3300305f515..449b457045c6 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -24,6 +24,7 @@ #include <android/content/pm/IDataLoaderManager.h> #include <android/content/pm/IDataLoaderStatusListener.h> #include <android/os/IVold.h> +#include <binder/AppOpsManager.h> #include <binder/IServiceManager.h> #include <incfs.h> @@ -81,12 +82,19 @@ public: virtual ErrorCode writeBlocks(Span<const DataBlock> blocks) const = 0; }; +class AppOpsManagerWrapper { +public: + virtual ~AppOpsManagerWrapper() = default; + virtual void startWatchingMode(int32_t op, const String16& packageName, const sp<IAppOpsCallback>& callback) = 0; +}; + class ServiceManagerWrapper { public: virtual ~ServiceManagerWrapper() = default; virtual std::unique_ptr<VoldServiceWrapper> getVoldService() = 0; virtual std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() = 0; virtual std::unique_ptr<IncFsWrapper> getIncFs() = 0; + virtual std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() = 0; }; // --- Real stuff --- @@ -137,13 +145,24 @@ private: sp<content::pm::IDataLoaderManager> mInterface; }; +class RealAppOpsManager : public AppOpsManagerWrapper { +public: + ~RealAppOpsManager() = default; + void startWatchingMode(int32_t op, const String16& packageName, const sp<IAppOpsCallback>& callback) override { + mAppOpsManager.startWatchingMode(op, packageName, callback); + } +private: + android::AppOpsManager mAppOpsManager; +}; + class RealServiceManager : public ServiceManagerWrapper { public: RealServiceManager(sp<IServiceManager> serviceManager); ~RealServiceManager() = default; - std::unique_ptr<VoldServiceWrapper> getVoldService() override; - std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() override; - std::unique_ptr<IncFsWrapper> getIncFs() override; + std::unique_ptr<VoldServiceWrapper> getVoldService() final; + std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final; + std::unique_ptr<IncFsWrapper> getIncFs() final; + std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final; private: template <class INTERFACE> diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index cde38fbb3ca2..5553f688060a 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -220,24 +220,32 @@ public: } }; +class MockAppOpsManager : public AppOpsManagerWrapper { + MOCK_METHOD3(startWatchingMode, void(int32_t, const String16&, const sp<IAppOpsCallback>&)); +}; + class MockServiceManager : public ServiceManagerWrapper { public: MockServiceManager(std::unique_ptr<MockVoldService> vold, std::unique_ptr<MockDataLoaderManager> manager, - std::unique_ptr<MockIncFs> incfs) + std::unique_ptr<MockIncFs> incfs, + std::unique_ptr<MockAppOpsManager> appOpsManager) : mVold(std::move(vold)), mDataLoaderManager(std::move(manager)), - mIncFs(std::move(incfs)) {} + mIncFs(std::move(incfs)), + mAppOpsManager(std::move(appOpsManager)) {} std::unique_ptr<VoldServiceWrapper> getVoldService() final { return std::move(mVold); } std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final { return std::move(mDataLoaderManager); } std::unique_ptr<IncFsWrapper> getIncFs() final { return std::move(mIncFs); } + std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final { return std::move(mAppOpsManager); } private: std::unique_ptr<MockVoldService> mVold; std::unique_ptr<MockDataLoaderManager> mDataLoaderManager; std::unique_ptr<MockIncFs> mIncFs; + std::unique_ptr<MockAppOpsManager> mAppOpsManager; }; // --- IncrementalServiceTest --- @@ -251,11 +259,13 @@ public: mDataLoaderManager = dataloaderManager.get(); auto incFs = std::make_unique<NiceMock<MockIncFs>>(); mIncFs = incFs.get(); + auto appOps = std::make_unique<NiceMock<MockAppOpsManager>>(); + mAppOpsManager = appOps.get(); mIncrementalService = std::make_unique<IncrementalService>(MockServiceManager(std::move(vold), - std::move( - dataloaderManager), - std::move(incFs)), + std::move(dataloaderManager), + std::move(incFs), + std::move(appOps)), mRootDir.path); mDataLoaderParcel.packageName = "com.test"; mDataLoaderParcel.arguments = "uri"; @@ -287,6 +297,7 @@ protected: NiceMock<MockVoldService>* mVold; NiceMock<MockIncFs>* mIncFs; NiceMock<MockDataLoaderManager>* mDataLoaderManager; + NiceMock<MockAppOpsManager>* mAppOpsManager; std::unique_ptr<IncrementalService> mIncrementalService; TemporaryDir mRootDir; DataLoaderParamsParcel mDataLoaderParcel; diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java index 5480b6ced4e1..c225d3feb063 100644 --- a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java +++ b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java @@ -108,7 +108,8 @@ final class RemoteSystemCaptionsManagerService { } mBinding = true; - int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; + int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE + | Context.BIND_INCLUDE_CAPABILITIES; boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection, flags, mHandler, new UserHandle(mUserId)); if (!willBind) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceBaseTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceBaseTest.java new file mode 100644 index 000000000000..4fe94583c8b3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceBaseTest.java @@ -0,0 +1,162 @@ +/* + * 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.server.biometrics; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.hardware.biometrics.BiometricAuthenticator; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@SmallTest +public class BiometricServiceBaseTest { + private static class TestableBiometricServiceBase extends BiometricServiceBase { + TestableBiometricServiceBase(Context context) { + super(context); + } + + @Override + protected String getTag() { + return null; + } + + @Override + protected DaemonWrapper getDaemonWrapper() { + return null; + } + + @Override + protected BiometricUtils getBiometricUtils() { + return null; + } + + @Override + protected Constants getConstants() { + return null; + } + + @Override + protected boolean hasReachedEnrollmentLimit(int userId) { + return false; + } + + @Override + protected void updateActiveGroup(int userId, String clientPackage) { + } + + @Override + protected String getLockoutResetIntent() { + return null; + } + + @Override + protected String getLockoutBroadcastPermission() { + return null; + } + + @Override + protected long getHalDeviceId() { + return 0; + } + + @Override + protected boolean hasEnrolledBiometrics(int userId) { + return false; + } + + @Override + protected String getManageBiometricPermission() { + return null; + } + + @Override + protected void checkUseBiometricPermission() { + } + + @Override + protected boolean checkAppOps(int uid, String opPackageName) { + return false; + } + + @Override + protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates( + int userId) { + return null; + } + + @Override + protected int statsModality() { + return 0; + } + + @Override + protected int getLockoutMode() { + return 0; + } + } + + private static final int CLIENT_COOKIE = 0xc00c1e; + + private BiometricServiceBase mBiometricServiceBase; + + @Mock + private Context mContext; + @Mock + private Resources mResources; + @Mock + private BiometricAuthenticator.Identifier mIdentifier; + @Mock + private ClientMonitor mClient; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getString(anyInt())).thenReturn(""); + when(mClient.getCookie()).thenReturn(CLIENT_COOKIE); + + mBiometricServiceBase = new TestableBiometricServiceBase(mContext); + } + + @Test + public void testHandleEnumerate_doesNotCrash_withNullClient() { + mBiometricServiceBase.handleEnumerate(mIdentifier, 0 /* remaining */); + } + + @Test + public void testStartClient_sendsErrorAndRemovesClient_onNonzeroErrorCode() { + when(mClient.start()).thenReturn(1); + + mBiometricServiceBase.startClient(mClient, false /* initiatedByClient */); + + verify(mClient).onError(anyLong(), anyInt(), anyInt()); + verify(mClient).destroy(); + } +} 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/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java index 05604b2b9aeb..1debd8c9ffb1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java @@ -61,19 +61,20 @@ public class ActivityDisplayTests extends ActivityTestsBase { @Test public void testLastFocusedStackIsUpdatedWhenMovingStack() { // Create a stack at bottom. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); + final TaskDisplayArea taskDisplayAreas = + mRootWindowContainer.getDefaultDisplay().getDefaultTaskDisplayArea(); final ActivityStack stack = new StackBuilder(mRootWindowContainer).setOnTop(!ON_TOP).build(); - final ActivityStack prevFocusedStack = display.getFocusedStack(); + final ActivityStack prevFocusedStack = taskDisplayAreas.getFocusedStack(); stack.moveToFront("moveStackToFront"); // After moving the stack to front, the previous focused should be the last focused. assertTrue(stack.isFocusedStackOnDisplay()); - assertEquals(prevFocusedStack, display.mTaskContainers.getLastFocusedStack()); + assertEquals(prevFocusedStack, taskDisplayAreas.getLastFocusedStack()); stack.moveToBack("moveStackToBack", null /* task */); // After moving the stack to back, the stack should be the last focused. - assertEquals(stack, display.mTaskContainers.getLastFocusedStack()); + assertEquals(stack, taskDisplayAreas.getLastFocusedStack()); } /** @@ -83,8 +84,8 @@ public class ActivityDisplayTests extends ActivityTestsBase { @Test public void testFullscreenStackCanBeFocusedWhenFocusablePinnedStackExists() { // Create a pinned stack and move to front. - final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP); + final ActivityStack pinnedStack = mRootWindowContainer.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP); final Task pinnedTask = new TaskBuilder(mService.mStackSupervisor) .setStack(pinnedStack).build(); new ActivityBuilder(mService).setActivityFlags(FLAG_ALWAYS_FOCUSABLE) @@ -104,8 +105,8 @@ public class ActivityDisplayTests extends ActivityTestsBase { } /** - * Test {@link DisplayContent#mPreferredTopFocusableStack} will be cleared when the stack is - * removed or moved to back, and the focused stack will be according to z-order. + * Test {@link TaskDisplayArea#mPreferredTopFocusableStack} will be cleared when + * the stack is removed or moved to back, and the focused stack will be according to z-order. */ @Test public void testStackShouldNotBeFocusedAfterMovingToBackOrRemoving() { @@ -124,7 +125,7 @@ public class ActivityDisplayTests extends ActivityTestsBase { assertTrue(stack1.isFocusedStackOnDisplay()); // Stack2 should be focused after removing stack1. - display.removeStack(stack1); + stack1.getDisplayArea().removeStack(stack1); assertTrue(stack2.isFocusedStackOnDisplay()); } @@ -156,7 +157,7 @@ public class ActivityDisplayTests extends ActivityTestsBase { } private ActivityStack createFullscreenStackWithSimpleActivityAt(DisplayContent display) { - final ActivityStack fullscreenStack = display.createStack( + final ActivityStack fullscreenStack = display.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); final Task fullscreenTask = new TaskBuilder(mService.mStackSupervisor) .setStack(fullscreenStack).build(); @@ -219,58 +220,56 @@ public class ActivityDisplayTests extends ActivityTestsBase { */ @Test public void testAlwaysOnTopStackLocation() { - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack alwaysOnTopStack = display.createStack(WINDOWING_MODE_FREEFORM, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack alwaysOnTopStack = taskDisplayArea.createStack(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) .setStack(alwaysOnTopStack).build(); alwaysOnTopStack.setAlwaysOnTop(true); - display.mTaskContainers.positionStackAtTop(alwaysOnTopStack, false /* includingParents */); + taskDisplayArea.positionStackAtTop(alwaysOnTopStack, false /* includingParents */); assertTrue(alwaysOnTopStack.isAlwaysOnTop()); // Ensure always on top state is synced to the children of the stack. assertTrue(alwaysOnTopStack.getTopNonFinishingActivity().isAlwaysOnTop()); - assertEquals(alwaysOnTopStack, display.getTopStack()); + assertEquals(alwaysOnTopStack, taskDisplayArea.getTopStack()); - final ActivityStack pinnedStack = display.createStack( + final ActivityStack pinnedStack = taskDisplayArea.createStack( WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - assertEquals(pinnedStack, display.getRootPinnedTask()); - assertEquals(pinnedStack, display.getTopStack()); + assertEquals(pinnedStack, taskDisplayArea.getRootPinnedTask()); + assertEquals(pinnedStack, taskDisplayArea.getTopStack()); - final ActivityStack anotherAlwaysOnTopStack = display.createStack( + final ActivityStack anotherAlwaysOnTopStack = taskDisplayArea.createStack( WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); anotherAlwaysOnTopStack.setAlwaysOnTop(true); - display.mTaskContainers.positionStackAtTop(anotherAlwaysOnTopStack, - false /* includingParents */); + taskDisplayArea.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */); assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop()); - int topPosition = display.getStackCount() - 1; + int topPosition = taskDisplayArea.getStackCount() - 1; // Ensure the new alwaysOnTop stack is put below the pinned stack, but on top of the // existing alwaysOnTop stack. - assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 1)); + assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 1)); - final ActivityStack nonAlwaysOnTopStack = display.createStack( + final ActivityStack nonAlwaysOnTopStack = taskDisplayArea.createStack( WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); - assertEquals(display, nonAlwaysOnTopStack.getDisplay()); - topPosition = display.getStackCount() - 1; + assertEquals(taskDisplayArea, nonAlwaysOnTopStack.getDisplayArea()); + topPosition = taskDisplayArea.getStackCount() - 1; // Ensure the non-alwaysOnTop stack is put below the three alwaysOnTop stacks, but above the // existing other non-alwaysOnTop stacks. - assertEquals(nonAlwaysOnTopStack, display.getStackAt(topPosition - 3)); + assertEquals(nonAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 3)); anotherAlwaysOnTopStack.setAlwaysOnTop(false); - display.mTaskContainers.positionStackAtTop(anotherAlwaysOnTopStack, - false /* includingParents */); + taskDisplayArea.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */); assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop()); // Ensure, when always on top is turned off for a stack, the stack is put just below all // other always on top stacks. - assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 2)); + assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 2)); anotherAlwaysOnTopStack.setAlwaysOnTop(true); // Ensure always on top state changes properly when windowing mode changes. anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop()); - assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 2)); + assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 2)); anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FREEFORM); assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop()); - assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 1)); + assertEquals(anotherAlwaysOnTopStack, taskDisplayArea.getStackAt(topPosition - 1)); } @Test @@ -286,14 +285,14 @@ public class ActivityDisplayTests extends ActivityTestsBase { } private void removeStackTests(Runnable runnable) { - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack stack1 = display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack stack1 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); - final ActivityStack stack2 = display.createStack(WINDOWING_MODE_FULLSCREEN, + final ActivityStack stack2 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); - final ActivityStack stack3 = display.createStack(WINDOWING_MODE_FULLSCREEN, + final ActivityStack stack3 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); - final ActivityStack stack4 = display.createStack(WINDOWING_MODE_FULLSCREEN, + final ActivityStack stack4 = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); final Task task1 = new TaskBuilder(mService.mStackSupervisor).setStack(stack1).build(); final Task task2 = new TaskBuilder(mService.mStackSupervisor).setStack(stack2).build(); @@ -302,13 +301,13 @@ public class ActivityDisplayTests extends ActivityTestsBase { // Reordering stacks while removing stacks. doAnswer(invocation -> { - display.mTaskContainers.positionStackAtTop(stack3, false); + taskDisplayArea.positionStackAtTop(stack3, false); return true; }).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any()); // Removing stacks from the display while removing stacks. doAnswer(invocation -> { - display.removeStack(stack2); + taskDisplayArea.removeStack(stack2); return true; }).when(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 747ae949c97e..08f6409cb902 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -82,6 +82,7 @@ import android.os.PersistableBundle; import android.platform.test.annotations.Presubmit; import android.util.MergedConfiguration; import android.util.MutableBoolean; +import android.view.DisplayInfo; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner.Stub; import android.view.RemoteAnimationAdapter; @@ -399,6 +400,16 @@ public class ActivityRecordTests extends ActivityTestsBase { mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); final Rect stableRect = new Rect(); mStack.getDisplay().mDisplayContent.getStableRect(stableRect); + + // Carve out non-decor insets from stableRect + final Rect insets = new Rect(); + final DisplayInfo displayInfo = mStack.getDisplay().getDisplayInfo(); + final DisplayPolicy policy = mStack.getDisplay().getDisplayPolicy(); + policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, + displayInfo.logicalHeight, displayInfo.displayCutout, insets); + policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation); + Task.intersectWithInsetsIfFits(stableRect, stableRect, insets); + final boolean isScreenPortrait = stableRect.width() <= stableRect.height(); final Rect bounds = new Rect(stableRect); if (isScreenPortrait) { @@ -427,7 +438,17 @@ public class ActivityRecordTests extends ActivityTestsBase { public void ignoreRequestedOrientationInSplitWindows() { mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); final Rect stableRect = new Rect(); - mStack.getDisplay().mDisplayContent.getStableRect(stableRect); + mStack.getDisplay().getStableRect(stableRect); + + // Carve out non-decor insets from stableRect + final Rect insets = new Rect(); + final DisplayInfo displayInfo = mStack.getDisplay().getDisplayInfo(); + final DisplayPolicy policy = mStack.getDisplay().getDisplayPolicy(); + policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, + displayInfo.logicalHeight, displayInfo.displayCutout, insets); + policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation); + Task.intersectWithInsetsIfFits(stableRect, stableRect, insets); + final boolean isScreenPortrait = stableRect.width() <= stableRect.height(); final Rect bounds = new Rect(stableRect); if (isScreenPortrait) { @@ -1379,8 +1400,8 @@ public class ActivityRecordTests extends ActivityTestsBase { display = new TestDisplayContent.Builder(mService, 2000, 1000).setDensityDpi(300) .setPosition(DisplayContent.POSITION_TOP).build(); } - final ActivityStack stack = display.createStack(WINDOWING_MODE_UNDEFINED, - ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack stack = display.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); final Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); return new ActivityBuilder(mService).setTask(task).setUseProcess(process).build(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java index e8c0362c9f32..22d7fcbb4162 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java @@ -60,7 +60,7 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { @Before public void setUp() throws Exception { - mFullscreenStack = mRootWindowContainer.getDefaultDisplay().createStack( + mFullscreenStack = mRootWindowContainer.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index af5afff4bed1..3d15401cdfb9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -82,15 +82,15 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(WindowTestRunner.class) public class ActivityStackTests extends ActivityTestsBase { - private DisplayContent mDefaultDisplay; + private TaskDisplayArea mDefaultTaskDisplayArea; private ActivityStack mStack; private Task mTask; @Before public void setUp() throws Exception { - mDefaultDisplay = mRootWindowContainer.getDefaultDisplay(); - mStack = mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + mDefaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + mStack = mDefaultTaskDisplayArea.createStack(WINDOWING_MODE_UNDEFINED, + ACTIVITY_TYPE_STANDARD, true /* onTop */); spyOn(mStack); mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); } @@ -112,7 +112,7 @@ public class ActivityStackTests extends ActivityTestsBase { r.setState(RESUMED, "testResumedActivityFromTaskReparenting"); assertEquals(r, mStack.getResumedActivity()); - final ActivityStack destStack = mRootWindowContainer.getDefaultDisplay().createStack( + final ActivityStack destStack = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); mTask.reparent(destStack, true /* toTop */, Task.REPARENT_KEEP_STACK_AT_FRONT, @@ -130,7 +130,7 @@ public class ActivityStackTests extends ActivityTestsBase { r.setState(RESUMED, "testResumedActivityFromActivityReparenting"); assertEquals(r, mStack.getResumedActivity()); - final ActivityStack destStack = mRootWindowContainer.getDefaultDisplay().createStack( + final ActivityStack destStack = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); mTask.reparent(destStack, true /*toTop*/, REPARENT_MOVE_STACK_TO_FRONT, false, false, "testResumedActivityFromActivityReparenting"); @@ -143,7 +143,7 @@ public class ActivityStackTests extends ActivityTestsBase { public void testPrimarySplitScreenRestoresWhenMovedToBack() { // Create primary splitscreen stack. This will create secondary stacks and places the // existing fullscreen stack on the bottom. - final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Assert windowing mode. @@ -154,7 +154,7 @@ public class ActivityStackTests extends ActivityTestsBase { null /* task */); // Assert that stack is at the bottom. - assertEquals(0, mDefaultDisplay.getIndexOf(primarySplitScreen)); + assertEquals(0, mDefaultTaskDisplayArea.getIndexOf(primarySplitScreen)); // Ensure no longer in splitscreen. assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); @@ -167,7 +167,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testPrimarySplitScreenRestoresPreviousWhenMovedToBack() { // This time, start with a fullscreen activitystack - final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); primarySplitScreen.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); @@ -180,7 +180,7 @@ public class ActivityStackTests extends ActivityTestsBase { null /* task */); // Assert that stack is at the bottom. - assertEquals(0, mDefaultDisplay.getIndexOf(primarySplitScreen)); + assertEquals(0, mDefaultTaskDisplayArea.getIndexOf(primarySplitScreen)); // Ensure that the override mode is restored to what it was (fullscreen) assertEquals(WINDOWING_MODE_FULLSCREEN, @@ -189,14 +189,14 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testStackInheritsDisplayWindowingMode() { - final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getRequestedOverrideWindowingMode()); - mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM); + mDefaultTaskDisplayArea.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(WINDOWING_MODE_FREEFORM, primarySplitScreen.getWindowingMode()); assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getRequestedOverrideWindowingMode()); @@ -204,7 +204,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testStackOverridesDisplayWindowingMode() { - final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + final ActivityStack primarySplitScreen = mDefaultTaskDisplayArea.createStack( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); @@ -216,7 +216,7 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getRequestedOverrideWindowingMode()); - mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM); + mDefaultTaskDisplayArea.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); } @@ -283,10 +283,11 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveStackToBackIncludingParent() { - final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP); - final ActivityStack stack1 = createStackForShouldBeVisibleTest(display, + final TaskDisplayArea taskDisplayArea = addNewDisplayContentAt(DisplayContent.POSITION_TOP) + .getDefaultTaskDisplayArea(); + final ActivityStack stack1 = createStackForShouldBeVisibleTest(taskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack stack2 = createStackForShouldBeVisibleTest(display, + final ActivityStack stack2 = createStackForShouldBeVisibleTest(taskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Do not move display to back because there is still another stack. @@ -294,16 +295,16 @@ public class ActivityStackTests extends ActivityTestsBase { verify(stack2).positionChildAtBottom(any(), eq(false) /* includingParents */); // Also move display to back because there is only one stack left. - display.removeStack(stack1); + taskDisplayArea.removeStack(stack1); stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.getTopMostTask()); verify(stack2).positionChildAtBottom(any(), eq(true) /* includingParents */); } @Test public void testShouldBeVisible_Fullscreen() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Add an activity to the pinned stack so it isn't considered empty for visibility check. final ActivityRecord pinnedActivity = new ActivityBuilder(mService) @@ -314,8 +315,9 @@ public class ActivityStackTests extends ActivityTestsBase { assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); - final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack // should be visible since it is always on-top. doReturn(false).when(fullscreenStack).isTranslucent(any()); @@ -331,15 +333,15 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testShouldBeVisible_SplitScreen() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); // Home stack should always be fullscreen for this test. doReturn(false).when(homeStack).supportsSplitScreenWindowingMode(); final ActivityStack splitScreenPrimary = - createStackForShouldBeVisibleTest(mDefaultDisplay, + createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack splitScreenSecondary = - createStackForShouldBeVisibleTest(mDefaultDisplay, + createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Home stack shouldn't be visible if both halves of split-screen are opaque. @@ -367,7 +369,7 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary.getVisibility(null /* starting */)); final ActivityStack splitScreenSecondary2 = - createStackForShouldBeVisibleTest(mDefaultDisplay, + createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // First split-screen secondary shouldn't be visible behind another opaque split-split // secondary. @@ -389,8 +391,9 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(STACK_VISIBILITY_VISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); - final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); + final ActivityStack assistantStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, + true /* onTop */); // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack. doReturn(false).when(assistantStack).isTranslucent(any()); @@ -530,7 +533,7 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack translucentStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, @@ -547,7 +550,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testShouldBeVisible_Finishing() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity(); if (topRunningHomeActivity == null) { @@ -558,7 +561,7 @@ public class ActivityStackTests extends ActivityTestsBase { } final ActivityStack translucentStack = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(true).when(translucentStack).isTranslucent(any()); @@ -580,7 +583,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testShouldBeVisible_FullscreenBehindTranslucentInHomeStack() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); final ActivityRecord firstActivity = new ActivityBuilder(mService) @@ -601,74 +604,77 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); doReturn(false).when(fullscreenStack).isTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack); assertEquals(fullscreenStack, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); - assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack)); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); + assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack)); } @Test public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); doReturn(true).when(fullscreenStack).isTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack); assertEquals(fullscreenStack, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); - assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack)); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); + assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack)); } @Test public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); doReturn(false).when(fullscreenStack).isTranslucent(any()); // Ensure we don't move the home stack if it is already on top - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack); assertNull(getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); - assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack)); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); + assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack)); } @Test public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); @@ -678,22 +684,22 @@ public class ActivityStackTests extends ActivityTestsBase { // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the // pinned stack assertEquals(fullscreenStack1, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); assertEquals(fullscreenStack2, getStackAbove(homeStack)); } @Test public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); @@ -703,21 +709,21 @@ public class ActivityStackTests extends ActivityTestsBase { // Ensure that we move the home stack behind the bottom most non-translucent fullscreen // stack assertEquals(fullscreenStack1, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack); + mDefaultTaskDisplayArea.moveStackBehindBottomMostVisibleStack(homeStack); assertEquals(fullscreenStack1, getStackAbove(homeStack)); } @Test public void testMoveHomeStackBehindStack_BehindHomeStack() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); doReturn(false).when(homeStack).isTranslucent(any()); @@ -725,50 +731,50 @@ public class ActivityStackTests extends ActivityTestsBase { doReturn(false).when(fullscreenStack2).isTranslucent(any()); // Ensure we don't move the home stack behind itself - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, homeStack); - assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack)); + int homeStackIndex = mDefaultTaskDisplayArea.getIndexOf(homeStack); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, homeStack); + assertEquals(homeStackIndex, mDefaultTaskDisplayArea.getIndexOf(homeStack)); } @Test public void testMoveHomeStackBehindStack() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack1); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack1); assertEquals(fullscreenStack1, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack2); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack2); assertEquals(fullscreenStack2, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack4); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack4); assertEquals(fullscreenStack4, getStackAbove(homeStack)); - mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack2); + mDefaultTaskDisplayArea.moveStackBehindStack(homeStack, fullscreenStack2); assertEquals(fullscreenStack2, getStackAbove(homeStack)); } @Test public void testSetAlwaysOnTop() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(pinnedStack, getStackAbove(homeStack)); final ActivityStack alwaysOnTopStack = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); alwaysOnTopStack.setAlwaysOnTop(true); assertTrue(alwaysOnTopStack.isAlwaysOnTop()); @@ -776,13 +782,13 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(pinnedStack, getStackAbove(alwaysOnTopStack)); final ActivityStack nonAlwaysOnTopStack = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Ensure non always on top stack is put below always on top stacks. assertEquals(alwaysOnTopStack, getStackAbove(nonAlwaysOnTopStack)); final ActivityStack alwaysOnTopStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); alwaysOnTopStack2.setAlwaysOnTop(true); assertTrue(alwaysOnTopStack2.isAlwaysOnTop()); @@ -807,13 +813,14 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testSplitScreenMoveToFront() { final ActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, + ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, + ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack assistantStack = createStackForShouldBeVisibleTest( + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); doReturn(false).when(splitScreenPrimary).isTranslucent(any()); doReturn(false).when(splitScreenSecondary).isTranslucent(any()); @@ -832,7 +839,7 @@ public class ActivityStackTests extends ActivityTestsBase { private ActivityStack createStandardStackForVisibilityTest(int windowingMode, boolean translucent) { - final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */); doReturn(translucent).when(stack).isTranslucent(any()); return stack; @@ -840,20 +847,20 @@ public class ActivityStackTests extends ActivityTestsBase { @SuppressWarnings("TypeParameterUnusedInFormals") private ActivityStack createStackForShouldBeVisibleTest( - DisplayContent display, int windowingMode, int activityType, boolean onTop) { + TaskDisplayArea taskDisplayArea, int windowingMode, int activityType, boolean onTop) { final ActivityStack stack; if (activityType == ACTIVITY_TYPE_HOME) { // Home stack and activity are created in ActivityTestsBase#setupActivityManagerService - stack = mDefaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); + stack = mDefaultTaskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); if (onTop) { - mDefaultDisplay.mTaskContainers.positionStackAtTop(stack, + mDefaultTaskDisplayArea.positionStackAtTop(stack, false /* includingParents */); } else { - mDefaultDisplay.mTaskContainers.positionStackAtBottom(stack); + mDefaultTaskDisplayArea.positionStackAtBottom(stack); } } else { stack = new StackBuilder(mRootWindowContainer) - .setDisplay(display) + .setTaskDisplayArea(taskDisplayArea) .setWindowingMode(windowingMode) .setActivityType(activityType) .setOnTop(onTop) @@ -1005,7 +1012,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testWontFinishHomeStackImmediately() { - final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); ActivityRecord activity = homeStack.topRunningActivity(); @@ -1025,9 +1032,11 @@ public class ActivityStackTests extends ActivityTestsBase { public void testFinishCurrentActivity() { // Create 2 activities on a new display. final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP); - final ActivityStack stack1 = createStackForShouldBeVisibleTest(display, + final ActivityStack stack1 = createStackForShouldBeVisibleTest( + display.getDefaultTaskDisplayArea(), WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityStack stack2 = createStackForShouldBeVisibleTest(display, + final ActivityStack stack2 = createStackForShouldBeVisibleTest( + display.getDefaultTaskDisplayArea(), WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // There is still an activity1 in stack1 so the activity2 should be added to finishing list @@ -1075,26 +1084,26 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testStackOrderChangedOnRemoveStack() { StackOrderChangedListener listener = new StackOrderChangedListener(); - mDefaultDisplay.registerStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener); try { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); } finally { - mDefaultDisplay.unregisterStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener); } assertTrue(listener.mChanged); } @Test public void testStackOrderChangedOnAddPositionStack() { - mDefaultDisplay.removeStack(mStack); + mDefaultTaskDisplayArea.removeStack(mStack); StackOrderChangedListener listener = new StackOrderChangedListener(); - mDefaultDisplay.registerStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener); try { mStack.mReparenting = true; - mDefaultDisplay.mTaskContainers.addStack(mStack, 0); + mDefaultTaskDisplayArea.addStack(mStack, 0); } finally { - mDefaultDisplay.unregisterStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener); } assertTrue(listener.mChanged); } @@ -1104,12 +1113,12 @@ public class ActivityStackTests extends ActivityTestsBase { StackOrderChangedListener listener = new StackOrderChangedListener(); try { final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - mDefaultDisplay.registerStackOrderChangedListener(listener); - mDefaultDisplay.mTaskContainers.positionStackAtBottom(fullscreenStack1); + mDefaultTaskDisplayArea.registerStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.positionStackAtBottom(fullscreenStack1); } finally { - mDefaultDisplay.unregisterStackOrderChangedListener(listener); + mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(listener); } assertTrue(listener.mChanged); } @@ -1189,7 +1198,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testClearUnknownAppVisibilityBehindFullscreenActivity() { final UnknownAppVisibilityController unknownAppVisibilityController = - mDefaultDisplay.mDisplayContent.mUnknownAppVisibilityController; + mDefaultTaskDisplayArea.mDisplayContent.mUnknownAppVisibilityController; final KeyguardController keyguardController = mSupervisor.getKeyguardController(); doReturn(true).when(keyguardController).isKeyguardLocked(); @@ -1254,7 +1263,7 @@ public class ActivityStackTests extends ActivityTestsBase { } private static class StackOrderChangedListener - implements DisplayContent.OnStackOrderChangedListener { + implements TaskDisplayArea.OnStackOrderChangedListener { public boolean mChanged = false; @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java index 76a761ce0e10..27782f5b3683 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java @@ -77,8 +77,8 @@ public class ActivityStartControllerTests extends ActivityTestsBase { .setCreateTask(true) .build(); final int startFlags = random.nextInt(); - final ActivityStack stack = mService.mRootWindowContainer.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack stack = mService.mRootWindowContainer.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final WindowProcessController wpc = new WindowProcessController(mService, mService.mContext.getApplicationInfo(), "name", 12345, UserHandle.getUserId(12345), mock(Object.class), diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 1d952bfcef2a..1cca207d5336 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -326,8 +326,9 @@ public class ActivityStarterTests extends ActivityTestsBase { if (mockGetLaunchStack) { // Instrument the stack and task used. - final ActivityStack stack = mRootWindowContainer.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack stack = mRootWindowContainer.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); // Direct starter to use spy stack. doReturn(stack).when(mRootWindowContainer) @@ -742,7 +743,7 @@ public class ActivityStarterTests extends ActivityTestsBase { final TestDisplayContent secondaryDisplay = new TestDisplayContent.Builder(mService, 1000, 1500) .setPosition(POSITION_BOTTOM).build(); - final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.mTaskContainers; + final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea(); final ActivityStack stack = secondaryTaskContainer.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); @@ -783,7 +784,7 @@ public class ActivityStarterTests extends ActivityTestsBase { new TestDisplayContent.Builder(mService, 1000, 1500).build(); mRootWindowContainer.positionChildAt(POSITION_TOP, secondaryDisplay, false /* includingParents */); - final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.mTaskContainers; + final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea(); final ActivityRecord singleTaskActivity = createSingleTaskActivityOn( secondaryTaskContainer.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); @@ -835,7 +836,7 @@ public class ActivityStarterTests extends ActivityTestsBase { // Create a secondary display at bottom. final TestDisplayContent secondaryDisplay = addNewDisplayContentAt(POSITION_BOTTOM); - final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.mTaskContainers; + final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea(); secondaryTaskContainer.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); @@ -951,7 +952,7 @@ public class ActivityStarterTests extends ActivityTestsBase { final ActivityStarter starter = prepareStarter(0 /* flags */); starter.mStartActivity = new ActivityBuilder(mService).build(); final Task task = new TaskBuilder(mService.mStackSupervisor) - .setStack(mService.mRootWindowContainer.getDefaultDisplay().createStack( + .setStack(mService.mRootWindowContainer.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)) .setUserId(10) .build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 9240b2222cd6..67d4769522b0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -372,7 +372,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { Task build() { if (mStack == null && mCreateStack) { - mStack = mSupervisor.mRootWindowContainer.getDefaultDisplay().createStack( + mStack = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); spyOn(mStack); } @@ -408,6 +408,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { static class StackBuilder { private final RootWindowContainer mRootWindowContainer; private DisplayContent mDisplay; + private TaskDisplayArea mTaskDisplayArea; private int mStackId = -1; private int mWindowingMode = WINDOWING_MODE_UNDEFINED; private int mActivityType = ACTIVITY_TYPE_STANDARD; @@ -419,6 +420,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { StackBuilder(RootWindowContainer root) { mRootWindowContainer = root; mDisplay = mRootWindowContainer.getDefaultDisplay(); + mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea(); } StackBuilder setWindowingMode(int windowingMode) { @@ -436,8 +438,20 @@ class ActivityTestsBase extends SystemServiceTestsBase { return this; } + /** + * Set the parent {@link DisplayContent} and use the default task display area. Overrides + * the task display area, if was set before. + */ StackBuilder setDisplay(DisplayContent display) { mDisplay = display; + mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea(); + return this; + } + + /** Set the parent {@link TaskDisplayArea}. Overrides the display, if was set before. */ + StackBuilder setTaskDisplayArea(TaskDisplayArea taskDisplayArea) { + mTaskDisplayArea = taskDisplayArea; + mDisplay = mTaskDisplayArea.mDisplayContent; return this; } @@ -462,9 +476,8 @@ class ActivityTestsBase extends SystemServiceTestsBase { } ActivityStack build() { - final int stackId = mStackId >= 0 ? mStackId - : mDisplay.mTaskContainers.getNextStackId(); - final ActivityStack stack = mDisplay.mTaskContainers.createStackUnchecked( + final int stackId = mStackId >= 0 ? mStackId : mTaskDisplayArea.getNextStackId(); + final ActivityStack stack = mTaskDisplayArea.createStackUnchecked( mWindowingMode, mActivityType, stackId, mOnTop, mInfo, mIntent, false /* createdByOrganizer */); final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor; diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java index 4cb50c7a9e4d..9b7ffd69da15 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -147,7 +147,7 @@ public class AppChangeTransitionTests extends WindowTestsBase { // Reparenting to a display with different windowing mode may trigger // a change transition internally, but it should be cleaned-up once // the display change is complete. - mStack.reparent(mDisplayContent, true); + mStack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true); assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode()); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index 8b91c7e5d5f3..8c8fd0516623 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -169,7 +169,7 @@ public class AppTransitionTests extends WindowTestsBase { assertTrue(dc1.mOpeningApps.size() > 0); // Move stack to another display. - stack1.reparent(dc2, true); + stack1.reparent(dc2.getDefaultTaskDisplayArea(), true); // Verify if token are cleared from both pending transition list in former display. assertFalse(dc1.mOpeningApps.contains(activity1)); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 38b3d76b447d..a901d1ebd890 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -281,7 +281,7 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(dc, activity.getDisplayContent()); // Move stack to first display. - mDisplayContent.moveStackToDisplay(stack, true /* onTop */); + stack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true /* onTop */); assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId()); assertEquals(mDisplayContent, stack.getDisplayContent()); assertEquals(mDisplayContent, task.getDisplayContent()); @@ -753,7 +753,7 @@ public class DisplayContentTests extends WindowTestsBase { doReturn(true).when(freeformStack).isVisible(); freeformStack.getTopChild().setBounds(100, 100, 300, 400); - assertTrue(dc.isStackVisible(WINDOWING_MODE_FREEFORM)); + assertTrue(dc.getDefaultTaskDisplayArea().isStackVisible(WINDOWING_MODE_FREEFORM)); freeformStack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE); stack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT); @@ -1096,8 +1096,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testGetOrCreateRootHomeTask_defaultDisplay() { - DisplayContent defaultDisplay = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY); - TaskDisplayArea defaultTaskDisplayArea = defaultDisplay.mTaskContainers; + TaskDisplayArea defaultTaskDisplayArea = mWm.mRoot.getDefaultTaskDisplayArea(); // Remove the current home stack if it exists so a new one can be created below. ActivityStack homeTask = defaultTaskDisplayArea.getRootHomeTask(); @@ -1116,7 +1115,7 @@ public class DisplayContentTests extends WindowTestsBase { doReturn(false).when(display).isUntrustedVirtualDisplay(); // Remove the current home stack if it exists so a new one can be created below. - TaskDisplayArea taskDisplayArea = display.mTaskContainers; + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); ActivityStack homeTask = taskDisplayArea.getRootHomeTask(); if (homeTask != null) { taskDisplayArea.removeChild(homeTask); @@ -1129,7 +1128,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() { DisplayContent display = createNewDisplay(); - TaskDisplayArea taskDisplayArea = display.mTaskContainers; + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); doReturn(false).when(display).supportsSystemDecorations(); assertNull(taskDisplayArea.getRootHomeTask()); @@ -1139,7 +1138,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testGetOrCreateRootHomeTask_untrustedVirtualDisplay() { DisplayContent display = createNewDisplay(); - TaskDisplayArea taskDisplayArea = display.mTaskContainers; + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); doReturn(true).when(display).isUntrustedVirtualDisplay(); assertNull(taskDisplayArea.getRootHomeTask()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index cf7411e67135..9b2a2db5d3a8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -860,6 +860,8 @@ public class DisplayRotationTests { mMockDisplayContent.isDefaultDisplay = mIsDefaultDisplay; when(mMockDisplayContent.calculateDisplayCutoutForRotation(anyInt())) .thenReturn(WmDisplayCutout.NO_CUTOUT); + when(mMockDisplayContent.getDefaultTaskDisplayArea()) + .thenReturn(mock(TaskDisplayArea.class)); mMockDisplayPolicy = mock(DisplayPolicy.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index ae467c0c811d..6a71a7dd24dd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -114,8 +114,8 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId))) .thenReturn(mTestDisplay); - ActivityStack stack = mTestDisplay.createStack(TEST_WINDOWING_MODE, - ACTIVITY_TYPE_STANDARD, /* onTop */ true); + ActivityStack stack = mTestDisplay.getDefaultTaskDisplayArea() + .createStack(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true); mTestTask = new TaskBuilder(mSupervisor).setComponent(TEST_COMPONENT).setStack(stack) .build(); mTestTask.mUserId = TEST_USER_ID; @@ -337,8 +337,8 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { public void testClearsRecordsOfTheUserOnUserCleanUp() { mTarget.saveTask(mTestTask); - ActivityStack stack = mTestDisplay.createStack(TEST_WINDOWING_MODE, - ACTIVITY_TYPE_STANDARD, /* onTop */ true); + ActivityStack stack = mTestDisplay.getDefaultTaskDisplayArea().createStack( + TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true); final Task anotherTaskOfTheSameUser = new TaskBuilder(mSupervisor) .setComponent(ALTERNATIVE_COMPONENT) .setUserId(TEST_USER_ID) @@ -349,7 +349,7 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { anotherTaskOfTheSameUser.setHasBeenVisible(true); mTarget.saveTask(anotherTaskOfTheSameUser); - stack = mTestDisplay.createStack(TEST_WINDOWING_MODE, + stack = mTestDisplay.getDefaultTaskDisplayArea().createStack(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true); final Task anotherTaskOfDifferentUser = new TaskBuilder(mSupervisor) .setComponent(TEST_COMPONENT) diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 071386fa9cbd..d9c3ace4589d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -28,7 +28,6 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.view.Display.DEFAULT_DISPLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -119,7 +118,7 @@ public class RecentTasksTest extends ActivityTestsBase { public void setUp() throws Exception { mTaskPersister = new TestTaskPersister(mContext.getFilesDir()); spyOn(mTaskPersister); - mTaskContainer = mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).mTaskContainers; + mTaskContainer = mRootWindowContainer.getDefaultTaskDisplayArea(); // Set the recent tasks we should use for testing in this class. mRecentTasks = new TestRecentTasks(mService, mTaskPersister); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index da07baca3ce1..6d2b7b1e86fe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -105,7 +105,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mDefaultDisplay = mWm.mRoot.getDefaultDisplay(); mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks, DEFAULT_DISPLAY)); - mRootHomeTask = mDefaultDisplay.getRootHomeTask(); + mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask(); assertNotNull(mRootHomeTask); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 6810f6442c66..881561f5750b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -88,7 +88,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testRecentsActivityVisiblility() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack recentsStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */); ActivityRecord recentActivity = new ActivityBuilder(mService) @@ -116,8 +116,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testPreloadRecentsActivity() { - TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultDisplay() - .mTaskContainers; + TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final ActivityStack homeStack = defaultTaskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); defaultTaskDisplayArea.positionStackAtTop(homeStack, false /* includingParents */); @@ -178,8 +177,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testRestartRecentsActivity() throws Exception { // Have a recents activity that is not attached to its process (ActivityRecord.app = null). - TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultDisplay() - .mTaskContainers; + TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack recentsStack = defaultTaskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */); ActivityRecord recentActivity = new ActivityBuilder(mService).setComponent( @@ -208,7 +206,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testSetLaunchTaskBehindOfTargetActivity() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack homeStack = taskDisplayArea.getRootHomeTask(); // Assume the home activity support recents. ActivityRecord targetActivity = homeStack.getTopNonFinishingActivity(); @@ -253,7 +251,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testCancelAnimationOnVisibleStackOrderChange() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack fullscreenStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); new ActivityBuilder(mService) @@ -298,7 +296,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testKeepAnimationOnHiddenStackOrderChange() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); ActivityStack fullscreenStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); new ActivityBuilder(mService) @@ -334,7 +332,8 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testMultipleUserHomeActivity_findUserHomeTask() { - TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay().mTaskContainers; + TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay() + .getDefaultTaskDisplayArea(); ActivityStack homeStack = taskDisplayArea.getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME); ActivityRecord otherUserHomeActivity = new ActivityBuilder(mService) diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index 836310496d0b..48d4e705ff4b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -89,7 +89,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Before public void setUp() throws Exception { - mFullscreenStack = mRootWindowContainer.getDefaultDisplay().createStack( + mFullscreenStack = mRootWindowContainer.getDefaultTaskDisplayArea().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); doNothing().when(mService).updateSleepIfNeededLocked(); } @@ -129,8 +129,8 @@ public class RootActivityContainerTests extends ActivityTestsBase { mRootWindowContainer.moveActivityToPinnedStack(firstActivity, sourceBounds, 0f /*aspectRatio*/, "initialMove"); - final DisplayContent display = mFullscreenStack.getDisplay(); - ActivityStack pinnedStack = display.getRootPinnedTask(); + final TaskDisplayArea taskDisplayArea = mFullscreenStack.getDisplayArea(); + ActivityStack pinnedStack = taskDisplayArea.getRootPinnedTask(); // Ensure a task has moved over. ensureStackPlacement(pinnedStack, firstActivity); ensureStackPlacement(mFullscreenStack, secondActivity); @@ -140,8 +140,9 @@ public class RootActivityContainerTests extends ActivityTestsBase { 0f /*aspectRatio*/, "secondMove"); // Need to get stacks again as a new instance might have been created. - pinnedStack = display.getRootPinnedTask(); - mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + pinnedStack = taskDisplayArea.getRootPinnedTask(); + mFullscreenStack = taskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); // Ensure stacks have swapped tasks. ensureStackPlacement(pinnedStack, secondActivity); ensureStackPlacement(mFullscreenStack, firstActivity); @@ -215,6 +216,8 @@ public class RootActivityContainerTests extends ActivityTestsBase { doReturn(isFocusedStack).when(stack).isFocusedStackOnDisplay(); doReturn(isFocusedStack ? stack : null).when(display).getFocusedStack(); + TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea(); + doReturn(isFocusedStack ? stack : null).when(defaultTaskDisplayArea).getFocusedStack(); mRootWindowContainer.applySleepTokens(true); verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked(); verify(stack, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked( @@ -226,26 +229,29 @@ public class RootActivityContainerTests extends ActivityTestsBase { */ @Test public void testRemovingStackOnAppCrash() { - final DisplayContent defaultDisplay = mRootWindowContainer.getDefaultDisplay(); - final int originalStackCount = defaultDisplay.getStackCount(); - final ActivityStack stack = mRootWindowContainer.getDefaultDisplay().createStack( + final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + final int originalStackCount = defaultTaskDisplayArea.getStackCount(); + final ActivityStack stack = defaultTaskDisplayArea.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) .setStack(stack).build(); - assertEquals(originalStackCount + 1, defaultDisplay.getStackCount()); + assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getStackCount()); // Let's pretend that the app has crashed. firstActivity.app.setThread(null); mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test"); // Verify that the stack was removed. - assertEquals(originalStackCount, defaultDisplay.getStackCount()); + assertEquals(originalStackCount, defaultTaskDisplayArea.getStackCount()); } @Test public void testFocusability() { - final ActivityStack stack = mRootWindowContainer.getDefaultDisplay().createStack( + final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + final ActivityStack stack = defaultTaskDisplayArea.createStack( WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) .setStack(stack).build(); @@ -259,7 +265,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { assertFalse(stack.isTopActivityFocusable()); assertFalse(activity.isFocusable()); - final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack( + final ActivityStack pinnedStack = defaultTaskDisplayArea.createStack( WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityRecord pinnedActivity = new ActivityBuilder(mService).setCreateTask(true) .setStack(pinnedStack).build(); @@ -288,7 +294,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() { // Create primary split-screen stack with a task and an activity. - final ActivityStack primaryStack = mRootWindowContainer.getDefaultDisplay() + final ActivityStack primaryStack = mRootWindowContainer.getDefaultTaskDisplayArea() .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); final Task task = new TaskBuilder(mSupervisor).setStack(primaryStack).build(); @@ -311,7 +317,6 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testFindTaskToMoveToFrontWhenRecentsOnTop() { // Create stack/task on default display. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); final ActivityStack targetStack = new StackBuilder(mRootWindowContainer) .setOnTop(false) .build(); @@ -325,7 +330,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason, false); - final TaskDisplayArea taskDisplayArea = display.mTaskContainers; + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); verify(taskDisplayArea).moveHomeStackToFront(contains(reason)); } @@ -336,8 +341,8 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testFindTaskToMoveToFrontWhenRecentsOnOtherDisplay() { // Create stack/task on default display. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack targetStack = display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack targetStack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); final Task targetTask = new TaskBuilder(mSupervisor).setStack(targetStack).build(); @@ -353,7 +358,6 @@ public class RootActivityContainerTests extends ActivityTestsBase { mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason, false); - final TaskDisplayArea taskDisplayArea = display.mTaskContainers; verify(taskDisplayArea, never()).moveHomeStackToFront(contains(reason)); } @@ -364,12 +368,12 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeActivityWhenNonTopmostStackIsTopFocused() { // Create a stack at bottom. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build(); final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build(); - display.mTaskContainers.positionStackAtBottom(targetStack); + taskDisplayArea.positionStackAtBottom(targetStack); // Assume the stack is not at the topmost position (e.g. behind always-on-top stacks) but it // is the current top focused stack. @@ -392,10 +396,9 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeFocusedStacksStartsHomeActivity_NoActivities() { mFullscreenStack.removeIfPossible(); - mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getRootHomeTask() - .removeIfPossible(); - mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY) - .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + taskDisplayArea.getRootHomeTask().removeIfPossible(); + taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), anyInt()); @@ -415,16 +418,15 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() { mFullscreenStack.removeIfPossible(); - mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY).getRootHomeTask() - .removeIfPossible(); - mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY) - .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + taskDisplayArea.getRootHomeTask().removeIfPossible(); + taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); // Create an activity on secondary display. final TestDisplayContent secondDisplay = addNewDisplayContentAt( DisplayContent.POSITION_TOP); - final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack stack = secondDisplay.getDefaultTaskDisplayArea() + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); new ActivityBuilder(mService).setTask(task).build(); @@ -446,8 +448,8 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeActivityLingeringTransition() { // Create a stack at top. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build(); final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build(); @@ -466,13 +468,13 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testResumeActivityLingeringTransition_notExecuted() { // Create a stack at bottom. - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN, + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final ActivityStack targetStack = spy(taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build(); final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build(); activity.setState(ActivityState.RESUMED, "test"); - display.mTaskContainers.positionStackAtBottom(targetStack); + taskDisplayArea.positionStackAtBottom(targetStack); // Assume the stack is at the topmost position assertFalse(targetStack.isTopStackInDisplayArea()); @@ -809,20 +811,20 @@ public class RootActivityContainerTests extends ActivityTestsBase { public void testSwitchUser_missingHomeRootTask() { doReturn(mFullscreenStack).when(mRootWindowContainer).getTopDisplayFocusedStack(); - DisplayContent defaultDisplay = mRootWindowContainer.getDefaultDisplay(); - ActivityStack homeStack = defaultDisplay.getRootHomeTask(); + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + ActivityStack homeStack = taskDisplayArea.getRootHomeTask(); if (homeStack != null) { homeStack.removeImmediately(); } - assertNull(defaultDisplay.getRootHomeTask()); + assertNull(taskDisplayArea.getRootHomeTask()); int currentUser = mRootWindowContainer.mCurrentUser; int otherUser = currentUser + 1; mRootWindowContainer.switchUser(otherUser, null); - assertNotNull(defaultDisplay.getRootHomeTask()); - assertEquals(defaultDisplay.getTopStack(), defaultDisplay.getRootHomeTask()); + assertNotNull(taskDisplayArea.getRootHomeTask()); + assertEquals(taskDisplayArea.getTopStack(), taskDisplayArea.getRootHomeTask()); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java index d6a67abc9e76..3d3a0f148db5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java @@ -72,7 +72,8 @@ public class RunningTasksTest extends ActivityTestsBase { final int numTasks = 10; int activeTime = 0; for (int i = 0; i < numTasks; i++) { - createTask(display.getStackAt(i % numStacks), ".Task" + i, i, activeTime++, null); + createTask(display.getDefaultTaskDisplayArea().getStackAt(i % numStacks), + ".Task" + i, i, activeTime++, null); } // Ensure that the latest tasks were returned in order of decreasing last active time, diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 893a14541c48..673469474709 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -189,7 +189,7 @@ public class SizeCompatTests extends ActivityTestsBase { final int originalDpi = mActivity.getConfiguration().densityDpi; // Move the non-resizable activity to the new display. - mStack.reparent(newDisplay.mDisplayContent, true /* onTop */); + mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */); assertEquals(originalBounds.width(), mActivity.getBounds().width()); assertEquals(originalBounds.height(), mActivity.getBounds().height()); @@ -257,7 +257,7 @@ public class SizeCompatTests extends ActivityTestsBase { .setCanRotate(false).setNotch(notchHeight).build(); // Move the non-resizable activity to the new display. - mStack.reparent(newDisplay, true /* onTop */); + mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */); // The configuration bounds should keep the same. assertEquals(origWidth, configBounds.width()); assertEquals(origHeight, configBounds.height()); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index af76e7fc0b76..af3ec38631ae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -316,7 +316,7 @@ public class SystemServicesTestRule implements TestRule { // that the default display is in fullscreen mode. display.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN); spyOn(display); - final TaskDisplayArea taskDisplayArea = display.mTaskContainers; + final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); spyOn(taskDisplayArea); final ActivityStack homeStack = taskDisplayArea.getStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index a3446d16d9f3..1a38ff283477 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -1312,14 +1312,14 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { } private ActivityRecord createSourceActivity(TestDisplayContent display) { - final ActivityStack stack = display.createStack(display.getWindowingMode(), - ACTIVITY_TYPE_STANDARD, true); + final ActivityStack stack = display.getDefaultTaskDisplayArea() + .createStack(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); return new ActivityBuilder(mService).setStack(stack).setCreateTask(true).build(); } private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) { - final ActivityStack stack = display.createStack(display.getWindowingMode(), - ACTIVITY_TYPE_STANDARD, true); + final ActivityStack stack = display.getDefaultTaskDisplayArea() + .createStack(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); stack.setWindowingMode(WINDOWING_MODE_FREEFORM); final Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); // Just work around the unnecessary adjustments for bounds. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index f76809b06510..50584c61cf92 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -203,9 +203,9 @@ public class TaskRecordTests extends ActivityTestsBase { @Test public void testFitWithinBounds() { final Rect parentBounds = new Rect(10, 10, 200, 200); - DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay(); - ActivityStack stack = display.createStack(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea(); + ActivityStack stack = taskDisplayArea.createStack(WINDOWING_MODE_FREEFORM, + ACTIVITY_TYPE_STANDARD, true /* onTop */); Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); final Configuration parentConfig = stack.getConfiguration(); parentConfig.windowConfiguration.setBounds(parentBounds); @@ -438,9 +438,9 @@ public class TaskRecordTests extends ActivityTestsBase { @Test public void testInsetDisregardedWhenFreeformOverlapsNavBar() { - DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay(); - ActivityStack stack = display.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea(); + ActivityStack stack = taskDisplayArea.createStack(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, true /* onTop */); DisplayInfo displayInfo = new DisplayInfo(); mService.mContext.getDisplay().getDisplayInfo(displayInfo); final int displayHeight = displayInfo.logicalHeight; @@ -959,8 +959,8 @@ public class TaskRecordTests extends ActivityTestsBase { private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds, Rect expectedConfigBounds) { - DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay(); - ActivityStack stack = display.createStack(windowingMode, ACTIVITY_TYPE_STANDARD, + TaskDisplayArea taskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea(); + ActivityStack stack = taskDisplayArea.createStack(windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */); Task task = new TaskBuilder(mSupervisor).setStack(stack).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java index 6387a3b7c474..d48e82723295 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java @@ -171,7 +171,7 @@ public class TaskStackTests extends WindowTestsBase { // Reparent clearInvocations(task1); // reset the number of onDisplayChanged for task. - stack1.reparent(dc, true /* onTop */); + stack1.reparent(dc.getDefaultTaskDisplayArea(), true /* onTop */); assertEquals(dc, stack1.getDisplayContent()); final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1); final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2); diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 900f014a0218..a4f1487dde1e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -254,7 +254,7 @@ public class WallpaperControllerTests extends WindowTestsBase { private WindowState createWallpaperTargetWindow(DisplayContent dc) { final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) - .setStack(dc.getRootHomeTask()) + .setStack(dc.getDefaultTaskDisplayArea().getRootHomeTask()) .setCreateTask(true) .build(); homeActivity.setVisibility(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 27ea37dfeb19..118c2e4db208 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -780,8 +780,8 @@ public class WindowContainerTests extends WindowTestsBase { WindowTestUtils.createActivityRecordInTask(mDisplayContent, task); final DisplayContent newDc = createNewDisplay(); - mDisplayContent.removeStack(stack); - newDc.mTaskContainers.addChild(stack, POSITION_TOP); + stack.getDisplayArea().removeStack(stack); + newDc.getDefaultTaskDisplayArea().addChild(stack, POSITION_TOP); verify(stack).onDisplayChanged(newDc); verify(task).onDisplayChanged(newDc); 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) { diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index 8de27e8eb281..0fc9be32f4cf 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -16,6 +16,8 @@ package android.net; +import static android.net.RouteInfo.RTN_UNREACHABLE; + import static com.android.testutils.ParcelUtilsKt.assertParcelSane; import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip; @@ -46,6 +48,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; @@ -1257,4 +1260,26 @@ public class LinkPropertiesTest { final LinkProperties Ipv6 = makeIpv6LinkProperties(); assertTrue(Ipv6.hasIpv6DnsServer()); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testHasIpv4UnreachableDefaultRoute() { + final LinkProperties lp = makeTestObject(); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); + assertTrue(lp.hasIpv4UnreachableDefaultRoute()); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testHasIpv6UnreachableDefaultRoute() { + final LinkProperties lp = makeTestObject(); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); + assertTrue(lp.hasIpv6UnreachableDefaultRoute()); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + } } diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java index 1658262c17f6..8204b494bbb8 100644 --- a/tests/net/common/java/android/net/RouteInfoTest.java +++ b/tests/net/common/java/android/net/RouteInfoTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.fail; import android.os.Build; +import androidx.core.os.BuildCompat; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -62,6 +63,11 @@ public class RouteInfoTest { return new IpPrefix(prefix); } + private static boolean isAtLeastR() { + // BuildCompat.isAtLeastR is documented to return false on release SDKs (including R) + return Build.VERSION.SDK_INT > Build.VERSION_CODES.Q || BuildCompat.isAtLeastR(); + } + @Test public void testConstructor() { RouteInfo r; @@ -195,78 +201,130 @@ public class RouteInfoTest { assertTrue(r.isDefaultRoute()); assertTrue(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0"); assertFalse(r.isHostRoute()); assertTrue(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertTrue(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0"); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0"); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0"); assertTrue(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertTrue(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE); assertFalse(r.isHostRoute()); assertFalse(r.isDefaultRoute()); assertFalse(r.isIPv4Default()); assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertTrue(r.isIPv6UnreachableDefault()); + } } @Test diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b02398d1f1f5..912a27f08f30 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -6336,6 +6336,7 @@ public class ConnectivityServiceTest { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange); @@ -6361,6 +6362,7 @@ public class ConnectivityServiceTest { public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); @@ -6392,6 +6394,7 @@ public class ConnectivityServiceTest { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange); @@ -6428,6 +6431,7 @@ public class ConnectivityServiceTest { reset(mMockNetd); lp = new LinkProperties(); lp.setInterfaceName("tun1"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); vpnNetworkAgent.sendLinkProperties(lp); waitForIdle(); @@ -6440,6 +6444,7 @@ public class ConnectivityServiceTest { public void testUidUpdateChangesInterfaceFilteringRule() throws Exception { LinkProperties lp = new LinkProperties(); lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); // The uid range needs to cover the test app so the network is visible to it. final UidRange vpnRange = UidRange.createForUser(VPN_USER); diff --git a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java index de2f5d9a3fe4..6c2e6ddf5dd2 100644 --- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java +++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java @@ -42,7 +42,7 @@ public abstract class EasyConnectStatusCallback { public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0; /** - * East Connect R2 Success event: Configuration applied by Enrollee (Configurator mode). + * Easy Connect R2 Success event: Configuration applied by Enrollee (Configurator mode). * This is the last and final Easy Connect event when both the local device and remote device * implement R2. If either the local device or remote device implement R1, this event will never * be received, and the {@link #EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT} will be received. diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index dfeea9f4b52e..f1be8b20eb53 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1887,12 +1887,20 @@ public class WifiManager { * <li> If user reset network settings, all added suggestions will be discarded. Apps can use * {@link #getNetworkSuggestions()} to check if their suggestions are in the device.</li> * <li> In-place modification of existing suggestions are allowed. - * If the provided suggestions {@link WifiNetworkSuggestion#equals(Object)} any previously - * provided suggestions by the app. Previous suggestions will be updated</li> + * <li>If the provided suggestions includes any previously provided suggestions by the app, + * previous suggestions will be updated.</li> + * <li>If one of the provided suggestions marks a previously unmetered suggestion as metered and + * the device is currently connected to that suggested network, then the device will disconnect + * from that network. The system will immediately re-evaluate all the network candidates + * and possibly reconnect back to the same suggestion. This disconnect is to make sure that any + * traffic flowing over unmetered networks isn't accidentally continued over a metered network. + * </li> + * </li> * * @param networkSuggestions List of network suggestions provided by the app. * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values. * @throws {@link SecurityException} if the caller is missing required permissions. + * @see WifiNetworkSuggestion#equals(Object) */ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public @NetworkSuggestionsStatusCode int addNetworkSuggestions( @@ -4152,6 +4160,10 @@ public class WifiManager { * * This function is used instead of a enableNetwork() and reconnect() * + * <li> This API will cause reconnect if the credentials of the current active + * connection has been changed.</li> + * <li> This API will cause reconnect if the current active connection is marked metered.</li> + * * @param networkId the ID of the network as returned by {@link #addNetwork} or {@link * getConfiguredNetworks}. * @param listener for callbacks on success or failure. Can be null. @@ -4180,8 +4192,9 @@ public class WifiManager { * * For an existing network, it accomplishes the task of updateNetwork() * - * This API will cause reconnect if the crecdentials of the current active - * connection has been changed. + * <li> This API will cause reconnect if the credentials of the current active + * connection has been changed.</li> + * <li> This API will cause disconnect if the current active connection is marked metered.</li> * * @param config the set of variables that describe the configuration, * contained in a {@link WifiConfiguration} object. diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index 4c524f49e4df..cedf9b0b872d 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -543,7 +543,7 @@ public final class WifiNetworkSuggestion implements Parcelable { wifiConfiguration.priority = mPriority; wifiConfiguration.meteredOverride = mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED - : WifiConfiguration.METERED_OVERRIDE_NONE; + : WifiConfiguration.METERED_OVERRIDE_NOT_METERED; wifiConfiguration.carrierId = mCarrierId; wifiConfiguration.trusted = !mIsNetworkUntrusted; return wifiConfiguration; diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java index ac2f6b26aa00..aca190910ed1 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java @@ -56,7 +56,7 @@ public class WifiNetworkSuggestionTest { .get(WifiConfiguration.KeyMgmt.NONE)); assertTrue(suggestion.isAppInteractionRequired); assertFalse(suggestion.isUserInteractionRequired); - assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE, + assertEquals(WifiConfiguration.METERED_OVERRIDE_NOT_METERED, suggestion.wifiConfiguration.meteredOverride); assertEquals(-1, suggestion.wifiConfiguration.priority); assertFalse(suggestion.isUserAllowedToManuallyConnect); @@ -86,7 +86,7 @@ public class WifiNetworkSuggestionTest { suggestion.wifiConfiguration.preSharedKey); assertTrue(suggestion.isAppInteractionRequired); assertFalse(suggestion.isUserInteractionRequired); - assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE, + assertEquals(WifiConfiguration.METERED_OVERRIDE_NOT_METERED, suggestion.wifiConfiguration.meteredOverride); assertEquals(0, suggestion.wifiConfiguration.priority); assertFalse(suggestion.isUserAllowedToManuallyConnect); |