diff --git a/Manifests/Manifest_LAOS-20.0.xml b/Manifests/Manifest_LAOS-20.0.xml
index 43767185..7be160cb 100644
--- a/Manifests/Manifest_LAOS-20.0.xml
+++ b/Manifests/Manifest_LAOS-20.0.xml
@@ -50,7 +50,7 @@
-
+
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/av-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/av-01.patch
new file mode 100644
index 00000000..52acdac9
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/av-01.patch
@@ -0,0 +1,49 @@
+From 41235bcc67a2122bc1d6a4d19e8356b3d1ada91e Mon Sep 17 00:00:00 2001
+From: Kyle Zhang
+Date: Fri, 10 Mar 2023 00:06:16 +0000
+Subject: [PATCH] Fix mSession NULL dereference
+
+Bug: 230793853
+Test: clearkeyV1.4_fuzzer
+Change-Id: I3f11b133a4854a9ef896c9f5042bd719527c3fa6
+---
+ drm/mediadrm/plugins/clearkey/aidl/CryptoPlugin.cpp | 3 +++
+ drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp | 4 ++++
+ 2 files changed, 7 insertions(+)
+
+diff --git a/drm/mediadrm/plugins/clearkey/aidl/CryptoPlugin.cpp b/drm/mediadrm/plugins/clearkey/aidl/CryptoPlugin.cpp
+index afc9b6a9a4..a63471fd53 100644
+--- a/drm/mediadrm/plugins/clearkey/aidl/CryptoPlugin.cpp
++++ b/drm/mediadrm/plugins/clearkey/aidl/CryptoPlugin.cpp
+@@ -137,6 +137,8 @@ ::ndk::ScopedAStatus CryptoPlugin::decrypt(const DecryptArgs& in_args, int32_t*
+ *_aidl_return = static_cast(offset);
+ return toNdkScopedAStatus(Status::OK);
+ } else if (in_args.mode == Mode::AES_CTR) {
++ if (!mSession) return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE,
++ "session not found");
+ size_t bytesDecrypted{};
+ std::vector clearDataLengths;
+ std::vector encryptedDataLengths;
+@@ -149,6 +151,7 @@ ::ndk::ScopedAStatus CryptoPlugin::decrypt(const DecryptArgs& in_args, int32_t*
+ detailedError = "invalid decrypt parameter size";
+ return toNdkScopedAStatus(Status::ERROR_DRM_CANNOT_HANDLE, detailedError);
+ }
++
+ auto res =
+ mSession->decrypt(in_args.keyId.data(), in_args.iv.data(),
+ srcPtr, static_cast(destPtr),
+diff --git a/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp
+index 7bc320db6b..64a43b0af7 100644
+--- a/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp
++++ b/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp
+@@ -211,6 +211,10 @@ Return CryptoPlugin::decrypt_1_2(
+ _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "invalid decrypt parameter size");
+ return Void();
+ }
++ if (!mSession) {
++ _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "session not found");
++ return Void();
++ }
+ Status_V1_2 res = mSession->decrypt(keyId.data(), iv.data(), srcPtr,
+ static_cast(destPtr), toVector(subSamples), &bytesDecrypted);
+ if (res == Status_V1_2::OK) {
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/av-02.patch b/Patches/LineageOS-20.0/ASB-2023-10/av-02.patch
new file mode 100644
index 00000000..ff0f27d7
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/av-02.patch
@@ -0,0 +1,26 @@
+From a89f704701e6af4a4809f4bb4911af88a023226f Mon Sep 17 00:00:00 2001
+From: Kyle Zhang
+Date: Wed, 22 Feb 2023 20:14:34 +0000
+Subject: [PATCH] Fix null pointer derefernce AMediaDrm_setPropertyByteArray
+
+Bug: 234798181
+Test: ndkDrmFuzzer
+Change-Id: I0d2d92ce7be108a904e811048f90a93571862ebc
+---
+ media/ndk/NdkMediaDrm.cpp | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/media/ndk/NdkMediaDrm.cpp b/media/ndk/NdkMediaDrm.cpp
+index f4674dea63..170506168f 100644
+--- a/media/ndk/NdkMediaDrm.cpp
++++ b/media/ndk/NdkMediaDrm.cpp
+@@ -758,6 +758,9 @@ media_status_t AMediaDrm_setPropertyString(AMediaDrm *mObj,
+ EXPORT
+ media_status_t AMediaDrm_setPropertyByteArray(AMediaDrm *mObj,
+ const char *propertyName, const uint8_t *value, size_t valueSize) {
++ if (!mObj || mObj->mDrm == NULL) {
++ return AMEDIA_ERROR_INVALID_OBJECT;
++ }
+
+ Vector byteArray;
+ byteArray.appendArray(value, valueSize);
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/av-03.patch b/Patches/LineageOS-20.0/ASB-2023-10/av-03.patch
new file mode 100644
index 00000000..b8e9f595
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/av-03.patch
@@ -0,0 +1,30 @@
+From 6d7cd80d77ed35efbe168f627dda021a5d8dd766 Mon Sep 17 00:00:00 2001
+From: Kwangkyu Park
+Date: Thu, 9 Feb 2023 16:29:10 +0900
+Subject: [PATCH] Fix an issue that a free memory is accessed
+
+In error state, request thread join is not executed. And,
+Camera3Device's mId can be accessed from String& after being freed with
+CLOGE().
+
+This fix will address by changing reference type to normal variable.
+
+Bug: 268437033
+Change-Id: Iad3fcd0fbbaf1e18a2b95093aba08015a933374c
+---
+ services/camera/libcameraservice/device3/Camera3Device.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
+index e6073a94d4..5187a96f59 100644
+--- a/services/camera/libcameraservice/device3/Camera3Device.h
++++ b/services/camera/libcameraservice/device3/Camera3Device.h
+@@ -1010,7 +1010,7 @@ class Camera3Device :
+
+ wp mListener;
+
+- const String8& mId; // The camera ID
++ const String8 mId; // The camera ID
+ int mStatusId; // The RequestThread's component ID for
+ // status tracking
+
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/av-04.patch b/Patches/LineageOS-20.0/ASB-2023-10/av-04.patch
new file mode 100644
index 00000000..d67fb6c6
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/av-04.patch
@@ -0,0 +1,300 @@
+From 75fc175a08c1a8e86d4649c19fd3136121518b96 Mon Sep 17 00:00:00 2001
+From: Ray Essick
+Date: Mon, 22 Aug 2022 15:58:51 -0500
+Subject: [PATCH] NdkMediaFormat_*() parameter checking
+
+verify non-null format and name fields in the NdkMediaFormat_set/get
+functions. Update unit tests to verify fixes are in place.
+
+Bug: 243204176
+Test: atest libmediandk_test
+Change-Id: I551b61c96dce71310bcd1d057abad0fde021cc55
+---
+ media/ndk/NdkMediaFormat.cpp | 97 ++++++++++++++++++++++++-
+ media/ndk/tests/NdkMediaFormat_test.cpp | 45 ++++++++++++
+ 2 files changed, 141 insertions(+), 1 deletion(-)
+
+diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
+index 923453ac1b..a95e87419a 100644
+--- a/media/ndk/NdkMediaFormat.cpp
++++ b/media/ndk/NdkMediaFormat.cpp
+@@ -147,38 +147,80 @@ const char* AMediaFormat_toString(AMediaFormat *mData) {
+
+ EXPORT
+ bool AMediaFormat_getInt32(AMediaFormat* format, const char *name, int32_t *out) {
++ if (format == nullptr) {
++ return false;
++ }
++ if (name == nullptr) {
++ return false;
++ }
+ return format->mFormat->findInt32(name, out);
+ }
+
+ EXPORT
+ bool AMediaFormat_getInt64(AMediaFormat* format, const char *name, int64_t *out) {
++ if (format == nullptr) {
++ return false;
++ }
++ if (name == nullptr) {
++ return false;
++ }
+ return format->mFormat->findInt64(name, out);
+ }
+
+ EXPORT
+ bool AMediaFormat_getFloat(AMediaFormat* format, const char *name, float *out) {
++ if (format == nullptr) {
++ return false;
++ }
++ if (name == nullptr) {
++ return false;
++ }
+ return format->mFormat->findFloat(name, out);
+ }
+
+ EXPORT
+ bool AMediaFormat_getDouble(AMediaFormat* format, const char *name, double *out) {
++ if (format == nullptr) {
++ return false;
++ }
++ if (name == nullptr) {
++ return false;
++ }
+ return format->mFormat->findDouble(name, out);
+ }
+
+ EXPORT
+ bool AMediaFormat_getSize(AMediaFormat* format, const char *name, size_t *out) {
++ if (format == nullptr) {
++ return false;
++ }
++ if (name == nullptr) {
++ return false;
++ }
+ return format->mFormat->findSize(name, out);
+ }
+
+ EXPORT
+ bool AMediaFormat_getRect(AMediaFormat* format, const char *name,
+ int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) {
++ if (format == nullptr) {
++ return false;
++ }
++ if (name == nullptr) {
++ return false;
++ }
+ return format->mFormat->findRect(name, left, top, right, bottom);
+ }
+
+ EXPORT
+ bool AMediaFormat_getBuffer(AMediaFormat* format, const char *name, void** data, size_t *outsize) {
+ sp buf;
++ if (format == nullptr) {
++ return false;
++ }
++ if (name == nullptr) {
++ return false;
++ }
+ if (format->mFormat->findBuffer(name, &buf)) {
+ *data = buf->data() + buf->offset();
+ *outsize = buf->size();
+@@ -189,7 +231,12 @@ bool AMediaFormat_getBuffer(AMediaFormat* format, const char *name, void** data,
+
+ EXPORT
+ bool AMediaFormat_getString(AMediaFormat* mData, const char *name, const char **out) {
+-
++ if (mData == nullptr) {
++ return false;
++ }
++ if (name == nullptr) {
++ return false;
++ }
+ for (size_t i = 0; i < mData->mStringCache.size(); i++) {
+ if (strcmp(mData->mStringCache.keyAt(i).string(), name) == 0) {
+ mData->mStringCache.removeItemsAt(i, 1);
+@@ -212,43 +259,91 @@ bool AMediaFormat_getString(AMediaFormat* mData, const char *name, const char **
+
+ EXPORT
+ void AMediaFormat_setInt32(AMediaFormat* format, const char *name, int32_t value) {
++ if (format == nullptr) {
++ return;
++ }
++ if (name == nullptr) {
++ return;
++ }
+ format->mFormat->setInt32(name, value);
+ }
+
+ EXPORT
+ void AMediaFormat_setInt64(AMediaFormat* format, const char *name, int64_t value) {
++ if (format == nullptr) {
++ return;
++ }
++ if (name == nullptr) {
++ return;
++ }
+ format->mFormat->setInt64(name, value);
+ }
+
+ EXPORT
+ void AMediaFormat_setFloat(AMediaFormat* format, const char* name, float value) {
++ if (format == nullptr) {
++ return;
++ }
++ if (name == nullptr) {
++ return;
++ }
+ format->mFormat->setFloat(name, value);
+ }
+
+ EXPORT
+ void AMediaFormat_setDouble(AMediaFormat* format, const char* name, double value) {
++ if (format == nullptr) {
++ return;
++ }
++ if (name == nullptr) {
++ return;
++ }
+ format->mFormat->setDouble(name, value);
+ }
+
+ EXPORT
+ void AMediaFormat_setSize(AMediaFormat* format, const char* name, size_t value) {
++ if (format == nullptr) {
++ return;
++ }
++ if (name == nullptr) {
++ return;
++ }
+ format->mFormat->setSize(name, value);
+ }
+
+ EXPORT
+ void AMediaFormat_setRect(AMediaFormat* format, const char *name,
+ int32_t left, int32_t top, int32_t right, int32_t bottom) {
++ if (format == nullptr) {
++ return;
++ }
++ if (name == nullptr) {
++ return;
++ }
+ format->mFormat->setRect(name, left, top, right, bottom);
+ }
+
+ EXPORT
+ void AMediaFormat_setString(AMediaFormat* format, const char* name, const char* value) {
++ if (format == nullptr) {
++ return;
++ }
++ if (name == nullptr) {
++ return;
++ }
+ // AMessage::setString() makes a copy of the string
+ format->mFormat->setString(name, value, strlen(value));
+ }
+
+ EXPORT
+ void AMediaFormat_setBuffer(AMediaFormat* format, const char* name, const void* data, size_t size) {
++ if (format == nullptr) {
++ return;
++ }
++ if (name == nullptr) {
++ return;
++ }
+ // the ABuffer(void*, size_t) constructor doesn't take ownership of the data, so create
+ // a new buffer and copy the data into it
+ sp buf = new ABuffer(size);
+diff --git a/media/ndk/tests/NdkMediaFormat_test.cpp b/media/ndk/tests/NdkMediaFormat_test.cpp
+index 668d0a4463..18690b8782 100644
+--- a/media/ndk/tests/NdkMediaFormat_test.cpp
++++ b/media/ndk/tests/NdkMediaFormat_test.cpp
+@@ -51,6 +51,13 @@ TEST(NdkMediaFormat_tests, test_int32) {
+ EXPECT_FALSE(AMediaFormat_getInt64(fmt1, "five", &i64));
+ EXPECT_EQ(i32, 5);
+
++ // verify detecting some bad parameters.
++ AMediaFormat_setInt32(nullptr, "whatever", 6);
++ AMediaFormat_setInt32(fmt1, nullptr, 6);
++
++ EXPECT_FALSE(AMediaFormat_getInt32(nullptr, "whatever", &i32));
++ EXPECT_FALSE(AMediaFormat_getInt32(fmt1, nullptr, &i32));
++
+ AMediaFormat_delete(fmt1);
+ }
+
+@@ -67,6 +74,13 @@ TEST(NdkMediaFormat_tests, test_int64) {
+ EXPECT_FALSE(AMediaFormat_getInt64(fmt1, "five", &i64));
+ EXPECT_EQ(i64, -1);
+
++ // verify detecting some bad parameters.
++ AMediaFormat_setInt64(nullptr, "whatever", 6);
++ AMediaFormat_setInt64(fmt1, nullptr, 6);
++
++ EXPECT_FALSE(AMediaFormat_getInt64(nullptr, "whatever", &i64));
++ EXPECT_FALSE(AMediaFormat_getInt64(fmt1, nullptr, &i64));
++
+ AMediaFormat_delete(fmt1);
+ }
+
+@@ -80,6 +94,13 @@ TEST(NdkMediaFormat_tests, test_size) {
+ EXPECT_TRUE(AMediaFormat_getSize(fmt1, "medium", &size));
+ EXPECT_EQ(size, 10);
+
++ // verify detecting some bad parameters.
++ AMediaFormat_setSize(nullptr, "whatever", 6);
++ AMediaFormat_setSize(fmt1, nullptr, 6);
++
++ EXPECT_FALSE(AMediaFormat_getSize(nullptr, "whatever", &size));
++ EXPECT_FALSE(AMediaFormat_getSize(fmt1, nullptr, &size));
++
+ AMediaFormat_delete(fmt1);
+ }
+
+@@ -90,6 +111,14 @@ TEST(NdkMediaFormat_tests, test_float) {
+ AMediaFormat_setFloat(fmt1, "ship", 0.5);
+ EXPECT_TRUE(AMediaFormat_getFloat(fmt1, "boat", &f));
+ EXPECT_EQ(f, 1.5);
++
++ // verify detecting some bad parameters.
++ AMediaFormat_setFloat(nullptr, "whatever", 1.5);
++ AMediaFormat_setFloat(fmt1, nullptr, 1.5);
++
++ EXPECT_FALSE(AMediaFormat_getFloat(nullptr, "whatever", &f));
++ EXPECT_FALSE(AMediaFormat_getFloat(fmt1, nullptr, &f));
++
+ AMediaFormat_delete(fmt1);
+ }
+
+@@ -100,6 +129,14 @@ TEST(NdkMediaFormat_tests, test_double) {
+ AMediaFormat_setDouble(fmt1, "dip", 0.5);
+ EXPECT_TRUE(AMediaFormat_getDouble(fmt1, "trouble", &d));
+ EXPECT_EQ(d, 100.5);
++
++ // verify detecting some bad parameters.
++ AMediaFormat_setDouble(nullptr, "whatever", 1.5);
++ AMediaFormat_setDouble(fmt1, nullptr, 1.5);
++
++ EXPECT_FALSE(AMediaFormat_getDouble(nullptr, "whatever", &d));
++ EXPECT_FALSE(AMediaFormat_getDouble(fmt1, nullptr, &d));
++
+ AMediaFormat_delete(fmt1);
+ }
+
+@@ -111,8 +148,16 @@ TEST(NdkMediaFormat_tests, test_string) {
+ AMediaFormat_setString(fmt1, "stringtheory", content);
+ EXPECT_TRUE(AMediaFormat_getString(fmt1, "stringtheory", &out));
+ EXPECT_NE(out, nullptr);
++ EXPECT_NE(out, content); // should not be the original
+ EXPECT_EQ(strcmp(out,content), 0);
+
++ // verify detecting some bad parameters.
++ AMediaFormat_setString(nullptr, "whatever", content);
++ AMediaFormat_setString(fmt1, nullptr, content);
++
++ EXPECT_FALSE(AMediaFormat_getString(nullptr, "whatever", &out));
++ EXPECT_FALSE(AMediaFormat_getString(fmt1, nullptr, &out));
++
+ AMediaFormat_delete(fmt1);
+ }
+
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/av-05.patch b/Patches/LineageOS-20.0/ASB-2023-10/av-05.patch
new file mode 100644
index 00000000..25bcdab2
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/av-05.patch
@@ -0,0 +1,33 @@
+From b023ec300f437494d6d6b23b03607e308dae43d2 Mon Sep 17 00:00:00 2001
+From: Jiajia Cong
+Date: Wed, 2 Dec 2020 12:00:49 +0800
+Subject: [PATCH] codec2: fix issue in allocating too many 8k buffers
+
+default output delay is too large, in 8k use case, framework doesn't
+allow to use so much memory to allocate those 8k buffers.
+
+if exceeds max capacity and underlying buffer alloc fail. do not
+create MediaCodecBuffer holds a null underlying ABuffer. Otherwise,
+some MediaCodecBuffer APIs may access ABuffer (nullptr).
+
+Bug: b/208481412
+Change-Id: Ic8e33f6c27ee1c435bf0dc4bc09df87cb0ad3c3a
+---
+ media/codec2/sfplugin/Codec2Buffer.cpp | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/media/codec2/sfplugin/Codec2Buffer.cpp b/media/codec2/sfplugin/Codec2Buffer.cpp
+index 55e0c45fad..876c96d484 100644
+--- a/media/codec2/sfplugin/Codec2Buffer.cpp
++++ b/media/codec2/sfplugin/Codec2Buffer.cpp
+@@ -843,6 +843,10 @@ sp ConstGraphicBlockBuffer::AllocateEmpty(
+ }
+ }
+ sp aBuffer(alloc(align(width, 16) * align(height, 16) * bpp / 8));
++ if (aBuffer == nullptr) {
++ ALOGD("%s: failed to allocate buffer", __func__);
++ return nullptr;
++ }
+ return new ConstGraphicBlockBuffer(
+ format,
+ aBuffer,
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/av-06.patch b/Patches/LineageOS-20.0/ASB-2023-10/av-06.patch
new file mode 100644
index 00000000..bb1e8c85
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/av-06.patch
@@ -0,0 +1,29 @@
+From c8117d1539078bb3339b5d5fffe063a9135c2c21 Mon Sep 17 00:00:00 2001
+From: Arun Johnson
+Date: Mon, 18 Jul 2022 19:16:04 +0000
+Subject: [PATCH] Adding null check for IResourceManagerClient in dump()
+
+bug: 191613308
+Change-Id: I1694f2d1c13163a9b8162630377de71303bc517f
+---
+ services/mediaresourcemanager/ResourceManagerService.cpp | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/services/mediaresourcemanager/ResourceManagerService.cpp b/services/mediaresourcemanager/ResourceManagerService.cpp
+index b4610bc71e..4d18876bfc 100644
+--- a/services/mediaresourcemanager/ResourceManagerService.cpp
++++ b/services/mediaresourcemanager/ResourceManagerService.cpp
+@@ -271,10 +271,9 @@ binder_status_t ResourceManagerService::dump(int fd, const char** /*args*/, uint
+ snprintf(buffer, SIZE, " Id: %lld\n", (long long)infos[j].clientId);
+ result.append(buffer);
+
+- std::string clientName;
+- Status status = infos[j].client->getName(&clientName);
+- if (!status.isOk()) {
+- clientName = "";
++ std::string clientName = "";
++ if (infos[j].client != nullptr) {
++ Status status = infos[j].client->getName(&clientName);
+ }
+ snprintf(buffer, SIZE, " Name: %s\n", clientName.c_str());
+ result.append(buffer);
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/av-07.patch b/Patches/LineageOS-20.0/ASB-2023-10/av-07.patch
new file mode 100644
index 00000000..e6983a6e
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/av-07.patch
@@ -0,0 +1,49 @@
+From f06d23d824f60e98299d03f21c0715477666936d Mon Sep 17 00:00:00 2001
+From: Jeffrey Kardatzke
+Date: Tue, 21 Mar 2023 11:38:33 -0700
+Subject: [PATCH] Fix incorrect buffer size in NuPlayer
+
+This was discovered from running GTS tests that were failing because the
+DRM implementation was receiving media packets of incorrect sizes for
+decryption. The problem is that it was copying content using the size of
+the underlying MediaBuffer object rather than the range that was set in
+it. That was leading to lots of trailing garbage in media packets.
+Generally this was fine and decoders would ignore them, but recent
+changes in decryption handling for AMD platforms exposed this problem.
+
+The fix is very straightforward in that we should be using the
+range_length rather than the size when copying them. This doesn't impact
+non-DRM content as those buffer sizes appear to be correct already based
+on testing.
+
+Bug: b:268158584
+Test: gts.MediaPlayerTest#testLLAMA_H264_BASELINE_240P_800_DOWNLOADED_V1_ASYNC
+ no longer shows corruption on guybrush and packet sizes now match
+ up as expected
+Change-Id: I14eda495fa76621436b212f2bd3ae9f7093137fe
+---
+ media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+index 52b2041ea8..8da09c434a 100644
+--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
++++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+@@ -1104,14 +1104,14 @@ bool NuPlayer::Decoder::onInputBufferFetched(const sp &msg) {
+ static_cast(holder.get())->mediaBuffer() : nullptr;
+ }
+ if (mediaBuf != NULL) {
+- if (mediaBuf->size() > codecBuffer->capacity()) {
++ if (mediaBuf->range_length() > codecBuffer->capacity()) {
+ handleError(ERROR_BUFFER_TOO_SMALL);
+ mDequeuedInputBuffers.push_back(bufferIx);
+ return false;
+ }
+
+- codecBuffer->setRange(0, mediaBuf->size());
+- memcpy(codecBuffer->data(), mediaBuf->data(), mediaBuf->size());
++ codecBuffer->setRange(0, mediaBuf->range_length());
++ memcpy(codecBuffer->data(), mediaBuf->data(), mediaBuf->range_length());
+
+ MetaDataBase &meta_data = mediaBuf->meta_data();
+ cryptInfo = NuPlayerDrm::getSampleCryptoInfo(meta_data);
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/av-08.patch b/Patches/LineageOS-20.0/ASB-2023-10/av-08.patch
new file mode 100644
index 00000000..e6a75611
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/av-08.patch
@@ -0,0 +1,75 @@
+From 9c7408ab0710a9e36fd2a258098afde863cb6544 Mon Sep 17 00:00:00 2001
+From: Harish Mahendrakar
+Date: Wed, 19 Oct 2022 18:17:07 -0700
+Subject: [PATCH] Codec2 decoders: Invalidate allocated graphic buffer in
+ stop()
+
+Bug: 243583691
+Test: atest CtsMediaV2TestCases -- --module-arg \
+CtsMediaV2TestCases:instrumentation-arg:codec-prefix:=c2.android.
+
+Change-Id: I312c106350b2eb072f0e6f2f9a83f16eb8424cb2
+---
+ media/codec2/components/avc/C2SoftAvcDec.cpp | 3 +++
+ media/codec2/components/hevc/C2SoftHevcDec.cpp | 3 +++
+ media/codec2/components/mpeg2/C2SoftMpeg2Dec.cpp | 3 +++
+ media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp | 4 +++-
+ 4 files changed, 12 insertions(+), 1 deletion(-)
+
+diff --git a/media/codec2/components/avc/C2SoftAvcDec.cpp b/media/codec2/components/avc/C2SoftAvcDec.cpp
+index 953afc57cd..96a4c4a756 100644
+--- a/media/codec2/components/avc/C2SoftAvcDec.cpp
++++ b/media/codec2/components/avc/C2SoftAvcDec.cpp
+@@ -671,6 +671,9 @@ status_t C2SoftAvcDec::resetDecoder() {
+ void C2SoftAvcDec::resetPlugin() {
+ mSignalledOutputEos = false;
+ mTimeStart = mTimeEnd = systemTime();
++ if (mOutBlock) {
++ mOutBlock.reset();
++ }
+ }
+
+ status_t C2SoftAvcDec::deleteDecoder() {
+diff --git a/media/codec2/components/hevc/C2SoftHevcDec.cpp b/media/codec2/components/hevc/C2SoftHevcDec.cpp
+index 5a660c5ca1..7d3b9d9399 100644
+--- a/media/codec2/components/hevc/C2SoftHevcDec.cpp
++++ b/media/codec2/components/hevc/C2SoftHevcDec.cpp
+@@ -664,6 +664,9 @@ status_t C2SoftHevcDec::resetDecoder() {
+ void C2SoftHevcDec::resetPlugin() {
+ mSignalledOutputEos = false;
+ mTimeStart = mTimeEnd = systemTime();
++ if (mOutBlock) {
++ mOutBlock.reset();
++ }
+ }
+
+ status_t C2SoftHevcDec::deleteDecoder() {
+diff --git a/media/codec2/components/mpeg2/C2SoftMpeg2Dec.cpp b/media/codec2/components/mpeg2/C2SoftMpeg2Dec.cpp
+index 9a4191025f..439323c76e 100644
+--- a/media/codec2/components/mpeg2/C2SoftMpeg2Dec.cpp
++++ b/media/codec2/components/mpeg2/C2SoftMpeg2Dec.cpp
+@@ -732,6 +732,9 @@ status_t C2SoftMpeg2Dec::resetDecoder() {
+ void C2SoftMpeg2Dec::resetPlugin() {
+ mSignalledOutputEos = false;
+ mTimeStart = mTimeEnd = systemTime();
++ if (mOutBlock) {
++ mOutBlock.reset();
++ }
+ }
+
+ status_t C2SoftMpeg2Dec::deleteDecoder() {
+diff --git a/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp
+index 54a1d0e354..3bf9c48dc3 100644
+--- a/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp
++++ b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp
+@@ -256,7 +256,9 @@ c2_status_t C2SoftMpeg4Dec::onStop() {
+ mFramesConfigured = false;
+ mSignalledOutputEos = false;
+ mSignalledError = false;
+-
++ if (mOutBlock) {
++ mOutBlock.reset();
++ }
+ return C2_OK;
+ }
+
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/base-01.patch
new file mode 100644
index 00000000..f5ac0473
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-01.patch
@@ -0,0 +1,60 @@
+From 89489ff5dd9b7717f0421ca4e90bc060af1ba8b7 Mon Sep 17 00:00:00 2001
+From: Jean-Michel Trivi
+Date: Wed, 7 Dec 2022 04:36:46 +0000
+Subject: [PATCH] RingtoneManager: verify default ringtone is audio
+
+When a ringtone picker tries to set a ringtone through
+RingtoneManager.setActualDefaultRingtoneUri (also
+called by com.android.settings.DefaultRingtonePreference),
+verify the mimeType can be obtained (not found when caller
+doesn't have access to it) and it is an audio resource.
+
+Bug: 205837340
+Test: atest android.media.audio.cts.RingtoneManagerTest
+(cherry picked from commit 38618f9fb16d3b5617e2289354d47abe5af17dad)
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b8c2d03b720f0cc200ac59f6cfb411fddc3b119c)
+Merged-In: I3f2c487ded405c0c1a83ef0a2fe99cff7cc9328e
+Change-Id: I3f2c487ded405c0c1a83ef0a2fe99cff7cc9328e
+---
+ media/java/android/media/RingtoneManager.java | 19 +++++++++++++++++--
+ 1 file changed, 17 insertions(+), 2 deletions(-)
+
+diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
+index 27db41cb9f4e..d3c3c370a641 100644
+--- a/media/java/android/media/RingtoneManager.java
++++ b/media/java/android/media/RingtoneManager.java
+@@ -814,10 +814,10 @@ public static Uri getActualDefaultRingtoneUri(Context context, int type) {
+
+ return ringtoneUri;
+ }
+-
++
+ /**
+ * Sets the {@link Uri} of the default sound for a given sound type.
+- *
++ *
+ * @param context A context used for querying.
+ * @param type The type whose default sound should be set. One of
+ * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or
+@@ -833,6 +833,21 @@ public static void setActualDefaultRingtoneUri(Context context, int type, Uri ri
+ if(!isInternalRingtoneUri(ringtoneUri)) {
+ ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
+ }
++
++ if (ringtoneUri != null) {
++ final String mimeType = resolver.getType(ringtoneUri);
++ if (mimeType == null) {
++ Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
++ + " ignored: failure to find mimeType (no access from this context?)");
++ return;
++ }
++ if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
++ Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
++ + " ignored: associated mimeType:" + mimeType + " is not an audio type");
++ return;
++ }
++ }
++
+ Settings.System.putStringForUser(resolver, setting,
+ ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
+
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-02.patch b/Patches/LineageOS-20.0/ASB-2023-10/base-02.patch
new file mode 100644
index 00000000..0f8dafb7
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-02.patch
@@ -0,0 +1,1168 @@
+From d1765c47157a99ecdc44537b5cadbb9726892967 Mon Sep 17 00:00:00 2001
+From: Beth Thibodeau
+Date: Tue, 30 May 2023 18:45:47 -0500
+Subject: [PATCH] Add placeholder when media control title is blank
+
+When an app posts a media control with no available title, show a
+placeholder string with the app name instead
+
+Bug: 274775190
+Test: atest MediaDataManagerTest
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a0fda1f36d04331c8d60c5540b09b1a30203581b)
+Merged-In: Ie406c180af48653595e8e222a15b4dda27de2e0e
+Change-Id: Ie406c180af48653595e8e222a15b4dda27de2e0e
+---
+ packages/SystemUI/res/values/strings.xml | 2 +
+ .../controls/pipeline/MediaDataManager.kt | 8 +-
+ .../systemui/media/MediaDataManagerTest.kt | 1106 +++++++++++++++++
+ 3 files changed, 1114 insertions(+), 2 deletions(-)
+ create mode 100644 packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+
+diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
+index b7036529a733..ac894de4c5d9 100644
+--- a/packages/SystemUI/res/values/strings.xml
++++ b/packages/SystemUI/res/values/strings.xml
+@@ -2386,6 +2386,8 @@
+ Play %1$s from %2$s
+
+ For You
++
++ %1$s is running
+
+
+
+diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+index 525b2fcb8dbc..4e20a24e9add 100644
+--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
++++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+@@ -786,12 +786,16 @@ class MediaDataManager(
+
+ // Song name
+ var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+- if (song == null) {
++ if (song.isNullOrBlank()) {
+ song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+ }
+- if (song == null) {
++ if (song.isNullOrBlank()) {
+ song = HybridGroupManager.resolveTitle(notif)
+ }
++ if (song.isNullOrBlank()) {
++ // For apps that don't include a title, add a placeholder
++ song = context.getString(R.string.controls_media_empty_title, appName)
++ }
+
+ // Explicit Indicator
+ var isExplicit = false
+diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+new file mode 100644
+index 000000000000..52266f983fdd
+--- /dev/null
++++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+@@ -0,0 +1,1106 @@
++package com.android.systemui.media
++
++import android.app.Notification
++import android.app.Notification.MediaStyle
++import android.app.PendingIntent
++import android.app.smartspace.SmartspaceAction
++import android.app.smartspace.SmartspaceTarget
++import android.content.Intent
++import android.graphics.Bitmap
++import android.graphics.drawable.Icon
++import android.media.MediaDescription
++import android.media.MediaMetadata
++import android.media.session.MediaController
++import android.media.session.MediaSession
++import android.media.session.PlaybackState
++import android.os.Bundle
++import android.provider.Settings
++import android.service.notification.StatusBarNotification
++import android.testing.AndroidTestingRunner
++import android.testing.TestableLooper.RunWithLooper
++import androidx.media.utils.MediaConstants
++import androidx.test.filters.SmallTest
++import com.android.internal.logging.InstanceId
++import com.android.systemui.InstanceIdSequenceFake
++import com.android.systemui.R
++import com.android.systemui.SysuiTestCase
++import com.android.systemui.broadcast.BroadcastDispatcher
++import com.android.systemui.dump.DumpManager
++import com.android.systemui.plugins.ActivityStarter
++import com.android.systemui.statusbar.SbnBuilder
++import com.android.systemui.tuner.TunerService
++import com.android.systemui.util.concurrency.FakeExecutor
++import com.android.systemui.util.mockito.any
++import com.android.systemui.util.mockito.argumentCaptor
++import com.android.systemui.util.mockito.capture
++import com.android.systemui.util.mockito.eq
++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.Ignore
++import org.junit.Rule
++import org.junit.Test
++import org.junit.runner.RunWith
++import org.mockito.ArgumentCaptor
++import org.mockito.ArgumentMatchers.anyBoolean
++import org.mockito.ArgumentMatchers.anyInt
++import org.mockito.Captor
++import org.mockito.Mock
++import org.mockito.Mockito
++import org.mockito.Mockito.never
++import org.mockito.Mockito.reset
++import org.mockito.Mockito.times
++import org.mockito.Mockito.verify
++import org.mockito.Mockito.verifyNoMoreInteractions
++import org.mockito.junit.MockitoJUnit
++import org.mockito.Mockito.`when` as whenever
++
++private const val KEY = "KEY"
++private const val KEY_2 = "KEY_2"
++private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
++private const val PACKAGE_NAME = "com.example.app"
++private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
++private const val APP_NAME = "com.android.systemui.tests"
++private const val SESSION_ARTIST = "artist"
++private const val SESSION_TITLE = "title"
++private const val SESSION_BLANK_TITLE = " "
++private const val SESSION_EMPTY_TITLE = ""
++private const val USER_ID = 0
++private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
++
++private fun anyObject(): T {
++ return Mockito.anyObject()
++}
++
++@SmallTest
++@RunWithLooper(setAsMainLooper = true)
++@RunWith(AndroidTestingRunner::class)
++class MediaDataManagerTest : SysuiTestCase() {
++
++ @JvmField @Rule val mockito = MockitoJUnit.rule()
++ @Mock lateinit var mediaControllerFactory: MediaControllerFactory
++ @Mock lateinit var controller: MediaController
++ @Mock lateinit var transportControls: MediaController.TransportControls
++ @Mock lateinit var playbackInfo: MediaController.PlaybackInfo
++ lateinit var session: MediaSession
++ lateinit var metadataBuilder: MediaMetadata.Builder
++ lateinit var backgroundExecutor: FakeExecutor
++ lateinit var foregroundExecutor: FakeExecutor
++ @Mock lateinit var dumpManager: DumpManager
++ @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
++ @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
++ @Mock lateinit var mediaResumeListener: MediaResumeListener
++ @Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter
++ @Mock lateinit var mediaDeviceManager: MediaDeviceManager
++ @Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
++ @Mock lateinit var mediaDataFilter: MediaDataFilter
++ @Mock lateinit var listener: MediaDataManager.Listener
++ @Mock lateinit var pendingIntent: PendingIntent
++ @Mock lateinit var activityStarter: ActivityStarter
++ lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
++ @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
++ @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
++ lateinit var validRecommendationList: List
++ @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
++ @Mock private lateinit var mediaFlags: MediaFlags
++ @Mock private lateinit var logger: MediaUiEventLogger
++ lateinit var mediaDataManager: MediaDataManager
++ lateinit var mediaNotification: StatusBarNotification
++ @Captor lateinit var mediaDataCaptor: ArgumentCaptor
++ private val clock = FakeSystemClock()
++ @Mock private lateinit var tunerService: TunerService
++ @Captor lateinit var tunableCaptor: ArgumentCaptor
++
++ private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
++
++ private val originalSmartspaceSetting = Settings.Secure.getInt(context.contentResolver,
++ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
++
++ @Before
++ fun setup() {
++ foregroundExecutor = FakeExecutor(clock)
++ backgroundExecutor = FakeExecutor(clock)
++ smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
++ Settings.Secure.putInt(context.contentResolver,
++ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
++ mediaDataManager = MediaDataManager(
++ context = context,
++ backgroundExecutor = backgroundExecutor,
++ foregroundExecutor = foregroundExecutor,
++ mediaControllerFactory = mediaControllerFactory,
++ broadcastDispatcher = broadcastDispatcher,
++ dumpManager = dumpManager,
++ mediaTimeoutListener = mediaTimeoutListener,
++ mediaResumeListener = mediaResumeListener,
++ mediaSessionBasedFilter = mediaSessionBasedFilter,
++ mediaDeviceManager = mediaDeviceManager,
++ mediaDataCombineLatest = mediaDataCombineLatest,
++ mediaDataFilter = mediaDataFilter,
++ activityStarter = activityStarter,
++ smartspaceMediaDataProvider = smartspaceMediaDataProvider,
++ useMediaResumption = true,
++ useQsMediaPlayer = true,
++ systemClock = clock,
++ tunerService = tunerService,
++ mediaFlags = mediaFlags,
++ logger = logger
++ )
++ verify(tunerService).addTunable(capture(tunableCaptor),
++ eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
++ session = MediaSession(context, "MediaDataManagerTestSession")
++ mediaNotification = SbnBuilder().run {
++ setPkg(PACKAGE_NAME)
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
++ }
++ build()
++ }
++ metadataBuilder = MediaMetadata.Builder().apply {
++ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
++ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
++ }
++ whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
++ whenever(controller.transportControls).thenReturn(transportControls)
++ whenever(controller.playbackInfo).thenReturn(playbackInfo)
++ whenever(playbackInfo.playbackType).thenReturn(
++ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
++
++ // This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal
++ // listeners in the internal processing pipeline. It receives events, but ince it is a
++ // mock, it doesn't pass those events along the chain to the external listeners. So, just
++ // treat mediaSessionBasedFilter as a listener for testing.
++ listener = mediaSessionBasedFilter
++
++ val recommendationExtras = Bundle().apply {
++ putString("package_name", PACKAGE_NAME)
++ putParcelable("dismiss_intent", DISMISS_INTENT)
++ }
++ val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
++ whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
++ whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
++ whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras)
++ whenever(mediaRecommendationItem.icon).thenReturn(icon)
++ validRecommendationList = listOf(
++ mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem
++ )
++ whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
++ whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
++ whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
++ whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
++ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
++ whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
++ }
++
++ @After
++ fun tearDown() {
++ session.release()
++ mediaDataManager.destroy()
++ Settings.Secure.putInt(context.contentResolver,
++ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, originalSmartspaceSetting)
++ }
++
++ @Test
++ fun testSetTimedOut_active_deactivatesMedia() {
++ addNotificationAndLoad()
++ val data = mediaDataCaptor.value
++ assertThat(data.active).isTrue()
++
++ mediaDataManager.setTimedOut(KEY, timedOut = true)
++ assertThat(data.active).isFalse()
++ verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
++ }
++
++ @Test
++ fun testSetTimedOut_resume_dismissesMedia() {
++ // WHEN resume controls are present, and time out
++ val desc = MediaDescription.Builder().run {
++ setTitle(SESSION_TITLE)
++ build()
++ }
++ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
++ APP_NAME, pendingIntent, PACKAGE_NAME)
++
++ backgroundExecutor.runAllReady()
++ foregroundExecutor.runAllReady()
++ verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
++ eq(true), eq(0), eq(false))
++
++ mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
++ verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME),
++ eq(mediaDataCaptor.value.instanceId))
++
++ // THEN it is removed and listeners are informed
++ foregroundExecutor.advanceClockToLast()
++ foregroundExecutor.runAllReady()
++ verify(listener).onMediaDataRemoved(PACKAGE_NAME)
++ }
++
++ @Test
++ fun testLoadsMetadataOnBackground() {
++ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
++ assertThat(backgroundExecutor.numPending()).isEqualTo(1)
++ }
++
++ @Test
++ fun testOnMetaDataLoaded_callsListener() {
++ addNotificationAndLoad()
++ verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
++ eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_LOCAL))
++ }
++
++ @Test
++ fun testOnMetaDataLoaded_conservesActiveFlag() {
++ whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
++ whenever(controller.metadata).thenReturn(metadataBuilder.build())
++ mediaDataManager.addListener(listener)
++ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ assertThat(mediaDataCaptor.value!!.active).isTrue()
++ }
++
++ @Test
++ fun testOnNotificationAdded_isRcn_markedRemote() {
++ val rcn = SbnBuilder().run {
++ setPkg(SYSTEM_PACKAGE_NAME)
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.setStyle(MediaStyle().apply {
++ setMediaSession(session.sessionToken)
++ setRemotePlaybackInfo("Remote device", 0, null)
++ })
++ }
++ build()
++ }
++
++ mediaDataManager.onNotificationAdded(KEY, rcn)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo(
++ MediaData.PLAYBACK_CAST_REMOTE)
++ verify(logger).logActiveMediaAdded(anyInt(), eq(SYSTEM_PACKAGE_NAME),
++ eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
++ }
++
++ @Test
++ fun testLoadMediaDataInBg_invalidTokenNoCrash() {
++ val bundle = Bundle()
++ // wrong data type
++ bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle())
++ val rcn = SbnBuilder().run {
++ setPkg(SYSTEM_PACKAGE_NAME)
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.addExtras(bundle)
++ it.setStyle(MediaStyle().apply {
++ setRemotePlaybackInfo("Remote device", 0, null)
++ })
++ }
++ build()
++ }
++
++ mediaDataManager.loadMediaDataInBg(KEY, rcn, null)
++ // no crash even though the data structure is incorrect
++ }
++
++ @Test
++ fun testLoadMediaDataInBg_invalidMediaRemoteIntentNoCrash() {
++ val bundle = Bundle()
++ // wrong data type
++ bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle())
++ val rcn = SbnBuilder().run {
++ setPkg(SYSTEM_PACKAGE_NAME)
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.addExtras(bundle)
++ it.setStyle(MediaStyle().apply {
++ setMediaSession(session.sessionToken)
++ setRemotePlaybackInfo("Remote device", 0, null)
++ })
++ }
++ build()
++ }
++
++ mediaDataManager.loadMediaDataInBg(KEY, rcn, null)
++ // no crash even though the data structure is incorrect
++ }
++
++ @Test
++ fun testOnNotificationRemoved_callsListener() {
++ addNotificationAndLoad()
++ val data = mediaDataCaptor.value
++ mediaDataManager.onNotificationRemoved(KEY)
++ verify(listener).onMediaDataRemoved(eq(KEY))
++ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
++ }
++
++ @Test
++ fun testOnNotificationAdded_emptyTitle_hasPlaceholder() {
++ // When the manager has a notification with an empty title
++ whenever(controller.metadata)
++ .thenReturn(
++ metadataBuilder
++ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
++ .build()
++ )
++ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
++
++ // Then a media control is created with a placeholder title string
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(listener)
++ .onMediaDataLoaded(
++ eq(KEY),
++ eq(null),
++ capture(mediaDataCaptor),
++ eq(true),
++ eq(0),
++ eq(false)
++ )
++ val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
++ assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
++ }
++
++ @Test
++ fun testOnNotificationAdded_blankTitle_hasPlaceholder() {
++ // GIVEN that the manager has a notification with a blank title
++ whenever(controller.metadata)
++ .thenReturn(
++ metadataBuilder
++ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
++ .build()
++ )
++ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
++
++ // Then a media control is created with a placeholder title string
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(listener)
++ .onMediaDataLoaded(
++ eq(KEY),
++ eq(null),
++ capture(mediaDataCaptor),
++ eq(true),
++ eq(0),
++ eq(false)
++ )
++ val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
++ assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
++ }
++
++ @Test
++ fun testOnNotificationAdded_emptyMetadata_usesNotificationTitle() {
++ // When the app sets the metadata title fields to empty strings, but does include a
++ // non-blank notification title
++ whenever(controller.metadata)
++ .thenReturn(
++ metadataBuilder
++ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
++ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE)
++ .build()
++ )
++ mediaNotification =
++ SbnBuilder().run {
++ setPkg(PACKAGE_NAME)
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.setContentTitle(SESSION_TITLE)
++ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
++ }
++ build()
++ }
++ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
++
++ // Then the media control is added using the notification's title
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(listener)
++ .onMediaDataLoaded(
++ eq(KEY),
++ eq(null),
++ capture(mediaDataCaptor),
++ eq(true),
++ eq(0),
++ eq(false)
++ )
++ assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE)
++ }
++
++ @Test
++ fun testOnNotificationRemoved_withResumption() {
++ // GIVEN that the manager has a notification with a resume action
++ whenever(controller.metadata).thenReturn(metadataBuilder.build())
++ addNotificationAndLoad()
++ val data = mediaDataCaptor.value
++ assertThat(data.resumption).isFalse()
++ mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
++ // WHEN the notification is removed
++ mediaDataManager.onNotificationRemoved(KEY)
++ // THEN the media data indicates that it is for resumption
++ verify(listener)
++ .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ assertThat(mediaDataCaptor.value.resumption).isTrue()
++ assertThat(mediaDataCaptor.value.isPlaying).isFalse()
++ verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
++ }
++
++ @Test
++ fun testOnNotificationRemoved_twoWithResumption() {
++ // GIVEN that the manager has two notifications with resume actions
++ whenever(controller.metadata).thenReturn(metadataBuilder.build())
++ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
++ mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
++ verify(listener)
++ .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ val data = mediaDataCaptor.value
++ assertThat(data.resumption).isFalse()
++ val resumableData = data.copy(resumeAction = Runnable {})
++ mediaDataManager.onMediaDataLoaded(KEY, null, resumableData)
++ mediaDataManager.onMediaDataLoaded(KEY_2, null, resumableData)
++ reset(listener)
++ // WHEN the first is removed
++ mediaDataManager.onNotificationRemoved(KEY)
++ // THEN the data is for resumption and the key is migrated to the package name
++ verify(listener)
++ .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ assertThat(mediaDataCaptor.value.resumption).isTrue()
++ verify(listener, never()).onMediaDataRemoved(eq(KEY))
++ // WHEN the second is removed
++ mediaDataManager.onNotificationRemoved(KEY_2)
++ // THEN the data is for resumption and the second key is removed
++ verify(listener)
++ .onMediaDataLoaded(
++ eq(PACKAGE_NAME), eq(PACKAGE_NAME), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ assertThat(mediaDataCaptor.value.resumption).isTrue()
++ verify(listener).onMediaDataRemoved(eq(KEY_2))
++ }
++
++ @Test
++ fun testOnNotificationRemoved_withResumption_butNotLocal() {
++ // GIVEN that the manager has a notification with a resume action, but is not local
++ whenever(controller.metadata).thenReturn(metadataBuilder.build())
++ whenever(playbackInfo.playbackType).thenReturn(
++ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
++ addNotificationAndLoad()
++ val data = mediaDataCaptor.value
++ val dataRemoteWithResume = data.copy(resumeAction = Runnable {},
++ playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
++ mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
++ verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
++ eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
++
++ // WHEN the notification is removed
++ mediaDataManager.onNotificationRemoved(KEY)
++
++ // THEN the media data is removed
++ verify(listener).onMediaDataRemoved(eq(KEY))
++ }
++
++ @Test
++ fun testAddResumptionControls() {
++ // WHEN resumption controls are added
++ val desc = MediaDescription.Builder().run {
++ setTitle(SESSION_TITLE)
++ build()
++ }
++ val currentTime = clock.elapsedRealtime()
++ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
++ APP_NAME, pendingIntent, PACKAGE_NAME)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ // THEN the media data indicates that it is for resumption
++ verify(listener)
++ .onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ val data = mediaDataCaptor.value
++ assertThat(data.resumption).isTrue()
++ assertThat(data.song).isEqualTo(SESSION_TITLE)
++ assertThat(data.app).isEqualTo(APP_NAME)
++ assertThat(data.actions).hasSize(1)
++ assertThat(data.semanticActions!!.playOrPause).isNotNull()
++ assertThat(data.lastActive).isAtLeast(currentTime)
++ verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
++ }
++
++ @Test
++ fun testResumptionDisabled_dismissesResumeControls() {
++ // WHEN there are resume controls and resumption is switched off
++ val desc = MediaDescription.Builder().run {
++ setTitle(SESSION_TITLE)
++ build()
++ }
++ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
++ APP_NAME, pendingIntent, PACKAGE_NAME)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
++ eq(true), eq(0), eq(false))
++ val data = mediaDataCaptor.value
++ mediaDataManager.setMediaResumptionEnabled(false)
++
++ // THEN the resume controls are dismissed
++ verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
++ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
++ }
++
++ @Test
++ fun testDismissMedia_listenerCalled() {
++ addNotificationAndLoad()
++ val data = mediaDataCaptor.value
++ val removed = mediaDataManager.dismissMediaData(KEY, 0L)
++ assertThat(removed).isTrue()
++
++ foregroundExecutor.advanceClockToLast()
++ foregroundExecutor.runAllReady()
++
++ verify(listener).onMediaDataRemoved(eq(KEY))
++ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
++ }
++
++ @Test
++ fun testDismissMedia_keyDoesNotExist_returnsFalse() {
++ val removed = mediaDataManager.dismissMediaData(KEY, 0L)
++ assertThat(removed).isFalse()
++ }
++
++ @Test
++ fun testBadArtwork_doesNotUse() {
++ // WHEN notification has a too-small artwork
++ val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
++ val notif = SbnBuilder().run {
++ setPkg(PACKAGE_NAME)
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
++ it.setLargeIcon(artwork)
++ }
++ build()
++ }
++ mediaDataManager.onNotificationAdded(KEY, notif)
++
++ // THEN it still loads
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(listener)
++ .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ }
++
++ @Test
++ fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
++ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
++ verify(logger).getNewInstanceId()
++ val instanceId = instanceIdSequence.lastInstanceId
++
++ verify(listener).onSmartspaceMediaDataLoaded(
++ eq(KEY_MEDIA_SMARTSPACE),
++ eq(SmartspaceMediaData(
++ targetId = KEY_MEDIA_SMARTSPACE,
++ isActive = true,
++ packageName = PACKAGE_NAME,
++ cardAction = mediaSmartspaceBaseAction,
++ recommendations = validRecommendationList,
++ dismissIntent = DISMISS_INTENT,
++ headphoneConnectionTimeMillis = 1234L,
++ instanceId = InstanceId.fakeInstanceId(instanceId))),
++ eq(false))
++ }
++
++ @Test
++ fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
++ whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
++ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
++ verify(logger).getNewInstanceId()
++ val instanceId = instanceIdSequence.lastInstanceId
++
++ verify(listener).onSmartspaceMediaDataLoaded(
++ eq(KEY_MEDIA_SMARTSPACE),
++ eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy(
++ targetId = KEY_MEDIA_SMARTSPACE,
++ isActive = true,
++ dismissIntent = DISMISS_INTENT,
++ headphoneConnectionTimeMillis = 1234L,
++ instanceId = InstanceId.fakeInstanceId(instanceId))),
++ eq(false))
++ }
++
++ @Test
++ fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
++ val recommendationExtras = Bundle().apply {
++ putString("package_name", PACKAGE_NAME)
++ putParcelable("dismiss_intent", null)
++ }
++ whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
++ whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
++ whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
++
++ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
++ verify(logger).getNewInstanceId()
++ val instanceId = instanceIdSequence.lastInstanceId
++
++ verify(listener).onSmartspaceMediaDataLoaded(
++ eq(KEY_MEDIA_SMARTSPACE),
++ eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy(
++ targetId = KEY_MEDIA_SMARTSPACE,
++ isActive = true,
++ dismissIntent = null,
++ headphoneConnectionTimeMillis = 1234L,
++ instanceId = InstanceId.fakeInstanceId(instanceId))),
++ eq(false))
++ }
++
++ @Test
++ fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
++ smartspaceMediaDataProvider.onTargetsAvailable(listOf())
++ verify(logger, never()).getNewInstanceId()
++ verify(listener, never())
++ .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
++ }
++
++ @Ignore("b/233283726")
++ @Test
++ fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
++ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
++ verify(logger).getNewInstanceId()
++
++ smartspaceMediaDataProvider.onTargetsAvailable(listOf())
++ foregroundExecutor.advanceClockToLast()
++ foregroundExecutor.runAllReady()
++
++ verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
++ verifyNoMoreInteractions(logger)
++ }
++
++ @Test
++ fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
++ // WHEN media recommendation setting is off
++ Settings.Secure.putInt(context.contentResolver,
++ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
++ tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
++
++ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
++
++ // THEN smartspace signal is ignored
++ verify(listener, never())
++ .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
++ }
++
++ @Ignore("b/229838140")
++ @Test
++ fun testMediaRecommendationDisabled_removesSmartspaceData() {
++ // GIVEN a media recommendation card is present
++ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
++ verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(),
++ anyBoolean())
++
++ // WHEN the media recommendation setting is turned off
++ Settings.Secure.putInt(context.contentResolver,
++ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
++ tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
++
++ // THEN listeners are notified
++ foregroundExecutor.advanceClockToLast()
++ foregroundExecutor.runAllReady()
++ verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
++ }
++
++ @Test
++ fun testOnMediaDataChanged_updatesLastActiveTime() {
++ val currentTime = clock.elapsedRealtime()
++ addNotificationAndLoad()
++ assertThat(mediaDataCaptor.value!!.lastActive).isAtLeast(currentTime)
++ }
++
++ @Test
++ fun testOnMediaDataTimedOut_doesNotUpdateLastActiveTime() {
++ // GIVEN that the manager has a notification
++ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++
++ // WHEN the notification times out
++ clock.advanceTime(100)
++ val currentTime = clock.elapsedRealtime()
++ mediaDataManager.setTimedOut(KEY, true, true)
++
++ // THEN the last active time is not changed
++ verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
++ }
++
++ @Test
++ fun testOnActiveMediaConverted_doesNotUpdateLastActiveTime() {
++ // GIVEN that the manager has a notification with a resume action
++ whenever(controller.metadata).thenReturn(metadataBuilder.build())
++ addNotificationAndLoad()
++ val data = mediaDataCaptor.value
++ val instanceId = data.instanceId
++ assertThat(data.resumption).isFalse()
++ mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
++
++ // WHEN the notification is removed
++ clock.advanceTime(100)
++ val currentTime = clock.elapsedRealtime()
++ mediaDataManager.onNotificationRemoved(KEY)
++
++ // THEN the last active time is not changed
++ verify(listener)
++ .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ assertThat(mediaDataCaptor.value.resumption).isTrue()
++ assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
++
++ // Log as a conversion event, not as a new resume control
++ verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId))
++ verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
++ }
++
++ @Test
++ fun testTooManyCompactActions_isTruncated() {
++ // GIVEN a notification where too many compact actions were specified
++ val notif = SbnBuilder().run {
++ setPkg(PACKAGE_NAME)
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.setStyle(MediaStyle().apply {
++ setMediaSession(session.sessionToken)
++ setShowActionsInCompactView(0, 1, 2, 3, 4)
++ })
++ }
++ build()
++ }
++
++ // WHEN the notification is loaded
++ mediaDataManager.onNotificationAdded(KEY, notif)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++
++ // THEN only the first MAX_COMPACT_ACTIONS are actually set
++ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
++ MediaDataManager.MAX_COMPACT_ACTIONS)
++ }
++
++ @Test
++ fun testTooManyNotificationActions_isTruncated() {
++ // GIVEN a notification where too many notification actions are added
++ val action = Notification.Action(R.drawable.ic_android, "action", null)
++ val notif = SbnBuilder().run {
++ setPkg(PACKAGE_NAME)
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.setStyle(MediaStyle().apply {
++ setMediaSession(session.sessionToken)
++ })
++ for (i in 0..MediaDataManager.MAX_NOTIFICATION_ACTIONS) {
++ it.addAction(action)
++ }
++ }
++ build()
++ }
++
++ // WHEN the notification is loaded
++ mediaDataManager.onNotificationAdded(KEY, notif)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++
++ // THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
++ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ assertThat(mediaDataCaptor.value.actions.size).isEqualTo(
++ MediaDataManager.MAX_NOTIFICATION_ACTIONS)
++ }
++
++ @Test
++ fun testPlaybackActions_noState_usesNotification() {
++ val desc = "Notification Action"
++ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
++ whenever(controller.playbackState).thenReturn(null)
++
++ val notifWithAction = SbnBuilder().run {
++ setPkg(PACKAGE_NAME)
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
++ it.addAction(android.R.drawable.ic_media_play, desc, null)
++ }
++ build()
++ }
++ mediaDataManager.onNotificationAdded(KEY, notifWithAction)
++
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++
++ assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
++ assertThat(mediaDataCaptor.value!!.actions).hasSize(1)
++ assertThat(mediaDataCaptor.value!!.actions[0]!!.contentDescription).isEqualTo(desc)
++ }
++
++ @Test
++ fun testPlaybackActions_hasPrevNext() {
++ val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
++ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
++ val stateActions = PlaybackState.ACTION_PLAY or
++ PlaybackState.ACTION_SKIP_TO_PREVIOUS or
++ PlaybackState.ACTION_SKIP_TO_NEXT
++ val stateBuilder = PlaybackState.Builder()
++ .setActions(stateActions)
++ customDesc.forEach {
++ stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
++ }
++ whenever(controller.playbackState).thenReturn(stateBuilder.build())
++
++ addNotificationAndLoad()
++
++ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
++ val actions = mediaDataCaptor.value!!.semanticActions!!
++
++ assertThat(actions.playOrPause).isNotNull()
++ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
++ context.getString(R.string.controls_media_button_play))
++ actions.playOrPause!!.action!!.run()
++ verify(transportControls).play()
++
++ assertThat(actions.prevOrCustom).isNotNull()
++ assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(
++ context.getString(R.string.controls_media_button_prev))
++ actions.prevOrCustom!!.action!!.run()
++ verify(transportControls).skipToPrevious()
++
++ assertThat(actions.nextOrCustom).isNotNull()
++ assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(
++ context.getString(R.string.controls_media_button_next))
++ actions.nextOrCustom!!.action!!.run()
++ verify(transportControls).skipToNext()
++
++ assertThat(actions.custom0).isNotNull()
++ assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0])
++
++ assertThat(actions.custom1).isNotNull()
++ assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
++ }
++
++ @Test
++ fun testPlaybackActions_noPrevNext_usesCustom() {
++ val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
++ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
++ val stateActions = PlaybackState.ACTION_PLAY
++ val stateBuilder = PlaybackState.Builder()
++ .setActions(stateActions)
++ customDesc.forEach {
++ stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
++ }
++ whenever(controller.playbackState).thenReturn(stateBuilder.build())
++
++ addNotificationAndLoad()
++
++ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
++ val actions = mediaDataCaptor.value!!.semanticActions!!
++
++ assertThat(actions.playOrPause).isNotNull()
++ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
++ context.getString(R.string.controls_media_button_play))
++
++ assertThat(actions.prevOrCustom).isNotNull()
++ assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(customDesc[0])
++
++ assertThat(actions.nextOrCustom).isNotNull()
++ assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1])
++
++ assertThat(actions.custom0).isNotNull()
++ assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[2])
++
++ assertThat(actions.custom1).isNotNull()
++ assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[3])
++ }
++
++ @Test
++ fun testPlaybackActions_connecting() {
++ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
++ val stateActions = PlaybackState.ACTION_PLAY
++ val stateBuilder = PlaybackState.Builder()
++ .setState(PlaybackState.STATE_BUFFERING, 0, 10f)
++ .setActions(stateActions)
++ whenever(controller.playbackState).thenReturn(stateBuilder.build())
++
++ addNotificationAndLoad()
++
++ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
++ val actions = mediaDataCaptor.value!!.semanticActions!!
++
++ assertThat(actions.playOrPause).isNotNull()
++ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
++ context.getString(R.string.controls_media_button_connecting))
++ }
++
++ @Test
++ fun testPlaybackActions_reservedSpace() {
++ val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
++ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
++ val stateActions = PlaybackState.ACTION_PLAY
++ val stateBuilder = PlaybackState.Builder()
++ .setActions(stateActions)
++ customDesc.forEach {
++ stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
++ }
++ val extras = Bundle().apply {
++ putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
++ putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
++ }
++ whenever(controller.playbackState).thenReturn(stateBuilder.build())
++ whenever(controller.extras).thenReturn(extras)
++
++ addNotificationAndLoad()
++
++ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
++ val actions = mediaDataCaptor.value!!.semanticActions!!
++
++ assertThat(actions.playOrPause).isNotNull()
++ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
++ context.getString(R.string.controls_media_button_play))
++
++ assertThat(actions.prevOrCustom).isNull()
++ assertThat(actions.nextOrCustom).isNull()
++
++ assertThat(actions.custom0).isNotNull()
++ assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0])
++
++ assertThat(actions.custom1).isNotNull()
++ assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
++
++ assertThat(actions.reserveNext).isTrue()
++ assertThat(actions.reservePrev).isTrue()
++ }
++
++ @Test
++ fun testPlaybackActions_playPause_hasButton() {
++ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
++ val stateActions = PlaybackState.ACTION_PLAY_PAUSE
++ val stateBuilder = PlaybackState.Builder().setActions(stateActions)
++ whenever(controller.playbackState).thenReturn(stateBuilder.build())
++
++ addNotificationAndLoad()
++
++ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
++ val actions = mediaDataCaptor.value!!.semanticActions!!
++
++ assertThat(actions.playOrPause).isNotNull()
++ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
++ context.getString(R.string.controls_media_button_play))
++ actions.playOrPause!!.action!!.run()
++ verify(transportControls).play()
++ }
++
++ @Test
++ fun testPlaybackLocationChange_isLogged() {
++ // Media control added for local playback
++ addNotificationAndLoad()
++ val instanceId = mediaDataCaptor.value.instanceId
++
++ // Location is updated to local cast
++ whenever(controller.metadata).thenReturn(metadataBuilder.build())
++ whenever(playbackInfo.playbackType).thenReturn(
++ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
++ addNotificationAndLoad()
++ verify(logger).logPlaybackLocationChange(anyInt(), eq(PACKAGE_NAME),
++ eq(instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
++
++ // update to remote cast
++ val rcn = SbnBuilder().run {
++ setPkg(SYSTEM_PACKAGE_NAME) // System package
++ modifyNotification(context).also {
++ it.setSmallIcon(android.R.drawable.ic_media_pause)
++ it.setStyle(MediaStyle().apply {
++ setMediaSession(session.sessionToken)
++ setRemotePlaybackInfo("Remote device", 0, null)
++ })
++ }
++ build()
++ }
++
++ mediaDataManager.onNotificationAdded(KEY, rcn)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(logger).logPlaybackLocationChange(anyInt(), eq(SYSTEM_PACKAGE_NAME),
++ eq(instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
++ }
++
++ @Test
++ fun testPlaybackStateChange_keyExists_callsListener() {
++ // Notification has been added
++ addNotificationAndLoad()
++ val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>()
++ verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
++
++ // Callback gets an updated state
++ val state = PlaybackState.Builder()
++ .setState(PlaybackState.STATE_PLAYING, 0L, 1f)
++ .build()
++ callbackCaptor.value.invoke(KEY, state)
++
++ // Listener is notified of updated state
++ verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
++ capture(mediaDataCaptor), eq(true), eq(0), eq(false))
++ assertThat(mediaDataCaptor.value.isPlaying).isTrue()
++ }
++
++ @Test
++ fun testPlaybackStateChange_keyDoesNotExist_doesNothing() {
++ val state = PlaybackState.Builder().build()
++ val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>()
++ verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
++
++ // No media added with this key
++
++ callbackCaptor.value.invoke(KEY, state)
++ verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(),
++ anyBoolean())
++ }
++
++ @Test
++ fun testPlaybackStateChange_keyHasNullToken_doesNothing() {
++ // When we get an update that sets the data's token to null
++ whenever(controller.metadata).thenReturn(metadataBuilder.build())
++ addNotificationAndLoad()
++ val data = mediaDataCaptor.value
++ assertThat(data.resumption).isFalse()
++ mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(token = null))
++
++ // And then get a state update
++ val state = PlaybackState.Builder().build()
++ val callbackCaptor = argumentCaptor<(String, PlaybackState) -> Unit>()
++ verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
++
++ // Then no changes are made
++ callbackCaptor.value.invoke(KEY, state)
++ verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(),
++ anyBoolean())
++ }
++
++ /**
++ * Helper function to add a media notification and capture the resulting MediaData
++ */
++ private fun addNotificationAndLoad() {
++ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
++ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
++ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
++ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
++ eq(0), eq(false))
++ }
++}
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-03.patch.disabled b/Patches/LineageOS-20.0/ASB-2023-10/base-03.patch.disabled
new file mode 100644
index 00000000..c9d0a398
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-03.patch.disabled
@@ -0,0 +1,53 @@
+From cbb1a0ecd6b67735bdb735d76606bc03f6b955bf Mon Sep 17 00:00:00 2001
+From: Ioana Alexandru
+Date: Mon, 8 May 2023 18:39:35 +0000
+Subject: [PATCH] Verify URI permissions for EXTRA_REMOTE_INPUT_HISTORY_ITEMS.
+
+Also added a step to serialize & deserialize the notification in the
+test, to prevent exceptions about not being able to cast e.g.
+Parcelable[] to RemoteInputHistoryItem[].
+
+Test: atest NotificationManagerServiceTest & tested with POC from bug
+Bug: 276729064
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4e19431a60300c6ea6c7f7dd64299916e4eb09bc)
+Merged-In: I7053ca59f9c7f1df5226418594109cfb8b609b1e
+Change-Id: I7053ca59f9c7f1df5226418594109cfb8b609b1e
+---
+ core/java/android/app/Notification.java | 5 +++--
+ .../notification/NotificationManagerServiceTest.java | 7 +++++++
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
+index 8a730fb0deaa..01528ae06cf5 100644
+--- a/core/java/android/app/Notification.java
++++ b/core/java/android/app/Notification.java
+@@ -2858,8 +2858,9 @@ public void visitUris(@NonNull Consumer visitor) {
+ visitor.accept(person.getIconUri());
+ }
+
+- final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
+- extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
++ final RemoteInputHistoryItem[] history = extras.getParcelableArray(
++ Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
++ RemoteInputHistoryItem.class);
+ if (history != null) {
+ for (int i = 0; i < history.length; i++) {
+ RemoteInputHistoryItem item = history[i];
+diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+index dcaca51c176b..cf33eb5b3647 100755
+--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
++++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+@@ -5439,6 +5439,13 @@ public void testVisitUris() throws Exception {
+ .addExtras(extras)
+ .build();
+
++ // Serialize and deserialize the notification to make sure nothing breaks in the process,
++ // since that's what will usually happen before we get to call visitUris.
++ Parcel parcel = Parcel.obtain();
++ n.writeToParcel(parcel, 0);
++ parcel.setDataPosition(0);
++ n = new Notification(parcel);
++
+ Consumer visitor = (Consumer) spy(Consumer.class);
+ n.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(audioContents));
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-04.patch b/Patches/LineageOS-20.0/ASB-2023-10/base-04.patch
new file mode 100644
index 00000000..733b57c8
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-04.patch
@@ -0,0 +1,47 @@
+From 4725772c0b3f0db2940e70851e145ec4ec71768b Mon Sep 17 00:00:00 2001
+From: Josep del Rio
+Date: Mon, 26 Jun 2023 09:30:06 +0000
+Subject: [PATCH] Do not share key mappings with JNI object
+
+The key mapping information between the native key mappings and
+the KeyCharacterMap object available in Java is currently shared,
+which means that a read can be attempted while it's being modified.
+
+Bug: 274058082
+Test: Patch tested by Oppo
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3d993de0d1ada8065d1fe561f690c8f82b6a7d4b)
+Merged-In: I745008a0a8ea30830660c45dcebee917b3913d13
+Change-Id: I745008a0a8ea30830660c45dcebee917b3913d13
+---
+ core/jni/android_view_InputDevice.cpp | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
+index 9cc72437a023..f7c770e0bffb 100644
+--- a/core/jni/android_view_InputDevice.cpp
++++ b/core/jni/android_view_InputDevice.cpp
+@@ -42,6 +42,13 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
+ return NULL;
+ }
+
++ // b/274058082: Pass a copy of the key character map to avoid concurrent
++ // access
++ std::shared_ptr map = deviceInfo.getKeyCharacterMap();
++ if (map != nullptr) {
++ map = std::make_shared(*map);
++ }
++
+ ScopedLocalRef descriptorObj(env,
+ env->NewStringUTF(deviceInfo.getIdentifier().descriptor.c_str()));
+ if (!descriptorObj.get()) {
+@@ -49,8 +56,8 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
+ }
+
+ ScopedLocalRef kcmObj(env,
+- android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
+- deviceInfo.getKeyCharacterMap()));
++ android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
++ map));
+ if (!kcmObj.get()) {
+ return NULL;
+ }
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-05.patch b/Patches/LineageOS-20.0/ASB-2023-10/base-05.patch
new file mode 100644
index 00000000..6a0727e6
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-05.patch
@@ -0,0 +1,197 @@
+From 19747f69235d208e3d61099c76fa47aa792fe3a7 Mon Sep 17 00:00:00 2001
+From: Tim Yu
+Date: Tue, 20 Jun 2023 21:24:36 +0000
+Subject: [PATCH] [DO NOT MERGE] Verify URI Permissions in Autofill RemoteViews
+
+Check permissions of URI inside of FillResponse's RemoteViews. If the
+current user does not have the required permissions to view the URI, the
+RemoteView is dropped from displaying.
+
+This fixes a security spill in which a user can view content of another
+user through a malicious Autofill provider.
+
+Bug: 283137865
+Fixes: b/283264674 b/281666022 b/281665050 b/281848557 b/281533566
+b/281534749 b/283101289
+Test: Verified by POC app attached in bugs
+Test: atest CtsAutoFillServiceTestCases (added new tests)
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:93810ba1c0a4d31f49adbf9454731e2b7defdfc0)
+Merged-In: I6f4d2a35e89bbed7bd9e07bf5cd3e2d68b20af9a
+Change-Id: I6f4d2a35e89bbed7bd9e07bf5cd3e2d68b20af9a
+---
+ .../com/android/server/autofill/Helper.java | 43 +++++++++++++++++++
+ .../server/autofill/ui/DialogFillUi.java | 12 ++++--
+ .../android/server/autofill/ui/FillUi.java | 11 +++--
+ .../android/server/autofill/ui/SaveUi.java | 3 +-
+ 4 files changed, 60 insertions(+), 9 deletions(-)
+
+diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
+index bc5d6457c945..48113a81cca5 100644
+--- a/services/autofill/java/com/android/server/autofill/Helper.java
++++ b/services/autofill/java/com/android/server/autofill/Helper.java
+@@ -18,6 +18,8 @@
+
+ import android.annotation.NonNull;
+ import android.annotation.Nullable;
++import android.annotation.UserIdInt;
++import android.app.ActivityManager;
+ import android.app.assist.AssistStructure;
+ import android.app.assist.AssistStructure.ViewNode;
+ import android.app.assist.AssistStructure.WindowNode;
+@@ -34,6 +36,7 @@
+ import android.view.WindowManager;
+ import android.view.autofill.AutofillId;
+ import android.view.autofill.AutofillValue;
++import android.widget.RemoteViews;
+
+ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+ import com.android.internal.util.ArrayUtils;
+@@ -42,6 +45,8 @@
+ import java.util.ArrayDeque;
+ import java.util.ArrayList;
+ import java.util.Arrays;
++import java.util.concurrent.atomic.AtomicBoolean;
++
+
+ public final class Helper {
+
+@@ -75,6 +80,44 @@ private Helper() {
+ throw new UnsupportedOperationException("contains static members only");
+ }
+
++ private static boolean checkRemoteViewUriPermissions(
++ @UserIdInt int userId, @NonNull RemoteViews rView) {
++ final AtomicBoolean permissionsOk = new AtomicBoolean(true);
++
++ rView.visitUris(uri -> {
++ int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri);
++ boolean allowed = uriOwnerId == userId;
++ permissionsOk.set(allowed && permissionsOk.get());
++ });
++
++ return permissionsOk.get();
++ }
++
++ /**
++ * Checks the URI permissions of the remote view,
++ * to see if the current userId is able to access it.
++ *
++ * Returns the RemoteView that is passed if user is able, null otherwise.
++ *
++ * TODO: instead of returning a null remoteview when
++ * the current userId cannot access an URI,
++ * return a new RemoteView with the URI removed.
++ */
++ public static @Nullable RemoteViews sanitizeRemoteView(RemoteViews rView) {
++ if (rView == null) return null;
++
++ int userId = ActivityManager.getCurrentUser();
++
++ boolean ok = checkRemoteViewUriPermissions(userId, rView);
++ if (!ok) {
++ Slog.w(TAG,
++ "sanitizeRemoteView() user: " + userId
++ + " tried accessing resource that does not belong to them");
++ }
++ return (ok ? rView : null);
++ }
++
++
+ @Nullable
+ static AutofillId[] toArray(@Nullable ArraySet set) {
+ if (set == null) return null;
+diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+index c2c630e01bee..59184e9ed288 100644
+--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
++++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+@@ -52,6 +52,7 @@
+
+ import com.android.internal.R;
+ import com.android.server.autofill.AutofillManagerService;
++import com.android.server.autofill.Helper;
+
+ import java.io.PrintWriter;
+ import java.util.ArrayList;
+@@ -197,7 +198,8 @@ private void setServiceIcon(View decor, Drawable serviceIcon) {
+ }
+
+ private void setHeader(View decor, FillResponse response) {
+- final RemoteViews presentation = response.getDialogHeader();
++ final RemoteViews presentation =
++ Helper.sanitizeRemoteView(response.getDialogHeader());
+ if (presentation == null) {
+ return;
+ }
+@@ -232,9 +234,10 @@ private void setContinueButton(View decor, View.OnClickListener listener) {
+ }
+
+ private void initialAuthenticationLayout(View decor, FillResponse response) {
+- RemoteViews presentation = response.getDialogPresentation();
++ RemoteViews presentation = Helper.sanitizeRemoteView(
++ response.getDialogPresentation());
+ if (presentation == null) {
+- presentation = response.getPresentation();
++ presentation = Helper.sanitizeRemoteView(response.getPresentation());
+ }
+ if (presentation == null) {
+ throw new RuntimeException("No presentation for fill dialog authentication");
+@@ -278,7 +281,8 @@ private ArrayList createDatasetItems(FillResponse response,
+ final Dataset dataset = response.getDatasets().get(i);
+ final int index = dataset.getFieldIds().indexOf(focusedViewId);
+ if (index >= 0) {
+- RemoteViews presentation = dataset.getFieldDialogPresentation(index);
++ RemoteViews presentation = Helper.sanitizeRemoteView(
++ dataset.getFieldDialogPresentation(index));
+ if (presentation == null) {
+ if (sDebug) {
+ Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+index 8fbdd81cc4cc..76fa258734cc 100644
+--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
++++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+@@ -144,8 +144,9 @@ public static boolean isFullScreen(Context context) {
+
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+
+- final RemoteViews headerPresentation = response.getHeader();
+- final RemoteViews footerPresentation = response.getFooter();
++ final RemoteViews headerPresentation = Helper.sanitizeRemoteView(response.getHeader());
++ final RemoteViews footerPresentation = Helper.sanitizeRemoteView(response.getFooter());
++
+ final ViewGroup decor;
+ if (mFullScreen) {
+ decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_fullscreen, null);
+@@ -223,6 +224,9 @@ public static boolean isFullScreen(Context context) {
+ ViewGroup container = decor.findViewById(R.id.autofill_dataset_picker);
+ final View content;
+ try {
++ if (Helper.sanitizeRemoteView(response.getPresentation()) == null) {
++ throw new RuntimeException("Permission error accessing RemoteView");
++ }
+ content = response.getPresentation().applyWithTheme(
+ mContext, decor, interceptionHandler, mThemeId);
+ container.addView(content);
+@@ -302,7 +306,8 @@ public static boolean isFullScreen(Context context) {
+ final Dataset dataset = response.getDatasets().get(i);
+ final int index = dataset.getFieldIds().indexOf(focusedViewId);
+ if (index >= 0) {
+- final RemoteViews presentation = dataset.getFieldPresentation(index);
++ final RemoteViews presentation = Helper.sanitizeRemoteView(
++ dataset.getFieldPresentation(index));
+ if (presentation == null) {
+ Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+ + "service didn't provide a presentation for it on " + dataset);
+diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+index 677871f6c85f..533a7b69a650 100644
+--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
++++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+@@ -368,8 +368,7 @@ private boolean applyCustomDescription(@NonNull Context context, @NonNull View s
+ return false;
+ }
+ writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION);
+-
+- final RemoteViews template = customDescription.getPresentation();
++ final RemoteViews template = Helper.sanitizeRemoteView(customDescription.getPresentation());
+ if (template == null) {
+ Slog.w(TAG, "No remote view on custom description");
+ return false;
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-06.patch b/Patches/LineageOS-20.0/ASB-2023-10/base-06.patch
new file mode 100644
index 00000000..f5fa691a
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-06.patch
@@ -0,0 +1,53 @@
+From e7a1aa9ed0bc69853bc83d098e15b8fa3b1881b4 Mon Sep 17 00:00:00 2001
+From: Hongwei Wang
+Date: Thu, 25 May 2023 12:18:44 -0700
+Subject: [PATCH] Disallow loading icon from content URI to PipMenu
+
+Bug: 278246904
+Test: manually, with the PoC app attached to the bug
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1aee65603e262affd815fa53dcc5416c605e4037)
+Merged-In: Ib3f5b8b6b9ce644fdf1173548d9078e4d969ae2e
+Change-Id: Ib3f5b8b6b9ce644fdf1173548d9078e4d969ae2e
+---
+ .../wm/shell/pip/phone/PipMenuView.java | 21 ++++++++++++-------
+ 1 file changed, 14 insertions(+), 7 deletions(-)
+
+diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+index 167c0321d3ad..779c539a2097 100644
+--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
++++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+@@ -45,6 +45,7 @@
+ import android.graphics.Color;
+ import android.graphics.Rect;
+ import android.graphics.drawable.Drawable;
++import android.graphics.drawable.Icon;
+ import android.net.Uri;
+ import android.os.Bundle;
+ import android.os.Handler;
+@@ -513,13 +514,19 @@ private void updateActionViews(int menuState, Rect stackBounds) {
+ final boolean isCloseAction = mCloseAction != null && Objects.equals(
+ mCloseAction.getActionIntent(), action.getActionIntent());
+
+- // TODO: Check if the action drawable has changed before we reload it
+- action.getIcon().loadDrawableAsync(mContext, d -> {
+- if (d != null) {
+- d.setTint(Color.WHITE);
+- actionView.setImageDrawable(d);
+- }
+- }, mMainHandler);
++ final int iconType = action.getIcon().getType();
++ if (iconType == Icon.TYPE_URI || iconType == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
++ // Disallow loading icon from content URI
++ actionView.setImageDrawable(null);
++ } else {
++ // TODO: Check if the action drawable has changed before we reload it
++ action.getIcon().loadDrawableAsync(mContext, d -> {
++ if (d != null) {
++ d.setTint(Color.WHITE);
++ actionView.setImageDrawable(d);
++ }
++ }, mMainHandler);
++ }
+ actionView.setCustomCloseBackgroundVisibility(
+ isCloseAction ? View.VISIBLE : View.GONE);
+ actionView.setContentDescription(action.getContentDescription());
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-07.patch b/Patches/LineageOS-20.0/ASB-2023-10/base-07.patch
new file mode 100644
index 00000000..1b807841
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-07.patch
@@ -0,0 +1,59 @@
+From 922a7860b1baf29ff5cb53a31d01c341cd2b9ecb Mon Sep 17 00:00:00 2001
+From: Kunal Malhotra
+Date: Fri, 2 Jun 2023 23:32:02 +0000
+Subject: [PATCH] Fixing DatabaseUtils to detect malformed UTF-16 strings
+
+Test: tested with POC in bug, also using atest
+Bug: 224771621
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:fb4a72e3943d166088407e61aa4439ac349f3f12)
+Merged-In: Ide65205b83063801971c5778af3154bcf3f0e530
+Change-Id: Ide65205b83063801971c5778af3154bcf3f0e530
+---
+ core/java/android/database/DatabaseUtils.java | 32 +++++++++++++------
+ 1 file changed, 23 insertions(+), 9 deletions(-)
+
+diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
+index 6c8a8500e4e3..d41df4f49d48 100644
+--- a/core/java/android/database/DatabaseUtils.java
++++ b/core/java/android/database/DatabaseUtils.java
+@@ -511,17 +511,31 @@ public static void cursorFillWindow(final Cursor cursor,
+ */
+ public static void appendEscapedSQLString(StringBuilder sb, String sqlString) {
+ sb.append('\'');
+- if (sqlString.indexOf('\'') != -1) {
+- int length = sqlString.length();
+- for (int i = 0; i < length; i++) {
+- char c = sqlString.charAt(i);
+- if (c == '\'') {
+- sb.append('\'');
++ int length = sqlString.length();
++ for (int i = 0; i < length; i++) {
++ char c = sqlString.charAt(i);
++ if (Character.isHighSurrogate(c)) {
++ if (i == length - 1) {
++ continue;
++ }
++ if (Character.isLowSurrogate(sqlString.charAt(i + 1))) {
++ // add them both
++ sb.append(c);
++ sb.append(sqlString.charAt(i + 1));
++ continue;
++ } else {
++ // this is a lone surrogate, skip it
++ continue;
+ }
+- sb.append(c);
+ }
+- } else
+- sb.append(sqlString);
++ if (Character.isLowSurrogate(c)) {
++ continue;
++ }
++ if (c == '\'') {
++ sb.append('\'');
++ }
++ sb.append(c);
++ }
+ sb.append('\'');
+ }
+
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-08.patch b/Patches/LineageOS-20.0/ASB-2023-10/base-08.patch
new file mode 100644
index 00000000..4b36962a
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-08.patch
@@ -0,0 +1,455 @@
+From ed183ed9122416026ed27d4877f96a545fe42316 Mon Sep 17 00:00:00 2001
+From: Anton Potapov
+Date: Tue, 4 Jul 2023 12:15:41 +0100
+Subject: [PATCH] Add userId check before loading icon in Device Controls
+
+Test: manual with the steps from the bug
+Test: manual with a normal icon
+Test: atest CanUseIconPredicate
+Test: atest ControlViewHolderTest
+Bug: 272025416
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:22f97f081ccc6f6a7230b15447a6c885dfe4fa59)
+Merged-In: Ib0e677f7ccbed6299ea07939519c7dcf6d371bec
+Change-Id: Ib0e677f7ccbed6299ea07939519c7dcf6d371bec
+---
+ .../controls/management/ControlAdapter.kt | 15 +++-
+ .../management/ControlsEditingActivity.kt | 2 +-
+ .../management/ControlsFavoritingActivity.kt | 4 +-
+ .../controls/management/StructureAdapter.kt | 11 ++-
+ .../controls/ui/CanUseIconPredicate.kt | 30 +++++++
+ .../systemui/controls/ui/ControlViewHolder.kt | 50 ++++++------
+ .../controls/ui/ControlsUiControllerImpl.kt | 3 +-
+ .../controls/ui/TemperatureControlBehavior.kt | 2 +-
+ .../systemui/controls/ui/ThumbnailBehavior.kt | 12 ++-
+ .../controls/ui/CanUseIconPredicateTest.kt | 81 +++++++++++++++++++
+ .../controls/ui/ControlViewHolderTest.kt | 3 +-
+ 11 files changed, 173 insertions(+), 40 deletions(-)
+ create mode 100644 packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt
+ create mode 100644 packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
+
+diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+index 3eb58bba1ca4..ec76f433b23b 100644
+--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
++++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+@@ -38,6 +38,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+ import androidx.recyclerview.widget.RecyclerView
+ import com.android.systemui.R
+ import com.android.systemui.controls.ControlInterface
++import com.android.systemui.controls.ui.CanUseIconPredicate
+ import com.android.systemui.controls.ui.RenderInfo
+
+ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
+@@ -51,7 +52,8 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
+ * @property elevation elevation of each control view
+ */
+ class ControlAdapter(
+- private val elevation: Float
++ private val elevation: Float,
++ private val currentUserId: Int,
+ ) : RecyclerView.Adapter() {
+
+ companion object {
+@@ -107,7 +109,8 @@ class ControlAdapter(
+ background = parent.context.getDrawable(
+ R.drawable.control_background_ripple)
+ },
+- model?.moveHelper // Indicates that position information is needed
++ currentUserId,
++ model?.moveHelper, // Indicates that position information is needed
+ ) { id, favorite ->
+ model?.changeFavoriteStatus(id, favorite)
+ }
+@@ -212,8 +215,9 @@ private class ZoneHolder(view: View) : Holder(view) {
+ */
+ internal class ControlHolder(
+ view: View,
++ currentUserId: Int,
+ val moveHelper: ControlsModel.MoveHelper?,
+- val favoriteCallback: ModelFavoriteChanger
++ val favoriteCallback: ModelFavoriteChanger,
+ ) : Holder(view) {
+ private val favoriteStateDescription =
+ itemView.context.getString(R.string.accessibility_control_favorite)
+@@ -228,6 +232,7 @@ internal class ControlHolder(
+ visibility = View.VISIBLE
+ }
+
++ private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
+ private val accessibilityDelegate = ControlHolderAccessibilityDelegate(
+ this::stateDescription,
+ this::getLayoutPosition,
+@@ -287,7 +292,9 @@ internal class ControlHolder(
+ val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
+
+ icon.imageTintList = null
+- ci.customIcon?.let {
++ ci.customIcon
++ ?.takeIf(canUseIconPredicate)
++ ?.let {
+ icon.setImageIcon(it)
+ } ?: run {
+ icon.setImageDrawable(ri.icon)
+diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+index 7df08651d5ab..8843349da2b5 100644
+--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
++++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+@@ -205,7 +205,7 @@ open class ControlsEditingActivity @Inject constructor(
+ val elevation = resources.getFloat(R.dimen.control_card_elevation)
+ val recyclerView = requireViewById(R.id.list)
+ recyclerView.alpha = 0.0f
+- val adapter = ControlAdapter(elevation).apply {
++ val adapter = ControlAdapter(elevation, controller.currentUserId).apply {
+ registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+ var hasAnimated = false
+ override fun onChanged() {
+diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+index 3e97d3132bc7..efc3fb662f1a 100644
+--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
++++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+@@ -175,7 +175,7 @@ open class ControlsFavoritingActivity @Inject constructor(
+ }
+
+ executor.execute {
+- structurePager.adapter = StructureAdapter(listOfStructures)
++ structurePager.adapter = StructureAdapter(listOfStructures, controller.currentUserId)
+ structurePager.setCurrentItem(structureIndex)
+ if (error) {
+ statusText.text = resources.getString(R.string.controls_favorite_load_error,
+@@ -221,7 +221,7 @@ open class ControlsFavoritingActivity @Inject constructor(
+ structurePager.alpha = 0.0f
+ pageIndicator.alpha = 0.0f
+ structurePager.apply {
+- adapter = StructureAdapter(emptyList())
++ adapter = StructureAdapter(emptyList(), controller.currentUserId)
+ registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
+ override fun onPageSelected(position: Int) {
+ super.onPageSelected(position)
+diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
+index 747bcbe1c229..5977d379acde 100644
+--- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
++++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt
+@@ -24,13 +24,15 @@ import androidx.recyclerview.widget.RecyclerView
+ import com.android.systemui.R
+
+ class StructureAdapter(
+- private val models: List
++ private val models: List,
++ private val currentUserId: Int,
+ ) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder {
+ val layoutInflater = LayoutInflater.from(parent.context)
+ return StructureHolder(
+- layoutInflater.inflate(R.layout.controls_structure_page, parent, false)
++ layoutInflater.inflate(R.layout.controls_structure_page, parent, false),
++ currentUserId,
+ )
+ }
+
+@@ -40,7 +42,8 @@ class StructureAdapter(
+ holder.bind(models[index].model)
+ }
+
+- class StructureHolder(view: View) : RecyclerView.ViewHolder(view) {
++ class StructureHolder(view: View, currentUserId: Int) :
++ RecyclerView.ViewHolder(view) {
+
+ private val recyclerView: RecyclerView
+ private val controlAdapter: ControlAdapter
+@@ -48,7 +51,7 @@ class StructureAdapter(
+ init {
+ recyclerView = itemView.requireViewById(R.id.listAll)
+ val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation)
+- controlAdapter = ControlAdapter(elevation)
++ controlAdapter = ControlAdapter(elevation, currentUserId)
+ setUpRecyclerView()
+ }
+
+diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt
+new file mode 100644
+index 000000000000..61c21237144d
+--- /dev/null
++++ b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt
+@@ -0,0 +1,30 @@
++/*
++ * Copyright (C) 2023 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.controls.ui
++
++import android.content.ContentProvider
++import android.graphics.drawable.Icon
++
++class CanUseIconPredicate(private val currentUserId: Int) : (Icon) -> Boolean {
++
++ override fun invoke(icon: Icon): Boolean =
++ if (icon.type == Icon.TYPE_URI || icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
++ ContentProvider.getUserIdFromUri(icon.uri, currentUserId) == currentUserId
++ } else {
++ true
++ }
++}
+diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+index 6a9aaf865251..931062865c64 100644
+--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
++++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+@@ -68,7 +68,8 @@ class ControlViewHolder(
+ val bgExecutor: DelayableExecutor,
+ val controlActionCoordinator: ControlActionCoordinator,
+ val controlsMetricsLogger: ControlsMetricsLogger,
+- val uid: Int
++ val uid: Int,
++ val currentUserId: Int,
+ ) {
+
+ companion object {
+@@ -85,29 +86,9 @@ class ControlViewHolder(
+ private val ATTR_DISABLED = intArrayOf(-android.R.attr.state_enabled)
+ const val MIN_LEVEL = 0
+ const val MAX_LEVEL = 10000
+-
+- fun findBehaviorClass(
+- status: Int,
+- template: ControlTemplate,
+- deviceType: Int
+- ): Supplier {
+- return when {
+- status != Control.STATUS_OK -> Supplier { StatusBehavior() }
+- template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() }
+- template is ThumbnailTemplate -> Supplier { ThumbnailBehavior() }
+-
+- // Required for legacy support, or where cameras do not use the new template
+- deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() }
+- template is ToggleTemplate -> Supplier { ToggleBehavior() }
+- template is StatelessTemplate -> Supplier { TouchBehavior() }
+- template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() }
+- template is RangeTemplate -> Supplier { ToggleRangeBehavior() }
+- template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() }
+- else -> Supplier { DefaultBehavior() }
+- }
+- }
+ }
+
++ private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
+ private val toggleBackgroundIntensity: Float = layout.context.resources
+ .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1)
+ private var stateAnimator: ValueAnimator? = null
+@@ -147,6 +128,27 @@ class ControlViewHolder(
+ status.setSelected(true)
+ }
+
++ fun findBehaviorClass(
++ status: Int,
++ template: ControlTemplate,
++ deviceType: Int
++ ): Supplier {
++ return when {
++ status != Control.STATUS_OK -> Supplier { StatusBehavior() }
++ template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() }
++ template is ThumbnailTemplate -> Supplier { ThumbnailBehavior(currentUserId) }
++
++ // Required for legacy support, or where cameras do not use the new template
++ deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() }
++ template is ToggleTemplate -> Supplier { ToggleBehavior() }
++ template is StatelessTemplate -> Supplier { TouchBehavior() }
++ template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() }
++ template is RangeTemplate -> Supplier { ToggleRangeBehavior() }
++ template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() }
++ else -> Supplier { DefaultBehavior() }
++ }
++ }
++
+ fun bindData(cws: ControlWithState, isLocked: Boolean) {
+ // If an interaction is in progress, the update may visually interfere with the action the
+ // action the user wants to make. Don't apply the update, and instead assume a new update
+@@ -473,7 +475,9 @@ class ControlViewHolder(
+
+ status.setTextColor(color)
+
+- control?.getCustomIcon()?.let {
++ control?.customIcon
++ ?.takeIf(canUseIconPredicate)
++ ?.let {
+ icon.setImageIcon(it)
+ icon.imageTintList = it.tintList
+ } ?: run {
+diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+index 554391649548..1c1f7702c4bd 100644
+--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
++++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+@@ -685,7 +685,8 @@ class ControlsUiControllerImpl @Inject constructor (
+ bgExecutor,
+ controlActionCoordinator,
+ controlsMetricsLogger,
+- selected.uid
++ selected.uid,
++ controlsController.get().currentUserId,
+ )
+ cvh.bindData(it, false /* isLocked, will be ignored on initial load */)
+ controlViewsById.put(key, cvh)
+diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+index a7dc09bb17e5..39d69704d817 100644
+--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
++++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+@@ -63,7 +63,7 @@ class TemperatureControlBehavior : Behavior {
+ // interactions (touch, range)
+ subBehavior = cvh.bindBehavior(
+ subBehavior,
+- ControlViewHolder.findBehaviorClass(
++ cvh.findBehaviorClass(
+ control.status,
+ subTemplate,
+ control.deviceType
+diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt
+index c2168aa8d9d9..0b57e792f9f7 100644
+--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt
++++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt
+@@ -33,7 +33,7 @@ import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
+ * Supports display of static images on the background of the tile. When marked active, the title
+ * and subtitle will not be visible. To be used with {@link Thumbnailtemplate} only.
+ */
+-class ThumbnailBehavior : Behavior {
++class ThumbnailBehavior(currentUserId: Int) : Behavior {
+ lateinit var template: ThumbnailTemplate
+ lateinit var control: Control
+ lateinit var cvh: ControlViewHolder
+@@ -42,6 +42,7 @@ class ThumbnailBehavior : Behavior {
+ private var shadowRadius: Float = 0f
+ private var shadowColor: Int = 0
+
++ private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
+ private val enabled: Boolean
+ get() = template.isActive()
+
+@@ -80,11 +81,16 @@ class ThumbnailBehavior : Behavior {
+ cvh.status.setShadowLayer(shadowOffsetX, shadowOffsetY, shadowRadius, shadowColor)
+
+ cvh.bgExecutor.execute {
+- val drawable = template.getThumbnail().loadDrawable(cvh.context)
++ val drawable = template.thumbnail
++ ?.takeIf(canUseIconPredicate)
++ ?.loadDrawable(cvh.context)
+ cvh.uiExecutor.execute {
+ val radius = cvh.context.getResources()
+ .getDimensionPixelSize(R.dimen.control_corner_radius).toFloat()
+- clipLayer.setDrawable(CornerDrawable(drawable, radius))
++ // TODO(b/290037843): Add a placeholder
++ drawable?.let {
++ clipLayer.drawable = CornerDrawable(it, radius)
++ }
+ clipLayer.setColorFilter(BlendModeColorFilter(cvh.context.resources
+ .getColor(R.color.control_thumbnail_tint), BlendMode.LUMINOSITY))
+ cvh.applyRenderInfo(enabled, colorOffset)
+diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
+new file mode 100644
+index 000000000000..bfdb9231a9f8
+--- /dev/null
++++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt
+@@ -0,0 +1,81 @@
++/*
++ * Copyright (C) 2023 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.controls.ui
++
++import android.content.ContentProvider
++import android.graphics.Bitmap
++import android.graphics.drawable.Icon
++import android.net.Uri
++import android.os.UserHandle
++import android.testing.AndroidTestingRunner
++import androidx.test.filters.SmallTest
++import com.android.systemui.SysuiTestCase
++import com.google.common.truth.Truth.assertThat
++import org.junit.Test
++import org.junit.runner.RunWith
++
++@SmallTest
++@RunWith(AndroidTestingRunner::class)
++class CanUseIconPredicateTest : SysuiTestCase() {
++
++ private companion object {
++ const val USER_ID_1 = 1
++ const val USER_ID_2 = 2
++ }
++
++ val underTest: CanUseIconPredicate = CanUseIconPredicate(USER_ID_1)
++
++ @Test
++ fun testReturnsFalseForDifferentUser() {
++ val user2Icon =
++ Icon.createWithContentUri(
++ ContentProvider.createContentUriForUser(
++ Uri.parse("content://test"),
++ UserHandle.of(USER_ID_2)
++ )
++ )
++
++ assertThat(underTest.invoke(user2Icon)).isFalse()
++ }
++
++ @Test
++ fun testReturnsTrueForCorrectUser() {
++ val user1Icon =
++ Icon.createWithContentUri(
++ ContentProvider.createContentUriForUser(
++ Uri.parse("content://test"),
++ UserHandle.of(USER_ID_1)
++ )
++ )
++
++ assertThat(underTest.invoke(user1Icon)).isTrue()
++ }
++
++ @Test
++ fun testReturnsTrueForUriWithoutUser() {
++ val uriIcon = Icon.createWithContentUri(Uri.parse("content://test"))
++
++ assertThat(underTest.invoke(uriIcon)).isTrue()
++ }
++
++ @Test
++ fun testReturnsTrueForNonUriIcon() {
++ val bitmapIcon = Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
++
++ assertThat(underTest.invoke(bitmapIcon)).isTrue()
++ }
++}
+diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
+index d3c465dab438..42f28c8c6043 100644
+--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
++++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
+@@ -66,7 +66,8 @@ class ControlViewHolderTest : SysuiTestCase() {
+ FakeExecutor(clock),
+ mock(ControlActionCoordinator::class.java),
+ mock(ControlsMetricsLogger::class.java),
+- uid = 100
++ uid = 100,
++ 0,
+ )
+
+ val cws = ControlWithState(
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-09.patch b/Patches/LineageOS-20.0/ASB-2023-10/base-09.patch
new file mode 100644
index 00000000..b080bcc5
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-09.patch
@@ -0,0 +1,135 @@
+From c6fbe1330a77c479ea3e29b54523682d0f248420 Mon Sep 17 00:00:00 2001
+From: Eric Biggers
+Date: Fri, 28 Jul 2023 22:03:03 +0000
+Subject: [PATCH] RESTRICT AUTOMERGE: SettingsProvider: exclude secure_frp_mode
+ from resets
+
+When RescueParty detects that a system process is crashing frequently,
+it tries to recover in various ways, such as by resetting all settings.
+Unfortunately, this included resetting the secure_frp_mode setting,
+which is the means by which the system keeps track of whether the
+Factory Reset Protection (FRP) challenge has been passed yet. With this
+setting reset, some FRP restrictions went away and it became possible to
+bypass FRP by setting a new lockscreen credential.
+
+Fix this by excluding secure_frp_mode from resets.
+
+Note: currently this bug isn't reproducible on 'main' due to ag/23727749
+disabling much of RescueParty, but that is a temporary change.
+
+Bug: 253043065
+Test: With ag/23727749 reverted and with my fix to prevent
+ com.android.settings from crashing *not* applied, tried repeatedly
+ setting lockscreen credential while in FRP mode, using the
+ smartlock setup activity launched by intent via adb. Verified
+ that although RescueParty is still triggered after 5 attempts,
+ secure_frp_mode is no longer reset (its value remains "1").
+Test: Verified that secure_frp_mode still gets changed from 1 to 0 when
+ FRP is passed legitimately.
+Test: atest com.android.providers.settings.SettingsProviderTest
+Test: atest android.provider.SettingsProviderTest
+(cherry picked from commit 9890dd7f15c091f7d1a09e4fddb9f85d32015955)
+(changed Global.SECURE_FRP_MODE to Secure.SECURE_FRP_MODE,
+ needed because this setting was moved in U)
+(removed static keyword from shouldExcludeSettingFromReset(),
+ needed for compatibility with Java 15 and earlier)
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8c2d2c6fc91c6b80809a91ac510667af24d2cf17)
+Merged-In: Id95ed43b9cc2208090064392bcd5dc012710af93
+Change-Id: Id95ed43b9cc2208090064392bcd5dc012710af93
+---
+ .../providers/settings/SettingsProvider.java | 17 ++++++++++---
+ .../settings/SettingsProviderTest.java | 25 +++++++++++++++++++
+ 2 files changed, 38 insertions(+), 4 deletions(-)
+
+diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+index c51f19510f40..31e64196d3b5 100644
+--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
++++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+@@ -3102,6 +3102,15 @@ public Setting getSettingLocked(int type, int userId, String name) {
+ return settingsState.getSettingLocked(name);
+ }
+
++ private boolean shouldExcludeSettingFromReset(Setting setting, String prefix) {
++ // If a prefix was specified, exclude settings whose names don't start with it.
++ if (prefix != null && !setting.getName().startsWith(prefix)) {
++ return true;
++ }
++ // Never reset SECURE_FRP_MODE, as it could be abused to bypass FRP via RescueParty.
++ return Secure.SECURE_FRP_MODE.equals(setting.getName());
++ }
++
+ public void resetSettingsLocked(int type, int userId, String packageName, int mode,
+ String tag) {
+ resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/
+@@ -3124,7 +3133,7 @@ public void resetSettingsLocked(int type, int userId, String packageName, int mo
+ Setting setting = settingsState.getSettingLocked(name);
+ if (packageName.equals(setting.getPackageName())) {
+ if ((tag != null && !tag.equals(setting.getTag()))
+- || (prefix != null && !setting.getName().startsWith(prefix))) {
++ || shouldExcludeSettingFromReset(setting, prefix)) {
+ continue;
+ }
+ if (settingsState.resetSettingLocked(name)) {
+@@ -3144,7 +3153,7 @@ public void resetSettingsLocked(int type, int userId, String packageName, int mo
+ Setting setting = settingsState.getSettingLocked(name);
+ if (!SettingsState.isSystemPackage(getContext(),
+ setting.getPackageName())) {
+- if (prefix != null && !setting.getName().startsWith(prefix)) {
++ if (shouldExcludeSettingFromReset(setting, prefix)) {
+ continue;
+ }
+ if (settingsState.resetSettingLocked(name)) {
+@@ -3164,7 +3173,7 @@ public void resetSettingsLocked(int type, int userId, String packageName, int mo
+ Setting setting = settingsState.getSettingLocked(name);
+ if (!SettingsState.isSystemPackage(getContext(),
+ setting.getPackageName())) {
+- if (prefix != null && !setting.getName().startsWith(prefix)) {
++ if (shouldExcludeSettingFromReset(setting, prefix)) {
+ continue;
+ }
+ if (setting.isDefaultFromSystem()) {
+@@ -3187,7 +3196,7 @@ public void resetSettingsLocked(int type, int userId, String packageName, int mo
+ for (String name : settingsState.getSettingNamesLocked()) {
+ Setting setting = settingsState.getSettingLocked(name);
+ boolean someSettingChanged = false;
+- if (prefix != null && !setting.getName().startsWith(prefix)) {
++ if (shouldExcludeSettingFromReset(setting, prefix)) {
+ continue;
+ }
+ if (setting.isDefaultFromSystem()) {
+diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+index eaf0dcb9b4e7..1c6d2b08136c 100644
+--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
++++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+@@ -464,6 +464,31 @@ private void testResetModeTrustedDefaultsCommon(int type) throws Exception {
+ }
+ }
+
++ // To prevent FRP bypasses, the SECURE_FRP_MODE setting should not be reset when all other
++ // settings are reset. But it should still be possible to explicitly set its value.
++ @Test
++ public void testSecureFrpModeSettingCannotBeReset() throws Exception {
++ final String name = Settings.Secure.SECURE_FRP_MODE;
++ final String origValue = getSetting(SETTING_TYPE_GLOBAL, name);
++ setSettingViaShell(SETTING_TYPE_GLOBAL, name, "1", false);
++ try {
++ assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name));
++ for (int type : new int[] { SETTING_TYPE_GLOBAL, SETTING_TYPE_SECURE }) {
++ resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
++ resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_CHANGES);
++ resetSettingsViaShell(type, Settings.RESET_MODE_TRUSTED_DEFAULTS);
++ }
++ // The value should still be "1". It should not have been reset to null.
++ assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name));
++ // It should still be possible to explicitly set the value to "0".
++ setSettingViaShell(SETTING_TYPE_GLOBAL, name, "0", false);
++ assertEquals("0", getSetting(SETTING_TYPE_GLOBAL, name));
++ } finally {
++ setSettingViaShell(SETTING_TYPE_GLOBAL, name, origValue, false);
++ assertEquals(origValue, getSetting(SETTING_TYPE_GLOBAL, name));
++ }
++ }
++
+ private void doTestQueryStringInBracketsViaProviderApiForType(int type) {
+ // Make sure we have a clean slate.
+ deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/base-10.patch b/Patches/LineageOS-20.0/ASB-2023-10/base-10.patch
new file mode 100644
index 00000000..9262ff8f
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/base-10.patch
@@ -0,0 +1,32 @@
+From 9141cac175caaf176377d088e334d0991482fd6a Mon Sep 17 00:00:00 2001
+From: Aaron Liu
+Date: Thu, 10 Aug 2023 15:38:08 +0000
+Subject: [PATCH] Revert "Dismiss keyguard when simpin auth'd and..."
+
+Revert submission 22621774-cherrypicker-L22000000959901080:N28400001357657640
+
+Reason for revert: causing a partner bug
+Fixes: 295205456
+Bug: 222446076
+
+Reverted changes: /q/submissionid:22621774-cherrypicker-L22000000959901080:N28400001357657640
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0afa6707fa632c1f5fba5dff46a94b5d734e2ab2)
+Merged-In: Icb27b4d897696b4fbb4e4a878751d925f5205dfd
+Change-Id: Icb27b4d897696b4fbb4e4a878751d925f5205dfd
+---
+ .../android/keyguard/KeyguardSecurityContainerController.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+index 16299c7aff7b..061bab8a7006 100644
+--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
++++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+@@ -752,7 +752,7 @@ public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetU
+ case SimPuk:
+ // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
+ SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
+- if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled(
++ if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
+ KeyguardUpdateMonitor.getCurrentUser())) {
+ finish = true;
+ eventSubtype = BOUNCER_DISMISS_SIM;
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/bluetooth-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/bluetooth-01.patch
new file mode 100644
index 00000000..a9afbcba
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/bluetooth-01.patch
@@ -0,0 +1,90 @@
+From 364a1d99624e8dca6501d98166efbb8061362970 Mon Sep 17 00:00:00 2001
+From: Hui Peng
+Date: Tue, 16 May 2023 02:09:38 +0000
+Subject: [PATCH] Fix an integer underflow in build_read_multi_rsp
+
+When p_buf->len is mtu - 1 and p_cmd->multi_req.variable_len
+evaluates to true, integer underflow is triggered
+in the following line, resulting OOB access.
+
+```
+ len = p_rsp->attr_value.len - (total_len - mtu);
+```
+
+Bug: 273874525
+Test: manual
+Ignore-AOSP-First: security
+Tag: #security
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:85f4d53c7bf90b806639a3a302f0007ffb3b9f23)
+Merged-In: Ia60dd829ff9152c083de1f4c1265bb3ad595dcc4
+Change-Id: Ia60dd829ff9152c083de1f4c1265bb3ad595dcc4
+---
+ system/stack/gatt/gatt_sr.cc | 32 +++++++++++++++++---------------
+ 1 file changed, 17 insertions(+), 15 deletions(-)
+
+diff --git a/system/stack/gatt/gatt_sr.cc b/system/stack/gatt/gatt_sr.cc
+index f2a3e22414..ce00ef7428 100644
+--- a/system/stack/gatt/gatt_sr.cc
++++ b/system/stack/gatt/gatt_sr.cc
+@@ -21,6 +21,7 @@
+ * this file contains the GATT server functions
+ *
+ ******************************************************************************/
++#include
+ #include
+
+ #include "bt_target.h"
+@@ -178,37 +179,38 @@ static void build_read_multi_rsp(tGATT_SR_CMD* p_cmd, uint16_t mtu) {
+ }
+
+ if (p_rsp != NULL) {
+- total_len = (p_buf->len + p_rsp->attr_value.len);
++ total_len = p_buf->len;
+ if (p_cmd->multi_req.variable_len) {
+ total_len += 2;
+ }
+
+ if (total_len > mtu) {
+- /* just send the partial response for the overflow case */
+- len = p_rsp->attr_value.len - (total_len - mtu);
++ VLOG(1) << "Buffer space not enough for this data item, skipping";
++ break;
++ }
++
++ len = std::min((size_t) p_rsp->attr_value.len, mtu - total_len);
++
++ if (len == 0) {
++ VLOG(1) << "Buffer space not enough for this data item, skipping";
++ break;
++ }
++
++ if (len < p_rsp->attr_value.len) {
+ is_overflow = true;
+ VLOG(1) << StringPrintf(
+ "multi read overflow available len=%zu val_len=%d", len,
+ p_rsp->attr_value.len);
+- } else {
+- len = p_rsp->attr_value.len;
+ }
+
+ if (p_cmd->multi_req.variable_len) {
+- UINT16_TO_STREAM(p, len);
++ UINT16_TO_STREAM(p, (uint16_t) len);
+ p_buf->len += 2;
+ }
+
+ if (p_rsp->attr_value.handle == p_cmd->multi_req.handles[ii]) {
+- // check for possible integer overflow
+- if (p_buf->len + len <= UINT16_MAX) {
+- memcpy(p, p_rsp->attr_value.value, len);
+- if (!is_overflow) p += len;
+- p_buf->len += len;
+- } else {
+- p_cmd->status = GATT_NOT_FOUND;
+- break;
+- }
++ ARRAY_TO_STREAM(p, p_rsp->attr_value.value, (uint16_t) len);
++ p_buf->len += (uint16_t) len;
+ } else {
+ p_cmd->status = GATT_NOT_FOUND;
+ break;
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/launcher-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/launcher-01.patch
new file mode 100644
index 00000000..5fe42be7
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/launcher-01.patch
@@ -0,0 +1,30 @@
+From cfbfcefb3ce6bcd4d099cba4f45a8c6a0c02e6e6 Mon Sep 17 00:00:00 2001
+From: Shen Lin
+Date: Tue, 20 Dec 2022 11:29:51 +0800
+Subject: [PATCH] Fix NPE in FallbackSwipeHandler when getRunningTask() returns
+ null
+
+mGestureState.getRunningTask() is probably null in this situation, we
+need to add a null check here refer to the usage in TaskAnimationManager.
+
+Bug: 263041039
+Test: manual
+Change-Id: I7707bf204190ff506f40640ff764f07cb98aab68
+---
+ quickstep/src/com/android/quickstep/FallbackSwipeHandler.java | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+index d4bebea55f..1913091695 100644
+--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
++++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+@@ -108,7 +108,8 @@ public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceS
+ super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
+ continuingLastGesture, inputConsumer);
+
+- mRunningOverHome = mGestureState.getRunningTask().isHomeTask();
++ mRunningOverHome = mGestureState.getRunningTask() != null
++ && mGestureState.getRunningTask().isHomeTask();
+ if (mRunningOverHome) {
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/libxml-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/libxml-01.patch
new file mode 100644
index 00000000..5caa3887
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/libxml-01.patch
@@ -0,0 +1,123 @@
+From 4a27a7f162907facfbeddf2d4ae4c6ab7c6eb15a Mon Sep 17 00:00:00 2001
+From: Nick Wellnhofer
+Date: Fri, 17 Feb 2023 15:53:07 +0100
+Subject: [PATCH] malloc-fail: Fix OOB read after xmlRegGetCounter
+
+Found with libFuzzer, see #344.
+
+(cherry picked from commit 1743c4c3fc58cf38ecce68db9de51d0f3651e033)
+
+I also copied the error label from
+e64653c0e7975594e27d7de2ed4be062c1e4ad03 to fix the build failure.
+
+Bug: http://b/274231102
+Test: TreeHugger
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0e6ed17dfe8e36e5618a592a600720bd61e015cc)
+Merged-In: I3bad3e03092e17a761cb6e299aff848ebd35b6f4
+Change-Id: I3bad3e03092e17a761cb6e299aff848ebd35b6f4
+---
+ xmlregexp.c | 28 ++++++++++++++++++++++++++++
+ 1 file changed, 28 insertions(+)
+
+diff --git a/xmlregexp.c b/xmlregexp.c
+index 984c7ac6e..ce09b2216 100644
+--- a/xmlregexp.c
++++ b/xmlregexp.c
+@@ -1673,6 +1673,8 @@ xmlFAGenerateTransitions(xmlRegParserCtxtPtr ctxt, xmlRegStatePtr from,
+ return(-1);
+ inter = ctxt->state;
+ counter = xmlRegGetCounter(ctxt);
++ if (counter < 0)
++ return(-1);
+ ctxt->counters[counter].min = atom->min - 1;
+ ctxt->counters[counter].max = atom->max - 1;
+ /* count the number of times we see it again */
+@@ -1691,6 +1693,8 @@ xmlFAGenerateTransitions(xmlRegParserCtxtPtr ctxt, xmlRegStatePtr from,
+ * epsilon transition.
+ */
+ counter = xmlRegGetCounter(ctxt);
++ if (counter < 0)
++ return(-1);
+ ctxt->counters[counter].min = atom->min - 1;
+ ctxt->counters[counter].max = atom->max - 1;
+ /* count the number of times we see it again */
+@@ -6015,6 +6019,8 @@ xmlAutomataNewCountTrans2(xmlAutomataPtr am, xmlAutomataStatePtr from,
+ * associate a counter to the transition.
+ */
+ counter = xmlRegGetCounter(am);
++ if (counter < 0)
++ goto error;
+ am->counters[counter].min = min;
+ am->counters[counter].max = max;
+
+@@ -6034,6 +6040,10 @@ xmlAutomataNewCountTrans2(xmlAutomataPtr am, xmlAutomataStatePtr from,
+ if (min == 0)
+ xmlFAGenerateEpsilonTransition(am, from, to);
+ return(to);
++
++error:
++ xmlRegFreeAtom(atom);
++ return(NULL);
+ }
+
+ /**
+@@ -6081,6 +6091,8 @@ xmlAutomataNewCountTrans(xmlAutomataPtr am, xmlAutomataStatePtr from,
+ * associate a counter to the transition.
+ */
+ counter = xmlRegGetCounter(am);
++ if (counter < 0)
++ goto error;
+ am->counters[counter].min = min;
+ am->counters[counter].max = max;
+
+@@ -6100,6 +6112,10 @@ xmlAutomataNewCountTrans(xmlAutomataPtr am, xmlAutomataStatePtr from,
+ if (min == 0)
+ xmlFAGenerateEpsilonTransition(am, from, to);
+ return(to);
++
++error:
++ xmlRegFreeAtom(atom);
++ return(NULL);
+ }
+
+ /**
+@@ -6167,6 +6183,8 @@ xmlAutomataNewOnceTrans2(xmlAutomataPtr am, xmlAutomataStatePtr from,
+ * associate a counter to the transition.
+ */
+ counter = xmlRegGetCounter(am);
++ if (counter < 0)
++ goto error;
+ am->counters[counter].min = 1;
+ am->counters[counter].max = 1;
+
+@@ -6179,6 +6197,10 @@ xmlAutomataNewOnceTrans2(xmlAutomataPtr am, xmlAutomataStatePtr from,
+ xmlRegAtomPush(am, atom);
+ am->state = to;
+ return(to);
++
++error:
++ xmlRegFreeAtom(atom);
++ return(NULL);
+ }
+
+
+@@ -6226,6 +6248,8 @@ xmlAutomataNewOnceTrans(xmlAutomataPtr am, xmlAutomataStatePtr from,
+ * associate a counter to the transition.
+ */
+ counter = xmlRegGetCounter(am);
++ if (counter < 0)
++ goto error;
+ am->counters[counter].min = 1;
+ am->counters[counter].max = 1;
+
+@@ -6238,6 +6262,10 @@ xmlAutomataNewOnceTrans(xmlAutomataPtr am, xmlAutomataStatePtr from,
+ xmlRegAtomPush(am, atom);
+ am->state = to;
+ return(to);
++
++error:
++ xmlRegFreeAtom(atom);
++ return(NULL);
+ }
+
+ /**
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/mediaprovider-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/mediaprovider-01.patch
new file mode 100644
index 00000000..161842af
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/mediaprovider-01.patch
@@ -0,0 +1,452 @@
+From 0fb5786dbf8b462eb106df912a7f65ab240f0d6a Mon Sep 17 00:00:00 2001
+From: Sergey Nikolaienkov
+Date: Tue, 28 Mar 2023 12:22:31 +0200
+Subject: [PATCH] Fix path traversal vulnerabilities in MediaProvider
+
+Canonicalize filepath provided by the caller when hanling SCAN_FILE_CALL
+method call in MediaProvider.
+Additionally, make sure to check access permission in SCAN_FILE_CALL
+(using enforceCallingPermissionInternal()).
+
+Preemptively canonicalize Files provided as an arguments to the public
+API methods in ModernMediaScanner (scanFile(), scanDirectory() and
+onDirectoryDirty()) to prevent path traversal attacks.
+
+Bug: 262244882
+Test: atest MediaProviderTests
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5bb32d7fba00b9e53c7e20ae8acaf6f84a8b2e8d)
+Merged-In: I61e77d69ae857984b819fa0ea27bec5c26a34842
+Change-Id: I61e77d69ae857984b819fa0ea27bec5c26a34842
+---
+ .../providers/media/MediaProvider.java | 61 +++++++++++--------
+ .../media/scan/LegacyMediaScanner.java | 7 ---
+ .../providers/media/scan/MediaScanner.java | 25 ++++----
+ .../media/scan/ModernMediaScanner.java | 56 +++++++++++------
+ .../media/scan/NullMediaScanner.java | 6 --
+ .../providers/media/util/FileUtils.java | 47 ++++++++++----
+ .../media/scan/LegacyMediaScannerTest.java | 6 --
+ .../media/scan/ModernMediaScannerTest.java | 18 ------
+ .../media/scan/NullMediaScannerTest.java | 2 -
+ 9 files changed, 117 insertions(+), 111 deletions(-)
+
+diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
+index 21eeed7f3..8c1df561b 100644
+--- a/src/com/android/providers/media/MediaProvider.java
++++ b/src/com/android/providers/media/MediaProvider.java
+@@ -1692,11 +1692,7 @@ public void scanDirectory(File file, int reason) {
+ }
+
+ public Uri scanFile(File file, int reason) {
+- return scanFile(file, reason, null);
+- }
+-
+- public Uri scanFile(File file, int reason, String ownerPackage) {
+- return mMediaScanner.scanFile(file, reason, ownerPackage);
++ return mMediaScanner.scanFile(file, reason);
+ }
+
+ private Uri scanFileAsMediaProvider(File file, int reason) {
+@@ -6197,38 +6193,51 @@ private Bundle callInternal(String method, String arg, Bundle extras) {
+ }
+ return null;
+ }
+- case MediaStore.SCAN_FILE_CALL:
++ case MediaStore.SCAN_FILE_CALL: {
++ final LocalCallingIdentity token = clearLocalCallingIdentity();
++ final CallingIdentity providerToken = clearCallingIdentity();
++
++ final String filePath = arg;
++ final Uri uri;
++ try {
++ File file;
++ try {
++ file = FileUtils.getCanonicalFile(filePath);
++ } catch (IOException e) {
++ file = null;
++ }
++
++ uri = file != null ? scanFile(file, REASON_DEMAND) : null;
++ } finally {
++ restoreCallingIdentity(providerToken);
++ restoreLocalCallingIdentity(token);
++ }
++
++ // TODO(b/262244882): maybe enforceCallingPermissionInternal(uri, ...)
++
++ final Bundle res = new Bundle();
++ res.putParcelable(Intent.EXTRA_STREAM, uri);
++ return res;
++ }
+ case MediaStore.SCAN_VOLUME_CALL: {
+ final int userId = uidToUserId(Binder.getCallingUid());
+ final LocalCallingIdentity token = clearLocalCallingIdentity();
+ final CallingIdentity providerToken = clearCallingIdentity();
++
++ final String volumeName = arg;
+ try {
+- final Bundle res = new Bundle();
+- switch (method) {
+- case MediaStore.SCAN_FILE_CALL: {
+- final File file = new File(arg);
+- res.putParcelable(Intent.EXTRA_STREAM, scanFile(file, REASON_DEMAND));
+- break;
+- }
+- case MediaStore.SCAN_VOLUME_CALL: {
+- final String volumeName = arg;
+- try {
+- MediaVolume volume = mVolumeCache.findVolume(volumeName,
+- UserHandle.of(userId));
+- MediaService.onScanVolume(getContext(), volume, REASON_DEMAND);
+- } catch (FileNotFoundException e) {
+- Log.w(TAG, "Failed to find volume " + volumeName, e);
+- }
+- break;
+- }
+- }
+- return res;
++ final MediaVolume volume = mVolumeCache.findVolume(volumeName,
++ UserHandle.of(userId));
++ MediaService.onScanVolume(getContext(), volume, REASON_DEMAND);
++ } catch (FileNotFoundException e) {
++ Log.w(TAG, "Failed to find volume " + volumeName, e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ restoreCallingIdentity(providerToken);
+ restoreLocalCallingIdentity(token);
+ }
++ return Bundle.EMPTY;
+ }
+ case MediaStore.GET_VERSION_CALL: {
+ final String volumeName = extras.getString(Intent.EXTRA_TEXT);
+diff --git a/src/com/android/providers/media/scan/LegacyMediaScanner.java b/src/com/android/providers/media/scan/LegacyMediaScanner.java
+index d8d3bed41..d73dda584 100644
+--- a/src/com/android/providers/media/scan/LegacyMediaScanner.java
++++ b/src/com/android/providers/media/scan/LegacyMediaScanner.java
+@@ -19,8 +19,6 @@
+ import android.content.Context;
+ import android.net.Uri;
+
+-import androidx.annotation.Nullable;
+-
+ import com.android.providers.media.MediaVolume;
+
+ import java.io.File;
+@@ -48,11 +46,6 @@ public Uri scanFile(File file, int reason) {
+ throw new UnsupportedOperationException();
+ }
+
+- @Override
+- public Uri scanFile(File file, int reason, @Nullable String ownerPackage) {
+- throw new UnsupportedOperationException();
+- }
+-
+ @Override
+ public void onDetachVolume(MediaVolume volume) {
+ throw new UnsupportedOperationException();
+diff --git a/src/com/android/providers/media/scan/MediaScanner.java b/src/com/android/providers/media/scan/MediaScanner.java
+index 45d2a2436..eb5e4d6c7 100644
+--- a/src/com/android/providers/media/scan/MediaScanner.java
++++ b/src/com/android/providers/media/scan/MediaScanner.java
+@@ -24,23 +24,20 @@
+ import android.content.Context;
+ import android.net.Uri;
+
+-import androidx.annotation.Nullable;
+-
+ import com.android.providers.media.MediaVolume;
+
+ import java.io.File;
+
+ public interface MediaScanner {
+- public static final int REASON_UNKNOWN = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__UNKNOWN;
+- public static final int REASON_MOUNTED = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__MOUNTED;
+- public static final int REASON_DEMAND = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__DEMAND;
+- public static final int REASON_IDLE = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__IDLE;
+-
+- public Context getContext();
+- public void scanDirectory(File file, int reason);
+- public Uri scanFile(File file, int reason);
+- public Uri scanFile(File file, int reason, @Nullable String ownerPackage);
+- public void onDetachVolume(MediaVolume volume);
+- public void onIdleScanStopped();
+- public void onDirectoryDirty(File file);
++ int REASON_UNKNOWN = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__UNKNOWN;
++ int REASON_MOUNTED = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__MOUNTED;
++ int REASON_DEMAND = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__DEMAND;
++ int REASON_IDLE = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__IDLE;
++
++ Context getContext();
++ void scanDirectory(File file, int reason);
++ Uri scanFile(File file, int reason);
++ void onDetachVolume(MediaVolume volume);
++ void onIdleScanStopped();
++ void onDirectoryDirty(File file);
+ }
+diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
+index 41f53d57a..81133f7aa 100644
+--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
++++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
+@@ -50,6 +50,8 @@
+
+ import static com.android.providers.media.util.Metrics.translateReason;
+
++import static java.util.Objects.requireNonNull;
++
+ import android.content.ContentProviderClient;
+ import android.content.ContentProviderOperation;
+ import android.content.ContentProviderResult;
+@@ -236,23 +238,36 @@ public Context getContext() {
+ }
+
+ @Override
+- public void scanDirectory(File file, int reason) {
+- try (Scan scan = new Scan(file, reason, /*ownerPackage*/ null)) {
++ public void scanDirectory(@NonNull File file, int reason) {
++ requireNonNull(file);
++ try {
++ file = file.getCanonicalFile();
++ } catch (IOException e) {
++ Log.e(TAG, "Couldn't canonicalize directory to scan" + file, e);
++ return;
++ }
++
++ try (Scan scan = new Scan(file, reason)) {
+ scan.run();
+- } catch (OperationCanceledException ignored) {
+ } catch (FileNotFoundException e) {
+- Log.e(TAG, "Couldn't find directory to scan", e) ;
++ Log.e(TAG, "Couldn't find directory to scan", e);
++ } catch (OperationCanceledException ignored) {
++ // No-op.
+ }
+ }
+
+ @Override
+- public Uri scanFile(File file, int reason) {
+- return scanFile(file, reason, /*ownerPackage*/ null);
+- }
++ @Nullable
++ public Uri scanFile(@NonNull File file, int reason) {
++ requireNonNull(file);
++ try {
++ file = file.getCanonicalFile();
++ } catch (IOException e) {
++ Log.e(TAG, "Couldn't canonicalize file to scan" + file, e);
++ return null;
++ }
+
+- @Override
+- public Uri scanFile(File file, int reason, @Nullable String ownerPackage) {
+- try (Scan scan = new Scan(file, reason, ownerPackage)) {
++ try (Scan scan = new Scan(file, reason)) {
+ scan.run();
+ return scan.getFirstResult();
+ } catch (OperationCanceledException ignored) {
+@@ -286,10 +301,18 @@ public void onIdleScanStopped() {
+ }
+
+ @Override
+- public void onDirectoryDirty(File dir) {
++ public void onDirectoryDirty(@NonNull File dir) {
++ requireNonNull(dir);
++ try {
++ dir = dir.getCanonicalFile();
++ } catch (IOException e) {
++ Log.e(TAG, "Couldn't canonicalize directory" + dir, e);
++ return;
++ }
++
+ synchronized (mPendingCleanDirectories) {
+ mPendingCleanDirectories.remove(dir.getPath());
+- FileUtils.setDirectoryDirty(dir, /*isDirty*/ true);
++ FileUtils.setDirectoryDirty(dir, /* isDirty */ true);
+ }
+ }
+
+@@ -320,7 +343,6 @@ private class Scan implements Runnable, FileVisitor, AutoCloseable {
+ private final String mVolumeName;
+ private final Uri mFilesUri;
+ private final CancellationSignal mSignal;
+- private final String mOwnerPackage;
+ private final List mExcludeDirs;
+
+ private final long mStartGeneration;
+@@ -349,7 +371,7 @@ private class Scan implements Runnable, FileVisitor, AutoCloseable {
+ */
+ private boolean mIsDirectoryTreeDirty;
+
+- public Scan(File root, int reason, @Nullable String ownerPackage)
++ public Scan(File root, int reason)
+ throws FileNotFoundException {
+ Trace.beginSection("ctor");
+
+@@ -371,7 +393,6 @@ public Scan(File root, int reason, @Nullable String ownerPackage)
+
+ mStartGeneration = MediaStore.getGeneration(mResolver, mVolumeName);
+ mSingleFile = mRoot.isFile();
+- mOwnerPackage = ownerPackage;
+ mExcludeDirs = new ArrayList<>();
+
+ Trace.endSection();
+@@ -800,10 +821,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ }
+ if (op != null) {
+ op.withValue(FileColumns._MODIFIER, FileColumns._MODIFIER_MEDIA_SCAN);
+- // Add owner package name to new insertions when package name is provided.
+- if (op.build().isInsert() && !attrs.isDirectory() && mOwnerPackage != null) {
+- op.withValue(MediaColumns.OWNER_PACKAGE_NAME, mOwnerPackage);
+- }
++
+ // Force DRM files to be marked as DRM, since the lower level
+ // stack may not set this correctly
+ if (isDrm) {
+diff --git a/src/com/android/providers/media/scan/NullMediaScanner.java b/src/com/android/providers/media/scan/NullMediaScanner.java
+index 7a1a39610..e53f96468 100644
+--- a/src/com/android/providers/media/scan/NullMediaScanner.java
++++ b/src/com/android/providers/media/scan/NullMediaScanner.java
+@@ -56,12 +56,6 @@ public Uri scanFile(File file, int reason) {
+ return null;
+ }
+
+- @Override
+- public Uri scanFile(File file, int reason, @Nullable String ownerPackage) {
+- Log.w(TAG, "Ignoring scan request for " + file);
+- return null;
+- }
+-
+ @Override
+ public void onDetachVolume(MediaVolume volume) {
+ // Ignored
+diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
+index 097eca8c9..6c2695c78 100644
+--- a/src/com/android/providers/media/util/FileUtils.java
++++ b/src/com/android/providers/media/util/FileUtils.java
+@@ -1110,18 +1110,25 @@ public static int extractUserId(@Nullable String data) {
+ }
+
+ public static @Nullable String extractRelativePath(@Nullable String data) {
+- data = getCanonicalPath(data);
+ if (data == null) return null;
+
+- final Matcher matcher = PATTERN_RELATIVE_PATH.matcher(data);
++ final String path;
++ try {
++ path = getCanonicalPath(data);
++ } catch (IOException e) {
++ Log.d(TAG, "Unable to get canonical path from invalid data path: " + data, e);
++ return null;
++ }
++
++ final Matcher matcher = PATTERN_RELATIVE_PATH.matcher(path);
+ if (matcher.find()) {
+- final int lastSlash = data.lastIndexOf('/');
++ final int lastSlash = path.lastIndexOf('/');
+ if (lastSlash == -1 || lastSlash < matcher.end()) {
+ // This is a file in the top-level directory, so relative path is "/"
+ // which is different than null, which means unknown path
+ return "/";
+ } else {
+- return data.substring(matcher.end(), lastSlash + 1);
++ return path.substring(matcher.end(), lastSlash + 1);
+ }
+ } else {
+ return null;
+@@ -1769,15 +1776,29 @@ public static File fromFuseFile(File file) {
+ return new File(file.getPath().replaceFirst(FUSE_FS_PREFIX, LOWER_FS_PREFIX));
+ }
+
+- @Nullable
+- private static String getCanonicalPath(@Nullable String path) {
+- if (path == null) return null;
++ /**
++ * Returns the canonical {@link File} for the provided abstract pathname.
++ *
++ * @return The canonical pathname string denoting the same file or directory as this abstract
++ * pathname
++ * @see File#getCanonicalFile()
++ */
++ @NonNull
++ public static File getCanonicalFile(@NonNull String path) throws IOException {
++ Objects.requireNonNull(path);
++ return new File(path).getCanonicalFile();
++ }
+
+- try {
+- return new File(path).getCanonicalPath();
+- } catch (IOException e) {
+- Log.d(TAG, "Unable to get canonical path from invalid data path: " + path, e);
+- return null;
+- }
++ /**
++ * Returns the canonical pathname string of the provided abstract pathname.
++ *
++ * @return The canonical pathname string denoting the same file or directory as this abstract
++ * pathname.
++ * @see File#getCanonicalPath()
++ */
++ @NonNull
++ public static String getCanonicalPath(@NonNull String path) throws IOException {
++ Objects.requireNonNull(path);
++ return new File(path).getCanonicalPath();
+ }
+ }
+diff --git a/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java b/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java
+index cf9cb395d..2831e963e 100644
+--- a/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java
++++ b/tests/src/com/android/providers/media/scan/LegacyMediaScannerTest.java
+@@ -47,12 +47,6 @@ public void testSimple() throws Exception {
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+- try {
+- scanner.scanFile(new File("/dev/null"), MediaScanner.REASON_UNKNOWN,
+- InstrumentationRegistry.getContext().getPackageName());
+- fail();
+- } catch (UnsupportedOperationException expected) {
+- }
+ try {
+ scanner.onDetachVolume(null);
+ fail();
+diff --git a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
+index 0450c1cee..06f10ed21 100644
+--- a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
++++ b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
+@@ -779,24 +779,6 @@ public void testScan_missingFile() throws Exception {
+ assertThat(mModern.scanFile(image, REASON_UNKNOWN)).isNull();
+ }
+
+- @Test
+- public void testScanFileAndUpdateOwnerPackageName() throws Exception {
+- final File image = new File(mDir, "image.jpg");
+- final String thisPackageName = InstrumentationRegistry.getContext().getPackageName();
+- stage(R.raw.test_image, image);
+-
+- assertQueryCount(0, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+- // scanning the image file inserts new database entry with OWNER_PACKAGE_NAME as
+- // thisPackageName.
+- assertNotNull(mModern.scanFile(image, REASON_UNKNOWN, thisPackageName));
+- try (Cursor cursor = mIsolatedResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+- new String[] {MediaColumns.OWNER_PACKAGE_NAME}, null, null, null)) {
+- assertEquals(1, cursor.getCount());
+- cursor.moveToNext();
+- assertEquals(thisPackageName, cursor.getString(0));
+- }
+- }
+-
+ /**
+ * Verify fix for obscure bug which would cause us to delete files outside a
+ * directory that share a common prefix.
+diff --git a/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java b/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java
+index 063f1b7a3..265d1a97a 100644
+--- a/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java
++++ b/tests/src/com/android/providers/media/scan/NullMediaScannerTest.java
+@@ -38,8 +38,6 @@ public void testSimple() throws Exception {
+
+ scanner.scanDirectory(new File("/dev/null"), MediaScanner.REASON_UNKNOWN);
+ scanner.scanFile(new File("/dev/null"), MediaScanner.REASON_UNKNOWN);
+- scanner.scanFile(new File("/dev/null"), MediaScanner.REASON_UNKNOWN,
+- InstrumentationRegistry.getContext().getPackageName());
+
+ scanner.onDetachVolume(null);
+ }
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/native-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/native-01.patch
new file mode 100644
index 00000000..f432a768
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/native-01.patch
@@ -0,0 +1,214 @@
+From 507304e1f59236675bfd820290b329f5f7334ec0 Mon Sep 17 00:00:00 2001
+From: sergiuferentz
+Date: Mon, 26 Jun 2023 18:01:47 +0000
+Subject: [PATCH] Fix for heap-use-after-free in GPUService.cpp
+
+This adds a unit test and fix for the bug reported by libfuzzer.
+Changes made:
+ * Expose GPUService as testable code.
+ * Update main_gpuservice.cpp to use the new GpuService now located at
+ gpuservice/GpuService.h
+ * Make initializer threads members of GpuService
+ * Join the threads in destructor to prevent heap-use-after-free.
+ * Add unit test that waits 3 seconds after deallocation to ensure no
+ wrong access is made.
+
+Bug: 282919145
+Test: Added unit test and ran on device with ASAN
+(cherry picked from commit 3c00cbc0f119c3f59325aa6d5061529feb58462b)
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7fb707802ee4c667d1ee6065ae2845d835b47aeb)
+Merged-In: I4d1d2d4658b575bf2c8f425f91f68f03114ad029
+Change-Id: I4d1d2d4658b575bf2c8f425f91f68f03114ad029
+---
+ services/gpuservice/Android.bp | 1 +
+ services/gpuservice/GpuService.cpp | 14 +++--
+ .../{ => include/gpuservice}/GpuService.h | 4 ++
+ services/gpuservice/main_gpuservice.cpp | 2 +-
+ .../gpuservice/tests/unittests/Android.bp | 2 +
+ .../tests/unittests/GpuServiceTest.cpp | 52 +++++++++++++++++++
+ 6 files changed, 69 insertions(+), 6 deletions(-)
+ rename services/gpuservice/{ => include/gpuservice}/GpuService.h (94%)
+ create mode 100644 services/gpuservice/tests/unittests/GpuServiceTest.cpp
+
+diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
+index 5b4ee21b42..020940f04e 100644
+--- a/services/gpuservice/Android.bp
++++ b/services/gpuservice/Android.bp
+@@ -71,6 +71,7 @@ filegroup {
+ cc_library_shared {
+ name: "libgpuservice",
+ defaults: ["libgpuservice_production_defaults"],
++ export_include_dirs: ["include"],
+ srcs: [
+ ":libgpuservice_sources",
+ ],
+diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
+index 7b9782f4e8..5643940a6e 100644
+--- a/services/gpuservice/GpuService.cpp
++++ b/services/gpuservice/GpuService.cpp
+@@ -16,7 +16,7 @@
+
+ #define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+-#include "GpuService.h"
++#include "gpuservice/GpuService.h"
+
+ #include
+ #include
+@@ -34,6 +34,7 @@
+ #include
+
+ #include
++#include
+
+ namespace android {
+
+@@ -55,18 +56,21 @@ GpuService::GpuService()
+ mGpuStats(std::make_unique()),
+ mGpuMemTracer(std::make_unique()) {
+
+- std::thread gpuMemAsyncInitThread([this]() {
++ mGpuMemAsyncInitThread = std::make_unique([this] (){
+ mGpuMem->initialize();
+ mGpuMemTracer->initialize(mGpuMem);
+ });
+- gpuMemAsyncInitThread.detach();
+
+- std::thread gpuWorkAsyncInitThread([this]() {
++ mGpuWorkAsyncInitThread = std::make_unique([this]() {
+ mGpuWork->initialize();
+ });
+- gpuWorkAsyncInitThread.detach();
+ };
+
++GpuService::~GpuService() {
++ mGpuWorkAsyncInitThread->join();
++ mGpuMemAsyncInitThread->join();
++}
++
+ void GpuService::setGpuStats(const std::string& driverPackageName,
+ const std::string& driverVersionName, uint64_t driverVersionCode,
+ int64_t driverBuildTime, const std::string& appPackageName,
+diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/include/gpuservice/GpuService.h
+similarity index 94%
+rename from services/gpuservice/GpuService.h
+rename to services/gpuservice/include/gpuservice/GpuService.h
+index d7313d165e..3e0ae66f39 100644
+--- a/services/gpuservice/GpuService.h
++++ b/services/gpuservice/include/gpuservice/GpuService.h
+@@ -24,6 +24,7 @@
+ #include
+
+ #include
++#include
+ #include
+
+ namespace android {
+@@ -41,6 +42,7 @@ class GpuService : public BnGpuService, public PriorityDumper {
+ static const char* const SERVICE_NAME ANDROID_API;
+
+ GpuService() ANDROID_API;
++ ~GpuService();
+
+ protected:
+ status_t shellCommand(int in, int out, int err, std::vector& args) override;
+@@ -86,6 +88,8 @@ class GpuService : public BnGpuService, public PriorityDumper {
+ std::unique_ptr mGpuMemTracer;
+ std::mutex mLock;
+ std::string mDeveloperDriverPath;
++ std::unique_ptr mGpuMemAsyncInitThread;
++ std::unique_ptr mGpuWorkAsyncInitThread;
+ };
+
+ } // namespace android
+diff --git a/services/gpuservice/main_gpuservice.cpp b/services/gpuservice/main_gpuservice.cpp
+index 64aafcab6a..200237219e 100644
+--- a/services/gpuservice/main_gpuservice.cpp
++++ b/services/gpuservice/main_gpuservice.cpp
+@@ -18,7 +18,7 @@
+ #include
+ #include
+ #include
+-#include "GpuService.h"
++#include "gpuservice/GpuService.h"
+
+ using namespace android;
+
+diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp
+index 4fb0d2e734..808c86bcae 100644
+--- a/services/gpuservice/tests/unittests/Android.bp
++++ b/services/gpuservice/tests/unittests/Android.bp
+@@ -31,6 +31,7 @@ cc_test {
+ "GpuMemTest.cpp",
+ "GpuMemTracerTest.cpp",
+ "GpuStatsTest.cpp",
++ "GpuServiceTest.cpp",
+ ],
+ header_libs: ["bpf_headers"],
+ shared_libs: [
+@@ -47,6 +48,7 @@ cc_test {
+ "libstatslog",
+ "libstatspull",
+ "libutils",
++ "libgpuservice",
+ ],
+ static_libs: [
+ "libgmock",
+diff --git a/services/gpuservice/tests/unittests/GpuServiceTest.cpp b/services/gpuservice/tests/unittests/GpuServiceTest.cpp
+new file mode 100644
+index 0000000000..62b3e53f53
+--- /dev/null
++++ b/services/gpuservice/tests/unittests/GpuServiceTest.cpp
+@@ -0,0 +1,52 @@
++#undef LOG_TAG
++#define LOG_TAG "gpuservice_unittest"
++
++#include "gpuservice/GpuService.h"
++
++#include
++#include
++
++#include
++#include
++
++namespace android {
++namespace {
++
++class GpuServiceTest : public testing::Test {
++public:
++ GpuServiceTest() {
++ const ::testing::TestInfo* const test_info =
++ ::testing::UnitTest::GetInstance()->current_test_info();
++ ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
++ }
++
++ ~GpuServiceTest() {
++ const ::testing::TestInfo* const test_info =
++ ::testing::UnitTest::GetInstance()->current_test_info();
++ ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
++ }
++
++};
++
++
++/*
++* The behaviour before this test + fixes was UB caused by threads accessing deallocated memory.
++*
++* This test creates the service (which initializes the culprit threads),
++* deallocates it immediately and sleeps.
++*
++* GpuService's destructor gets called and joins the threads.
++* If we haven't crashed by the time the sleep time has elapsed, we're good
++* Let the test pass.
++*/
++TEST_F(GpuServiceTest, onInitializeShouldNotCauseUseAfterFree) {
++ sp service = new GpuService();
++ service.clear();
++ std::this_thread::sleep_for(std::chrono::seconds(3));
++
++ // If we haven't crashed yet due to threads accessing freed up memory, let the test pass
++ EXPECT_TRUE(true);
++}
++
++} // namespace
++} // namespace android
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/settings-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/settings-01.patch
new file mode 100644
index 00000000..1c6b0c7a
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/settings-01.patch
@@ -0,0 +1,67 @@
+From 87a06448b96e1ccd2403ae5c90b15efdd8585444 Mon Sep 17 00:00:00 2001
+From: Weng Su
+Date: Fri, 7 Jul 2023 19:52:04 +0800
+Subject: [PATCH] [RESTRICT AUTOMERGE] Restrict ApnEditor settings
+
+- Finish ApnEditor settings if user is not an admin
+
+- Finish ApnEditor settings if user has DISALLOW_CONFIG_MOBILE_NETWORKS restriction
+
+Bug: 279902472
+Test: manual test
+atest -c ApnEditorTest
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ef2fd5b7cd74426568e8e82fb0dcddbfdaa943bf)
+Merged-In: Iecdbbff7e21dfb11e3ba385858747a220cfd3e04
+Change-Id: Iecdbbff7e21dfb11e3ba385858747a220cfd3e04
+---
+ .../settings/network/apn/ApnEditor.java | 23 +++++++++++++++++++
+ 1 file changed, 23 insertions(+)
+
+diff --git a/src/com/android/settings/network/apn/ApnEditor.java b/src/com/android/settings/network/apn/ApnEditor.java
+index bfb49434378..afaf9bc3f8b 100644
+--- a/src/com/android/settings/network/apn/ApnEditor.java
++++ b/src/com/android/settings/network/apn/ApnEditor.java
+@@ -25,6 +25,7 @@
+ import android.net.Uri;
+ import android.os.Bundle;
+ import android.os.PersistableBundle;
++import android.os.UserManager;
+ import android.provider.Telephony;
+ import android.telephony.CarrierConfigManager;
+ import android.telephony.SubscriptionInfo;
+@@ -281,6 +282,11 @@ public class ApnEditor extends SettingsPreferenceFragment
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
++ if (isUserRestricted()) {
++ Log.e(TAG, "This setting isn't available due to user restriction.");
++ finish();
++ return;
++ }
+
+ setLifecycleForAllControllers();
+
+@@ -1453,6 +1459,23 @@ ApnData getApnDataFromUri(Uri uri) {
+ return apnData;
+ }
+
++ @VisibleForTesting
++ boolean isUserRestricted() {
++ UserManager userManager = getContext().getSystemService(UserManager.class);
++ if (userManager == null) {
++ return false;
++ }
++ if (!userManager.isAdminUser()) {
++ Log.e(TAG, "User is not an admin");
++ return true;
++ }
++ if (userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
++ Log.e(TAG, "User is not allowed to configure mobile network");
++ return true;
++ }
++ return false;
++ }
++
+ @VisibleForTesting
+ static class ApnData {
+ /**
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/settings-02.patch b/Patches/LineageOS-20.0/ASB-2023-10/settings-02.patch
new file mode 100644
index 00000000..19e7cc80
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/settings-02.patch
@@ -0,0 +1,65 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Eric Biggers
+Date: Thu, 27 Jul 2023 21:45:05 +0000
+Subject: [PATCH] RESTRICT AUTOMERGE: Catch exceptions from setLockCredential()
+
+When LockPatternUtils#setLockCredential() fails, it can either return
+false or throw an exception. Catch the exception and treat it the same
+way as a false return value, to prevent crashing com.android.settings.
+
+Bug: 253043065
+Test: Tried setting lockscreen credential while in secure FRP mode using
+ smartlock setup activity launched by intent via adb. Verified
+ that com.android.settings no longer crashes due to the exception
+ from LockPatternUtils#setLockCredential().
+(cherry picked from commit 05f1eff1c9c3f82797f1a0f92ff7665b9f463488)
+(moved change into ChooseLockPassword.java and ChooseLockPattern.java,
+ which are merged into SaveAndFinishWorker.java on udc-qpr-dev and main)
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e0b5a793a19198370d479401101cea97c2f1d835)
+Merged-In: I48b9119c19fb6378b1f88d36433ee4f4c8501d76
+Change-Id: I48b9119c19fb6378b1f88d36433ee4f4c8501d76
+---
+ .../android/settings/password/ChooseLockPassword.java | 9 +++++++--
+ src/com/android/settings/password/ChooseLockPattern.java | 9 +++++++--
+ 2 files changed, 14 insertions(+), 4 deletions(-)
+
+diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java
+index c4a3159e00..613388b21f 100644
+--- a/src/com/android/settings/password/ChooseLockPassword.java
++++ b/src/com/android/settings/password/ChooseLockPassword.java
+@@ -1048,8 +1048,13 @@ public class ChooseLockPassword extends SettingsActivity {
+
+ @Override
+ protected Pair saveAndVerifyInBackground() {
+- final boolean success = mUtils.setLockCredential(
+- mChosenPassword, mCurrentCredential, mUserId);
++ boolean success;
++ try {
++ success = mUtils.setLockCredential(mChosenPassword, mCurrentCredential, mUserId);
++ } catch (RuntimeException e) {
++ Log.e(TAG, "Failed to set lockscreen credential", e);
++ success = false;
++ }
+ if (success) {
+ unifyProfileCredentialIfRequested();
+ }
+diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java
+index e54568060a..964a268510 100644
+--- a/src/com/android/settings/password/ChooseLockPattern.java
++++ b/src/com/android/settings/password/ChooseLockPattern.java
+@@ -925,8 +925,13 @@ public class ChooseLockPattern extends SettingsActivity {
+ protected Pair saveAndVerifyInBackground() {
+ final int userId = mUserId;
+ mUtils.setLockPatternSize(mPatternSize, userId);
+- final boolean success = mUtils.setLockCredential(mChosenPattern, mCurrentCredential,
+- userId);
++ boolean success;
++ try {
++ success = mUtils.setLockCredential(mChosenPattern, mCurrentCredential, userId);
++ } catch (RuntimeException e) {
++ Log.e(TAG, "Failed to set lockscreen credential", e);
++ success = false;
++ }
+ if (success) {
+ unifyProfileCredentialIfRequested();
+ }
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/telecomm-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/telecomm-01.patch
new file mode 100644
index 00000000..3764d0d2
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/telecomm-01.patch
@@ -0,0 +1,48 @@
+From c873988898e1b520e0e4cfda77e26ec4377a4ce9 Mon Sep 17 00:00:00 2001
+From: Grace Jia
+Date: Thu, 20 Jul 2023 13:42:50 -0700
+Subject: [PATCH] Fix vulnerability in CallRedirectionService.
+
+Currently when the CallRedirectionService binding died, we didn't do
+anything, which cause malicious app start activities even not run in the
+background by implementing a CallRedirectionService and overriding the
+onPlaceCall method to schedule a activity start job in an independent
+process and then kill itself. In that way, the activity can still
+start after the CallRedirectionService died. Fix this by unbinding the
+service when the binding died.
+
+Bug: b/289809991
+Test: Using testapp provided in bug to make sure the test activity can't
+be started
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:29b52e3cd027da2d8644450a4dee3a7d95dc0043)
+Merged-In: I065d361b83700474a1efab2a75928427ee0a14ba
+Change-Id: I065d361b83700474a1efab2a75928427ee0a14ba
+---
+ .../callredirection/CallRedirectionProcessor.java | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+index 226382bde..02debcd6c 100644
+--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
++++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+@@ -175,6 +175,20 @@ public void onNullBinding(ComponentName componentName) {
+ Log.endSession();
+ }
+ }
++
++ @Override
++ public void onBindingDied(ComponentName componentName) {
++ // Make sure we unbind the service if binding died to avoid background stating
++ // activity leaks
++ Log.startSession("CRSC.oBD");
++ try {
++ synchronized (mTelecomLock) {
++ finishCallRedirection();
++ }
++ } finally {
++ Log.endSession();
++ }
++ }
+ }
+
+ private class CallRedirectionAdapter extends ICallRedirectionAdapter.Stub {
diff --git a/Patches/LineageOS-20.0/ASB-2023-10/wifi-01.patch b/Patches/LineageOS-20.0/ASB-2023-10/wifi-01.patch
new file mode 100644
index 00000000..959e4d0e
--- /dev/null
+++ b/Patches/LineageOS-20.0/ASB-2023-10/wifi-01.patch
@@ -0,0 +1,96 @@
+From 1a4b9ef510410a8d8c90e80352357f08c49f10c5 Mon Sep 17 00:00:00 2001
+From: Oscar Shu
+Date: Fri, 7 Jul 2023 02:21:41 +0000
+Subject: [PATCH] Update password check for WAPI
+
+Do not allow arbitrarily large passwords.
+
+Bug: 275339978
+
+Test: compile
+(cherry picked from commit 38707fb4ff1405663cc24affc95244f4cc830499)
+(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:36deae20de1a8905e6cc72764e449b2d6e469f9e)
+Merged-In: I15f3aff373af56c253a50c308d886a7acf661e59
+Change-Id: I15f3aff373af56c253a50c308d886a7acf661e59
+---
+ .../server/wifi/WifiConfigurationUtil.java | 22 +++++++++++++------
+ .../wifi/WifiConfigurationUtilTest.java | 3 ++-
+ 2 files changed, 17 insertions(+), 8 deletions(-)
+
+diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
+index 9e8b660374..40837ff703 100644
+--- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java
++++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
+@@ -467,7 +467,8 @@ private static boolean validateBssid(String bssid) {
+ return true;
+ }
+
+- private static boolean validatePassword(String password, boolean isAdd, boolean isSae) {
++ private static boolean validatePassword(String password, boolean isAdd, boolean isSae,
++ boolean isWapi) {
+ if (isAdd) {
+ if (password == null) {
+ Log.e(TAG, "validatePassword: null string");
+@@ -509,7 +510,14 @@ private static boolean validatePassword(String password, boolean isAdd, boolean
+ }
+ } else {
+ // HEX PSK string
+- if (password.length() != PSK_SAE_HEX_LEN) {
++ if (isWapi) {
++ // Protect system against malicious actors injecting arbitrarily large passwords.
++ if (password.length() > 100) {
++ Log.e(TAG, "validatePassword failed: WAPI hex string too long: "
++ + password.length());
++ return false;
++ }
++ } else if (password.length() != PSK_SAE_HEX_LEN) {
+ Log.e(TAG, "validatePassword failed: hex string size mismatch: "
+ + password.length());
+ return false;
+@@ -713,15 +721,15 @@ public static boolean validate(WifiConfiguration config, long supportedFeatureSe
+ return false;
+ }
+ if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)
+- && !validatePassword(config.preSharedKey, isAdd, false)) {
++ && !validatePassword(config.preSharedKey, isAdd, false, false)) {
+ return false;
+ }
+ if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
+- && !validatePassword(config.preSharedKey, isAdd, true)) {
++ && !validatePassword(config.preSharedKey, isAdd, true, false)) {
+ return false;
+ }
+ if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_PSK)
+- && !validatePassword(config.preSharedKey, isAdd, false)) {
++ && !validatePassword(config.preSharedKey, isAdd, false, true)) {
+ return false;
+ }
+ if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_DPP)
+@@ -880,11 +888,11 @@ public static boolean validateNetworkSpecifier(WifiNetworkSpecifier specifier) {
+ return false;
+ }
+ if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)
+- && !validatePassword(config.preSharedKey, true, false)) {
++ && !validatePassword(config.preSharedKey, true, false, false)) {
+ return false;
+ }
+ if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
+- && !validatePassword(config.preSharedKey, true, true)) {
++ && !validatePassword(config.preSharedKey, true, true, false)) {
+ return false;
+ }
+ // TBD: Validate some enterprise params as well in the future here.
+diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
+index 7cabcd873a..b505c0c0d3 100644
+--- a/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
++++ b/service/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
+@@ -466,7 +466,8 @@ public void testValidateNegativeCases_BadHexPskLengthWapi() {
+ assertTrue(WifiConfigurationUtil.validate(config, SUPPORTED_FEATURES_ALL,
+ WifiConfigurationUtil.VALIDATE_FOR_ADD));
+
+- config.preSharedKey = "abcd123456788990013453445345465465476546";
++ config.preSharedKey = "01234567890123456789012345678901234567890123456789012345678901234567"
++ + "890123456789012345678901234567890";
+ assertFalse(WifiConfigurationUtil.validate(config, SUPPORTED_FEATURES_ALL,
+ WifiConfigurationUtil.VALIDATE_FOR_ADD));
+ config.preSharedKey = "";
diff --git a/Scripts/LineageOS-20.0/Patch.sh b/Scripts/LineageOS-20.0/Patch.sh
index 88e32cce..c56fb24f 100644
--- a/Scripts/LineageOS-20.0/Patch.sh
+++ b/Scripts/LineageOS-20.0/Patch.sh
@@ -97,7 +97,7 @@ applyPatch "$DOS_PATCHES/android_build/0004-Selective_APEX.patch"; #Only enable
sed -i '75i$(my_res_package): PRIVATE_AAPT_FLAGS += --auto-add-overlay' core/aapt2.mk; #Enable auto-add-overlay for packages, this allows the vendor overlay to easily work across all branches.
sed -i 's/PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := 23/PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION := 28/' core/version_util.mk; #Set the minimum supported target SDK to Pie (GrapheneOS)
#sed -i 's/PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS := true/PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS := false/' core/product_config.mk; #broken by hardenDefconfig
-sed -i 's/2023-08-05/2023-09-01/' core/version_defaults.mk; #Bump Security String #XXX
+sed -i 's/2023-09-05/2023-10-01/' core/version_defaults.mk; #Bump Security String #XXX ASB-2023-10
fi;
if enterAndClear "build/soong"; then
@@ -127,7 +127,16 @@ if enterAndClear "external/libvpx"; then
applyPatch "$DOS_PATCHES_COMMON/android_external_libvpx/CVE-2023-5217.patch"; #VP8: disallow thread count changes
fi;
+if enterAndClear "external/libxml2"; then
+git am $DOS_PATCHES/ASB-2023-10/libxml-*.patch;
+fi;
+
+if enterAndClear "frameworks/av"; then
+git am $DOS_PATCHES/ASB-2023-10/av-*.patch;
+fi;
+
if enterAndClear "frameworks/base"; then
+git am $DOS_PATCHES/ASB-2023-10/base-*.patch;
git revert --no-edit d36faad3267522c6d3ff91ba9dcca8f6274bccd1; #Reverts "JobScheduler: Respect allow-in-power-save perm" in favor of below patch
git revert --no-edit 90d6826548189ca850d91692e71fcc1be426f453; #Reverts "Remove sensitive info from SUPL requests" in favor of below patch
applyPatch "$DOS_PATCHES/android_frameworks_base/0007-Always_Restict_Serial.patch"; #Always restrict access to Build.SERIAL (GrapheneOS)
@@ -218,6 +227,7 @@ applyPatch "$DOS_PATCHES/android_frameworks_libs_systemui/0001-Icon_Cache.patch"
fi;
if enterAndClear "frameworks/native"; then
+git am $DOS_PATCHES/ASB-2023-10/native-*.patch;
applyPatch "$DOS_PATCHES/android_frameworks_native/0001-Sensors_Permission.patch"; #Require OTHER_SENSORS permission for sensors (GrapheneOS)
applyPatch "$DOS_PATCHES/android_frameworks_native/0001-Sensors_Permission-a1.patch"; #Protect step sensors with OTHER_SENSORS permission for targetSdk<29 apps (GrapheneOS)
fi;
@@ -306,6 +316,7 @@ applyPatch "$DOS_PATCHES/android_packages_apps_OpenEUICC/0001-hacky-fix.patch";
fi;
if enterAndClear "packages/apps/Settings"; then
+git am $DOS_PATCHES/ASB-2023-10/settings-*.patch;
git revert --no-edit 41b4ed345a91da1dd46c00ee11a151c2b5ff4f43;
applyPatch "$DOS_PATCHES/android_packages_apps_Settings/0004-Private_DNS.patch"; #More 'Private DNS' options (heavily based off of a CalyxOS patch)
applyPatch "$DOS_PATCHES/android_packages_apps_Settings/0005-Automatic_Reboot.patch"; #Timeout for reboot (GrapheneOS)
@@ -334,6 +345,7 @@ git revert --no-edit fcf658d2005dc557a95d5a7fb89cb90d06b31d33; #grant permission
fi;
if enterAndClear "packages/apps/Trebuchet"; then
+git am $DOS_PATCHES/ASB-2023-10/launcher-*.patch;
cp $DOS_BUILD_BASE/vendor/divested/overlay/common/packages/apps/Trebuchet/res/xml/default_workspace_*.xml res/xml/; #XXX: Likely no longer needed
fi;
@@ -349,6 +361,10 @@ applyPatch "$DOS_PATCHES/android_packages_inputmethods_LatinIME/0001-Voice.patch
applyPatch "$DOS_PATCHES/android_packages_inputmethods_LatinIME/0002-Disable_Personalization.patch"; #Disable personalization dictionary by default (GrapheneOS)
fi;
+if enterAndClear "packages/modules/Bluetooth"; then
+git am $DOS_PATCHES/ASB-2023-10/bluetooth-*.patch;
+fi;
+
if enterAndClear "packages/modules/Connectivity"; then
applyPatch "$DOS_PATCHES/android_packages_modules_Connectivity/0001-Network_Permission-1.patch"; #Skip reportNetworkConnectivity() when permission is revoked (GrapheneOS)
applyPatch "$DOS_PATCHES/android_packages_modules_Connectivity/0001-Network_Permission-2.patch"; #Enforce INTERNET permission per-uid instead of per-appId (GrapheneOS)
@@ -375,6 +391,7 @@ applyPatch "$DOS_PATCHES/android_packages_modules_Permission/0006-Location_Indic
fi;
if enterAndClear "packages/modules/Wifi"; then
+git am $DOS_PATCHES/ASB-2023-10/wifi-*.patch;
applyPatch "$DOS_PATCHES/android_packages_modules_Wifi/344228.patch"; #wifi: resurrect mWifiLinkLayerStatsSupported counter (sassmann)
applyPatch "$DOS_PATCHES/android_packages_modules_Wifi/0001-Random_MAC.patch"; #Add support for always generating new random MAC (GrapheneOS)
fi;
@@ -383,10 +400,19 @@ if enterAndClear "packages/providers/DownloadProvider"; then
applyPatch "$DOS_PATCHES/android_packages_providers_DownloadProvider/0001-Network_Permission.patch"; #Expose the NETWORK permission (GrapheneOS)
fi;
+if enterAndClear "packages/providers/MediaProvider"; then
+git am $DOS_PATCHES/ASB-2023-10/mediaprovider-*.patch;
+fi;
+
+
#if enterAndClear "packages/providers/TelephonyProvider"; then
#cp $DOS_PATCHES_COMMON/android_packages_providers_TelephonyProvider/carrier_list.* assets/latest_carrier_id/;
#fi;
+if enterAndClear "packages/services/Telecomm"; then
+git am $DOS_PATCHES/ASB-2023-10/telecomm-*.patch;
+fi;
+
if enterAndClear "system/ca-certificates"; then
rm -rf files; #Remove old certs
cp -r "$DOS_PATCHES_COMMON/android_system_ca-certificates/files" .; #Copy the new ones into place