Merge changes Id15887ff,I78262baa into rvc-dev
* changes:
AF: Clear the existing response for the partition on a new request.
AF: Fix bug where Session#setViewStatesLocked doesn't clear response.
diff --git a/api/test-current.txt b/api/test-current.txt
index 0ca8b2d..4eeaaf87 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1513,6 +1513,10 @@
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 4bce89f..482d66f 100644
--- a/cmds/statsd/benchmark/metric_util.cpp
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -370,13 +370,6 @@
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 6199fa9..c5fcf7c 100644
--- a/cmds/statsd/benchmark/metric_util.h
+++ b/cmds/statsd/benchmark/metric_util.h
@@ -120,9 +120,6 @@
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 a6ae389..0ec11f9 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -81,74 +81,6 @@
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 @@
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 @@
}
}
-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 0a89be4..6c6aab1 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -28,27 +28,6 @@
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 @@
*/
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 @@
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 2f81c2d..7d765d3 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -1060,13 +1060,6 @@
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 715ba2b..f24705a 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -291,9 +291,6 @@
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 27c9cfc..aa290404 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.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 @@
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 @@
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 6c161fc..87dc0a1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1046,8 +1046,7 @@
// 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 @@
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 0000000..d02600a
--- /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 0000000..c613441
--- /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]\[email protected](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 65f45d8..ea5cc7f 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -634,17 +634,39 @@
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 526db85..4d645e6 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -451,35 +451,26 @@
}
}
- 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 @@
}
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 d22188e..c697106 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.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 @@
// 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 0000000..c28f1df
--- /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 0000000..10e1c7c
--- /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]\[email protected](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 7ff954b..651494d 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -1074,6 +1074,21 @@
}
/**
+ * 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 @@
}
/**
+ * 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 dbdaa4c..e550f85 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -426,6 +426,16 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 20e5f24..683993f7 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -251,6 +251,11 @@
}
}
}
+ // 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 @@
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 fb81d67..e86aa62 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -266,6 +266,13 @@
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 35f955f..3534bb0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1747,17 +1747,18 @@
|| !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 @@
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 561ee60..316a5f2 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -36,7 +36,6 @@
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 @@
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 f007768..99b4b5f 100644
--- a/core/java/com/android/internal/policy/DecorContext.java
+++ b/core/java/com/android/internal/policy/DecorContext.java
@@ -41,17 +41,17 @@
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 @@
@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 0c74387..7c32ca6 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -696,7 +696,7 @@
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 0000000..8f8488f
--- /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 d649b94..61f58b0 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -645,14 +645,60 @@
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(modifiedProperties2.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+ 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(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 d019704..3e40466 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 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 @@
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 @@
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 ad9486c..405410a 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -19,6 +19,7 @@
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 @@
}
}
+ /**
+ * 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 632cfb0..37e1415 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -21,6 +21,7 @@
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 @@
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 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 f45114a..77e6a14 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 1cfd55d..4042ce8b 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 0000000..ebe62b0
--- /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 c51c8fa..8d18b77 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 @@
}
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 0000000..4d4ae9e
--- /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 0000000..4d27e1e
--- /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 0000000..02e7f0d
--- /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 0000000..57859bc
--- /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 0000000..3285f59
--- /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 0000000..6ecc539
--- /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 0000000..a169c0d
--- /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 0000000..8cda6eb
--- /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 2fde87c..d3d04e5 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 @@
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 d93c015..6b8219e 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 @@
}
@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 0000000..8f8f1b6
--- /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 0000000..5e02f67
--- /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 65b04fd..3060619 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 d67c81d..88a05ec 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 7dcc46c..ec00429 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 fe8557b..fc3bf94 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 a9e5fa9..5b213ed 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 80fd826..35ad422 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 @@
/**
* Temporary callbacks into SystemUI.
- * Next id = 25
+ * Next id = 26
*/
interface ISystemUiProxy {
@@ -140,4 +140,10 @@
* 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 29100ef8..8bd7c79 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 @@
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 @@
if (mSurfaceControlViewHost != null) {
mSurfaceControlViewHost.die();
}
+
SurfaceControl surfaceControl = SurfaceViewRequestUtils.getSurfaceControl(bundle);
if (surfaceControl != null) {
if (viewSize == null) {
@@ -70,8 +72,10 @@
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 4409276..6742a4dc 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 @@
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 2231d11..37841f2 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_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 @@
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 @@
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 @@
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 @@
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 @@
@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 6a7b0da..eff6934 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 @@
} 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 6d99ef1..fdd8593 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.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 @@
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 @@
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 @@
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 @@
mIActivityManager = iActivityManager;
mTelecomManager = telecomManager;
mMetricsLogger = metricsLogger;
+ mUiEventLogger = uiEventLogger;
mDepthController = depthController;
mSysuiColorExtractor = colorExtractor;
mStatusBarService = statusBarService;
@@ -997,6 +1019,7 @@
*/
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 0000000..aa5ebaa
--- /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 0000000..cf8f268
--- /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 15c9dba..8cff20a 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.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 @@
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 8922e14..339a408 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.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 @@
};
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 @@
* @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 @@
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 @@
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 33cc086..c8412ff 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.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 @@
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 @@
QSLogger qsLogger,
NotificationMediaManager notificationMediaManager,
@Main Executor foregroundExecutor,
- @Background Executor backgroundExecutor,
+ @Background DelayableExecutor backgroundExecutor,
@Nullable LocalBluetoothManager localBluetoothManager
) {
super(context, attrs);
@@ -278,7 +279,7 @@
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 @@
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 be01d75..8fa64d3 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.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 @@
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 df85ed5..66bc177 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.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 @@
}
}
+ @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 @@
}
}
+ 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 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 fe2f1f3..1297f99 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 @@
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 @@
}
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 7ef1d0e..1696f07 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.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 @@
@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 @@
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 059d6ff..148cdea 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 @@
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 d2f781d..295adae 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 @@
}
@Override
- public void onReorderingAllowed() {
+ public void onChangeAllowed() {
updateNotifications("reordering is now allowed");
}
@@ -539,7 +539,8 @@
}
}
- private void addNotificationInternal(StatusBarNotification notification,
+ private void addNotificationInternal(
+ StatusBarNotification notification,
RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
if (DEBUG) {
@@ -579,6 +580,9 @@
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryAdded(entry);
}
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onRankingApplied();
+ }
}
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
@@ -635,6 +639,9 @@
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPostEntryUpdated(entry);
}
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onRankingApplied();
+ }
}
public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
@@ -693,6 +700,9 @@
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingUpdate(rankingMap);
}
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onRankingApplied();
+ }
}
private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) {
@@ -799,6 +809,9 @@
*/
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 b357ada..7ac5995 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 @@
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 @@
/**
* 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 void setPanelExpanded(boolean expanded) {
mPanelExpanded = expanded;
- updateReorderingAllowed();
+ updateAllowedStates();
}
/**
@@ -105,7 +116,7 @@
*/
public void setScreenOn(boolean screenOn) {
mScreenOn = screenOn;
- updateReorderingAllowed();
+ updateAllowedStates();
}
/**
@@ -116,25 +127,30 @@
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 @@
}
/**
+ * @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 @@
mTemporaryReorderingStart = SystemClock.elapsedRealtime();
}
mIsTemporaryReorderingAllowed = true;
- updateReorderingAllowed();
+ updateAllowedStates();
}
private final Runnable mOnTemporaryReorderingExpired = () -> {
mIsTemporaryReorderingAllowed = false;
- updateReorderingAllowed();
+ updateAllowedStates();
};
/**
@@ -229,9 +252,9 @@
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 0377c090..dd7be27 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.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 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 @@
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 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 0000000..1bac938
--- /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 573c129..2a3b2b7 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.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 @@
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 @@
@Inject
public HeadsUpCoordinator(
HeadsUpManager headsUpManager,
+ HeadsUpViewBinder headsUpViewBinder,
+ NotificationInterruptStateProvider notificationInterruptStateProvider,
NotificationRemoteInputManager remoteInputManager) {
mHeadsUpManager = headsUpManager;
+ mHeadsUpViewBinder = headsUpViewBinder;
+ mNotificationInterruptStateProvider = notificationInterruptStateProvider;
mRemoteInputManager = remoteInputManager;
}
@@ -84,8 +93,51 @@
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 @@
mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput);
}
}
+
+ @Override
+ public void onEntryCleanUp(NotificationEntry entry) {
+ mHeadsUpViewBinder.abortBindCallback(entry);
+ }
};
private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() {
@@ -153,6 +210,9 @@
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 03c0ae6..2b279bbd 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 @@
DeviceProvisionedCoordinator deviceProvisionedCoordinator,
BubbleCoordinator bubbleCoordinator,
HeadsUpCoordinator headsUpCoordinator,
+ ConversationCoordinator conversationCoordinator,
PreparationCoordinator preparationCoordinator) {
dumpManager.registerDumpable(TAG, this);
mCoordinators.add(new HideLocallyDismissedNotifsCoordinator());
@@ -66,6 +67,7 @@
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 742615c..9973ef9 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.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 @@
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 @@
NotifInflaterImpl notifInflater,
NotifInflationErrorManager errorManager,
NotifViewBarn viewBarn,
- IStatusBarService service,
- NotificationInterruptStateProvider notificationInterruptStateProvider,
- HeadsUpManager headsUpManager
+ IStatusBarService service
) {
mLogger = logger;
mNotifInflater = notifInflater;
@@ -83,8 +77,6 @@
mNotifErrorManager.addInflationErrorListener(mInflationErrorListener);
mViewBarn = viewBarn;
mStatusBarService = service;
- mNotificationInterruptStateProvider = notificationInterruptStateProvider;
- mHeadsUpManager = headsUpManager;
}
@Override
@@ -158,11 +150,6 @@
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 7237284..32f1822 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 @@
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 0c0cded..41ca52d 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 @@
* 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 0000000..a7b1f37
--- /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 0000000..37acfa8
--- /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 7a7178c..d1cceae 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.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.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 @@
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 @@
presenter,
listContainer,
bindRowCallback)
+ headsUpViewBinder.setPresenter(presenter)
notifBindPipelineInitializer.initialize()
if (featureFlags.isNewNotifPipelineEnabled) {
@@ -109,6 +114,7 @@
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 b572502..5d07098 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 @@
// 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 be3873a..88cca43 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 @@
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 255c2ea..2917346 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 @@
* 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 @@
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 d744fc3..893e849 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.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 @@
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 @@
final BindEntry bindEntry = getBindEntry(entry);
bindEntry.row = row;
if (bindEntry.invalidated) {
- startPipeline(entry);
+ requestPipelineRun(entry);
}
}
@@ -130,7 +140,28 @@
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 @@
return;
}
- mStage.abortStage(entry, row);
mStage.executeStage(entry, row, (en) -> onPipelineComplete(en));
}
@@ -162,10 +192,15 @@
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 @@
mStage.abortStage(entry, row);
}
mStage.deleteStageParams(entry);
+ mMainHandler.removeMessages(START_PIPELINE_MSG, entry);
}
};
@@ -211,4 +247,25 @@
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 2717d7a..1997304 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 @@
})
}
+ 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 719f74f..9d54437 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 @@
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 @@
}
/**
- * 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 8efdc1b..b18bf01 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 void setContractedChild(@Nullable View child) {
if (mContractedChild != null) {
+ mOnContentViewInactiveListeners.remove(mContractedChild);
mContractedChild.animate().cancel();
removeView(mContractedChild);
}
@@ -432,6 +433,7 @@
((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput);
}
}
+ mOnContentViewInactiveListeners.remove(mExpandedChild);
mExpandedChild.animate().cancel();
removeView(mExpandedChild);
mExpandedRemoteInput = null;
@@ -470,6 +472,7 @@
((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput);
}
}
+ mOnContentViewInactiveListeners.remove(mHeadsUpChild);
mHeadsUpChild.animate().cancel();
removeView(mHeadsUpChild);
mHeadsUpRemoteInput = null;
@@ -1108,7 +1111,6 @@
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 @@
* @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 @@
}
/**
+ * 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 8e2bfb8..6fc1264 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.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.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.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 @@
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 88ed0bb..d3fec69 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 @@
}
/**
- * 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 0c311b40..5205bab 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 @@
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 d38bc9f..d02037c 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 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 @@
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 823b186..6054b50 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.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 @@
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 @@
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 bc25c71..a1d898f 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 @@
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 @@
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 a3d8eec..5777ba1 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 @@
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 8efda21..f103bd0 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 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.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.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.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 @@
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 @@
}
};
+ 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 @@
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 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 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 @@
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 @@
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 @@
@Override
public void onDisplayChanged(int displayId) {
+ if (mStartingQuickstepRotation > -1) {
+ updateDisabledForQuickstep();
+ }
+
if (displayId == mDisplayId) {
updateDisplaySize();
}
@@ -502,6 +577,17 @@
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 @@
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 63fe700..6b0df95 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 @@
// 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 8c31a37..dd9c820 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 @@
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 ccf6707..84dd48b 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 @@
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 @@
}
}
+ /**
+ * 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 @@
}
private void onEntryBecomingChild(NotificationEntry entry) {
- if (shouldIsolate(entry)) {
- isolateNotification(entry);
- }
+ updateIsolation(entry);
}
private void updateSuppression(NotificationGroup group) {
@@ -242,15 +260,6 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
}
/**
+ * 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 66a1d3f..0d5a149 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 @@
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 94aa391..86998ab 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 711a0df..d731753 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 @@
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 471149c..6decb88 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 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 @@
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 6f3fbb9..037f04ec 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 @@
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 7df3983..d2f9127 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 @@
@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 a31e3f8d..545de21 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 @@
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 0000000..137a126
--- /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)
[email protected]
+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 0000000..260f520
--- /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)
[email protected]
+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 0000000..f316d04
--- /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)
[email protected]
+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 dbbbaac..862ebe13 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.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 @@
@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 83877f2..e55ea41 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 @@
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 0a38f16..2b94561 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 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 @@
@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 bf2d598..2904014 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 @@
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 97e0a31..277ac24 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.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 @@
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 9079223..3d06c57 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 @@
mVisualStabilityManager.setScreenOn(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
mVisualStabilityManager.setScreenOn(false);
- verify(mCallback).onReorderingAllowed();
+ verify(mCallback).onChangeAllowed();
}
@Test
@@ -119,7 +119,7 @@
mVisualStabilityManager.setScreenOn(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
mVisualStabilityManager.setPanelExpanded(false);
- verify(mCallback).onReorderingAllowed();
+ verify(mCallback).onChangeAllowed();
}
@Test
@@ -130,7 +130,7 @@
mVisualStabilityManager.setScreenOn(false);
mVisualStabilityManager.setScreenOn(true);
mVisualStabilityManager.setScreenOn(false);
- verify(mCallback).onReorderingAllowed();
+ verify(mCallback).onChangeAllowed();
}
@Test
@@ -190,7 +190,7 @@
mVisualStabilityManager.setPulsing(true);
mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
mVisualStabilityManager.setPulsing(false);
- verify(mCallback).onReorderingAllowed();
+ verify(mCallback).onChangeAllowed();
}
@Test
@@ -204,7 +204,7 @@
mVisualStabilityManager.temporarilyAllowReordering();
// THEN callbacks are notified that reordering is allowed
- verify(mCallback).onReorderingAllowed();
+ verify(mCallback).onChangeAllowed();
assertTrue(mVisualStabilityManager.isReorderingAllowed());
}
@@ -218,7 +218,7 @@
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 0000000..dfc627e
--- /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)
[email protected]
+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 0c109c4..f3038ce 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 @@
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.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 @@
@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 @@
mCoordinator = new HeadsUpCoordinator(
mHeadsUpManager,
+ mHeadsUpViewBinder,
+ mNotificationInterruptStateProvider,
mRemoteInputManager
);
@@ -168,6 +177,36 @@
}
@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 8143cf5..6b9e43b 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.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.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 @@
@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 @@
mNotifInflater,
mErrorManager,
mock(NotifViewBarn.class),
- mService,
- mNotificationInterruptStateProvider,
- mHeadsUpManager);
+ mService);
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
mCoordinator.attach(mNotifPipeline);
@@ -180,24 +172,4 @@
// 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 cb37920..43dcbe3 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 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 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 @@
@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 @@
}
@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 6408f7a..bdd82fd 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 @@
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 @@
// 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 @@
// 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 @@
// 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 481bac2..7c8328d 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 @@
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 6a65269..25da741 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.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 @@
.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 0f26898..b018b59 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 @@
@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 6998edd..b6bd5e2 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 @@
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 a5d8a84..be026f7 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 @@
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 462da93..ed46423 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 @@
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 0e67feb..07f2085 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 0f2482c..96a58e2 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 @@
// 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 b661b28..45f7c5a 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 @@
@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 18ea774..fbe4d73 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.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 @@
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 830e8d9..085bd90 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 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 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 a2029c7..7037891 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 @@
@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 ba2b946..d795cba 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.when;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
@@ -66,7 +67,10 @@
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 @@
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 a74657e..e546dff 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.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 @@
@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 f6a099d..67f94130 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.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 @@
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 b9c5b7c..dd28687 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 @@
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 86add98a..e88b514 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 @@
@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 @@
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 @@
@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 ce8085a..486939d 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 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.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 @@
@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 4ab035e7..2eaa766 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -6313,7 +6313,8 @@
&& !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 7aaf9be..e1f9a7a 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 @@
@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 85d28831..62d7eb1 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 @@
}
}
- 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 45b9383..4431abe 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.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.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 @@
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 @@
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 @@
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 @@
// 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 @@
}
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 @@
}
removeClient(client);
startCleanupUnknownHALTemplates();
- } else {
+ } else if (client != null) {
removeClient(client);
}
}
@@ -898,12 +902,16 @@
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 @@
* @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 @@
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 @@
}
}
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 63a8d7c..696daca 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 @@
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 @@
}
/**
+ * 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 a232051..3afbf66 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.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 @@
}
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 @@
}
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 @@
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 @@
}
}
+ @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 @@
}
@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 @@
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 (virtualDisplayConfig == null) {
+ throw new IllegalArgumentException("virtualDisplayConfig must not be null");
}
- if (width <= 0 || height <= 0 || densityDpi <= 0) {
- throw new IllegalArgumentException("width, height, and densityDpi must be "
- + "greater than 0");
- }
+ 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 @@
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 f4f2ead..ccd88483 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 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 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 @@
}
}
+ @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 @@
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 @@
mPendingChanges |= PENDING_SURFACE_CHANGE;
mUniqueIndex = uniqueIndex;
mIsDisplayOn = surface != null;
+ mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
}
@Override
@@ -260,6 +273,16 @@
}
@Override
+ public int getDisplayIdToMirrorLocked() {
+ return mDisplayIdToMirror;
+ }
+
+ @VisibleForTesting
+ Surface getSurfaceLocked() {
+ return mSurface;
+ }
+
+ @Override
public boolean hasStableUniqueId() {
return false;
}
@@ -332,6 +355,7 @@
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 e8d8ed7..ed3b9f1 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 @@
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 @@
// 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 @@
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 @@
// 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 @@
}
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 1b271a7..1d5c304 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.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 @@
}
@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 @@
}
@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 @@
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 @@
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 7786833..c2ecd41 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 @@
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 @@
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 a16dbb7..3f2b5c2 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.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 @@
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 10b335e..a446720 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 @@
}
}
- 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 2c7ce91..8af8624 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 @@
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 @@
}
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 @@
// 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 7803c73..0b19687 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 @@
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 @@
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 682a142..e8becfa 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 @@
* - 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 55b7be779..85517a4 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 @@
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 @@
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 @@
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 @@
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 @@
*/
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
}
/**
- * 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 221258e..264da9f 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 @@
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 @@
}
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 af89a05..bef80f0 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 @@
// 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 a9a9df8..a606668 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 @@
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 18332b9..8b34b9b 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 @@
@Override
public void dispose() {
synchronized (mService.mGlobalLock) {
- disposeChannelsLw();
+ disposeChannelsLw(mInputMonitor.mInputTransaction);
mInputEventReceiver.dispose();
+ mInputMonitor.updateInputWindowsLw(true /* force */);
}
}
}
@@ -195,8 +196,7 @@
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 007af24..e4e5716 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 @@
}
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 a936e74..57a54d0 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 @@
// 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 66dbfd5..02a2741 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 @@
}
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 08b0abf..a031fe8 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 @@
* 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 @@
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 @@
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 @@
// 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 @@
}
// 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 @@
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 @@
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 @@
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 @@
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 @@
mWindowManager.getRecentsAnimationController(), reorderMode);
// Unregister for stack order changes
- mDefaultDisplay.unregisterStackOrderChangedListener(this);
+ mDefaultTaskDisplayArea.unregisterStackOrderChangedListener(this);
final RecentsAnimationController controller =
mWindowManager.getRecentsAnimationController();
@@ -308,7 +308,7 @@
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 @@
@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 @@
* @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 a30b70d..84229f0 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 @@
// 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 @@
}
// 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 770c088..32de699 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 @@
}
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 @@
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 @@
}
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 2764b12..2eeda4d 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 @@
}
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 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
continue;
}
- display.mTaskContainers.findTaskLocked(r, false /* isPreferredDisplay */,
+ display.getDefaultTaskDisplayArea().findTaskLocked(r, false /* isPreferredDisplay */,
mTmpFindTaskResult);
if (mTmpFindTaskResult.mIdealMatch) {
return mTmpFindTaskResult.mRecord;
@@ -2788,8 +2794,10 @@
}
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 @@
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 @@
// 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 @@
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 @@
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 @@
// 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 @@
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 77ef011..13e4d8b 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 @@
* 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 @@
// 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 @@
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 @@
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 @@
@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 @@
mPreferredTopFocusableStack = null;
}
mDisplayContent.releaseSelfIfNeeded();
- mDisplayContent.onStackOrderChanged(stack);
+ onStackOrderChanged(stack);
}
void positionStackAt(int position, ActivityStack child, boolean includingParents) {
@@ -716,7 +722,7 @@
}
}
- mDisplayContent.onStackOrderChanged(stack);
+ onStackOrderChanged(stack);
}
ActivityStack getStack(int rootTaskId) {
@@ -765,11 +771,11 @@
} 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 @@
// 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 @@
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 @@
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 b641e4c..9ffd8d2 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 @@
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 e43f4b4..b9b6c08 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 @@
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 dfaa0ec..687af64 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 @@
@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 a332b69..3e2e9be 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 @@
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 b87d181..8e7585a 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 @@
// 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 b25383b1..d4470f8 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 @@
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 @@
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 d7c97b9..f7ea953 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 @@
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 3c2b6ec..7457a1d 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 @@
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 b13d330..02bb0bc 100644
--- a/services/incremental/Android.bp
+++ b/services/incremental/Android.bp
@@ -50,6 +50,7 @@
"libbinder",
"libcrypto",
"libcutils",
+ "libdataloader",
"libincfs",
"liblog",
"libz",
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 5e3c337d..0da1673 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 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 @@
: 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 @@
if (!mDataLoaderManager) {
LOG(FATAL) << "DataLoaderManagerService is unavailable";
}
+ if (!mAppOpsManager) {
+ LOG(FATAL) << "AppOpsManager is unavailable";
+ }
mountExistingImages();
}
@@ -275,12 +284,12 @@
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::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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
}
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::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 @@
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 @@
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 @@
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 @@
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 90d58a7..ff69633 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 @@
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 @@
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 @@
/*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 @@
: 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 @@
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 @@
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 @@
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 @@
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 0000000..24f9f7f
--- /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 2e31ef1..9f4192f 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -59,4 +59,8 @@
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 c330030..449b457 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 @@
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 @@
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 cde38fb..5553f68 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -220,24 +220,32 @@
}
};
+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 @@
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 @@
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 5480b6c..c225d3f 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 @@
}
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 0000000..4fe9458
--- /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 7027185..7ad39b4 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.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 @@
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 @@
@Mock InputManagerInternal mMockInputManagerInternal;
@Mock IVirtualDisplayCallback.Stub mMockAppToken;
+ @Mock IVirtualDisplayCallback.Stub mMockAppToken2;
@Mock WindowManagerInternal mMockWindowManagerInternal;
@Mock LightsManager mMockLightsManager;
@Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter;
@@ -135,10 +141,12 @@
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 @@
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 @@
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 05604b2..1debd8c 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 @@
@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 @@
@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 @@
}
/**
- * 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 @@
assertTrue(stack1.isFocusedStackOnDisplay());
// Stack2 should be focused after removing stack1.
- display.removeStack(stack1);
+ stack1.getDisplayArea().removeStack(stack1);
assertTrue(stack2.isFocusedStackOnDisplay());
}
@@ -156,7 +157,7 @@
}
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 @@
*/
@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 @@
}
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 @@
// 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 747ae94..08f6409 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.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 @@
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 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 @@
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 e8c0362..22d7fcb 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 @@
@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 af5afff..3d15401 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 @@
@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 @@
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 @@
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 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 @@
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 @@
@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 @@
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 @@
@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 @@
@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 @@
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 @@
@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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
@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 @@
}
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 @@
@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 @@
@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 @@
// 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 @@
// 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 @@
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 @@
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 @@
@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 @@
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 @@
@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 @@
@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 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 @@
@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 @@
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 @@
@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 @@
}
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 76a761c..27782f5 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 @@
.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 1d952bf..1cca207 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 9240b22..67d4769 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 @@
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 @@
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 @@
StackBuilder(RootWindowContainer root) {
mRootWindowContainer = root;
mDisplay = mRootWindowContainer.getDefaultDisplay();
+ mTaskDisplayArea = mDisplay.getDefaultTaskDisplayArea();
}
StackBuilder setWindowingMode(int windowingMode) {
@@ -436,8 +438,20 @@
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 @@
}
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 4cb50c7..9b7ffd6 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 @@
// 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 8b91c7e..8c8fd05 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 @@
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 38b3d76..a901d1e 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 @@
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 @@
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 @@
@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 @@
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 @@
@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 @@
@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 cf7411e..9b2a2db 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 @@
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 ae467c0..6a71a7d 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 @@
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 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 @@
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 071386f..d9c3ace 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_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 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 da07bac..6d2b7b1 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 @@
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 6810f64..881561f 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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 83631049..48d4e70 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 @@
@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 @@
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 @@
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 @@
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 @@
*/
@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 @@
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 @@
@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 @@
@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 @@
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 @@
@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 @@
mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
false);
- final TaskDisplayArea taskDisplayArea = display.mTaskContainers;
verify(taskDisplayArea, never()).moveHomeStackToFront(contains(reason));
}
@@ -364,12 +368,12 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 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 d6a67ab..3d3a0f1 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 @@
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 893a145..6734694 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 @@
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 @@
.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 af76e7f..af3ec38 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 @@
// 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 a3446d1..1a38ff2 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 @@
}
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 f76809b..50584c6 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 @@
@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 @@
@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 @@
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 6387a3b..d48e827 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 @@
// 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 900f014..a4f1487 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 @@
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 27ea37d..118c2e4 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 @@
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 c37735c..13a8273 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -153,8 +153,10 @@
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 3f95596..6dffe92 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -128,8 +128,11 @@
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 38c4ed4..eab174a 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -123,8 +123,10 @@
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 8de27e8..0fc9be3 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.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 @@
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 1658262..8204b49 100644
--- a/tests/net/common/java/android/net/RouteInfoTest.java
+++ b/tests/net/common/java/android/net/RouteInfoTest.java
@@ -31,6 +31,7 @@
import android.os.Build;
+import androidx.core.os.BuildCompat;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -62,6 +63,11 @@
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 @@
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 b02398d..912a27f 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -6336,6 +6336,7 @@
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 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 @@
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 @@
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 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 de2f5d9..6c2e6dd 100644
--- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
+++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
@@ -42,7 +42,7 @@
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 dfeea9f..f1be8b2 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1887,12 +1887,20 @@
* <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 @@
*
* 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 @@
*
* 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 4c524f4..cedf9b0 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -543,7 +543,7 @@
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 ac2f6b2..aca1909 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -56,7 +56,7 @@
.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 @@
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);