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