From 7d493872797aa892672c7de2a507bcf8817b5615 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 1 Apr 2026 18:15:10 -0400 Subject: [PATCH 1/7] Add ASB patches --- apply.sh | 52 +- ...ate_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch | 6109 +++++++++++++++++ ...he_size_of_images_to_be_d6df825fda3a.patch | 3965 +++++++++++ ...treated_as_untrusted_in_b51a58ecec96.patch | 36 + ...xpectedlly_turned_ON_af_cea235f00865.patch | 222 + ...ing_against_large_metad_2bca2265ff3e.patch | 503 ++ ...from_specifying_proxied_a4523e227733.patch | 313 + ..._permission_group_names_e770e9f02341.patch | 51 + ...OM_MANAGED_USER_OR_PROF_09055276288a.patch | 35 + ...fMemoryError_in_DeviceA_cee45869c491.patch | 41 + ...utMethodSubtypeSafeList_a438ce172b44.patch | 838 +++ ..._spoofing_via_FLAG_ACTI_c148b4fae634.patch | 149 + ...t_types_for_message_arr_014dea279c49.patch | 238 + ...s_NLSes_etc_that_can_be_e363f8210456.patch | 335 + ...sociations_after_reboot_924df83d73d9.patch | 42 + ...nstallation_with_shared_51368785bd09.patch | 101 + ...en_drawing_blur_regions_c81cf361489e.patch | 38 + ...r-free_in_eventCallback_c6da9eeb710c.patch | 82 + ...ling_package_in_multi-p_48af8a13dd12.patch | 71 + ...xpectedlly_turned_ON_af_7d8fbee887fc.patch | 600 ++ ...n_dcache_to_PoC_not_PoU_f0271f36388e.patch | 68 + ...N_bypass_via_SAF_picker_69a25763cdb4.patch | 65 + ...re_createRequest_for_no_119013a3d7e8.patch | 91 + 23 files changed, 14039 insertions(+), 6 deletions(-) create mode 100644 asb/2026-03/external/dng_sdk/2026-01-07_21-35_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch create mode 100644 asb/2026-03/frameworks/base/2025-10-10_14-16_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-37_Ensure_sandboxed_UIDs_are_treated_as_untrusted_in_b51a58ecec96.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-37_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-37_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-37_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-37_Trim_permission_permission_group_names_e770e9f02341.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-38_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-38_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-38_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-38_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-39_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-39_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-07_21-39_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch create mode 100644 asb/2026-03/frameworks/base/2026-01-15_06-49_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch create mode 100644 asb/2026-03/frameworks/native/2026-01-07_21-39_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch create mode 100644 asb/2026-03/hardware/st/nfc/2026-01-07_21-40_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch create mode 100644 asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch create mode 100644 asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch create mode 100644 asb/2026-03/packages/modules/Virtualization/2026-01-07_21-42_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch create mode 100644 asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch create mode 100644 asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch diff --git a/apply.sh b/apply.sh index 7c81ab4..e0cfd10 100755 --- a/apply.sh +++ b/apply.sh @@ -2,7 +2,7 @@ # Apply or reset patches # Usage: apply.sh [options] # Commands: -# apply Apply all patches from petergsi subdirectory +# apply Apply all patches from asb// and petergsi subdirectories # reset Reset all repos to their original state set -e @@ -12,7 +12,36 @@ pushd "$(dirname "$(realpath "$0")")" COMMAND="${1:-apply}" shift || true -apply_patches() { +apply_asb_patches() { + local asb_base="$(realpath asb 2>/dev/null || echo '')" + if [ -z "$asb_base" ] || [ ! -d "$asb_base" ]; then + return 0 + fi + + local patch_dirs + patch_dirs=$(find "$asb_base" -name "*.patch" -printf "%h\n" | sort -u) + + for patch_dir in $patch_dirs; do + local repo_path="${patch_dir#$asb_base/}" + repo_path="${repo_path#20*/}" + local target_dir="$(dirname "$asb_base")/../$repo_path" + + if [ ! -d "$target_dir" ]; then + echo "Warning: Repository $repo_path not found for ASB patches, skipping" + continue + fi + + cd "$target_dir" + + if ! git am "$patch_dir"/*.patch 2>/dev/null; then + echo "Failed to apply ASB patches to $repo_path" + echo "ASB patch application failed, aborting." + exit 1 + fi + done +} + +apply_petergsi_patches() { local patch_base="$(realpath petergsi)" local failed_file="/tmp/apply_failed_repos_$$.txt" @@ -23,9 +52,12 @@ apply_patches() { : > "$failed_file" - find "$patch_base" -name "*.patch" -printf "%h\n" | sort -u | while read -r patch_dir; do - repo_path="${patch_dir#$patch_base/}" - target_dir="$(dirname "$patch_base")/../$repo_path" + local patch_dirs + patch_dirs=$(find "$patch_base" -name "*.patch" -printf "%h\n" | sort -u) + + for patch_dir in $patch_dirs; do + local repo_path="${patch_dir#$patch_base/}" + local target_dir="$(dirname "$patch_base")/../$repo_path" if [ ! -d "$target_dir" ]; then echo "Warning: Repository $repo_path not found, skipping" @@ -34,6 +66,9 @@ apply_patches() { cd "$target_dir" + git tag -d "before-petergsi" 2>/dev/null || true + git tag "before-petergsi" 2>/dev/null || true + if ! git am "$patch_dir"/*.patch 2>/dev/null; then echo "$repo_path" >> "$failed_file" echo "Failed to apply patches to $repo_path" @@ -51,6 +86,11 @@ apply_patches() { rm -f "$failed_file" } +apply_patches() { + apply_asb_patches + apply_petergsi_patches +} + reset_one() { check_baseline() { current=$(git rev-parse HEAD) @@ -140,7 +180,7 @@ case "$COMMAND" in echo "Usage: $0 [options]" echo "" echo "Commands:" - echo " apply Apply all patches from petergsi subdirectory" + echo " apply Apply all patches from asb// and petergsi subdirectories" echo " reset Reset all repos to their original state" echo "" echo "Options for reset:" diff --git a/asb/2026-03/external/dng_sdk/2026-01-07_21-35_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch b/asb/2026-03/external/dng_sdk/2026-01-07_21-35_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch new file mode 100644 index 0000000..ac11b0e --- /dev/null +++ b/asb/2026-03/external/dng_sdk/2026-01-07_21-35_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch @@ -0,0 +1,6109 @@ +From 6b5cf2a88ebd2b099e56d0d4717e962772ff9067 Mon Sep 17 00:00:00 2001 +From: John Reck +Date: Tue, 9 Dec 2025 11:40:14 -0500 +Subject: [PATCH] Update to DNG SDK 1.7.1 2410 + +Bug: 465781139 +Test: make +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:403d0a45641560631b0d6a9653c94a22163e3008 +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:2101eead79db44325953c0350db9bf1c868a44f0 +Merged-In: Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8 +Change-Id: Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8 +--- + source/dng_abort_sniffer.cpp | 20 + + source/dng_abort_sniffer.h | 18 +- + source/dng_area_task.cpp | 1 + + source/dng_area_task.h | 1 + + source/dng_big_table.cpp | 95 ++- + source/dng_big_table.h | 36 +- + source/dng_bmff.cpp | 6 +- + source/dng_camera_profile.cpp | 35 +- + source/dng_classes.h | 9 + + source/dng_errors.h | 14 +- + source/dng_exceptions.cpp | 6 + + source/dng_exceptions.h | 88 +- + source/dng_exif.cpp | 4 +- + source/dng_file_stream.cpp | 8 +- + source/dng_fingerprint.cpp | 86 +- + source/dng_fingerprint.h | 128 ++- + source/dng_flags.h | 125 ++- + source/dng_gain_map.cpp | 54 +- + source/dng_gain_map.h | 2 +- + source/dng_host.cpp | 29 +- + source/dng_hue_sat_map.cpp | 35 +- + source/dng_ifd.cpp | 57 +- + source/dng_ifd.h | 4 + + source/dng_image_writer.cpp | 145 +++- + source/dng_image_writer.h | 22 +- + source/dng_info.cpp | 31 +- + source/dng_info.h | 2 + + source/dng_jpeg_image.cpp | 19 +- + source/dng_jxl.cpp | 1217 ++++++++++++++++++++------- + source/dng_jxl.h | 4 + + source/dng_linearization_info.cpp | 89 +- + source/dng_linearization_info.h | 5 + + source/dng_lossless_jpeg_shared.cpp | 4 +- + source/dng_matrix.cpp | 39 + + source/dng_matrix.h | 7 +- + source/dng_memory_stream.cpp | 12 +- + source/dng_misc_opcodes.cpp | 128 +-- + source/dng_negative.cpp | 96 ++- + source/dng_negative.h | 13 +- + source/dng_parse_utils.cpp | 3 +- + source/dng_pixel_buffer.cpp | 60 ++ + source/dng_preview.cpp | 74 +- + source/dng_preview.h | 21 + + source/dng_read_image.cpp | 23 +- + source/dng_render.cpp | 7 +- + source/dng_resample.cpp | 46 +- + source/dng_safe_arithmetic.h | 8 +- + source/dng_shared.cpp | 53 +- + source/dng_shared.h | 3 + + source/dng_stream.cpp | 189 ++++- + source/dng_stream.h | 84 +- + source/dng_string.cpp | 12 +- + source/dng_string.h | 17 + + source/dng_tag_codes.h | 3 + + source/dng_tag_values.h | 4 + + source/dng_update_meta.cpp | 8 +- + source/dng_validate.cpp | 7 +- + source/dng_xmp.cpp | 48 ++ + source/dng_xmp.h | 9 + + source/dng_xmp_sdk.cpp | 37 +- + source/dng_xmp_sdk.h | 5 + + 61 files changed, 2721 insertions(+), 694 deletions(-) + +diff --git a/source/dng_abort_sniffer.cpp b/source/dng_abort_sniffer.cpp +index 8590546..a7f1d32 100644 +--- a/source/dng_abort_sniffer.cpp ++++ b/source/dng_abort_sniffer.cpp +@@ -316,3 +316,23 @@ void dng_abort_sniffer::UpdateProgress (real64 /* fract */) + } + + /*****************************************************************************/ ++/*****************************************************************************/ ++/*****************************************************************************/ ++ ++dng_sniffer_task:: ~dng_sniffer_task () ++ { ++ ++ try ++ { ++ ++ if (fSniffer) ++ fSniffer->EndTask (); ++ ++ } ++ ++ catch (...) ++ { ++ ++ } ++ ++ } +diff --git a/source/dng_abort_sniffer.h b/source/dng_abort_sniffer.h +index 2a7bc47..497fb1e 100644 +--- a/source/dng_abort_sniffer.h ++++ b/source/dng_abort_sniffer.h +@@ -139,6 +139,16 @@ class dng_abort_sniffer + return 0.1; + } + ++ // Intended to be called as a hint that the upcoming task is about to ++ // take a long time and may block the thread, so if the goal is to ++ // provide some initial visual hint to the user (e.g., an ++ // indeterminate progress UX, etc.), now would be a good time to do ++ // so. The default implementation does nothing. ++ ++ virtual void SniffSlowHint () ++ { ++ } ++ + protected: + + /// Should be implemented by derived classes to check for an user +@@ -200,12 +210,8 @@ class dng_sniffer_task: private dng_uncopyable + fSniffer->StartTask (name, fract); + } + +- ~dng_sniffer_task () +- { +- if (fSniffer) +- fSniffer->EndTask (); +- } +- ++ virtual ~dng_sniffer_task (); ++ + /// Check for pending user cancellation or other abort. ThrowUserCanceled + /// will be called if one is pending. + +diff --git a/source/dng_area_task.cpp b/source/dng_area_task.cpp +index 7eac4a4..21a9401 100644 +--- a/source/dng_area_task.cpp ++++ b/source/dng_area_task.cpp +@@ -552,6 +552,7 @@ class dng_range_parallel_func_task: public dng_range_parallel_task + r.fBegin = startIndex; + r.fEnd = stopIndex; + r.fSniffer = sniffer; ++ r.fAllocator = &fHost.Allocator (); + + fFunc (r); + +diff --git a/source/dng_area_task.h b/source/dng_area_task.h +index e6d6db7..0bd24d4 100644 +--- a/source/dng_area_task.h ++++ b/source/dng_area_task.h +@@ -369,6 +369,7 @@ class dng_range_parallel_task: public dng_area_task, dng_uncopyable + int32 fBegin; + int32 fEnd; + dng_abort_sniffer *fSniffer; ++ dng_memory_allocator *fAllocator; + }; + + typedef std::function function_t; +diff --git a/source/dng_big_table.cpp b/source/dng_big_table.cpp +index d53595b..e8988a9 100644 +--- a/source/dng_big_table.cpp ++++ b/source/dng_big_table.cpp +@@ -943,9 +943,7 @@ const dng_fingerprint & dng_big_table::Fingerprint () const + dng_fingerprint dng_big_table::ComputeFingerprint () const + { + +- dng_md5_printer_stream stream; +- +- stream.SetLittleEndian (); ++ dng_md5_printer_le_stream stream; + + PutStream (stream, true); + +@@ -2695,6 +2693,7 @@ void dng_image_table_jxl_compression_info::Compress (dng_host &host, + false, // has transparency + true, // allow big tiff + nullptr, // gain map, ++ nullptr, // gain map metadata block + fPreferHalfFloat); + + #else +@@ -2866,15 +2865,15 @@ dng_fingerprint dng_image_table::ComputeFingerprint () const + + dng_memory_stream tempStream (host->Allocator ()); + ++ tempStream.SetLittleEndian (); ++ + PutStream (tempStream, true); + + tempStream.Flush (); + + tempStream.SetReadPosition (0); + +- dng_md5_printer_stream stream; +- +- stream.SetLittleEndian (); ++ dng_md5_printer_le_stream stream; + + tempStream.CopyToStream (stream, + tempStream.Length ()); +@@ -2892,9 +2891,7 @@ dng_fingerprint dng_image_table::ComputeFingerprint () const + + AutoPtr host (MakeHost (nullptr)); + +- dng_md5_printer_stream stream; +- +- stream.SetLittleEndian (); ++ dng_md5_printer_le_stream stream; + + stream.Put_uint32 (btt_ImageTable); + +@@ -2914,7 +2911,7 @@ dng_fingerprint dng_image_table::ComputeFingerprint () const + *fImage, + fImage->PixelType ()); + +- stream.Put (imageDigest.data, 16); ++ stream.Put (imageDigest); + + return stream.Result (); + +@@ -3150,6 +3147,8 @@ void dng_image_table::PutCompressedStream (dng_stream &stream, + + dng_memory_stream tempStream (host->Allocator ()); + ++ tempStream.SetLittleEndian (); ++ + info.Compress (*host, + tempStream, + *tiffImage); +@@ -3196,6 +3195,8 @@ void dng_image_table::CompressImage (const dng_image_table_compression_info &inf + + dng_memory_stream tempStream (host->Allocator ()); + ++ tempStream.SetLittleEndian (); ++ + tempStream.SetSniffer (sniffer); + + PutCompressedStream (tempStream, +@@ -3522,7 +3523,7 @@ bool dng_packed_image_table::GetStream (dng_stream &stream) + + // Read digest. + +- stream.Get (fTableDigest.data, 16); ++ stream.Get (fTableDigest); + + // Read size. + +@@ -3585,7 +3586,7 @@ void dng_packed_image_table::PutStream (dng_stream &stream, + + // Write digest. + +- stream.Put (fTableDigest.data, 16); ++ stream.Put (fTableDigest); + + // Write properties. + +@@ -4304,38 +4305,35 @@ void dng_masked_rgb_table::PutStream (dng_stream &stream) const + + /*****************************************************************************/ + +-void dng_masked_rgb_table::AddDigest (dng_md5_printer &printer) const ++void dng_masked_rgb_table::AddDigest (dng_md5_printer_stream &printer) const + { + + // Header. + +- printer.Process ("dng_masked_rgb_table", 20); ++ printer.ProcessPtr ("dng_masked_rgb_table", 20); + + // Name. + + uint32 nameLen = SemanticName ().Length (); + +- printer.Process (&nameLen, +- (uint32) sizeof (nameLen)); ++ printer.Put_uint32 (nameLen); + + if (nameLen > 0) + { + +- printer.Process (SemanticName ().Get (), nameLen); ++ printer.ProcessPtr (SemanticName ().Get (), nameLen); + + } + + // Pixel type. + +- printer.Process (&fPixelType, +- (uint32) sizeof (fPixelType)); ++ printer.Put_uint32 (fPixelType); + + // Table digest. + + dng_fingerprint tableDigest = fTable.Fingerprint (); + +- printer.Process (tableDigest.data, +- sizeof (tableDigest.data)); ++ printer.Process (tableDigest); + + } + +@@ -4359,6 +4357,8 @@ void dng_masked_rgb_table_render_data::Initialize (const dng_negative &negative, + + fUseSequentialMethod = tables.UseSequentialMethod (); + ++ fInputDigest.Clear (); // Null fingerprint for profile case ++ + // Find correspondence between RGBTables and SemanticMasks tags. It is + // still possible that we have a NOP situation if every table uses a mask + // name that is not found in SemanticMasks. +@@ -4815,19 +4815,18 @@ void dng_masked_rgb_tables::Validate () const + + /*****************************************************************************/ + +-void dng_masked_rgb_tables::AddDigest (dng_md5_printer &printer) const ++void dng_masked_rgb_tables::AddDigest (dng_md5_printer_stream &printer) const + { + + // Header. + +- printer.Process ("dng_masked_rgb_tables", 21); ++ printer.ProcessPtr ("dng_masked_rgb_tables", 21); + + // Number of tables. + +- uint32 numTables = (uint32) fTables.size (); ++ const uint32 numTables = (uint32) fTables.size (); + +- printer.Process (&numTables, +- (uint32) sizeof (numTables)); ++ printer.Put_uint32 (numTables); + + // Process each table. + +@@ -4838,8 +4837,7 @@ void dng_masked_rgb_tables::AddDigest (dng_md5_printer &printer) const + + // Composite method. + +- printer.Process (&fCompositeMethod, +- (uint32) sizeof (fCompositeMethod)); ++ printer.Put_uint32 (uint32 (fCompositeMethod)); + + } + +@@ -5280,15 +5278,14 @@ void dng_rgb_to_rgb_table_data::Process_32 (dng_pixel_buffer &buffer, + + /*****************************************************************************/ + +-void dng_rgb_to_rgb_table_data::AddDigest (dng_md5_printer &printer) const ++void dng_rgb_to_rgb_table_data::AddDigest (dng_md5_printer_stream &printer) const + { + + { + + const dng_fingerprint tableFingerPrint = fTable.Fingerprint (); + +- printer.Process (tableFingerPrint.data, +- dng_fingerprint::kDNGFingerprintSize); ++ printer.Process (tableFingerPrint); + + } + +@@ -5298,9 +5295,20 @@ void dng_rgb_to_rgb_table_data::AddDigest (dng_md5_printer &printer) const + for (uint32 i = 0; i < 3; i++) + { + ++ #if 1 ++ ++ static_assert (sizeof (fEncodeMatrix [i] [0]) == 8, "matrix coeffs not 8 bytes"); ++ ++ printer.Put_swap8 (fEncodeMatrix [i], uint32 (3 * sizeof (fEncodeMatrix [i] [0]))); ++ printer.Put_swap8 (fDecodeMatrix [i], uint32 (3 * sizeof (fEncodeMatrix [i] [0]))); ++ ++ #else ++ + printer.Process (fEncodeMatrix [i], 3 * sizeof (fEncodeMatrix [i] [0])); + printer.Process (fDecodeMatrix [i], 3 * sizeof (fEncodeMatrix [i] [0])); + ++ #endif ++ + } + + } +@@ -5308,12 +5316,26 @@ void dng_rgb_to_rgb_table_data::AddDigest (dng_md5_printer &printer) const + if (fEncodeTable.Get () && fDecodeTable.Get ()) + { + ++ #if 1 ++ ++ static_assert (sizeof (fEncodeTable->Table () [0]) == 4, "table data not 4 bytes"); ++ ++ printer.Put_swap4 (fEncodeTable->Table (), ++ uint32 ((2 + fEncodeTable->Count ()) * sizeof (fEncodeTable->Table () [0]))); ++ ++ printer.Put_swap4 (fDecodeTable->Table (), ++ uint32 ((2 + fEncodeTable->Count ()) * sizeof (fEncodeTable->Table () [0]))); ++ ++ #else ++ + printer.Process (fEncodeTable->Table (), + (2 + fEncodeTable->Count ()) * sizeof (fEncodeTable->Table () [0])); + + printer.Process (fDecodeTable->Table (), + (2 + fEncodeTable->Count ()) * sizeof (fEncodeTable->Table () [0])); + ++ #endif ++ + } + + if (fTable.Dimensions () != 3) +@@ -5322,9 +5344,20 @@ void dng_rgb_to_rgb_table_data::AddDigest (dng_md5_printer &printer) const + for (uint32 i = 0; i < 3; i++) + { + ++ static_assert (sizeof (fTable1D [i]->Table () [0]) == 4, "table data not 4 bytes"); ++ ++ #if 1 ++ ++ printer.Put_swap4 (fTable1D [i]->Table (), ++ uint32 ((2 + fTable1D [i]->Count ()) * sizeof (fTable1D [i]->Table () [0]))); ++ ++ #else ++ + printer.Process (fTable1D [i]->Table (), + (2 + fTable1D [i]->Count ()) * sizeof (fTable1D [i]->Table () [0])); + ++ #endif ++ + } + + } +diff --git a/source/dng_big_table.h b/source/dng_big_table.h +index 3b21626..a669fcb 100644 +--- a/source/dng_big_table.h ++++ b/source/dng_big_table.h +@@ -25,6 +25,12 @@ + + /*****************************************************************************/ + ++// TODO(erichan): rename this ++ ++#define qDebugMaskedRGBTableRender (qDNGValidate && 0) ++ ++/*****************************************************************************/ ++ + void dng_big_table_cache_flush (); + + void dng_big_table_cache_clear (); +@@ -109,8 +115,7 @@ class dng_big_table + + dng_memory_block * EncodeAsString (dng_memory_allocator &allocator) const; + +- bool ExtractFromCache (const dng_fingerprint &fingerprint); +- ++ virtual bool ExtractFromCache (const dng_fingerprint &fingerprint); + + #if qDNGUseXMP + +@@ -146,6 +151,14 @@ class dng_big_table + virtual dng_fingerprint ComputeFingerprint () const; + + void RecomputeFingerprint (); ++ ++ // Use this only in very specific cases (e.g., for custom cache ++ // extraction). ++ ++ void SetFingerprintDirect (const dng_fingerprint &digest) ++ { ++ fFingerprint = digest; ++ } + + virtual bool UseCompression () const + { +@@ -1170,6 +1183,11 @@ class dng_packed_image_table : public dng_big_table + + public: + ++ const dng_fingerprint & TableDigest () const ++ { ++ return fTableDigest; ++ } ++ + const dng_image_table & Table () const; + + const dng_image & Image () const +@@ -1349,7 +1367,7 @@ class dng_masked_rgb_table: private dng_uncopyable + + void PutStream (dng_stream &stream) const; + +- void AddDigest (dng_md5_printer &printer) const; ++ void AddDigest (dng_md5_printer_stream &printer) const; + + const dng_string & SemanticName () const + { +@@ -1424,7 +1442,7 @@ class dng_masked_rgb_tables: private dng_uncopyable + + void Validate () const; + +- void AddDigest (dng_md5_printer &printer) const; ++ void AddDigest (dng_md5_printer_stream &printer) const; + + void PutStream (dng_stream &stream) const; + +@@ -1491,7 +1509,7 @@ class dng_rgb_to_rgb_table_data + uint32 bufferStartPlane, + bool needOverrange); + +- void AddDigest (dng_md5_printer &printer) const; ++ void AddDigest (dng_md5_printer_stream &printer) const; + + }; + +@@ -1530,6 +1548,14 @@ class dng_masked_rgb_table_render_data + + uint32 fBackgroundTableIndex = 0; + ++ // Digest representing the inputs used to compute the masks and tables. ++ // In cases where the masks and tables are loaded from the negative, ++ // this fingerprint can be set to null. It is intended for cases where ++ // the tables and/or masks are computed dynamically based on other settings. ++ // This digest represents those other settings. ++ ++ dng_fingerprint fInputDigest; ++ + public: + + void Initialize (const dng_negative &negative, +diff --git a/source/dng_bmff.cpp b/source/dng_bmff.cpp +index 966a9af..584cc8f 100644 +--- a/source/dng_bmff.cpp ++++ b/source/dng_bmff.cpp +@@ -294,8 +294,7 @@ void dng_bmff_io::UpdateBigTables (dng_host &host, + + dng_fingerprint digest; + +- tableStream.Get (digest.data, +- uint32 (sizeof (digest.data))); ++ tableStream.Get (digest); + + if (digest.IsValid () && + (digests.find (digest) == digests.end ())) +@@ -378,8 +377,7 @@ void dng_bmff_io::UpdateBigTables (dng_host &host, + + // Write fingerprint. + +- memStream.Put (fingerprint.data, +- uint32 (sizeof (fingerprint.data))); ++ memStream.Put (fingerprint); + + // Write table data. + +diff --git a/source/dng_camera_profile.cpp b/source/dng_camera_profile.cpp +index 02f8405..d5dc962 100644 +--- a/source/dng_camera_profile.cpp ++++ b/source/dng_camera_profile.cpp +@@ -69,21 +69,20 @@ dng_string StripProfileGroupPrefix (const dng_string &name) + void dng_camera_profile_id::AddDigest (dng_md5_printer &printer) const + { + +- printer.Process ("DCPI", 4); ++ printer.ProcessPtr ("DCPI", 4); + + if (Name ().NotEmpty ()) + { + +- printer.Process (Name ().Get (), +- Name ().Length ()); ++ printer.ProcessPtr (Name ().Get (), ++ Name ().Length ()); + + } + + if (Fingerprint ().IsValid ()) + { + +- printer.Process (fFingerprint.data, +- uint32 (sizeof (fFingerprint.data))); ++ printer.Process (fFingerprint); + + } + +@@ -516,6 +515,8 @@ static void FingerprintMatrix (dng_md5_printer_stream &printer, + + // Tag's Put routine doesn't write the header, only the data + ++ // The Put routine handles endian byte swapping if needed. ++ + tag.Put (printer); + + } +@@ -563,12 +564,10 @@ dng_fingerprint dng_camera_profile::CalculateFingerprint (bool renderDataOnly) c + + DNG_ASSERT (!fWasStubbed, "CalculateFingerprint on stubbed profile"); + +- dng_md5_printer_stream printer; +- + // MD5 hash is always calculated on little endian data. + +- printer.SetLittleEndian (); +- ++ dng_md5_printer_le_stream printer; ++ + // The data that we fingerprint closely matches that saved + // by the profile_tag_set class in dng_image_writer.cpp, with + // the exception of the fingerprint itself. +@@ -784,8 +783,7 @@ dng_fingerprint dng_camera_profile::CalculateFingerprint (bool renderDataOnly) c + + dng_fingerprint digest = pgtm->GetFingerprint (); + +- printer.Put (digest.data, +- uint32 (sizeof (digest.data))); ++ printer.Put (digest); + + } + +@@ -803,8 +801,7 @@ dng_fingerprint dng_camera_profile::CalculateFingerprint (bool renderDataOnly) c + printer.Put ("hdr", 3); + + if (range.fHintMaxOutputValue != 1.0f) +- printer.Put (&range.fHintMaxOutputValue, +- uint32 (sizeof (range.fHintMaxOutputValue))); ++ printer.Put_real32 (range.fHintMaxOutputValue); + + } + +@@ -815,14 +812,13 @@ dng_fingerprint dng_camera_profile::CalculateFingerprint (bool renderDataOnly) c + if (HasMaskedRGBTables ()) + { + +- dng_md5_printer rgbTablesPrinter; ++ dng_md5_printer_le_stream rgbTablesPrinter; + + MaskedRGBTables ().AddDigest (rgbTablesPrinter); + + auto rgbTableDigest = rgbTablesPrinter.Result (); + +- printer.Put (rgbTableDigest.data, +- uint32 (sizeof (rgbTableDigest.data))); ++ printer.Put (rgbTableDigest); + + } + +@@ -835,18 +831,15 @@ dng_fingerprint dng_camera_profile::CalculateFingerprint (bool renderDataOnly) c + dng_fingerprint dng_camera_profile::UniqueID () const + { + +- dng_md5_printer_stream printer; +- + // MD5 hash is always calculated on little endian data. + +- printer.SetLittleEndian (); ++ dng_md5_printer_le_stream printer; + + // Start with the existing fingerprint. + + dng_fingerprint fingerprint = Fingerprint (); + +- printer.Put (fingerprint.data, +- (uint32) sizeof (fingerprint.data)); ++ printer.Put (fingerprint); + + // Also include the UniqueCameraModelRestriction tag. + +diff --git a/source/dng_classes.h b/source/dng_classes.h +index 84a29b5..c3fef23 100644 +--- a/source/dng_classes.h ++++ b/source/dng_classes.h +@@ -18,6 +18,7 @@ + /*****************************************************************************/ + + #include ++#include + + /*****************************************************************************/ + +@@ -75,6 +76,8 @@ class dng_matrix; + class dng_matrix_3by3; + class dng_matrix_4by3; + class dng_md5_printer; ++class dng_md5_printer_le_stream; ++class dng_md5_printer_stream; + class dng_memory_allocator; + class dng_memory_block; + class dng_memory_data; +@@ -137,8 +140,14 @@ typedef std::shared_ptr dng_masked_rgb_table_sptr; + typedef std::shared_ptr dng_rgb_to_rgb_table_data_sptr; + typedef std::shared_ptr dng_masked_rgb_table_render_data_sptr; + ++typedef std::shared_ptr dng_image_sptr; ++typedef std::weak_ptr dng_image_wptr; ++ + typedef std::shared_ptr const_dng_image_sptr; + ++typedef std::shared_ptr dng_pixel_buffer_sptr; ++typedef std::vector dng_pixel_buffer_vec; ++ + typedef std::shared_ptr const_dng_memory_block_sptr; + + /*****************************************************************************/ +diff --git a/source/dng_errors.h b/source/dng_errors.h +index 607c656..5d6fd8b 100644 +--- a/source/dng_errors.h ++++ b/source/dng_errors.h +@@ -31,8 +31,12 @@ typedef std::atomic dng_atomic_error_code; + + enum + { +- dng_error_none = 0, //!< No error. Success. +- dng_error_unknown = 100000, //!< Logic or program error or other unclassifiable error. ++ ++ dng_error_none = 0, //!< No error. Success. ++ ++ dng_error_sdk_first = 100000, ++ ++ dng_error_unknown = dng_error_sdk_first, //!< Logic or program error or other unclassifiable error. + dng_error_not_yet_implemented, //!< Functionality requested is not yet implemented. + dng_error_silent, //!< An error which should not be signalled to user. + dng_error_user_canceled, //!< Processing stopped by user (or host application) request +@@ -51,6 +55,12 @@ enum + dng_error_overflow, //!< Arithmetic overflow. + dng_error_jxl_encoder, //!< JPEG XL encoder error. + dng_error_jxl_decoder, //!< JPEG XL decoder error. ++ ++ // Count of number of error codes in the DNG SDK, including dng_error_none. ++ ++ dng_error_sdk_end, ++ dng_error_sdk_count = dng_error_sdk_end - dng_error_sdk_first + 1, ++ + }; + + /*****************************************************************************/ +diff --git a/source/dng_exceptions.cpp b/source/dng_exceptions.cpp +index 15276bd..9fdef87 100644 +--- a/source/dng_exceptions.cpp ++++ b/source/dng_exceptions.cpp +@@ -206,13 +206,19 @@ void Throw_dng_error (dng_error_code err, + + #else + ++ #if !qDNGVerboseExceptions + (void) message; + (void) sub_message; ++ #endif + (void) silent; + + #endif + ++ #if qDNGVerboseExceptions ++ throw dng_exception (err, message, sub_message); ++ #else + throw dng_exception (err); ++ #endif + + } + +diff --git a/source/dng_exceptions.h b/source/dng_exceptions.h +index dcf9b3e..68107b2 100644 +--- a/source/dng_exceptions.h ++++ b/source/dng_exceptions.h +@@ -20,13 +20,47 @@ + #include "dng_errors.h" + #include "dng_flags.h" + ++#include ++#include ++ + /*****************************************************************************/ + ++// DNG_NO_RETURN ++// DNG_NO_RETURN_ENABLED ++ ++#if defined(DNG_NO_RETURN) != defined(DNG_NO_RETURN_ENABLED) ++#error DNG_NO_RETURN and DNG_NO_RETURN_ENABLED must both be defined or undefined here. ++#endif ++ ++// Use DNG_NO_RETURN in header file declaration of a function that ++// does not return. ++// ++// It should be placed ++// * before the return type of the function, AND ++// * before any 'inline' keyword, AND ++// * before any 'static' keyword. ++// as the gcc version supports this and the C++11 version requires it. ++ ++// If DNG_NO_RETURN not defined, attempt to define using C++11 attribute. ++ ++#ifndef DNG_NO_RETURN ++#if defined(__cplusplus) ++#if __cplusplus >= 201103L ++#define DNG_NO_RETURN [[noreturn]] ++#define DNG_NO_RETURN_ENABLED 1 ++#endif ++#endif ++#endif ++ ++// If DNG_NO_RETURN still not defined, use GCC version or define to nothing. ++ + #ifndef DNG_NO_RETURN + #ifdef __GNUC__ + #define DNG_NO_RETURN __attribute__((noreturn)) ++#define DNG_NO_RETURN_ENABLED 1 + #else + #define DNG_NO_RETURN ++#define DNG_NO_RETURN_ENABLED 0 + #endif + #endif + +@@ -48,12 +82,14 @@ void ReportError (const char *message, + + /// \brief All exceptions thrown by the DNG SDK use this exception class. + +-class dng_exception ++class dng_exception : public std::exception + { + + private: + + dng_error_code fErrorCode; ++ ++ std::string fErrorMsg; + + public: + +@@ -63,10 +99,43 @@ class dng_exception + dng_exception (dng_error_code code) + + : fErrorCode (code) ++ + + { ++ fErrorMsg = "dng_error: "; ++ fErrorMsg += std::to_string (fErrorCode); + } + ++ #if qDNGVerboseExceptions ++ ++ dng_exception (dng_error_code code, ++ const char *message, ++ const char *sub_message) ++ ++ : fErrorCode (code) ++ ++ { ++ ++ fErrorMsg = "dng_error: "; ++ fErrorMsg += std::to_string (fErrorCode); ++ ++ if (message) ++ { ++ fErrorMsg += ": "; ++ fErrorMsg += message; ++ } ++ ++ if (sub_message) ++ { ++ fErrorMsg += " ("; ++ fErrorMsg += sub_message; ++ fErrorMsg += ")"; ++ } ++ ++ } ++ ++ #endif // qDNGVerboseExceptions ++ + virtual ~dng_exception () + { + } +@@ -79,16 +148,21 @@ class dng_exception + return fErrorCode; + } + ++ virtual char const * what () const noexcept override ++ { ++ return fErrorMsg.c_str (); ++ } ++ + }; + + /******************************************************************************/ + + /// \brief Throw an exception based on an arbitrary error code. + +-void Throw_dng_error (dng_error_code err, +- const char * message = NULL, +- const char * sub_message = NULL, +- bool silent = false) DNG_NO_RETURN; ++DNG_NO_RETURN void Throw_dng_error (dng_error_code err, ++ const char * message = NULL, ++ const char * sub_message = NULL, ++ bool silent = false); + + /******************************************************************************/ + +@@ -148,10 +222,10 @@ inline void ThrowNotYetImplemented (const char * sub_message = NULL) + /// \brief Convenience function to throw dng_exception with error code + /// dng_error_silent . + +-inline void ThrowSilentError () ++inline void ThrowSilentError (const char *sub_message = NULL) + { + +- Throw_dng_error (dng_error_silent); ++ Throw_dng_error (dng_error_silent, NULL, sub_message); + + } + +diff --git a/source/dng_exif.cpp b/source/dng_exif.cpp +index cdee762..36ffeed 100644 +--- a/source/dng_exif.cpp ++++ b/source/dng_exif.cpp +@@ -3674,8 +3674,8 @@ bool dng_exif::Parse_ifd0_exif (dng_stream &stream, + else + return false; + +- f.data [j >> 1] *= 16; +- f.data [j >> 1] += (uint8) digit; ++ f.MutableData () [j >> 1] *= 16; ++ f.MutableData () [j >> 1] += (uint8) digit; + + } + +diff --git a/source/dng_file_stream.cpp b/source/dng_file_stream.cpp +index 39242a0..8fb984b 100644 +--- a/source/dng_file_stream.cpp ++++ b/source/dng_file_stream.cpp +@@ -120,11 +120,9 @@ dng_file_stream::dng_file_stream (int fd, + bool output, + uint32 bufferSize) + +- : dng_stream ((dng_abort_sniffer *) NULL, +- bufferSize, +- 0) +- +- , fFile (NULL) ++ : dng_file_stream (fd, ++ output ? "wb" : "rb", ++ bufferSize) + + { + +diff --git a/source/dng_fingerprint.cpp b/source/dng_fingerprint.cpp +index 4dcd1f5..6113ca7 100644 +--- a/source/dng_fingerprint.cpp ++++ b/source/dng_fingerprint.cpp +@@ -10,6 +10,9 @@ + + #include "dng_assertions.h" + #include "dng_flags.h" ++#include "dng_types.h" ++ ++#include + + /*****************************************************************************/ + +@@ -41,6 +44,45 @@ dng_fingerprint::dng_fingerprint (const char *hex) + + /*****************************************************************************/ + ++dng_fingerprint::dng_fingerprint (const dng_fingerprint& print) ++ ++ { ++ ++ if (this != &print) ++ { ++ ++ for (uint32 j = 0; j < kDNGFingerprintSize; j++) ++ { ++ ++ data [j] = print.data [j]; ++ ++ } ++ } ++ ++ } ++ ++/*****************************************************************************/ ++ ++dng_fingerprint& dng_fingerprint::operator= (const dng_fingerprint& print) ++ { ++ ++ if (this != &print) ++ { ++ ++ for (uint32 j = 0; j < kDNGFingerprintSize; j++) ++ { ++ ++ data [j] = print.data [j]; ++ ++ } ++ } ++ ++ return *this; ++ ++ } ++ ++/*****************************************************************************/ ++ + bool dng_fingerprint::IsNull () const + { + +@@ -309,8 +351,8 @@ void dng_md5_printer::Reset () + + /******************************************************************************/ + +-void dng_md5_printer::Process (const void *data, +- uint32 inputLen) ++void dng_md5_printer::ProcessPtr (const void *data, ++ uint32 inputLen) + { + + DNG_ASSERT (!final, "Fingerprint already finalized!"); +@@ -363,6 +405,42 @@ void dng_md5_printer::Process (const void *data, + inputLen - i); + + } ++ ++/*****************************************************************************/ ++ ++void dng_md5_printer::Process_bool (bool x) ++ { ++ ++ // sizeof(bool) is implemention-defined, which makes it non-portable. ++ ++ // std::uint8_t exists in standard C++ since C++11 and is guaranteed to be ++ // exactly 8 bits (1 byte) by the standard. ++ ++ std::uint8_t value = x ? 1 : 0; ++ ++ static_assert (sizeof (value) == 1, "uint8_t not 1 byte?"); ++ ++ ProcessPtr (&value, 1); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_md5_printer::Process_size (size_t x) ++ { ++ ++ // sizeof(size_t) is implemention-defined, which makes it non-portable. ++ ++ // std::uint64_t exists in standard C++ since C++11 and is guaranteed to ++ // be exactly 64 bits (8 bytes) by the standard. ++ ++ std::uint64_t value = (std::uint64_t) x; ++ ++ static_assert (sizeof (value) == 8, "uint64_t not 8 bytes?"); ++ ++ ProcessPtr (&value, 8); ++ ++ } + + /******************************************************************************/ + +@@ -391,11 +469,11 @@ const dng_fingerprint & dng_md5_printer::Result () + + uint32 padLen = (index < 56) ? (56 - index) : (120 - index); + +- Process (PADDING, padLen); ++ ProcessPtr (PADDING, padLen); + + // Append length (before padding) + +- Process (bits, 8); ++ ProcessPtr (bits, 8); + + // Store state in digest + +diff --git a/source/dng_fingerprint.h b/source/dng_fingerprint.h +index c19fb25..3d8fb75 100644 +--- a/source/dng_fingerprint.h ++++ b/source/dng_fingerprint.h +@@ -33,11 +33,16 @@ + + class dng_fingerprint + { ++ ++ friend struct dng_fingerprint_less_than; ++ friend class dng_md5_printer; + + public: + + static const size_t kDNGFingerprintSize = 16; + ++ private: ++ + uint8 data [kDNGFingerprintSize]; + + public: +@@ -46,6 +51,10 @@ class dng_fingerprint + + explicit dng_fingerprint (const char *hex); + ++ dng_fingerprint (const dng_fingerprint& print); ++ ++ dng_fingerprint& operator= (const dng_fingerprint& print); ++ + /// Check if fingerprint is all zeros. + + bool IsNull () const; +@@ -64,6 +73,16 @@ class dng_fingerprint + *this = dng_fingerprint (); + } + ++ const uint8 * Data () const ++ { ++ return data; ++ } ++ ++ uint8 * MutableData () ++ { ++ return data; ++ } ++ + /// Test if two fingerprints are equal. + + bool operator== (const dng_fingerprint &print) const; +@@ -196,10 +215,12 @@ typedef std::vector dng_fingerprint_vector; + class dng_md5_printer + { + +- public: ++ protected: + + dng_md5_printer (); + ++ public: ++ + virtual ~dng_md5_printer () + { + } +@@ -212,22 +233,48 @@ class dng_md5_printer + /// \param data The data to be hashed. + /// \param inputLen The length of data, in bytes. + +- void Process (const void *data, +- uint32 inputLen); ++ virtual void ProcessPtr (const void *data, ++ uint32 inputLen); + + /// Append the string to the stream to be hashed. + /// \param text The string to be hashed. + +- void Process (const char *text) ++ void ProcessString (const char *text) + { + +- Process (text, (uint32) strlen (text)); ++ ProcessPtr (text, (uint32) strlen (text)); + + } ++ ++ void Process (const dng_fingerprint &digest) ++ { ++ ++ ProcessPtr (digest.data, ++ uint32 (sizeof (digest.data))); ++ ++ } ++ ++ void Process (const dng_string &str) ++ { ++ ++ ProcessPtr (str.Get (), ++ str.Length ()); ++ ++ } ++ ++ /// Process the Boolean value x. ++ /// \param x The value to be hashed. ++ ++ void Process_bool (bool x); ++ ++ /// Process the size_t value x. ++ /// \param x The value to be hashed. ++ ++ void Process_size (size_t x); + + /// Get the fingerprint (i.e., result of the hash). + +- const dng_fingerprint & Result (); ++ virtual const dng_fingerprint & Result (); + + private: + +@@ -348,7 +395,7 @@ class dng_md5_printer + + /// \brief A dng_stream based interface to the MD5 printing logic. + +-class dng_md5_printer_stream : public dng_stream, dng_md5_printer ++class dng_md5_printer_stream : public dng_stream, public dng_md5_printer + { + + private: +@@ -366,23 +413,37 @@ class dng_md5_printer_stream : public dng_stream, dng_md5_printer + { + } + +- virtual uint64 DoGetLength () ++ void ProcessPtr (const void *data, ++ uint32 inputLen) override ++ { ++ ++ // Force data to be flushed, which ensures that interleaved ++ // Process* and Put* calls get serialized correctly. ++ ++ Flush (); ++ ++ dng_md5_printer::ProcessPtr (data, ++ inputLen); ++ ++ } ++ ++ uint64 DoGetLength () override + { + + return fNextOffset; + + } + +- virtual void DoRead (void * /* data */, +- uint32 /* count */, +- uint64 /* offset */) ++ void DoRead (void * /* data */, ++ uint32 /* count */, ++ uint64 /* offset */) override + { + + ThrowProgramError (); + + } + +- virtual void DoSetLength (uint64 length) ++ void DoSetLength (uint64 length) override + { + + if (length != fNextOffset) +@@ -392,9 +453,9 @@ class dng_md5_printer_stream : public dng_stream, dng_md5_printer + + } + +- virtual void DoWrite (const void *data, +- uint32 count2, +- uint64 offset) ++ void DoWrite (const void *data, ++ uint32 count2, ++ uint64 offset) override + { + + if (offset != fNextOffset) +@@ -402,13 +463,13 @@ class dng_md5_printer_stream : public dng_stream, dng_md5_printer + ThrowProgramError (); + } + +- Process (data, count2); ++ dng_md5_printer::ProcessPtr (data, count2); + + fNextOffset += count2; + + } + +- const dng_fingerprint & Result () ++ const dng_fingerprint & Result () override + { + + Flush (); +@@ -421,6 +482,37 @@ class dng_md5_printer_stream : public dng_stream, dng_md5_printer + + /*****************************************************************************/ + +-#endif ++/// \brief A version of dng_md5_printer_stream that is always little-endian. ++ ++class dng_md5_printer_le_stream : public dng_md5_printer_stream ++ { ++ ++ public: ++ ++ dng_md5_printer_le_stream () ++ { ++ SetLittleEndian (); ++ } ++ ++ }; ++ ++/*****************************************************************************/ ++ ++// A MD5 printer intended to be used directly with no portability requirements. ++ ++class dng_md5_direct_printer: public dng_md5_printer ++ { ++ ++ public: ++ ++ dng_md5_direct_printer () ++ { ++ } ++ ++ }; ++ ++/*****************************************************************************/ ++ ++#endif // __dng_fingerprint__ + + /*****************************************************************************/ +diff --git a/source/dng_flags.h b/source/dng_flags.h +index f625725..bc764e2 100644 +--- a/source/dng_flags.h ++++ b/source/dng_flags.h +@@ -19,6 +19,21 @@ + + /*****************************************************************************/ + ++// Update these for each public DNG SDK release. ++ ++#define kDNGSDK_MajorVersion 1 ++#define kDNGSDK_MinorVersion 7 ++#define kDNGSDK_DotVersion 1 ++ ++#define kDNGSDK_VersionString "1.7.1" ++ ++#define kDNGSDK_BuildVersion 2410 ++#define kDNGSDK_BuildString "2410" ++ ++#define kDNGSDK_GetInfoVersion "1.7.1 (" kDNGSDK_BuildString ")" ++ ++/*****************************************************************************/ ++ + /// \def qMacOS + /// 1 if compiling for Mac OS X. + +@@ -205,7 +220,7 @@ + #ifndef qDNGBigEndian + + #if defined(qDNGLittleEndian) +-#define qDNGBigEndian !qDNGLittleEndian ++#define qDNGBigEndian (!qDNGLittleEndian) + + #elif defined(__POWERPC__) + #define qDNGBigEndian 1 +@@ -244,7 +259,7 @@ + #ifndef qXCodeRez + + #ifndef qDNGLittleEndian +-#define qDNGLittleEndian !qDNGBigEndian ++#define qDNGLittleEndian (!qDNGBigEndian) + #endif + + #endif +@@ -316,7 +331,7 @@ + + #ifdef __cplusplus + #if defined(__clang__) && !defined(__INTEL_LLVM_COMPILER) +-#define DNG_ALWAYS_INLINE __attribute((__always_inline__)) inline ++#define DNG_ALWAYS_INLINE __attribute__((__always_inline__)) inline + #else + #define DNG_ALWAYS_INLINE inline + #endif +@@ -324,6 +339,32 @@ + + /*****************************************************************************/ + ++// Switch statement [[fallthrough]]; attribute defined for C++17 and C23. ++// ++// This can be used to indicate an intentional "fall through". ++// For example, if one is seeing the clang diagnostic warning or error: ++// Unannotated fall-through between switch labels ++// then if "fall through" is desired, this macro can be placed at the ++// point of "fall through". ++ ++#if defined(__cplusplus) ++#if __cplusplus >= 201703L ++#define DNG_FALLTHROUGH [[fallthrough]]; ++#else ++#define DNG_FALLTHROUGH ++#endif ++#elif defined(__STDC_VERSION__) ++#if __STDC_VERSION__ >= 202311L ++#define DNG_FALLTHROUGH [[fallthrough]]; ++#else ++#define DNG_FALLTHROUGH ++#endif ++#else ++#define DNG_FALLTHROUGH ++#endif ++ ++/*****************************************************************************/ ++ + /// \def qDNGThreadSafe + /// 1 if target platform has thread support and threadsafe libraries, 0 otherwise. + +@@ -413,22 +454,90 @@ + + /*****************************************************************************/ + ++/// \def qDNGUsingAddressSanitizer ++/// Set to 1 when using the Address Sanitizer tool. ++ ++#ifndef qDNGUsingAddressSanitizer ++#if defined(__clang__) && defined(__has_feature) ++#if __has_feature(address_sanitizer) ++#define qDNGUsingAddressSanitizer (1) ++#endif ++#endif ++#endif ++ ++#ifndef qDNGUsingAddressSanitizer ++#if defined(__SANITIZE_ADDRESS__) ++#define qDNGUsingAddressSanitizer (1) ++#endif ++#endif ++ ++#ifndef qDNGUsingAddressSanitizer ++#define qDNGUsingAddressSanitizer (0) ++#endif ++ ++/*****************************************************************************/ ++ ++/// \def qDNGUsingThreadSanitizer ++/// Set to 1 when using the Thread Sanitizer tool. ++ ++#ifndef qDNGUsingThreadSanitizer ++#if defined(__clang__) && defined(__has_feature) ++#if __has_feature(thread_sanitizer) ++#define qDNGUsingThreadSanitizer (1) ++#endif ++#endif ++#endif ++ ++#ifndef qDNGUsingThreadSanitizer ++#if defined(__SANITIZE_THREAD__) ++#define qDNGUsingThreadSanitizer (1) ++#endif ++#endif ++ ++#ifndef qDNGUsingThreadSanitizer ++#define qDNGUsingThreadSanitizer (0) ++#endif ++ ++/*****************************************************************************/ ++ ++/// \def qDNGUsingUndefinedBehaviorSanitizer ++/// Set to 1 when using the UB Sanitizer tool. ++ ++#ifndef qDNGUsingUndefinedBehaviorSanitizer ++#if defined(__clang__) && defined(__has_feature) ++#if __has_feature(undefined_behavior_sanitizer) ++#define qDNGUsingUndefinedBehaviorSanitizer (1) ++#endif ++#endif ++#endif ++ ++// Currently no GCC macro available to check if UBSAN enabled. ++ ++#ifndef qDNGUsingUndefinedBehaviorSanitizer ++#define qDNGUsingUndefinedBehaviorSanitizer (0) ++#endif ++ ++/*****************************************************************************/ ++ + /// \def qDNGUsingSanitizer + /// Set to 1 when using a Sanitizer tool. + + #ifndef qDNGUsingSanitizer +-#define qDNGUsingSanitizer (0) ++#define qDNGUsingSanitizer ((qDNGUsingAddressSanitizer || qDNGUsingThreadSanitizer || qDNGUsingUndefinedBehaviorSanitizer) || 0) + #endif + + /*****************************************************************************/ + + #ifndef DNG_ATTRIB_NO_SANITIZE ++// Disabled if RC_INVOKED is defined to quiet RC.EXE RC4011 warning. ++#ifndef RC_INVOKED + #if qDNGUsingSanitizer && defined(__clang__) + #define DNG_ATTRIB_NO_SANITIZE(type) __attribute__((no_sanitize(type))) + #else + #define DNG_ATTRIB_NO_SANITIZE(type) + #endif + #endif ++#endif + + /*****************************************************************************/ + +@@ -464,6 +573,14 @@ + + /*****************************************************************************/ + ++// Enable verbose exceptions ++ ++#ifndef qDNGVerboseExceptions ++#define qDNGVerboseExceptions 1 ++#endif ++ ++/*****************************************************************************/ ++ + // Place deprecated flags into this file. + + #include "dng_deprecated_flags.h" +diff --git a/source/dng_gain_map.cpp b/source/dng_gain_map.cpp +index 3cc623a..6295584 100644 +--- a/source/dng_gain_map.cpp ++++ b/source/dng_gain_map.cpp +@@ -588,18 +588,18 @@ uint32 dng_gain_table_map::PutStreamSize () const + + /*****************************************************************************/ + +-void dng_gain_table_map::AddDigest (dng_md5_printer &printer) const ++void dng_gain_table_map::AddDigest (dng_md5_printer_stream &printer) const + { + + if (SupportsVersion1 ()) +- printer.Process ("ProfileGainTableMap", 19); ++ printer.ProcessPtr ("ProfileGainTableMap", 19); + + else +- printer.Process ("ProfileGainTableMap2", 20); ++ printer.ProcessPtr ("ProfileGainTableMap2", 20); + + EnsureFingerprint (); + +- printer.Process (fFingerprint.data, dng_fingerprint::kDNGFingerprintSize); ++ printer.Process (fFingerprint); + + } + +@@ -611,7 +611,7 @@ void dng_gain_table_map::EnsureFingerprint () const + if (fFingerprint.IsNull ()) + { + +- dng_md5_printer_stream stream; ++ dng_md5_printer_le_stream stream; + + PutStream (stream); + +@@ -1306,7 +1306,7 @@ void dng_opcode_GainMap::ProcessArea (dng_negative &negative, + const dng_rect &imageBounds) + { + +- dng_rect overlap = fAreaSpec.ScaledOverlap (dstArea); ++ const dng_rect overlap = fAreaSpec.ScaledOverlap (dstArea); + + if (overlap.NotEmpty ()) + { +@@ -1328,12 +1328,12 @@ void dng_opcode_GainMap::ProcessArea (dng_negative &negative, + + } + +- uint32 cols = overlap.W (); +- +- uint32 colPitch = fAreaSpec.ColPitch (); +- +- colPitch = Min_uint32 (colPitch, cols); +- ++ const uint32 rowPitch = Min_uint32 (fAreaSpec.RowPitch (), overlap.H ()); ++ const uint32 colPitch = Min_uint32 (fAreaSpec.ColPitch (), overlap.W ()); ++ ++ const uint32 rows = (overlap.H () + rowPitch - 1) / rowPitch; ++ const uint32 cols = (overlap.W () + colPitch - 1) / colPitch; ++ + for (uint32 plane = fAreaSpec.Plane (); + plane < fAreaSpec.Plane () + fAreaSpec.Planes () && + plane < buffer.Planes (); +@@ -1341,8 +1341,10 @@ void dng_opcode_GainMap::ProcessArea (dng_negative &negative, + { + + uint32 mapPlane = Min_uint32 (plane, fGainMap->Planes () - 1); +- +- for (int32 row = overlap.t; row < overlap.b; row += fAreaSpec.RowPitch ()) ++ ++ int32 row = overlap.t; ++ ++ for (uint32 rowIdx = 0; rowIdx < rows; rowIdx++) + { + + real32 *dPtr = buffer.DirtyPixel_real32 (row, overlap.l, plane); +@@ -1355,17 +1357,19 @@ void dng_opcode_GainMap::ProcessArea (dng_negative &negative, + + if (blackLevel != 0) + { +- +- for (uint32 col = 0; col < cols; col += colPitch) ++ ++ for (uint32 colIdx = 0, col = 0; colIdx < cols; colIdx++) + { + + dPtr [col] = dPtr [col] * blackScale1 + blackOffset1; + ++ col += colPitch; ++ + } + + } + +- for (uint32 col = 0; col < cols; col += colPitch) ++ for (uint32 colIdx = 0, col = 0; colIdx < cols; colIdx++) + { + + real32 gain = interp.Interpolate (); +@@ -1377,25 +1381,31 @@ void dng_opcode_GainMap::ProcessArea (dng_negative &negative, + interp.Increment (); + } + ++ col += colPitch; ++ + } + + if (blackLevel != 0) + { + +- for (uint32 col = 0; col < cols; col += colPitch) ++ for (uint32 colIdx = 0, col = 0; colIdx < cols; colIdx++) + { + + dPtr [col] = dPtr [col] * blackScale2 + blackOffset2; + ++ col += colPitch; ++ + } + + } +- +- } ++ ++ row += rowPitch; ++ ++ } // rows + +- } ++ } // planes + +- } ++ } // overlap not empty + + } + +diff --git a/source/dng_gain_map.h b/source/dng_gain_map.h +index a5a8f48..6a69a34 100644 +--- a/source/dng_gain_map.h ++++ b/source/dng_gain_map.h +@@ -335,7 +335,7 @@ class dng_gain_table_map: private dng_uncopyable + + /// Add the gain table map to the given digest printer. + +- void AddDigest (dng_md5_printer &printer) const; ++ void AddDigest (dng_md5_printer_stream &printer) const; + + /// Fingerprint for the gain table map. Computed lazily. + +diff --git a/source/dng_host.cpp b/source/dng_host.cpp +index e48a393..dba0389 100644 +--- a/source/dng_host.cpp ++++ b/source/dng_host.cpp +@@ -655,9 +655,26 @@ dng_jxl_encode_settings * + case use_case_LosslessEnhancedImage: + case use_case_LosslessGainMap: + { +- settings->SetDistance (0.0f); +- settings->SetUseOriginalColorEncoding (true); ++ ++ // If we have special settings attached to the host, just use them. ++ ++ if (JXLEncodeSettings ()) ++ { ++ ++ *settings = *JXLEncodeSettings (); ++ ++ } ++ ++ else ++ { ++ ++ settings->SetDistance (0.0f); ++ settings->SetUseOriginalColorEncoding (true); ++ ++ } ++ + break; ++ + } + + case use_case_MainImage: +@@ -733,7 +750,9 @@ dng_jxl_encode_settings * + } + + // Fall through +- ++ ++ DNG_FALLTHROUGH ++ + } + + case use_case_LosslessTransparency: +@@ -760,7 +779,9 @@ dng_jxl_encode_settings * + } + + // Fall through +- ++ ++ DNG_FALLTHROUGH ++ + } + + case use_case_LosslessDepth: +diff --git a/source/dng_hue_sat_map.cpp b/source/dng_hue_sat_map.cpp +index bb58d54..54bee53 100644 +--- a/source/dng_hue_sat_map.cpp ++++ b/source/dng_hue_sat_map.cpp +@@ -249,8 +249,8 @@ void dng_hue_sat_map::AssignNewUniqueRuntimeFingerprint () + + const uint64 uid = ++sRuntimeFingerprintCounter; + +- dng_md5_printer printer; +- printer.Process (&uid, sizeof (uid)); ++ dng_md5_printer_stream printer; ++ printer.Put_uint64 (uid); + fRuntimeFingerprint = printer.Result (); + + } +@@ -380,17 +380,15 @@ dng_hue_sat_map * dng_hue_sat_map::Interpolate (const dng_hue_sat_map &map1, + + { + +- dng_md5_printer printer; ++ dng_md5_printer_le_stream printer; + +- printer.Process ("Interpolate", 11); ++ printer.ProcessPtr ("Interpolate", 11); + +- printer.Process (&weight1, sizeof(weight1)); ++ printer.Put_real64 (weight1); + +- printer.Process (map1.RuntimeFingerprint ().data, +- dng_fingerprint::kDNGFingerprintSize); ++ printer.Process (map1.RuntimeFingerprint ()); + +- printer.Process (map2.RuntimeFingerprint ().data, +- dng_fingerprint::kDNGFingerprintSize); ++ printer.Process (map2.RuntimeFingerprint ()); + + result->SetRuntimeFingerprint (printer.Result ()); + +@@ -513,21 +511,16 @@ dng_hue_sat_map * dng_hue_sat_map::Interpolate (const dng_hue_sat_map &map1, + + { + +- dng_md5_printer printer; ++ dng_md5_printer_le_stream printer; + +- printer.Process ("Interpolate3", 12); ++ printer.ProcessPtr ("Interpolate3", 12); + +- printer.Process (&weight1, sizeof (weight1)); +- printer.Process (&weight2, sizeof (weight2)); ++ printer.Put_real64 (weight1); ++ printer.Put_real64 (weight2); + +- printer.Process (map1.RuntimeFingerprint ().data, +- dng_fingerprint::kDNGFingerprintSize); +- +- printer.Process (map2.RuntimeFingerprint ().data, +- dng_fingerprint::kDNGFingerprintSize); +- +- printer.Process (map3.RuntimeFingerprint ().data, +- dng_fingerprint::kDNGFingerprintSize); ++ printer.Process (map1.RuntimeFingerprint ()); ++ printer.Process (map2.RuntimeFingerprint ()); ++ printer.Process (map3.RuntimeFingerprint ()); + + result->SetRuntimeFingerprint (printer.Result ()); + +diff --git a/source/dng_ifd.cpp b/source/dng_ifd.cpp +index a55b489..5e20dca 100644 +--- a/source/dng_ifd.cpp ++++ b/source/dng_ifd.cpp +@@ -2218,7 +2218,7 @@ bool dng_ifd::ParseTag (dng_host &host, + if (!CheckTagCount (parentCode, tagCode, tagCount, 16)) + return false; + +- stream.Get (fPreviewInfo.fSettingsDigest.data, 16); ++ stream.Get (fPreviewInfo.fSettingsDigest); + + #if qDNGValidate + +@@ -2790,7 +2790,7 @@ bool dng_ifd::ParseTag (dng_host &host, + if (pgtm && gVerbose) + { + +- dng_md5_printer printer; ++ dng_md5_printer_le_stream printer; + + pgtm->AddDigest (printer); + +@@ -3144,6 +3144,38 @@ bool dng_ifd::ParseTag (dng_host &host, + + } + ++ case tcGainMapMetadata_ISO_21496_1: ++ { ++ ++ if (!CheckTagType (parentCode, tagCode, tagType, ttUndefined)) ++ return false; ++ ++ constexpr uint32 kMaxTagCount = 4 * 1024 * 1024; ++ ++ if (tagCount > kMaxTagCount) ++ ThrowBadFormat ("Gain map metadata block too large"); ++ ++ AutoPtr block (host.Allocate (tagCount)); ++ ++ stream.Get (block->Buffer (), ++ tagCount); ++ ++ fGainMapMetadata.reset (block.Release ()); ++ ++ #if qDNGValidate ++ ++ if (gVerbose) ++ { ++ printf ("GainMapMetadata: %u bytes\n", ++ tagCount); ++ } ++ ++ #endif ++ ++ break; ++ ++ } ++ + default: + { + +@@ -3456,7 +3488,8 @@ bool dng_ifd::IsValidDNG (dng_shared &shared, + + bool isMainOrEnhancedIFD = isMainIFD || isEnhancedIFD; + +- bool isGainMapIFD = (fNewSubFileType == sfGainMap); ++ bool isGainMapIFD = (fNewSubFileType == sfGainMap || ++ fNewSubFileType == sfPreviewGainMap); + + // Check NewSubFileType. + +@@ -3483,6 +3516,7 @@ bool dng_ifd::IsValidDNG (dng_shared &shared, + fNewSubFileType != sfEnhancedImage && + fNewSubFileType != sfAltPreviewImage && + fNewSubFileType != sfGainMap && ++ fNewSubFileType != sfPreviewGainMap && + fNewSubFileType != sfSemanticMask) + { + +@@ -3630,7 +3664,8 @@ bool dng_ifd::IsValidDNG (dng_shared &shared, + + } + +- else if (fNewSubFileType == sfGainMap) ++ else if (fNewSubFileType == sfGainMap || ++ fNewSubFileType == sfPreviewGainMap) + { + + if (fPhotometricInterpretation != piGainMap) +@@ -3800,7 +3835,8 @@ bool dng_ifd::IsValidDNG (dng_shared &shared, + // Check ColorimetricReference. + + if (isGainMapIFD && +- (shared.fColorimetricReference == crSceneReferred)) ++ (shared.fColorimetricReference == crSceneReferred) && ++ (fNewSubFileType != sfPreviewGainMap)) + { + + #if qDNGValidate +@@ -5305,6 +5341,17 @@ bool dng_ifd::IsBaselineJPEG () const + break; + + } ++ ++ // Some scanned TIFF files have JPEG + alpha channel. Treat these as ++ // baseline for purposes of reading. See CR-4205789. ++ ++ if ((fCompression == ccJPEG) && ++ (fPhotometricInterpretation == piRGB) && ++ (fSamplesPerPixel == 3 || ++ fSamplesPerPixel == 4)) ++ { ++ return true; ++ } + + return false; + +diff --git a/source/dng_ifd.h b/source/dng_ifd.h +index 60459d4..bdf2fa4 100644 +--- a/source/dng_ifd.h ++++ b/source/dng_ifd.h +@@ -265,6 +265,10 @@ class dng_ifd + real32 fJXLDistance = -1.0f; + int32 fJXLEffort = -1; + int32 fJXLDecodeSpeed = -1; ++ ++ // Metadata for ISO 21496-1. ++ ++ std::shared_ptr fGainMapMetadata; + + public: + +diff --git a/source/dng_image_writer.cpp b/source/dng_image_writer.cpp +index bf9b20c..b97ab74 100644 +--- a/source/dng_image_writer.cpp ++++ b/source/dng_image_writer.cpp +@@ -167,7 +167,7 @@ static void SpoolAdobeData (dng_stream &stream, + + stream.Put_uint32 (16); + +- stream.Put (iptcDigest.data, 16); ++ stream.Put (iptcDigest); + + } + +@@ -399,8 +399,8 @@ void tag_data_ptr::Put (dng_stream &stream) const + + } + +- // Entries don't need to be byte swapped. Fall through +- // to non-byte swapped case. ++ // Entries don't need to be byte swapped. ++ // Default to non-byte swapped case. + + default: + { +@@ -1546,7 +1546,7 @@ exif_tag_set::exif_tag_set (dng_tiff_directory &directory, + snprintf (fImageUniqueIDData + j * 2, + 33, + "%02X", +- (unsigned) exif.fImageUniqueID.data [j]); ++ (unsigned) exif.fImageUniqueID.Data () [j]); + + } + +@@ -2524,11 +2524,18 @@ class profile_tag_set + + AutoPtr fRGBTablesTag; + ++ bool fProfileDidWritePGTM = false; ++ + public: + + profile_tag_set (dng_host &host, + dng_tiff_directory &directory, + const dng_camera_profile &profile); ++ ++ bool ProfileDidWritePGTMToMainIFD () const ++ { ++ return fProfileDidWritePGTM; ++ } + + }; + +@@ -3027,6 +3034,8 @@ profile_tag_set::profile_tag_set (dng_host &host, + + directory.Add (fProfileGainTableMapTag.Get ()); + ++ fProfileDidWritePGTM = true; ++ + } + + // ProfileDynamicRange. +@@ -3315,7 +3324,7 @@ void big_table_tag_set::WriteData (dng_stream &stream) + const dng_fingerprint &fingerprint = it->first; + + memcpy (fDigestsBuffer->Buffer_uint8 () + index * 16, +- fingerprint.data, ++ fingerprint.Data (), + 16); + + const dng_ref_counted_block &block = it->second; +@@ -3342,8 +3351,8 @@ void big_table_tag_set::WriteData (dng_stream &stream) + for (const auto &group : fGroupIndex.Map ()) + { + +- memcpy (dPtr , group.first .data, 16); +- memcpy (dPtr + 16, group.second.data, 16); ++ memcpy (dPtr , group.first .Data (), 16); ++ memcpy (dPtr + 16, group.second.Data (), 16); + + dPtr += 32; + +@@ -5142,7 +5151,7 @@ void dng_write_tiles_task::Process (uint32 /* threadIndex */, + + tileStream.SetReadPosition (0); + +- dng_md5_printer_stream md5stream; ++ dng_md5_printer_le_stream md5stream; + + tileStream.CopyToStream (md5stream, tileByteCount); + +@@ -5188,8 +5197,7 @@ void dng_write_tiles_task::Process (uint32 /* threadIndex */, + if (fNeedDigest) + { + +- fOverallPrinter.Process (tileDigest.data, +- uint32 (sizeof (tileDigest.data))); ++ fOverallPrinter.Process (tileDigest); + + } + +@@ -5550,7 +5558,7 @@ void dng_image_writer::WriteImage (dng_host &host, + subTileBlockBuffer.Reset (host.Allocate (uncompressedSize.Get ())); + } + +- dng_md5_printer overallPrinter; ++ dng_md5_direct_printer overallPrinter; + + const bool needDigest = (outDigest != nullptr); + +@@ -5628,7 +5636,7 @@ void dng_image_writer::WriteImage (dng_host &host, + + tileStream.SetReadPosition (0); + +- dng_md5_printer_stream md5stream; ++ dng_md5_printer_le_stream md5stream; + + tileStream.CopyToStream (md5stream, tileByteCount); + +@@ -5636,8 +5644,7 @@ void dng_image_writer::WriteImage (dng_host &host, + + // Update the overall digest. + +- overallPrinter.Process (tileDigest.data, +- uint32 (sizeof (tileDigest.data))); ++ overallPrinter.Process (tileDigest); + + // Copy the tile data to the main stream. + +@@ -6481,7 +6488,6 @@ void dng_image_writer::CleanUpMetadata (dng_host &host, + #endif // qDNGUseXMP + + } +- + + /*****************************************************************************/ + +@@ -6547,7 +6553,10 @@ void dng_image_writer::WriteTIFF (dng_host &host, + bool hasTransparency, + bool allowBigTIFF, + const dng_image *gainMapImage, +- const bool useHalfFloat) ++ const const_dng_memory_block_sptr gainMapMetadataBlock, ++ const bool useHalfFloat, ++ const void *gainMapAltProfileData, ++ const uint32 gainMapAltProfileSize) + { + + const void *profileData = NULL; +@@ -6579,7 +6588,10 @@ void dng_image_writer::WriteTIFF (dng_host &host, + hasTransparency, + allowBigTIFF, + gainMapImage, +- useHalfFloat); ++ gainMapMetadataBlock, ++ useHalfFloat, ++ gainMapAltProfileData, ++ gainMapAltProfileSize); + + } + +@@ -6711,7 +6723,10 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, + bool hasTransparency, + bool allowBigTIFF, + const dng_image *gainMapImage, +- const bool useHalfFloat) ++ const const_dng_memory_block_sptr gainMapMetadataBlock, ++ const bool useHalfFloat, ++ const void *gainMapAltProfileData, ++ const uint32 gainMapAltProfileSize) + { + + // Force writing all TIFF files in BigTIFF format. +@@ -6869,7 +6884,12 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, + + AutoPtr gainMapImageIFD; + +- const bool hasGainMap = (gainMapImage != nullptr); ++ AutoPtr tagGainMapMetadata; ++ ++ AutoPtr tagGainMapAlternateProfile; ++ ++ const bool hasGainMap = ((gainMapImage != nullptr) && ++ (gainMapMetadataBlock != nullptr)); + + if (hasGainMap) + { +@@ -6889,6 +6909,26 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, + gainMapTagSet.Reset (new dng_basic_tag_set (gainMapIFD, + *gainMapImageIFD)); + ++ tagGainMapMetadata.Reset ++ (new tag_owned_data_ptr (tcGainMapMetadata_ISO_21496_1, ++ ttUndefined, ++ gainMapMetadataBlock->LogicalSize (), ++ gainMapMetadataBlock)); ++ ++ gainMapIFD.Add (tagGainMapMetadata.Get ()); ++ ++ if (gainMapAltProfileData && ++ (gainMapAltProfileSize > 0)) ++ { ++ ++ tagGainMapAlternateProfile.Reset ++ (new tag_icc_profile (gainMapAltProfileData, ++ gainMapAltProfileSize)); ++ ++ gainMapIFD.Add (tagGainMapAlternateProfile.Get ()); ++ ++ } ++ + } + + // Resolution. +@@ -7055,7 +7095,7 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, + + // Write the gain map image. + +- if (gainMapImage) ++ if (hasGainMap) + { + + WriteImage (host, +@@ -7170,7 +7210,10 @@ void dng_image_writer::WriteDNG (dng_host &host, + bool uncompressed, + bool allowBigTIFF, + const dng_image *gainMapImage, +- const dng_lossy_compressed_image *gainMapLossyCompressed) ++ const dng_lossy_compressed_image *gainMapLossyCompressed, ++ const const_dng_memory_block_sptr gainMapMetadataBlock, ++ const void *gainMapAltProfileData, ++ const uint32 gainMapAltProfileSize) + { + + WriteDNGWithMetadata (host, +@@ -7182,7 +7225,10 @@ void dng_image_writer::WriteDNG (dng_host &host, + uncompressed, + allowBigTIFF, + gainMapImage, +- gainMapLossyCompressed); ++ gainMapLossyCompressed, ++ gainMapMetadataBlock, ++ gainMapAltProfileData, ++ gainMapAltProfileSize); + + } + +@@ -7380,7 +7426,10 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, + const bool uncompressed, + bool allowBigTIFF, + const dng_image *gainMapImage, +- const dng_lossy_compressed_image *gainMapLossyCompressed) ++ const dng_lossy_compressed_image *gainMapLossyCompressed, ++ const const_dng_memory_block_sptr gainMapMetadataBlock, ++ const void *gainMapAltProfileData, ++ const uint32 gainMapAltProfileSize) + { + + // Force writing all DNG files in 64-bit format. +@@ -7583,6 +7632,8 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, + + bool hasProfileWith_1_6_Features = false; + bool hasProfileWith_1_7_Features = false; ++ ++ bool mainProfileDidWritePGTMtoMainIFD = false; + + // Create the main IFD. + +@@ -7606,6 +7657,9 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, + profileSet.Reset (new profile_tag_set (host, + mainIFD, + mainProfile)); ++ ++ mainProfileDidWritePGTMtoMainIFD = ++ profileSet->ProfileDidWritePGTMToMainIFD (); + + colorSet.Reset (new color_tag_set (mainIFD, + negative)); +@@ -7677,6 +7731,10 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, + + AutoPtr gainMapImageIFD; + ++ AutoPtr tagGainMapMetadata; ++ ++ AutoPtr tagGainMapAlternateProfile; ++ + const bool hasGainMap = (gainMapImage != nullptr); + + if (hasGainMap) +@@ -7692,6 +7750,31 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, + + gainMapTagSet.Reset (new dng_basic_tag_set (gainMapIFD, + *gainMapImageIFD)); ++ ++ if (gainMapMetadataBlock) ++ { ++ ++ tagGainMapMetadata.Reset ++ (new tag_owned_data_ptr (tcGainMapMetadata_ISO_21496_1, ++ ttUndefined, ++ gainMapMetadataBlock->LogicalSize (), ++ gainMapMetadataBlock)); ++ ++ gainMapIFD.Add (tagGainMapMetadata.Get ()); ++ ++ if (gainMapAltProfileData && ++ (gainMapAltProfileSize > 0)) ++ { ++ ++ tagGainMapAlternateProfile.Reset ++ (new tag_icc_profile (gainMapAltProfileData, ++ gainMapAltProfileSize)); ++ ++ gainMapIFD.Add (tagGainMapAlternateProfile.Get ()); ++ ++ } ++ ++ } + + } + +@@ -8516,7 +8599,7 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, + } + + tag_uint8_ptr tagRawImageDigest (useNewDigest ? tcNewRawImageDigest : tcRawImageDigest, +- mainImageRawImageDigest.data, ++ mainImageRawImageDigest.Data (), + 16); + + if (mainImageRawImageDigest.IsValid ()) +@@ -8533,7 +8616,7 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, + const auto rawDataUniqueID = negative.RawDataUniqueID (); + + tag_uint8_ptr tagRawDataUniqueID (tcRawDataUniqueID, +- rawDataUniqueID.data, ++ rawDataUniqueID.Data (), + 16); + + if (rawDataUniqueID.IsValid ()) +@@ -8562,7 +8645,7 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, + negative.OriginalRawFileData ()); + + tag_uint8_ptr tagOriginalRawFileDigest (tcOriginalRawFileDigest, +- negative.OriginalRawFileDigest ().data, ++ negative.OriginalRawFileDigest ().Data (), + 16); + + if (negative.OriginalRawFileData ()) +@@ -8717,12 +8800,10 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, + + if (negative.HasProfileGainTableMap () && + +- // If the main profile already has the PGTM attached, let the profile +- // logic take care of writing the PGTM. Otherwise we would end up +- // writing two identical PGTM tags into IFD 0. +- +- (negative.ShareProfileGainTableMap () != +- mainProfile.ShareProfileGainTableMap ())) ++ // If the main profile already wrote the PGTM attached, then ++ // ignore whatever PGTM is attached to the negative. ++ ++ !mainProfileDidWritePGTMtoMainIFD) + { + + dng_memory_stream tempStream (host.Allocator (), +diff --git a/source/dng_image_writer.h b/source/dng_image_writer.h +index 842f262..36d6bb3 100644 +--- a/source/dng_image_writer.h ++++ b/source/dng_image_writer.h +@@ -1326,7 +1326,10 @@ class dng_image_writer + bool hasTransparency = false, + bool allowBigTIFF = true, + const dng_image *gainMapImage = nullptr, +- bool useHalfFloat = false); ++ const const_dng_memory_block_sptr gainMapMetadataBlock = nullptr, ++ bool useHalfFloat = false, ++ const void *gainMapAltProfileData = nullptr, ++ const uint32 gainMapAltProfileSize = 0); + + /// Write a dng_image to a dng_stream in TIFF format. + /// \param host Host interface used for progress updates, abort testing, buffer allocation, etc. +@@ -1360,7 +1363,10 @@ class dng_image_writer + bool hasTransparency = false, + bool allowBigTIFF = true, + const dng_image *gainMapImage = nullptr, +- bool useHalfFloat = false); ++ const const_dng_memory_block_sptr gainMapMetadataBlock = nullptr, ++ bool useHalfFloat = false, ++ const void *gainMapAltProfileData = nullptr, ++ const uint32 gainMapAltProfileSize = 0); + + /// Write a dng_image to a dng_stream in DNG format. + /// \param host Host interface used for progress updates, abort testing, buffer allocation, etc. +@@ -1380,7 +1386,10 @@ class dng_image_writer + bool uncompressed = false, + bool allowBigTIFF = true, + const dng_image *gainMapImage = nullptr, +- const dng_lossy_compressed_image *gainMapLossyCompressed = nullptr); ++ const dng_lossy_compressed_image *gainMapLossyCompressed = nullptr, ++ const const_dng_memory_block_sptr gainMapMetadataBlock = nullptr, ++ const void *gainMapAltProfileData = nullptr, ++ const uint32 gainMapAltProfileSize = 0); + + /// Write a dng_image to a dng_stream in DNG format. + /// \param host Host interface used for progress updates, abort testing, buffer allocation, etc. +@@ -1402,7 +1411,10 @@ class dng_image_writer + bool uncompressed = false, + bool allowBigTIFF = true, + const dng_image *gainMapImage = nullptr, +- const dng_lossy_compressed_image *gainMapLossyCompressed = nullptr); ++ const dng_lossy_compressed_image *gainMapLossyCompressed = nullptr, ++ const const_dng_memory_block_sptr gainMapMetadataBlock = nullptr, ++ const void *gainMapAltProfileData = nullptr, ++ const uint32 gainMapAltProfileSize = 0); + + /// Resolve metadata conflicts and apply metadata policies in keeping + /// with Metadata Working Group (MWG) guidelines. +@@ -1510,7 +1522,7 @@ class dng_write_tiles_task : public dng_area_task, + + const bool fNeedDigest; + +- mutable dng_md5_printer fOverallPrinter; ++ mutable dng_md5_direct_printer fOverallPrinter; + + public: + +diff --git a/source/dng_info.cpp b/source/dng_info.cpp +index a44ac26..07dd90c 100644 +--- a/source/dng_info.cpp ++++ b/source/dng_info.cpp +@@ -25,7 +25,7 @@ + dng_info::dng_info () + + : fTIFFBlockOffset (0) +- , fTIFFBlockOriginalOffset (kDNGStreamInvalidOffset) ++ , fTIFFBlockOriginalOffset (0) + , fBigEndian (false) + , fMagic (0) + , fExif () +@@ -1798,6 +1798,35 @@ void dng_info::ParseDNGPrivateData (dng_host &host, + return; + + } ++ ++ else if (privateName.StartsWith ("RICOH")) ++ { ++ ++ #if qDNGValidate ++ ++ if (gVerbose) ++ { ++ printf ("Parsing RICOH-PENTAX DNGPrivateData\n\n"); ++ } ++ ++ #endif ++ ++ stream.SetReadPosition (fShared->fDNGPrivateDataOffset + 8); ++ ++ TempBigEndian temp_endian (stream, false); ++ ++ ParseMakerNoteIFD (host, ++ stream, ++ fShared->fDNGPrivateDataCount - 8, ++ fShared->fDNGPrivateDataOffset + 8, ++ fShared->fDNGPrivateDataOffset, ++ fShared->fDNGPrivateDataOffset, ++ fShared->fDNGPrivateDataOffset + fShared->fDNGPrivateDataCount, ++ tcPentaxMakerNote); ++ ++ return; ++ ++ } + + // Stop parsing if this is not an Adobe format block. + +diff --git a/source/dng_info.h b/source/dng_info.h +index 068b447..a5890f3 100644 +--- a/source/dng_info.h ++++ b/source/dng_info.h +@@ -59,6 +59,8 @@ class dng_info: private dng_uncopyable + + int32 fEnhancedIndex; + ++ int32 fGainMapIndex = -1; // ISO 21496-1 Gain Map. ++ + std::vector fSemanticMaskIndices; + + std::vector fIFD; +diff --git a/source/dng_jpeg_image.cpp b/source/dng_jpeg_image.cpp +index a9373ea..c8e97cc 100644 +--- a/source/dng_jpeg_image.cpp ++++ b/source/dng_jpeg_image.cpp +@@ -21,7 +21,7 @@ + #include "dng_uncopyable.h" + + #include +-#include ++#include + + /*****************************************************************************/ + +@@ -350,10 +350,10 @@ dng_fingerprint dng_lossy_compressed_image::FindDigest (dng_host &host) const + for (int32 i = ra.fBegin; i < ra.fEnd; i++) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (fData [i]->Buffer (), +- fData [i]->LogicalSize ()); ++ printer.ProcessPtr (fData [i]->Buffer (), ++ fData [i]->LogicalSize ()); + + digests [i] = printer.Result (); + +@@ -369,11 +369,10 @@ dng_fingerprint dng_lossy_compressed_image::FindDigest (dng_host &host) const + + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + + for (const auto &digest : digests) +- printer.Process (digest.data, +- uint32 (sizeof (digest.data))); ++ printer.Process (digest); + + return printer.Result (); + +@@ -486,10 +485,10 @@ void dng_jpeg_image::DoFindDigest (dng_host & /* host */, + if (fJPEGTables.Get ()) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (fJPEGTables->Buffer (), +- fJPEGTables->LogicalSize ()); ++ printer.ProcessPtr (fJPEGTables->Buffer (), ++ fJPEGTables->LogicalSize ()); + + digests.push_back (printer.Result ()); + +diff --git a/source/dng_jxl.cpp b/source/dng_jxl.cpp +index 17e28c1..eabd114 100644 +--- a/source/dng_jxl.cpp ++++ b/source/dng_jxl.cpp +@@ -31,6 +31,7 @@ + #include "dng_negative.h" + #include "dng_parse_utils.h" + #include "dng_pixel_buffer.h" ++#include "dng_safe_arithmetic.h" + #include "dng_tag_codes.h" + #include "dng_utils.h" + #include "dng_xmp.h" +@@ -38,10 +39,7 @@ + + #include + #include +- +-/*****************************************************************************/ +- +-#define qLogJXL (qDNGValidate && 0) ++#include + + /*****************************************************************************/ + +@@ -316,7 +314,7 @@ static void CheckResult (JxlEncoderStatus status, + + /*****************************************************************************/ + +-static void * dng_jxl_alloc (void *opaque, size_t size) ++static void * dng_jxl_alloc (void *opaque, size_t size) // TODO(erichan): instrument + { + + #if 0 +@@ -490,137 +488,769 @@ class dng_jxl_io_buffer + fIndex = 0; + } + +- void ReadChunk (dng_stream &stream, +- size_t remaining) +- { ++ void ReadChunk (dng_stream &stream, ++ size_t remaining) ++ { ++ ++ size_t bytes_remaining_in_stream = ++ (size_t) (stream.Length () - stream.Position ()); ++ ++ if (bytes_remaining_in_stream == 0) ++ { ++ #if qLogJXL ++ printf ("dng_jxl_io_buffer -- EOF reached in ReadChunk"); ++ #endif ++ ThrowEndOfFile ("dng_jxl_io_buffer"); ++ } ++ ++ // This is the number of bytes we're going to try to read from the ++ // stream. It cannot be more than the number of bytes available in ++ // the stream. ++ ++ size_t next_read_size = std::min (fChunkSize, ++ bytes_remaining_in_stream); ++ ++ // This is the number of bytes that jxl previously consumed from ++ // our buffer. ++ ++ size_t prev_read_size = fDataSize - remaining; ++ ++ // Update our pointer past the previously-consumed bytes. ++ ++ fIndex += prev_read_size; ++ ++ // If reading the next chunk would spill beyond the end of our ++ // buffer, then reset to the beginning of our buffer. We need to ++ // move the remaining data. If our internal buffer is much larger ++ // than the chunk size, then this will be relatively rare. ++ ++ if (fIndex + next_read_size > fMaxBufferSize) ++ { ++ ++ memmove (fBlock->Buffer (), ++ fBlock->Buffer_uint8 () + fIndex, ++ remaining); ++ ++ fIndex = 0; ++ ++ } ++ ++ // Read from the stream. ++ ++ auto ptr = Ptr () + remaining; ++ ++ stream.Get (ptr, (uint32) next_read_size); ++ ++ // Update the amount of data in our buffer. ++ ++ fDataSize = remaining + next_read_size; ++ ++ } ++ ++ }; ++ ++/*****************************************************************************/ ++ ++static void EnsureUseBoxes (JxlEncoder *enc, ++ bool &once_flag) ++ { ++ ++ if (!once_flag) ++ { ++ ++ once_flag = true; ++ ++ CheckResult (JxlEncoderUseBoxes (enc), ++ "JxlEncoderUseBoxes", ++ nullptr); ++ ++ } ++ ++ } ++ ++/*****************************************************************************/ ++ ++#ifndef qLogStreamOutput ++#define qLogStreamOutput (qDNGValidate && 0) ++#endif ++ ++#ifndef qLogStreamIntput ++#define qLogStreamIntput (qDNGValidate && 0) ++#endif ++ ++/*****************************************************************************/ ++ ++// Simple output processor that writes all of the data to a single internal ++// byte stream (fOutput). ++ ++class jxl_data_writer ++ { ++ ++ public: ++ ++ dng_host &fHost; ++ ++ size_t fPosition = 0ULL; ++ ++ size_t fFinalizedPosition = 0ULL; ++ ++ std::vector fOutput; ++ ++ public: ++ ++ jxl_data_writer (dng_host &host) ++ : fHost (host) ++ { ++ } ++ ++ void * get_buffer (size_t *size) ++ { ++ ++ if (!size) ++ return nullptr; ++ ++ if (*size == 0) ++ return nullptr; ++ ++ // Deal with cancellation. ++ ++ try ++ { ++ fHost.SniffForAbort (); ++ } ++ ++ catch (...) ++ { ++ *size = 0; ++ return nullptr; ++ } ++ ++ #if qLogStreamOutput ++ ++ printf ("output: get_buffer (size %d)\n", ++ int (*size)); ++ ++ #endif ++ ++ if (fPosition + *size > fOutput.size ()) ++ fOutput.resize (fPosition + *size, 0xDA); ++ ++ *size = fOutput.size () - fPosition; ++ ++ return fOutput.data () + fPosition; ++ ++ } ++ ++ void release_buffer (size_t written_bytes) ++ { ++ ++ #if qLogStreamOutput ++ ++ printf ("output: release_buffer (wb=%d)\n", ++ int (written_bytes)); ++ ++ #endif ++ ++ fPosition += written_bytes; ++ ++ } ++ ++ void seek (size_t position) ++ { ++ ++ #if qLogStreamOutput ++ ++ printf ("output: seek (pos=%d)\n", ++ int (position)); ++ ++ #endif ++ ++ fPosition = position; ++ ++ } ++ ++ void set_finalized_position (size_t finalized_position) ++ { ++ ++ #if qLogStreamOutput ++ ++ printf ("output: set_finalized_position (pos=%d)\n", ++ int (finalized_position)); ++ ++ #endif ++ ++ fFinalizedPosition = finalized_position; ++ ++ } ++ ++ }; ++ ++/*****************************************************************************/ ++ ++// Output processor that doesn't support arbitrary seeking but can write ++// smaller chunks to the given output stream in-sequence. ++ ++class jxl_data_writer2 ++ { ++ ++ private: ++ ++ static constexpr size_t kMaxSize = 1073741824; ++ ++ dng_host &fHost; ++ ++ dng_stream &fStream; ++ ++ AutoPtr fBlock; ++ ++ size_t fPosition = 0ULL; ++ ++ size_t fFinalizedPosition = 0ULL; ++ ++ size_t fStartPosition = 0ULL; ++ ++ public: ++ ++ jxl_data_writer2 (dng_host &host, ++ dng_stream &stream) ++ : fHost (host) ++ , fStream (stream) ++ { ++ } ++ ++ void * get_buffer (size_t *size) ++ { ++ ++ if (!size) ++ return nullptr; ++ ++ if (*size == 0) ++ return nullptr; ++ ++ // Deal with cancellation. ++ ++ try ++ { ++ fHost.SniffForAbort (); ++ } ++ ++ catch (...) ++ { ++ *size = 0; ++ return nullptr; ++ } ++ ++ #if qLogStreamOutput ++ ++ printf ("output: get_buffer (size %d)\n", ++ int (*size)); ++ ++ #endif ++ ++ if ((*size) > kMaxSize) ++ { ++ *size = 0; ++ return nullptr; ++ } ++ ++ const uint32 requestedBytes = uint32 (*size); ++ ++ if (!fBlock.Get () || ++ (fBlock->LogicalSize () < requestedBytes)) ++ { ++ ++ // Round up to 1 MB. ++ ++ const uint32 roundedBytes = ((requestedBytes + 1048575) & uint32 (~1048575)); ++ ++ fBlock.Reset (fHost.Allocate (roundedBytes)); ++ ++ } ++ ++ fStartPosition = fPosition; ++ ++ *size = (size_t) fBlock->LogicalSize (); ++ ++ return fBlock->Buffer (); ++ ++ } ++ ++ void release_buffer (size_t written_bytes) ++ { ++ ++ #if qLogStreamOutput ++ ++ printf ("output: release_buffer (wb=%d)\n", ++ int (written_bytes)); ++ ++ #endif ++ ++ fPosition += written_bytes; ++ ++ } ++ ++ void set_finalized_position (size_t finalized_position) ++ { ++ ++ #if qLogStreamOutput ++ ++ printf ("output: set_finalized_position (sp=%d, pos=%d)\n", ++ int (fStartPosition), ++ int (finalized_position)); ++ ++ #endif ++ ++ fFinalizedPosition = finalized_position; ++ ++ if (finalized_position > fStartPosition && ++ (finalized_position < fStartPosition + kMaxSize)) ++ { ++ ++ fStream.Put (fBlock->Buffer (), ++ uint32 (finalized_position - fStartPosition)); ++ ++ } ++ ++ fStartPosition = finalized_position; ++ ++ } ++ ++ }; ++ ++/*****************************************************************************/ ++ ++static void * jxl_output_get_buffer (void *opaque, ++ size_t *size) ++ { ++ ++ auto writer = (jxl_data_writer2 *) opaque; ++ ++ if (!writer) ++ { ++ *size = 0; ++ return nullptr; ++ } ++ ++ return writer->get_buffer (size); ++ ++ } ++ ++/*****************************************************************************/ ++ ++static void jxl_output_release_buffer (void *opaque, ++ size_t written_bytes) ++ { ++ ++ auto writer = (jxl_data_writer2 *) opaque; ++ ++ if (writer) ++ writer->release_buffer (written_bytes); ++ ++ } ++ ++/*****************************************************************************/ ++ ++static void output_set_finalized_position (void *opaque, ++ uint64_t finalized_position) ++ { ++ ++ auto writer = (jxl_data_writer2 *) opaque; ++ ++ if (writer) ++ writer->set_finalized_position (size_t (finalized_position)); ++ ++ } ++ ++/*****************************************************************************/ ++ ++class jxl_image_chunk_reader ++ { ++ ++ private: ++ ++ dng_host &fHost; ++ ++ const dng_image &fImage; ++ ++ JxlPixelFormat fPixelFormat; ++ ++ std::mutex fMutex; ++ ++ std::unordered_map > fTable; ++ ++ public: ++ ++ jxl_image_chunk_reader (dng_host &host, ++ const dng_image &srcImage, ++ const JxlPixelFormat &pixelFormat) ++ : fHost (host) ++ , fImage (srcImage) ++ , fPixelFormat (pixelFormat) ++ { ++ } ++ ++ void get_color_channels_pixel_format (JxlPixelFormat *pixel_format) ++ { ++ if (pixel_format) ++ *pixel_format = fPixelFormat; ++ } ++ ++ const void * get_color_channel_data_at (size_t xpos, ++ size_t ypos, ++ size_t xsize, ++ size_t ysize, ++ size_t *row_offset) ++ { ++ ++ #if qLogStreamIntput ++ ++ printf ("input: get_color_channel_data_at (x=%d, y=%d, w=%d, h=%d)\n", ++ int (xpos), ++ int (ypos), ++ int (xsize), ++ int (ysize)); ++ ++ #endif ++ ++ dng_pixel_buffer buffer; ++ ++ buffer.fArea.t = int32 (ypos); ++ buffer.fArea.l = int32 (xpos); ++ buffer.fArea.b = int32 (ypos + ysize); ++ buffer.fArea.r = int32 (xpos + xsize); ++ ++ buffer.fPlanes = fImage.Planes (); ++ ++ // Assume row-col-plane interleaved. ++ ++ buffer.fPlaneStep = 1; ++ buffer.fColStep = buffer.fPlanes; ++ buffer.fRowStep = SafeUint32Mult (buffer.fPlanes, ++ buffer.fArea.W ()); ++ ++ buffer.fPixelType = fImage.PixelType (); ++ buffer.fPixelSize = TagTypeSize (buffer.fPixelType); ++ ++ const uint32 bytesNeeded = SafeUint32Mult (buffer.fRowStep, ++ buffer.fArea.H (), ++ buffer.fPixelSize); ++ ++ std::shared_ptr block (fHost.Allocate (bytesNeeded)); ++ ++ buffer.fData = block->Buffer (); ++ ++ const void *ptr = buffer.fData; ++ ++ // Fetch the image. ++ ++ fImage.Get (buffer); ++ ++ // Store the row stride. ++ ++ if (row_offset) ++ { ++ ++ *row_offset = size_t (buffer.fRowStep * buffer.fPixelSize); ++ ++ } ++ ++ // Remember the allocation in our table. ++ ++ { ++ std::lock_guard lock (fMutex); ++ fTable.insert ++ (std::pair > ++ (ptr, ++ block)); ++ } ++ ++ return ptr; ++ ++ } ++ ++ void release_buffer (const void *buf) ++ { ++ ++ #if qLogStreamIntput ++ ++ printf ("input: release (%llu)\n", ++ (unsigned long long) (buf)); ++ ++ #endif // qLogStreamIntput ++ ++ // Remove it from our table. ++ ++ { ++ std::lock_guard lock (fMutex); ++ fTable.erase (buf); ++ } ++ ++ } ++ ++ public: ++ ++ static void GetPixelFormat (void *opaque, ++ JxlPixelFormat *pixel_format) ++ { ++ ++ auto reader = (jxl_image_chunk_reader *) opaque; ++ ++ if (reader) ++ reader->get_color_channels_pixel_format (pixel_format); ++ ++ } ++ ++ static const void * GetData (void *opaque, ++ size_t xpos, ++ size_t ypos, ++ size_t xsize, ++ size_t ysize, ++ size_t *row_offset) ++ { ++ ++ auto reader = (jxl_image_chunk_reader *) opaque; ++ ++ if (reader) ++ return reader->get_color_channel_data_at (xpos, ++ ypos, ++ xsize, ++ ysize, ++ row_offset); ++ ++ return nullptr; ++ ++ } ++ ++ static void Release (void *opaque, ++ const void *buf) ++ { ++ ++ auto reader = (jxl_image_chunk_reader *) opaque; ++ ++ if (reader) ++ reader->release_buffer (buf); ++ ++ } ++ ++ }; ++ ++/*****************************************************************************/ ++ ++class jxl_buffer_chunk_reader ++ { ++ ++ private: ++ ++ dng_host &fHost; ++ ++ const dng_pixel_buffer &fBuffer; ++ ++ JxlPixelFormat fPixelFormat; ++ ++ std::mutex fMutex; ++ ++ std::unordered_map > fTable; ++ ++ public: ++ ++ jxl_buffer_chunk_reader (dng_host &host, ++ const dng_pixel_buffer &srcBuffer, ++ const JxlPixelFormat &pixelFormat) ++ : fHost (host) ++ , fBuffer (srcBuffer) ++ , fPixelFormat (pixelFormat) ++ { ++ ++ #if qLogStreamIntput ++ ++ printf ("--- srcBuffer: t=%d, l=%d, b=%d, r=%d (w=%d, h=%d)\n", ++ srcBuffer.fArea.t, ++ srcBuffer.fArea.l, ++ srcBuffer.fArea.b, ++ srcBuffer.fArea.r, ++ int (srcBuffer.fArea.W ()), ++ int (srcBuffer.fArea.H ())); ++ ++ #endif ++ ++ } ++ ++ void get_color_channels_pixel_format (JxlPixelFormat *pixel_format) ++ { ++ if (pixel_format) ++ *pixel_format = fPixelFormat; ++ } ++ ++ const void * get_color_channel_data_at (size_t xpos, ++ size_t ypos, ++ size_t xsize, ++ size_t ysize, ++ size_t *row_offset) ++ { ++ ++ #if qLogStreamIntput ++ ++ printf ("input: get_color_channel_data_at (x=%d, y=%d, w=%d, h=%d)\n", ++ int (xpos), ++ int (ypos), ++ int (xsize), ++ int (ysize)); ++ ++ #endif ++ ++ dng_pixel_buffer buffer = fBuffer; ++ ++ buffer.fArea.t = int32 (ypos) + fBuffer.fArea.t; ++ buffer.fArea.l = int32 (xpos) + fBuffer.fArea.l; ++ buffer.fArea.b = buffer.fArea.t + int32 (ysize); ++ buffer.fArea.r = buffer.fArea.l + int32 (xsize); ++ ++ // Assume row-col-plane interleaved. ++ ++ buffer.fPlaneStep = 1; ++ buffer.fColStep = buffer.fPlanes; ++ buffer.fRowStep = SafeUint32Mult (buffer.fPlanes, ++ buffer.fArea.W ()); ++ ++ const uint32 bytesNeeded = SafeUint32Mult (buffer.fRowStep, ++ buffer.fArea.H (), ++ buffer.fPixelSize); ++ ++ std::shared_ptr block (fHost.Allocate (bytesNeeded)); ++ ++ buffer.fData = block->Buffer (); ++ ++ const void *ptr = buffer.fData; ++ ++ // Copy the data. ++ ++ #if 0 ++ ++ dng_copy_buffer_task task (fBuffer, ++ buffer); ++ ++ fHost.PerformAreaTask (task, ++ buffer.fArea); ++ ++ #else ++ ++ buffer.CopyArea (fBuffer, ++ buffer.fArea, ++ 0, ++ 0, ++ buffer.fPlanes); + +- size_t bytes_remaining_in_stream = +- (size_t) (stream.Length () - stream.Position ()); ++ #endif + +- if (bytes_remaining_in_stream == 0) ++ // Store the row stride. ++ ++ if (row_offset) + { +- #if qLogJXL +- printf ("dng_jxl_io_buffer -- EOF reached in ReadChunk"); +- #endif +- ThrowEndOfFile ("dng_jxl_io_buffer"); ++ ++ *row_offset = size_t (buffer.fRowStep * buffer.fPixelSize); ++ + } + +- // This is the number of bytes we're going to try to read from the +- // stream. It cannot be more than the number of bytes available in +- // the stream. ++ // Remember the allocation in our table. + +- size_t next_read_size = std::min (fChunkSize, +- bytes_remaining_in_stream); ++ { ++ std::lock_guard lock (fMutex); ++ fTable.insert ++ (std::pair > ++ (ptr, ++ block)); ++ } + +- // This is the number of bytes that jxl previously consumed from +- // our buffer. +- +- size_t prev_read_size = fDataSize - remaining; ++ return ptr; + +- // Update our pointer past the previously-consumed bytes. ++ } + +- fIndex += prev_read_size; ++ void release_buffer (const void *buf) ++ { + +- // If reading the next chunk would spill beyond the end of our +- // buffer, then reset to the beginning of our buffer. We need to +- // move the remaining data. If our internal buffer is much larger +- // than the chunk size, then this will be relatively rare. ++ #if qLogStreamIntput + +- if (fIndex + next_read_size > fMaxBufferSize) +- { ++ printf ("input: release (%llu)\n", ++ (unsigned long long) (buf)); + +- memmove (fBlock->Buffer (), +- fBlock->Buffer_uint8 () + fIndex, +- remaining); +- +- fIndex = 0; +- ++ #endif // qLogStreamIntput ++ ++ // Remove it from our table. ++ ++ { ++ std::lock_guard lock (fMutex); ++ fTable.erase (buf); + } + +- // Read from the stream. ++ } + +- auto ptr = Ptr () + remaining; +- +- stream.Get (ptr, (uint32) next_read_size); ++ public: + +- // Update the amount of data in our buffer. ++ static void GetPixelFormat (void *opaque, ++ JxlPixelFormat *pixel_format) ++ { + +- fDataSize = remaining + next_read_size; ++ auto reader = (jxl_buffer_chunk_reader *) opaque; ++ ++ if (reader) ++ reader->get_color_channels_pixel_format (pixel_format); + + } +- +- }; + +-/*****************************************************************************/ ++ static const void * GetData (void *opaque, ++ size_t xpos, ++ size_t ypos, ++ size_t xsize, ++ size_t ysize, ++ size_t *row_offset) ++ { + +-static void EnsureUseBoxes (JxlEncoder *enc, +- bool &once_flag) +- { +- +- if (!once_flag) +- { +- +- once_flag = true; ++ auto reader = (jxl_buffer_chunk_reader *) opaque; + +- CheckResult (JxlEncoderUseBoxes (enc), +- "JxlEncoderUseBoxes", +- nullptr); +- +- } +- +- } ++ if (reader) ++ return reader->get_color_channel_data_at (xpos, ++ ypos, ++ xsize, ++ ysize, ++ row_offset); + +-/*****************************************************************************/ ++ return nullptr; + +-// Move to operator== ? ++ } + +-static bool SamePixelBufferGeometry (const dng_pixel_buffer &a, +- const dng_pixel_buffer &b) +- { +- +- if (a.fArea != b.fArea) +- return false; ++ static void Release (void *opaque, ++ const void *buf) ++ { + +- if (a.fPlane != b.fPlane || +- a.fPlanes != b.fPlanes) +- return false; +- +- if (a.fRowStep != b.fRowStep || +- a.fColStep != b.fColStep || +- a.fPlaneStep != b.fPlaneStep) +- return false; +- +- if (a.fPixelType != b.fPixelType || +- a.fPixelSize != b.fPixelSize) +- return false; ++ auto reader = (jxl_buffer_chunk_reader *) opaque; + +- // Ignore the fData and fDirty fields. ++ if (reader) ++ reader->release_buffer (buf); + +- return true; +- +- } ++ } ++ ++ }; + + /*****************************************************************************/ + +-static void EncodeJXL (dng_host &host, +- dng_stream &stream, +- const dng_pixel_buffer &inBuffer, +- const dng_jxl_encode_settings &settings, +- const bool useContainer, +- const dng_jxl_color_space_info &colorSpaceInfo, +- const dng_metadata *metadata, +- const bool includeExif, +- const bool includeXMP, +- const bool includeIPTC, +- const dng_bmff_box_list *additionalBoxes) ++static JxlEncoderPtr EncodeJXL_Common (dng_host &host, ++ dng_pixel_buffer &buffer, ++ const dng_jxl_encode_settings &settings, ++ const bool useStreamingEncoder, ++ const bool useContainer, ++ const dng_jxl_color_space_info &colorSpaceInfo, ++ const dng_metadata *metadata, ++ const bool includeExif, ++ const bool includeXMP, ++ const bool includeIPTC, ++ const dng_bmff_box_list *additionalBoxes, ++ dng_jxl_parallel_runner_data ¶llelData, ++ JxlPixelFormat &outPixelFormat, ++ JxlEncoderFrameSettings **outFrameSettings) + { +- +- #if qDNGValidate && 0 +- dng_timer timerOuter ("EncodeJXL"); +- #endif +- +- dng_pixel_buffer buffer = inBuffer; +- ++ + const bool isLossless = (settings.Distance () <= 0.0f); + + uint32 &srcPixelType = buffer.fPixelType; +@@ -657,7 +1287,7 @@ static void EncodeJXL (dng_host &host, + + //printf ("srcPixelType: %u\n", srcPixelType); + //printf ("effort: %u\n", unsigned (settings.Effort ())); +- ++ + const uint32 planes = buffer.Planes (); + + DNG_REQUIRE (planes == 1 || // monochrome +@@ -680,9 +1310,9 @@ static void EncodeJXL (dng_host &host, + + auto enc = encoder.get (); + +- // Hook into our thread pool. ++ DNG_REQUIRE (enc, "JXL encoder - JxlEncoderMake failed"); + +- dng_jxl_parallel_runner_data parallelData; ++ // Hook into our thread pool. + + parallelData.fHost = &host; + +@@ -760,7 +1390,7 @@ static void EncodeJXL (dng_host &host, + + // Add metadata if needed. + +- if (metadata && useContainer) ++ if ((metadata || additionalBoxes) && useContainer) + { + + bool useBoxesOnceFlag = false; +@@ -769,7 +1399,7 @@ static void EncodeJXL (dng_host &host, + + // EXIF. + +- if (includeExif) ++ if (includeExif && metadata) + { + + const dng_resolution *resolution = nullptr; +@@ -808,7 +1438,7 @@ static void EncodeJXL (dng_host &host, + + // XMP. + +- if (includeXMP && metadata->GetXMP ()) ++ if (includeXMP && metadata && metadata->GetXMP ()) + { + + // TODO(erichan): Serialize routine has a forJPEG parameter. Does +@@ -848,7 +1478,7 @@ static void EncodeJXL (dng_host &host, + + // IPTC. + +- if (includeIPTC) ++ if (includeIPTC && metadata) + { + + auto iptcData = metadata->IPTCData (); +@@ -932,7 +1562,7 @@ static void EncodeJXL (dng_host &host, + + } + +- } // metadata and container ++ } // (metadata || additionalBoxes) and container + + // Add color profile info. + +@@ -1136,6 +1766,11 @@ static void EncodeJXL (dng_host &host, + + auto frameSettings = JxlEncoderFrameSettingsCreate (enc, NULL); + ++ DNG_REQUIRE (frameSettings, ++ "JXL encoder - JxlEncoderFrameSettingsCreate failed"); ++ ++ *outFrameSettings = frameSettings; ++ + // Set quality. + + if (isLossless) +@@ -1182,12 +1817,36 @@ static void EncodeJXL (dng_host &host, + "JxlEncoderFrameSettingsSetOption", + ¶llelData); + ++ // Set buffering. ++ ++ if (useStreamingEncoder) ++ { ++ ++ CheckResult ++ (JxlEncoderFrameSettingsSetOption (frameSettings, ++ JXL_ENC_FRAME_SETTING_BUFFERING, ++ (int) 3), ++ "JxlEncoderFrameSettingsSetOption - buffering", ++ ¶llelData); ++ ++ } ++ + // Set various parameters that affect the lossy case. + + // Note that as of libjxl 0.7.0 (2022-9-21), enabling the Gaborish and + // Edge Preserving Filter params in the lossless case will cause the + // result not to be lossless! + ++ #if 0 ++ ++ printf ("lossless: %s\n", isLossless ? "yes" : "no"); ++ ++ printf ("effort: %u\n", unsigned (settings.Effort ())); ++ ++ printf ("distance: %f\n", float (settings.Distance ())); ++ ++ #endif ++ + if (!isLossless) + { + +@@ -1249,7 +1908,7 @@ static void EncodeJXL (dng_host &host, + // TODO(erichan): For now it seems the encoder implementation requires the + // entire image to be allocated at once. + +- JxlPixelFormat pixelFormat; ++ auto &pixelFormat = outPixelFormat; + + memset (&pixelFormat, 0, sizeof (pixelFormat)); + +@@ -1285,46 +1944,6 @@ static void EncodeJXL (dng_host &host, + + } + +- // Set up pixel buffer to hold data to hand off to encoder. As of version +- // 0.7.0 libjxl requires starting from a single whole-image buffer +- // (instead of reading chunks at a time). This is a chunky (row-col-plane +- // interleaved) buffer. +- +- dng_pixel_buffer pixelBuffer = buffer; +- +- pixelBuffer.fPlaneStep = 1; +- pixelBuffer.fColStep = (int32) planes; +- pixelBuffer.fRowStep = (int32) planes * (int32) pixelBuffer.fArea.W (); +- +- const uint64 bytesNeeded = (uint64 (pixelBuffer.fRowStep) * +- uint64 (pixelBuffer.fArea.H ()) * +- uint64 (pixelBuffer.fPixelSize)); +- +- AutoPtr block; +- +- // If not already tightly-packed chunky, then allocate a temp buffer and +- // convert to chunky layout. +- +- if (!SamePixelBufferGeometry (pixelBuffer, buffer)) +- { +- +- #if qLogJXL +- printf ("JXL copy step\n"); +- #endif +- +- block.Reset (new jxl_memory_block (host.Allocator (), +- bytesNeeded)); +- +- pixelBuffer.fData = block->Buffer (); +- +- dng_copy_buffer_task task (buffer, +- pixelBuffer); +- +- host.PerformAreaTask (task, +- pixelBuffer.fArea); +- +- } +- + // TODO(erichan): It seems that preview frames are not yet supported in + // libjxl 0.7.0, so turn this off for now. + +@@ -1356,6 +1975,9 @@ static void EncodeJXL (dng_host &host, + auto previewFrameSettings = JxlEncoderFrameSettingsCreate (enc, + frameSettings); + ++ DNG_REQUIRE (previewFrameSettings, ++ "JXL encoder - JxlEncoderFrameSettingsCreate failed"); ++ + CheckResult + (JxlEncoderAddImageFrame (previewFrameSettings, + &pixelFormat, +@@ -1379,112 +2001,104 @@ static void EncodeJXL (dng_host &host, + + #endif + +- // Add the main image. +- +- CheckResult +- (JxlEncoderAddImageFrame (frameSettings, +- &pixelFormat, +- pixelBuffer.fData, +- bytesNeeded), +- "JxlEncoderAddImageFrame-main", +- ¶llelData); +- +- // Nothing more to encode. ++ return encoder; ++ ++ } + +- JxlEncoderCloseInput (enc); ++/*****************************************************************************/ + +- // Make temp buffer. ++static void EncodeJXL (dng_host &host, ++ dng_stream &stream, ++ const dng_image &srcImage, ++ const dng_jxl_encode_settings &settings, ++ const bool useContainer, ++ const dng_jxl_color_space_info &colorSpaceInfo, ++ const dng_metadata *metadata, ++ const bool includeExif, ++ const bool includeXMP, ++ const bool includeIPTC, ++ const dng_bmff_box_list *additionalBoxes) ++ { + +- const uint32 tempSize = 64 * 1024; ++ #if qDNGValidate && 0 ++ dng_timer timerOuter ("EncodeJXL-Image"); ++ #endif + +- AutoPtr tempBlock (host.Allocate (tempSize)); +- +- uint8 *outBuffer = tempBlock->Buffer_uint8 (); ++ dng_pixel_buffer buffer; + +- size_t outAvailableBytes = (size_t) tempSize; ++ buffer.fArea = srcImage.Bounds (); ++ buffer.fPlanes = srcImage.Planes (); ++ ++ buffer.fPixelType = srcImage.PixelType (); ++ buffer.fPixelSize = TagTypeSize (buffer.fPixelType); + +- uint8 *outBufferPtr = outBuffer; ++ dng_jxl_parallel_runner_data parallelData; + +- // Main encode loop. +- +- for (;;) +- { +- +- JxlEncoderStatus status = JxlEncoderProcessOutput (enc, +- &outBufferPtr, +- &outAvailableBytes); ++ JxlPixelFormat pixelFormat; + +- // Handle errors. ++ JxlEncoderFrameSettings *frameSettings = nullptr; + +- if (status == JXL_ENC_ERROR) +- { +- if (parallelData.fErrorCode == dng_error_user_canceled) +- { +- ThrowUserCanceled (); +- } +- #if qLogJXL +- printf ("Unknown jxl encoder error"); +- #endif +- ThrowBadFormat ("JXL_ENC_ERROR"); +- } ++ // For now, always request streaming (input and output) for images with a ++ // long side over 2K. + +- // Check if we're done. ++ const bool useStreamingEncoder = (srcImage.Bounds ().LongSide () > 2048); + +- else if (status == JXL_ENC_SUCCESS) +- { ++ auto encoder = EncodeJXL_Common (host, ++ buffer, ++ settings, ++ useStreamingEncoder, ++ useContainer, ++ colorSpaceInfo, ++ metadata, ++ includeExif, ++ includeXMP, ++ includeIPTC, ++ additionalBoxes, ++ parallelData, ++ pixelFormat, ++ &frameSettings); + +- uint32 bytesToFlush = (uint32) (outBufferPtr - outBuffer); ++ auto enc = encoder.get (); + +- #if qLogJXL +- printf ("jxl encoder success!! bytes to flush: %u\n", +- bytesToFlush); +- #endif +- +- stream.Put (outBuffer, bytesToFlush); +- +- break; +- +- } ++ jxl_data_writer2 writer (host, stream); + +- // Check if we need to update the output buffer. ++ JxlEncoderOutputProcessor outputProcessor { }; + +- else if (status == JXL_ENC_NEED_MORE_OUTPUT) +- { +- +- uint32 bytesToFlush = (uint32) (outBufferPtr - outBuffer); ++ outputProcessor.opaque = &writer; ++ outputProcessor.get_buffer = jxl_output_get_buffer; ++ outputProcessor.release_buffer = jxl_output_release_buffer; ++ outputProcessor.seek = nullptr; // no seeking ++ outputProcessor.set_finalized_position = output_set_finalized_position; + +- #if qLogJXL +- printf ("--- jxl encoder need more output. bytes to flush: %u\n", +- bytesToFlush); +- #endif +- +- // Copy encoded data to the stream. ++ CheckResult (JxlEncoderSetOutputProcessor (enc, ++ outputProcessor), ++ "JxlEncoderSetOutputProcessor-main", ++ ¶llelData); + +- stream.Put (outBuffer, bytesToFlush); ++ // Add the main image. + +- // Reset temp buffer. +- +- outBufferPtr = outBuffer; ++ jxl_image_chunk_reader reader (host, ++ srcImage, ++ pixelFormat); + +- outAvailableBytes = tempSize; +- +- } ++ JxlChunkedFrameInputSource inputSource { }; + +- else +- { ++ inputSource.opaque = &reader; ++ inputSource.get_color_channels_pixel_format = jxl_image_chunk_reader::GetPixelFormat; ++ inputSource.get_color_channel_data_at = jxl_image_chunk_reader::GetData; ++ inputSource.release_buffer = jxl_image_chunk_reader::Release; ++ ++ CheckResult (JxlEncoderAddChunkedFrame (frameSettings, ++ JXL_TRUE, // is_last_frame ++ inputSource), ++ "JxlEncoderAddChunkedFrame-main", ++ ¶llelData); + +- #if qLogJXL +- +- printf ("Unexpected jxl encoder status 0x%x\n", +- (unsigned) status); ++ stream.Flush (); + +- #endif ++ // Nothing more to encode. + +- ThrowNotYetImplemented ("unhandled jxl encoder status"); +- +- } +- +- } ++ JxlEncoderCloseInput (enc); + + } + +@@ -1492,7 +2106,7 @@ static void EncodeJXL (dng_host &host, + + static void EncodeJXL (dng_host &host, + dng_stream &stream, +- const dng_image &image, ++ const dng_pixel_buffer &inBuffer, + const dng_jxl_encode_settings &settings, + const bool useContainer, + const dng_jxl_color_space_info &colorSpaceInfo, +@@ -1503,72 +2117,76 @@ static void EncodeJXL (dng_host &host, + const dng_bmff_box_list *additionalBoxes) + { + +- // Set up pixel buffer to hold data to hand off to encoder. As of version +- // 0.7.0 libjxl requires starting from a single whole-image buffer +- // (instead of reading chunks at a time). This is a chunky (row-col-plane +- // interleaved) buffer. ++ #if qDNGValidate && 0 ++ dng_timer timerOuter ("EncodeJXL"); ++ #endif + +- const uint32 planes = image.Planes (); ++ dng_pixel_buffer buffer = inBuffer; + +- DNG_REQUIRE (planes == 1 || // monochrome +- planes == 2 || // monochrome + alpha +- planes == 3 || // RGB +- planes == 4, // RGB + alpha +- "Unsupported plane count in EncodeJXL"); ++ dng_jxl_parallel_runner_data parallelData; + +- dng_pixel_buffer pixelBuffer; ++ JxlPixelFormat pixelFormat; + +- pixelBuffer.fArea = image.Bounds (); ++ JxlEncoderFrameSettings *frameSettings = nullptr; ++ ++ const bool useStreamingEncoder = (inBuffer.Area ().LongSide () > 2048); ++ ++ auto encoder = EncodeJXL_Common (host, ++ buffer, ++ settings, ++ useStreamingEncoder, ++ useContainer, ++ colorSpaceInfo, ++ metadata, ++ includeExif, ++ includeXMP, ++ includeIPTC, ++ additionalBoxes, ++ parallelData, ++ pixelFormat, ++ &frameSettings); + +- pixelBuffer.fPlanes = planes; ++ auto enc = encoder.get (); + +- pixelBuffer.fPlaneStep = 1; +- pixelBuffer.fColStep = (int32) planes; +- pixelBuffer.fRowStep = (int32) planes * (int32) pixelBuffer.fArea.W (); ++ jxl_data_writer2 writer (host, stream); + +- pixelBuffer.fPixelType = image.PixelType (); +- pixelBuffer.fPixelSize = TagTypeSize (pixelBuffer.fPixelType); ++ JxlEncoderOutputProcessor outputProcessor { }; + +- const uint64 bytesNeeded = (uint64 (pixelBuffer.fRowStep) * +- uint64 (pixelBuffer.fArea.H ()) * +- uint64 (pixelBuffer.fPixelSize)); ++ outputProcessor.opaque = &writer; ++ outputProcessor.get_buffer = jxl_output_get_buffer; ++ outputProcessor.release_buffer = jxl_output_release_buffer; ++ outputProcessor.seek = nullptr; // no seeking ++ outputProcessor.set_finalized_position = output_set_finalized_position; + +- jxl_memory_block block (host.Allocator (), +- bytesNeeded); ++ CheckResult (JxlEncoderSetOutputProcessor (enc, ++ outputProcessor), ++ "JxlEncoderSetOutputProcessor-main", ++ ¶llelData); + +- pixelBuffer.fData = block.Buffer (); ++ // Add the main image. + +- // Doing a direct "dng_image::Get" is easy but slow because it's +- // single-threaded. Using a pipe reduces time on a 10 megapixel image from +- // 40 ms to 8 ms on a 2017 10-core iMac Pro. ++ jxl_buffer_chunk_reader reader (host, ++ buffer, ++ pixelFormat); + +- // dng_timer timer2 ("EncodeJXL-image-get"); +- +- #if 1 +- +- dng_get_buffer_task task (image, +- pixelBuffer); ++ JxlChunkedFrameInputSource inputSource { }; + +- host.PerformAreaTask (task, +- image.Bounds ()); ++ inputSource.opaque = &reader; ++ inputSource.get_color_channels_pixel_format = jxl_buffer_chunk_reader::GetPixelFormat; ++ inputSource.get_color_channel_data_at = jxl_buffer_chunk_reader::GetData; ++ inputSource.release_buffer = jxl_buffer_chunk_reader::Release; ++ ++ CheckResult (JxlEncoderAddChunkedFrame (frameSettings, ++ JXL_TRUE, // is_last_frame ++ inputSource), ++ "JxlEncoderAddChunkedFrame-main", ++ ¶llelData); + +- #else +- +- image.Get (pixelBuffer); +- +- #endif ++ stream.Flush (); + +- EncodeJXL (host, +- stream, +- pixelBuffer, +- settings, +- useContainer, +- colorSpaceInfo, +- metadata, +- includeExif, +- includeXMP, +- includeIPTC, +- additionalBoxes); ++ // Nothing more to encode. ++ ++ JxlEncoderCloseInput (enc); + + } + +@@ -1659,7 +2277,7 @@ class dng_jxl_box_reader + + #if 1 + +- else if (size > 4 * 1024 * 1024) ++ else if (size > 128 * 1024 * 1024) + { + + #if qLogJXL +@@ -1932,6 +2550,8 @@ void dng_jxl_decoder::Decode (dng_host &host, + + auto dec = decoder.get (); + ++ DNG_REQUIRE (dec, "JXL decoder - JxlDecoderMake failed"); ++ + // Preserve orientation as-in-bitstream (do not reorient). + + CheckResult (JxlDecoderSetKeepOrientation (dec, 1), +@@ -2278,9 +2898,7 @@ void dng_jxl_decoder::Decode (dng_host &host, + // TODO(erichan): Does the choice of this format unduly affect the + // result of JxlDecoderGetColorAsEncodedProfile? + +-// BEGIN GOOGLE MODIFICATION +- // JxlPixelFormat not part of the relevant JXL API's anymore +-// END GOOGLE MODIFICATION ++ //JxlPixelFormat format = { 3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 }; + + #if qDNGValidate + if (gVerbose) +@@ -2290,11 +2908,10 @@ void dng_jxl_decoder::Decode (dng_host &host, + JxlColorEncoding color_encoding; + + if (JXL_DEC_SUCCESS == +-// BEGIN GOOGLE MODIFICATION + JxlDecoderGetColorAsEncodedProfile (dec, ++ //&format, + JXL_COLOR_PROFILE_TARGET_ORIGINAL, + &color_encoding)) +-// END GOOGLE MODIFICATION + { + + // JXL-specific color encoding. +@@ -2439,14 +3056,13 @@ void dng_jxl_decoder::Decode (dng_host &host, + + size_t profile_size; + +-// BEGIN GOOGLE MODIFICATION + CheckResult (JxlDecoderGetICCProfileSize + (dec, +- JXL_COLOR_PROFILE_TARGET_ORIGINAL, ++ //&format, ++ JXL_COLOR_PROFILE_TARGET_DATA, + &profile_size), + "JxlDecoderGetICCProfileSize", + nullptr); +-// END GOOGLE MODIFICATION + + if (profile_size < 132) + { +@@ -2467,15 +3083,14 @@ void dng_jxl_decoder::Decode (dng_host &host, + + auto *profile = profileBlock->Buffer_uint8 (); + +-// BEGIN GOOGLE MODIFICATION + CheckResult (JxlDecoderGetColorAsICCProfile + (dec, +- JXL_COLOR_PROFILE_TARGET_ORIGINAL, ++ //&format, ++ JXL_COLOR_PROFILE_TARGET_DATA, + profile, + profile_size), + "JxlDecoderGetColorAsICCProfile", + nullptr); +-// END GOOGLE MODIFICATION + + fColorSpaceInfo.fICCProfile.Reset (profileBlock.Release ()); + +@@ -3511,4 +4126,4 @@ bool SupportsJXL (const dng_image &image) + return false; + } + +-/*****************************************************************************/ ++/*****************************************************************************/ +\ No newline at end of file +diff --git a/source/dng_jxl.h b/source/dng_jxl.h +index f5b3fef..7407d3d 100644 +--- a/source/dng_jxl.h ++++ b/source/dng_jxl.h +@@ -37,6 +37,10 @@ + + /*****************************************************************************/ + ++#define qLogJXL (qDNGValidate && 0) ++ ++/*****************************************************************************/ ++ + // jxl requires exif data be prepended with a 4-byte TIFF offset which can be + // all zero as long as the exif data comes immediately after. + +diff --git a/source/dng_linearization_info.cpp b/source/dng_linearization_info.cpp +index 5137aa9..a58873a 100644 +--- a/source/dng_linearization_info.cpp ++++ b/source/dng_linearization_info.cpp +@@ -459,6 +459,12 @@ void dng_linearize_plane::Process (const dng_rect &srcTile) + // Process tile. + + dng_rect dstTile = srcTile - fActiveArea.TL (); ++ ++ DNG_REQUIRE ((fSrcImage.Bounds () & srcTile) == srcTile, ++ "Invalid srcTile in dng_linearize_plane::Process"); ++ ++ DNG_REQUIRE ((fDstImage.Bounds () & dstTile) == dstTile, ++ "Invalid dstTile in dng_linearize_plane::Process"); + + dng_const_tile_buffer srcBuffer (fSrcImage, srcTile); + dng_dirty_tile_buffer dstBuffer (fDstImage, dstTile); +@@ -1452,7 +1458,49 @@ real64 dng_linearization_info::MaxBlackLevel (uint32 plane) const + return maxBlack; + + } +- ++ ++/*****************************************************************************/ ++ ++uint16 dng_linearization_info::Stage3BlackLevel (dng_negative &negative, ++ uint32 numPlanes) const ++ { ++ ++ real64 zeroFract = 0.0; ++ ++ for (uint32 plane = 0; plane < numPlanes; plane++) ++ { ++ ++ real64 maxBlackLevel = MaxBlackLevel (plane); ++ real64 whiteLevel = fWhiteLevel [plane]; ++ ++ if (maxBlackLevel > 0.0 && maxBlackLevel < whiteLevel) ++ { ++ ++ zeroFract = Max_real64 (zeroFract, maxBlackLevel / whiteLevel); ++ ++ } ++ ++ } ++ ++ zeroFract = Min_real64 (zeroFract, kMaxStage3BlackLevelNormalized); ++ ++ uint16 dstBlackLevel = (uint16) Round_uint32 (65535.0 * zeroFract); ++ ++ if (negative.GetMosaicInfo ()) ++ { ++ ++ // If we have a mosaic image that supports non-zero black levels, ++ // enforce a minimum black level to give the demosaic algorithms ++ // some "footroom". ++ ++ dstBlackLevel = (uint16) Max_uint32 (dstBlackLevel, 0x0404); ++ ++ } ++ ++ return dstBlackLevel; ++ ++ } ++ + /*****************************************************************************/ + + void dng_linearization_info::Linearize (dng_host &host, +@@ -1468,37 +1516,8 @@ void dng_linearization_info::Linearize (dng_host &host, + dstImage.PixelType () == ttShort) + { + +- real64 zeroFract = 0.0; +- +- for (uint32 plane = 0; plane < srcImage.Planes (); plane++) +- { +- +- real64 maxBlackLevel = MaxBlackLevel (plane); +- real64 whiteLevel = fWhiteLevel [plane]; +- +- if (maxBlackLevel > 0.0 && maxBlackLevel < whiteLevel) +- { +- +- zeroFract = Max_real64 (zeroFract, maxBlackLevel / whiteLevel); +- +- } +- +- } +- +- zeroFract = Min_real64 (zeroFract, kMaxStage3BlackLevelNormalized); +- +- uint16 dstBlackLevel = (uint16) Round_uint32 (65535.0 * zeroFract); +- +- if (negative.GetMosaicInfo ()) +- { +- +- // If we have a mosaic image that supports non-zero black levels, +- // enforce a minimum black level to give the demosaic algorithms +- // some "footroom". +- +- dstBlackLevel = (uint16) Max_uint32 (dstBlackLevel, 0x0404); +- +- } ++ uint16 dstBlackLevel = Stage3BlackLevel (negative, ++ srcImage.Planes ()); + + negative.SetStage3BlackLevel (dstBlackLevel); + +@@ -1512,9 +1531,11 @@ void dng_linearization_info::Linearize (dng_host &host, + forceClipBlackLevel, + srcImage, + dstImage); +- ++ ++ dng_rect overlap = fActiveArea & srcImage.Bounds (); ++ + host.PerformAreaTask (processor, +- fActiveArea); ++ overlap); + + } + +diff --git a/source/dng_linearization_info.h b/source/dng_linearization_info.h +index 70c9976..b3ce1b1 100644 +--- a/source/dng_linearization_info.h ++++ b/source/dng_linearization_info.h +@@ -123,6 +123,11 @@ class dng_linearization_info + const dng_image &srcImage, + dng_image &dstImage); + ++ /// Computes the stage3 black level. ++ ++ uint16 Stage3BlackLevel (dng_negative &negative, ++ uint32 numPlanes) const; ++ + /// Compute black level for one coordinate and sample plane in the image. + /// \param row Row to compute black level for. + /// \param col Column to compute black level for. +diff --git a/source/dng_lossless_jpeg_shared.cpp b/source/dng_lossless_jpeg_shared.cpp +index fe42f7a..586e445 100644 +--- a/source/dng_lossless_jpeg_shared.cpp ++++ b/source/dng_lossless_jpeg_shared.cpp +@@ -1555,7 +1555,7 @@ DNG_ALWAYS_INLINE void dng_lossless_decoder::FillBitBuffer (int32 nbits) + + if (except.ErrorCode () != dng_error_end_of_file) + { +- throw except; ++ throw; // rethrow except + } + + // Some Hasselblad files now use the JPEG end of image marker. +@@ -1570,7 +1570,7 @@ DNG_ALWAYS_INLINE void dng_lossless_decoder::FillBitBuffer (int32 nbits) + if (!((c0 == 0xFF && c1 == 0xD9) || + (c1 == 0xFF && c2 == 0xD9))) + { +- throw except; ++ throw; // rethrow except + } + + // Swallow the case where we hit EOF with the JPEG EOI marker. +diff --git a/source/dng_matrix.cpp b/source/dng_matrix.cpp +index a26c651..9ea880f 100644 +--- a/source/dng_matrix.cpp ++++ b/source/dng_matrix.cpp +@@ -10,6 +10,7 @@ + + #include "dng_assertions.h" + #include "dng_exceptions.h" ++#include "dng_fingerprint.h" + #include "dng_utils.h" + + /*****************************************************************************/ +@@ -353,6 +354,28 @@ bool dng_matrix::AlmostIdentity (real64 slop) const + + /*****************************************************************************/ + ++void dng_matrix::Process (dng_md5_printer_stream &printer) const ++ { ++ ++ printer.Put_uint32 (fRows); ++ printer.Put_uint32 (fCols); ++ ++ for (uint32 j = 0; j < Rows (); j++) ++ { ++ ++ for (uint32 k = 0; k < Cols (); k++) ++ { ++ ++ printer.Put_real64 (fData [j] [k]); ++ ++ } ++ ++ } ++ ++ } ++ ++/*****************************************************************************/ ++ + dng_matrix_3by3::dng_matrix_3by3 () + + : dng_matrix (3, 3) +@@ -802,6 +825,22 @@ dng_matrix dng_vector::AsColumn () const + + } + ++/*****************************************************************************/ ++ ++void dng_vector::Process (dng_md5_printer_stream &printer) const ++ { ++ ++ printer.Put_uint32 (fCount); ++ ++ for (uint32 j = 0; j < Count (); j++) ++ { ++ ++ printer.Put_real64 (fData [j]); ++ ++ } ++ ++ } ++ + /******************************************************************************/ + + dng_vector_3::dng_vector_3 () +diff --git a/source/dng_matrix.h b/source/dng_matrix.h +index a69728c..8ed59f1 100644 +--- a/source/dng_matrix.h ++++ b/source/dng_matrix.h +@@ -19,6 +19,7 @@ + /*****************************************************************************/ + + #include "dng_sdk_limits.h" ++#include "dng_classes.h" + #include "dng_types.h" + + /*****************************************************************************/ +@@ -109,6 +110,8 @@ class dng_matrix + + bool AlmostIdentity (real64 slop = 1.0e-8) const; + ++ void Process (dng_md5_printer_stream &printer) const; ++ + }; + + /*****************************************************************************/ +@@ -247,7 +250,9 @@ class dng_vector + dng_matrix AsDiagonal () const; + + dng_matrix AsColumn () const; +- ++ ++ void Process (dng_md5_printer_stream &printer) const; ++ + }; + + /*****************************************************************************/ +diff --git a/source/dng_memory_stream.cpp b/source/dng_memory_stream.cpp +index ee4005a..f6b5e97 100644 +--- a/source/dng_memory_stream.cpp ++++ b/source/dng_memory_stream.cpp +@@ -56,9 +56,17 @@ dng_memory_stream::~dng_memory_stream () + free (fPageList); + + } +- ++ ++ #if qDNGStreamCheckForUnflushedStreams ++ ++ // Clear fBufferDirty. This class allows stream to be destructed UNFLUSHED. ++ ++ DestructionOfUnflushedInstancesIsAllowed (); ++ ++ #endif ++ + } +- ++ + /*****************************************************************************/ + + uint64 dng_memory_stream::DoGetLength () +diff --git a/source/dng_misc_opcodes.cpp b/source/dng_misc_opcodes.cpp +index 56e1fd0..38ee103 100644 +--- a/source/dng_misc_opcodes.cpp ++++ b/source/dng_misc_opcodes.cpp +@@ -994,15 +994,17 @@ void dng_opcode_DeltaPerRow::ProcessArea (dng_negative &negative, + const dng_rect & /* imageBounds */) + { + +- dng_rect overlap = fAreaSpec.Overlap (dstArea); ++ const dng_rect overlap = fAreaSpec.Overlap (dstArea); + + if (overlap.NotEmpty ()) + { + +- uint32 cols = overlap.W (); +- +- uint32 colPitch = fAreaSpec.ColPitch (); ++ const uint32 rowPitch = fAreaSpec.RowPitch (); ++ const uint32 colPitch = fAreaSpec.ColPitch (); + ++ const uint32 rows = (overlap.H () + rowPitch - 1) / rowPitch; ++ const uint32 cols = (overlap.W () + colPitch - 1) / colPitch; ++ + real32 scale = fScale; + + if (Stage () >= 2 && negative.Stage3BlackLevel () != 0) +@@ -1018,16 +1020,20 @@ void dng_opcode_DeltaPerRow::ProcessArea (dng_negative &negative, + + const real32 *table = fTable->Buffer_real32 () + + ((overlap.t - fAreaSpec.Area ().t) / +- fAreaSpec.RowPitch ()); ++ rowPitch); ++ ++ int32 row = overlap.t; + +- for (int32 row = overlap.t; row < overlap.b; row += fAreaSpec.RowPitch ()) ++ for (uint32 rowIdx = 0; rowIdx < rows; rowIdx++) + { + + real32 rowDelta = *(table++) * scale; + + real32 *dPtr = buffer.DirtyPixel_real32 (row, overlap.l, plane); + +- for (uint32 col = 0; col < cols; col += colPitch) ++ uint32 col = 0; ++ ++ for (uint32 colIdx = 0; colIdx < cols; colIdx++) + { + + real32 x = dPtr [col]; +@@ -1035,14 +1041,18 @@ void dng_opcode_DeltaPerRow::ProcessArea (dng_negative &negative, + real32 y = x + rowDelta; + + dPtr [col] = Pin_real32 (-1.0f, y, 1.0f); ++ ++ col += colPitch; + +- } ++ } // cols ++ ++ row += rowPitch; + +- } ++ } // rows + +- } ++ } // planes + +- } ++ } // overlap not empty + + } + +@@ -1208,15 +1218,18 @@ void dng_opcode_DeltaPerColumn::ProcessArea (dng_negative &negative, + const dng_rect & /* imageBounds */) + { + +- dng_rect overlap = fAreaSpec.Overlap (dstArea); ++ const dng_rect overlap = fAreaSpec.Overlap (dstArea); + + if (overlap.NotEmpty ()) + { + +- uint32 rows = (overlap.H () + fAreaSpec.RowPitch () - 1) / +- fAreaSpec.RowPitch (); +- +- int32 rowStep = buffer.RowStep () * fAreaSpec.RowPitch (); ++ const uint32 rowPitch = fAreaSpec.RowPitch (); ++ const uint32 colPitch = fAreaSpec.ColPitch (); ++ ++ const uint32 rows = (overlap.H () + rowPitch - 1) / rowPitch; ++ const uint32 cols = (overlap.W () + colPitch - 1) / colPitch; ++ ++ const int32 rowStep = buffer.RowStep () * rowPitch; + + real32 scale = fScale; + +@@ -1233,16 +1246,18 @@ void dng_opcode_DeltaPerColumn::ProcessArea (dng_negative &negative, + + const real32 *table = fTable->Buffer_real32 () + + ((overlap.l - fAreaSpec.Area ().l) / +- fAreaSpec.ColPitch ()); ++ colPitch); + +- for (int32 col = overlap.l; col < overlap.r; col += fAreaSpec.ColPitch ()) ++ int32 col = overlap.l; ++ ++ for (uint32 colIdx = 0; colIdx < cols; colIdx++) + { + + real32 colDelta = *(table++) * scale; + + real32 *dPtr = buffer.DirtyPixel_real32 (overlap.t, col, plane); + +- for (uint32 row = 0; row < rows; row++) ++ for (uint32 rowIdx = 0; rowIdx < rows; rowIdx++) + { + + real32 x = dPtr [0]; +@@ -1253,13 +1268,15 @@ void dng_opcode_DeltaPerColumn::ProcessArea (dng_negative &negative, + + dPtr += rowStep; + +- } ++ } // rows ++ ++ col += colPitch; + +- } ++ } // columns + +- } ++ } // planes + +- } ++ } // overlap not empty + + } + +@@ -1396,15 +1413,17 @@ void dng_opcode_ScalePerRow::ProcessArea (dng_negative &negative, + const dng_rect & /* imageBounds */) + { + +- dng_rect overlap = fAreaSpec.Overlap (dstArea); ++ const dng_rect overlap = fAreaSpec.Overlap (dstArea); + + if (overlap.NotEmpty ()) + { + +- uint32 cols = overlap.W (); +- +- uint32 colPitch = fAreaSpec.ColPitch (); ++ const uint32 rowPitch = fAreaSpec.RowPitch (); ++ const uint32 colPitch = fAreaSpec.ColPitch (); + ++ const uint32 rows = (overlap.H () + rowPitch - 1) / rowPitch; ++ const uint32 cols = (overlap.W () + colPitch - 1) / colPitch; ++ + real32 blackOffset = 0.0f; + + if (Stage () >= 2 && negative.Stage3BlackLevel () != 0) +@@ -1420,16 +1439,20 @@ void dng_opcode_ScalePerRow::ProcessArea (dng_negative &negative, + + const real32 *table = fTable->Buffer_real32 () + + ((overlap.t - fAreaSpec.Area ().t) / +- fAreaSpec.RowPitch ()); ++ rowPitch); ++ ++ int32 row = overlap.t; + +- for (int32 row = overlap.t; row < overlap.b; row += fAreaSpec.RowPitch ()) ++ for (uint32 rowIdx = 0; rowIdx < rows; rowIdx++) + { + + real32 rowScale = *(table++); + + real32 *dPtr = buffer.DirtyPixel_real32 (row, overlap.l, plane); +- +- for (uint32 col = 0; col < cols; col += colPitch) ++ ++ int32 col = 0; ++ ++ for (uint32 colIdx = 0; colIdx < cols; colIdx++) + { + + real32 x = dPtr [col]; +@@ -1437,14 +1460,18 @@ void dng_opcode_ScalePerRow::ProcessArea (dng_negative &negative, + real32 y = (x - blackOffset) * rowScale + blackOffset; + + dPtr [col] = Pin_real32 (-1.0f, y, 1.0f); ++ ++ col += colPitch; + +- } ++ } // cols + +- } ++ row += rowPitch; ++ ++ } // rows + +- } ++ } // planes + +- } ++ } // overlap not empty + + } + +@@ -1581,15 +1608,18 @@ void dng_opcode_ScalePerColumn::ProcessArea (dng_negative &negative, + const dng_rect & /* imageBounds */) + { + +- dng_rect overlap = fAreaSpec.Overlap (dstArea); ++ const dng_rect overlap = fAreaSpec.Overlap (dstArea); + + if (overlap.NotEmpty ()) + { + +- uint32 rows = (overlap.H () + fAreaSpec.RowPitch () - 1) / +- fAreaSpec.RowPitch (); +- +- int32 rowStep = buffer.RowStep () * fAreaSpec.RowPitch (); ++ const uint32 rowPitch = fAreaSpec.RowPitch (); ++ const uint32 colPitch = fAreaSpec.ColPitch (); ++ ++ const uint32 rows = (overlap.H () + rowPitch - 1) / rowPitch; ++ const uint32 cols = (overlap.W () + colPitch - 1) / colPitch; ++ ++ const int32 rowStep = buffer.RowStep () * rowPitch; + + real32 blackOffset = 0.0f; + +@@ -1606,16 +1636,18 @@ void dng_opcode_ScalePerColumn::ProcessArea (dng_negative &negative, + + const real32 *table = fTable->Buffer_real32 () + + ((overlap.l - fAreaSpec.Area ().l) / +- fAreaSpec.ColPitch ()); ++ colPitch); ++ ++ int32 col = overlap.l; + +- for (int32 col = overlap.l; col < overlap.r; col += fAreaSpec.ColPitch ()) ++ for (uint32 colIdx = 0; colIdx < cols; colIdx++) + { + + real32 colScale = *(table++); + + real32 *dPtr = buffer.DirtyPixel_real32 (overlap.t, col, plane); + +- for (uint32 row = 0; row < rows; row++) ++ for (uint32 rowIdx = 0; rowIdx < rows; rowIdx++) + { + + real32 x = dPtr [0]; +@@ -1626,13 +1658,15 @@ void dng_opcode_ScalePerColumn::ProcessArea (dng_negative &negative, + + dPtr += rowStep; + +- } ++ } // rows + +- } ++ col += colPitch; ++ ++ } // cols + +- } ++ } // planes + +- } ++ } // overlap not empty + + } + +diff --git a/source/dng_negative.cpp b/source/dng_negative.cpp +index 4564a8c..2126416 100644 +--- a/source/dng_negative.cpp ++++ b/source/dng_negative.cpp +@@ -578,7 +578,7 @@ dng_fingerprint dng_metadata::IPTCDigest (bool includePadding) const + if (IPTCLength ()) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + + const uint8 *data = (const uint8 *) IPTCData (); + +@@ -604,7 +604,7 @@ dng_fingerprint dng_metadata::IPTCDigest (bool includePadding) const + + } + +- printer.Process (data, count); ++ printer.ProcessPtr (data, count); + + return printer.Result (); + +@@ -727,9 +727,9 @@ void dng_metadata::SetEmbeddedXMP (dng_host &host, + if (SetXMP (host, buffer, count)) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (buffer, count); ++ printer.ProcessPtr (buffer, count); + + fEmbeddedXMPDigest = printer.Result (); + +@@ -1606,7 +1606,8 @@ bool dng_negative::GetProfileByIDFromList (const dng_profile_metadata_list &list + + bool dng_negative::GetProfileToEmbedFromList (const dng_profile_metadata_list &list, + const dng_metadata & /* metadata */, +- dng_camera_profile &foundProfile) const ++ dng_camera_profile &foundProfile, ++ bool /* skipAdobeStandard = false */) const + { + + // How many profiles in list? +@@ -1704,7 +1705,8 @@ bool dng_negative::GetProfileByID (const dng_camera_profile_id &id, + /*****************************************************************************/ + + bool dng_negative::GetProfileToEmbed (const dng_metadata &metadata, +- dng_camera_profile &foundProfile) const ++ dng_camera_profile &foundProfile, ++ bool skipAdobeStandard /* = false */) const + { + + // Monochrome negatives don't have profiles. +@@ -1724,7 +1726,8 @@ bool dng_negative::GetProfileToEmbed (const dng_metadata &metadata, + + return GetProfileToEmbedFromList (list, + metadata, +- foundProfile); ++ foundProfile, ++ skipAdobeStandard); + + } + +@@ -1757,7 +1760,7 @@ dng_fingerprint dng_negative::FindImageDigest (dng_host &host, + const dng_image &image) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + + dng_pixel_buffer buffer (image.Bounds (), + 0, +@@ -1844,8 +1847,8 @@ dng_fingerprint dng_negative::FindImageDigest (dng_host &host, + + #endif + +- printer.Process (buffer.fData, +- count); ++ printer.ProcessPtr (buffer.fData, ++ count); + + } + +@@ -2039,9 +2042,9 @@ class dng_find_new_raw_image_digest_task : public dng_area_task + + #endif + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (buffer.fData, count); ++ printer.ProcessPtr (buffer.fData, count); + + fTileHash [tileIndex] = printer.Result (); + +@@ -2050,12 +2053,12 @@ class dng_find_new_raw_image_digest_task : public dng_area_task + dng_fingerprint Result () + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + + for (uint32 tileIndex = 0; tileIndex < fTileCount; tileIndex++) + { + +- printer.Process (fTileHash [tileIndex] . data, 16); ++ printer.Process (fTileHash [tileIndex]); + + } + +@@ -2163,11 +2166,11 @@ void dng_negative::FindNewRawImageDigest (dng_host &host) const + + // Combine the two digests into a single digest. + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (fNewRawImageDigest.data, 16); ++ printer.Process (fNewRawImageDigest); + +- printer.Process (maskDigest.data, 16); ++ printer.Process (maskDigest); + + fNewRawImageDigest = printer.Result (); + +@@ -2291,7 +2294,7 @@ void dng_negative::ValidateRawImageDigest (dng_host &host) + + for (uint32 j = 4; j < 16; j++) + { +- matchLast12 = matchLast12 && (oldDigest.data [j] == fRawImageDigest.data [j]); ++ matchLast12 = matchLast12 && (oldDigest.Data () [j] == fRawImageDigest.Data () [j]); + } + + if (matchLast12) +@@ -2305,10 +2308,10 @@ void dng_negative::ValidateRawImageDigest (dng_host &host) + // bytes, but for all those files that I have seen so far the + // resulting first four bytes are 0x08 0x00 0x00 0x00. + +- if (oldDigest.data [0] == 0x08 && +- oldDigest.data [1] == 0x00 && +- oldDigest.data [2] == 0x00 && +- oldDigest.data [3] == 0x00) ++ if (oldDigest.Data () [0] == 0x08 && ++ oldDigest.Data () [1] == 0x00 && ++ oldDigest.Data () [2] == 0x00 && ++ oldDigest.Data () [3] == 0x00) + { + return; + } +@@ -2337,12 +2340,12 @@ dng_fingerprint dng_negative::RawDataUniqueID () const + if (fRawDataUniqueID.IsValid () && fEnhanceParams.NotEmpty ()) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (fRawDataUniqueID.data, 16); ++ printer.Process (fRawDataUniqueID); + +- printer.Process (fEnhanceParams.Get (), +- fEnhanceParams.Length ()); ++ printer.ProcessPtr (fEnhanceParams.Get (), ++ fEnhanceParams.Length ()); + + return printer.Result (); + +@@ -2364,7 +2367,7 @@ void dng_negative::FindRawDataUniqueID (dng_host &host) const + if (RawDataUniqueID ().IsNull ()) + { + +- dng_md5_printer_stream printer; ++ dng_md5_printer_le_stream printer; + + // If we have a raw lossy image, it is much faster to use its digest as + // part of the unique ID since the data size is much smaller. We +@@ -2376,8 +2379,7 @@ void dng_negative::FindRawDataUniqueID (dng_host &host) const + + FindRawLossyCompressedImageDigest (host); + +- printer.Put (fRawLossyCompressedImageDigest.data, +- uint32 (sizeof (fRawLossyCompressedImageDigest.data))); ++ printer.Put (fRawLossyCompressedImageDigest); + + } + +@@ -2388,7 +2390,7 @@ void dng_negative::FindRawDataUniqueID (dng_host &host) const + + FindNewRawImageDigest (host); + +- printer.Put (fNewRawImageDigest.data, 16); ++ printer.Put (fNewRawImageDigest); + + } + +@@ -2463,10 +2465,10 @@ void dng_negative::FindOriginalRawFileDigest () const + if (fOriginalRawFileDigest.IsNull () && fOriginalRawFileData.Get ()) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (fOriginalRawFileData->Buffer (), +- fOriginalRawFileData->LogicalSize ()); ++ printer.ProcessPtr (fOriginalRawFileData->Buffer (), ++ fOriginalRawFileData->LogicalSize ()); + + fOriginalRawFileDigest = printer.Result (); + +@@ -3975,7 +3977,7 @@ void dng_negative::PostParse (dng_host &host, + + // If the MakerNote is safe, preserve it as a MakerNote. + +- if (IsMakerNoteSafe ()) ++ if (IsMakerNoteSafe () && info.fTIFFBlockOriginalOffset != kDNGStreamInvalidOffset) + { + + AutoPtr block (host.Allocate (shared.fMakerNoteCount)); +@@ -4430,7 +4432,17 @@ void dng_negative::ReadStage1Image (dng_host &host, + if (fStage1Image->PixelType () == ttFloat) + { + +- SetRawFloatBitDepth (rawIFD.fBitsPerSample [0]); ++ // If we are reading from a lossy JXL floating point image, and ++ // we are not keeping the lossy compressed data, we need to treat ++ // the raw floating point image as 32 bit, and ignore the bit ++ // depth that we fed into the JXL decoder. ++ ++ if (lossyImage.Get () || rawIFD.fCompression != ccJXL) ++ { ++ ++ SetRawFloatBitDepth (rawIFD.fBitsPerSample [0]); ++ ++ } + + } + +@@ -7342,6 +7354,16 @@ void dng_negative::ResizeTransparencyToMatchStage3 (dng_host &host, + (TransparencyMask ()->PixelType () != ttByte && convertTo8Bit)) + { + ++ if (host.SaveDNGVersion () != dngVersion_None && !convertTo8Bit) ++ { ++ ++ if (!fRawTransparencyMask.Get ()) ++ { ++ fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); ++ } ++ ++ } ++ + AutoPtr newMask (host.Make_dng_image (fStage3Image->Bounds (), + 1, + convertTo8Bit ? +@@ -7362,7 +7384,9 @@ void dng_negative::ResizeTransparencyToMatchStage3 (dng_host &host, + { + fRawTransparencyMaskBitDepth = 8; + } +- ++ ++ fRawLossyCompressedTransparencyMask.Reset (); ++ + } + + } +diff --git a/source/dng_negative.h b/source/dng_negative.h +index 786329a..f470f00 100644 +--- a/source/dng_negative.h ++++ b/source/dng_negative.h +@@ -2132,7 +2132,8 @@ class dng_negative + // Returns the camera profile to embed when saving to DNG. + + bool GetProfileToEmbed (const dng_metadata &metadata, +- dng_camera_profile &foundProfile) const; ++ dng_camera_profile &foundProfile, ++ bool skipAdobeStandard = false) const; + + // API for AsShotProfileName. + +@@ -2702,6 +2703,13 @@ class dng_negative + // Returns the raw image data. + + const dng_image & RawImage () const; ++ ++ // Clears any saved raw image. ++ ++ void ClearRawImage () ++ { ++ fRawImage.Reset (); ++ } + + // Returns the raw image black level in 16-bit space. + +@@ -3143,7 +3151,8 @@ class dng_negative + + virtual bool GetProfileToEmbedFromList (const dng_profile_metadata_list &list, + const dng_metadata &metadata, +- dng_camera_profile &foundProfile) const; ++ dng_camera_profile &foundProfile, ++ bool skipAdobeStandard = false) const; + + void CompressTransparencyMaskJXL (dng_host &host, + dng_image_writer &writer, +diff --git a/source/dng_parse_utils.cpp b/source/dng_parse_utils.cpp +index 3f90b7d..86f5de6 100644 +--- a/source/dng_parse_utils.cpp ++++ b/source/dng_parse_utils.cpp +@@ -435,6 +435,7 @@ const char * LookupTagCode (uint32 parentCode, + { tcEnhanceParams, "EnhanceParams" }, + { tcProfileGainTableMap, "ProfileGainTableMap" }, + { tcProfileGainTableMap2, "ProfileGainTableMap2" }, ++ { tcC2PAManifest, "C2PAManifest" }, + { tcRGBTablesDraft, "RGBTablesDraft" }, + { tcRGBTables, "RGBTables" }, + { tcBigTableDigests, "BigTableDigests" }, +@@ -2455,7 +2456,7 @@ void DumpFingerprint (const dng_fingerprint &p) + + for (uint32 j = 0; j < 16; j++) + { +- printf ("%02x", p.data [j]); ++ printf ("%02x", p.Data () [j]); + } + + printf (">"); +diff --git a/source/dng_pixel_buffer.cpp b/source/dng_pixel_buffer.cpp +index 66c2ea0..990799b 100644 +--- a/source/dng_pixel_buffer.cpp ++++ b/source/dng_pixel_buffer.cpp +@@ -574,6 +574,30 @@ void dng_pixel_buffer::SetConstant (const dng_rect &area, + uint32 value) + { + ++ if (planes == 0) ++ { ++ ReportWarning ("dng_pixel_buffer::SetConstant: planes = 0"); ++ return; ++ } ++ ++ DNG_REQUIRE ((area & Area ()) == area, ++ "SetConstant: area OOB"); ++ ++ { ++ ++ uint32 endPlane = plane + planes; ++ ++ uint32 endFullPlane = fPlane + fPlanes; ++ ++ DNG_REQUIRE (plane < endPlane, ++ "SetConstant: planes overflow"); ++ ++ DNG_REQUIRE (fPlane <= plane && ++ endPlane <= endFullPlane, ++ "SetConstant: planes range"); ++ ++ } ++ + uint32 rows = area.H (); + uint32 cols = area.W (); + +@@ -743,6 +767,42 @@ void dng_pixel_buffer::CopyArea (const dng_pixel_buffer &src, + uint32 planes) + { + ++ if (planes == 0) ++ { ++ ReportWarning ("dng_pixel_buffer::CopyArea: planes = 0"); ++ return; ++ } ++ ++ DNG_REQUIRE ((area & src.Area ()) == area, ++ "CopyArea: area OOB src"); ++ ++ DNG_REQUIRE ((area & Area ()) == area, ++ "CopyArea: area OOB dst"); ++ ++ { ++ ++ uint32 endSrcPlane = srcPlane + planes; ++ uint32 endDstPlane = dstPlane + planes; ++ ++ uint32 endFullSrcPlane = src.fPlane + src.fPlanes; ++ uint32 endFullDstPlane = fPlane + fPlanes; ++ ++ DNG_REQUIRE (srcPlane < endSrcPlane, ++ "CopyArea: src planes overflow"); ++ ++ DNG_REQUIRE (dstPlane < endDstPlane, ++ "CopyArea: dst planes overflow"); ++ ++ DNG_REQUIRE (src.fPlane <= srcPlane && ++ endSrcPlane <= endFullSrcPlane, ++ "CopyArea: src planes range"); ++ ++ DNG_REQUIRE ( fPlane <= dstPlane && ++ endDstPlane <= endFullDstPlane, ++ "CopyArea: dst planes range"); ++ ++ } ++ + uint32 rows = area.H (); + uint32 cols = area.W (); + +diff --git a/source/dng_preview.cpp b/source/dng_preview.cpp +index 9ea94df..40d1874 100644 +--- a/source/dng_preview.cpp ++++ b/source/dng_preview.cpp +@@ -76,7 +76,7 @@ dng_preview_tag_set::dng_preview_tag_set (dng_tiff_directory &directory, + , fSettingsDigest (preview.fInfo.fSettingsDigest) + + , fSettingsDigestTag (tcPreviewSettingsDigest, +- fSettingsDigest.data, ++ fSettingsDigest.Data (), + 16) + + , fColorSpaceTag (tcPreviewColorSpace, +@@ -723,6 +723,78 @@ dng_basic_tag_set * dng_raw_preview::AddTagSet (dng_host & /* host */, + } + + /*****************************************************************************/ ++/*****************************************************************************/ ++/*****************************************************************************/ ++ ++class dng_hdr_gain_map_preview_tag_set: public dng_preview_tag_set ++ { ++ ++ private: ++ ++ tag_data_ptr fGainMapMetadataTag; ++ ++ public: ++ ++ dng_hdr_gain_map_preview_tag_set (dng_tiff_directory &directory, ++ const dng_hdr_gain_map_preview &preview, ++ const dng_ifd &ifd) ++ ++ : dng_preview_tag_set (directory, preview, ifd) ++ ++ , fGainMapMetadataTag (tcGainMapMetadata_ISO_21496_1, ++ ttUndefined, // type ++ 0, // count ++ nullptr) // data ++ ++ { ++ ++ if (preview.fGainMapMetadata) ++ { ++ ++ const auto &block = *preview.fGainMapMetadata; ++ ++ auto &tag = fGainMapMetadataTag; ++ ++ tag.SetData (block.Buffer ()); ++ tag.SetCount (block.LogicalSize ()); ++ ++ directory.Add (&tag); ++ ++ } ++ ++ } ++ ++ }; ++ ++/*****************************************************************************/ ++ ++void dng_hdr_gain_map_preview::SetIFDInfo (dng_host &host, ++ const dng_image &image) ++ { ++ ++ dng_raw_preview::SetIFDInfo (host, image); ++ ++ fIFD.fNewSubFileType = sfPreviewGainMap; ++ ++ fIFD.fPhotometricInterpretation = piGainMap; ++ ++ } ++ ++/*****************************************************************************/ ++ ++dng_basic_tag_set * dng_hdr_gain_map_preview::AddTagSet (dng_host & /* host */, ++ dng_tiff_directory &directory) const ++ { ++ ++ return new dng_hdr_gain_map_preview_tag_set (directory, ++ *this, ++ fIFD); ++ ++ } ++ ++/*****************************************************************************/ ++/*****************************************************************************/ ++/*****************************************************************************/ + + void dng_mask_preview::SetIFDInfo (dng_host &host, + const dng_image &image) +diff --git a/source/dng_preview.h b/source/dng_preview.h +index 4eb0425..badd6d3 100644 +--- a/source/dng_preview.h ++++ b/source/dng_preview.h +@@ -250,6 +250,27 @@ class dng_raw_preview: public dng_preview + + /*****************************************************************************/ + ++class dng_hdr_gain_map_preview: public dng_raw_preview ++ { ++ ++ public: ++ ++ // ISO 21496-1 data here. ++ ++ std::shared_ptr fGainMapMetadata; ++ ++ public: ++ ++ void SetIFDInfo (dng_host &host, ++ const dng_image &image) override; ++ ++ dng_basic_tag_set * AddTagSet (dng_host &host, ++ dng_tiff_directory &directory) const override; ++ ++ }; ++ ++/*****************************************************************************/ ++ + class dng_mask_preview: public dng_preview + { + +diff --git a/source/dng_read_image.cpp b/source/dng_read_image.cpp +index e1ab7fd..b48cda4 100644 +--- a/source/dng_read_image.cpp ++++ b/source/dng_read_image.cpp +@@ -2899,10 +2899,10 @@ void dng_read_tiles_task::ProcessTask (uint32 tileIndex, + if (fLossyTileDigest) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (compressedBuffer->Buffer (), +- byteCount); ++ printer.ProcessPtr (compressedBuffer->Buffer (), ++ byteCount); + + fLossyTileDigest [tileIndex] = printer.Result (); + +@@ -3715,10 +3715,10 @@ void dng_read_image::Read (dng_host &host, + if (lossyDigest) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (compressedBuffer->Buffer (), +- subByteCount); ++ printer.ProcessPtr (compressedBuffer->Buffer (), ++ subByteCount); + + lossyTileDigests [tileIndex] = printer.Result (); + +@@ -3759,20 +3759,19 @@ void dng_read_image::Read (dng_host &host, + if (fJPEGTables.Get ()) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (fJPEGTables->Buffer (), +- fJPEGTables->LogicalSize ()); ++ printer.ProcessPtr (fJPEGTables->Buffer (), ++ fJPEGTables->LogicalSize ()); + + lossyTileDigests.push_back (printer.Result ()); + + } + +- dng_md5_printer printer2; ++ dng_md5_direct_printer printer2; + + for (const auto &digest : lossyTileDigests) +- printer2.Process (digest.data, +- uint32 (sizeof (digest.data))); ++ printer2.Process (digest); + + *lossyDigest = printer2.Result (); + +diff --git a/source/dng_render.cpp b/source/dng_render.cpp +index a4f8a13..db54a9c 100644 +--- a/source/dng_render.cpp ++++ b/source/dng_render.cpp +@@ -1796,7 +1796,12 @@ void dng_render_task::ProcessArea (uint32 threadIndex, + + else + { +- ++ ++ // Expect 4 src planes here. This method expects and only ++ // supports fSrcPlanes being 1,3 or 4. ++ ++ DNG_REQUIRE (fSrcPlanes == 4, "fSrcPlanes"); ++ + const real32 *sPtrD = sPtrC + srcBuffer.fPlaneStep; + + DoBaselineABCDtoRGB (sPtrA, +diff --git a/source/dng_resample.cpp b/source/dng_resample.cpp +index d74247c..c87ac6c 100644 +--- a/source/dng_resample.cpp ++++ b/source/dng_resample.cpp +@@ -160,18 +160,50 @@ void dng_resample_weights::Initialize (real64 scale, + + uint32 j; + ++ // Ensure scale is positive and finite. ++ ++ if (!isfinite (scale)) ++ { ++ ++ ThrowProgramError ("scale not finite"); ++ ++ } ++ ++ if (scale <= 0.0) ++ { ++ ++ ThrowProgramError ("scale not positive"); ++ ++ } ++ + // We only adjust the kernel size for scale factors less than 1.0. + + scale = Min_real64 (scale, 1.0); +- ++ + // Find radius of this kernel. ++ ++ fRadius = ConvertDoubleToUint32 (std::ceil (kernel.Extent () / scale)); ++ ++ // Ensure fRadius > 0. ++ ++ if (fRadius == 0) ++ { ++ ++ ThrowProgramError ("fRadius zero"); ++ ++ } + +- fRadius = (uint32) (kernel.Extent () / scale + 0.9999); +- +- // Width is twice the radius. +- +- uint32 width = fRadius * 2; +- ++ // Width is twice the radius. Check for overflow. ++ ++ uint32 width = 0; ++ ++ if (!SafeUint32Mult (fRadius, 2, &width)) ++ { ++ ++ ThrowOverflow ("Arithmetic overflow computing width"); ++ ++ } ++ + // Round to each set to weights to a multiple of 8 entries. + + if (!RoundUpUint32ToMultiple (width, 8, &fWeightStep)) +diff --git a/source/dng_safe_arithmetic.h b/source/dng_safe_arithmetic.h +index fb5933a..9e4ad1b 100644 +--- a/source/dng_safe_arithmetic.h ++++ b/source/dng_safe_arithmetic.h +@@ -123,9 +123,11 @@ int64 SafeInt64MultSlow(int64 arg1, int64 arg2); + #ifdef __ANDROID__ + // While clang says it supports __builtin_smull_overflow, the Android NDK + // doesn't use the right runtime library per https://bugs.llvm.org/show_bug.cgi?id=28629 +-// BEGIN GOOGLE MODIFICATION +-#define __USE_BUILTIN_SMULL_OVERFLOW __has_builtin(__builtin_smull_overflow) +-// END GOOGLE MODIFICATION ++// Disable the __builtin_smull_overflow for now until we verify that it works fine for android. ++// Currently we use it on other platforms like macOS, iOS and Linux so we should be good. We will ++// revisit this when LrM android team has bandwidth to test it. ++// krishnas - 11/9/2025 ++#define __USE_BUILTIN_SMULL_OVERFLOW (0 && __has_builtin(__builtin_smull_overflow)) + #else + #define __USE_BUILTIN_SMULL_OVERFLOW __has_builtin(__builtin_smull_overflow) + #endif // __ANDROID__ +diff --git a/source/dng_shared.cpp b/source/dng_shared.cpp +index fb3cc0b..57e08de 100644 +--- a/source/dng_shared.cpp ++++ b/source/dng_shared.cpp +@@ -1411,7 +1411,7 @@ bool dng_camera_profile_info::ParseTag (dng_stream &stream, + if (pgtm && gVerbose) + { + +- dng_md5_printer printer; ++ dng_md5_printer_le_stream printer; + + pgtm->AddDigest (printer); + +@@ -1525,7 +1525,7 @@ bool dng_camera_profile_info::ParseTag (dng_stream &stream, + if (gVerbose && fMaskedRGBTables) + { + +- dng_md5_printer printer; ++ dng_md5_printer_le_stream printer; + + fMaskedRGBTables->AddDigest (printer); + +@@ -1932,9 +1932,9 @@ bool dng_shared::Parse_ifd0 (dng_stream &stream, + + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (data, count); ++ printer.ProcessPtr (data, count); + + printf ("IPTCDigest: "); + +@@ -1959,9 +1959,9 @@ bool dng_shared::Parse_ifd0 (dng_stream &stream, + if (removed != 0) + { + +- dng_md5_printer printer; ++ dng_md5_direct_printer printer; + +- printer.Process (data, count); ++ printer.ProcessPtr (data, count); + + printf ("IPTCDigest (ignoring zero padding): "); + +@@ -2068,7 +2068,34 @@ bool dng_shared::Parse_ifd0 (dng_stream &stream, + break; + + } ++ ++ case tcC2PAManifest: ++ { + ++ CheckTagType (parentCode, tagCode, tagType, ttUndefined); ++ ++ fC2PAManifestOffset = stream.Position (); ++ fC2PAManifestCount = tagCount; ++ ++ #if qDNGValidate ++ ++ if (gVerbose) ++ { ++ ++ printf ("C2PAManifest: offset = %llu, count = %u\n", ++ (unsigned long long) fC2PAManifestOffset, ++ (unsigned) fC2PAManifestCount); ++ ++ DumpHexAscii (stream, fC2PAManifestCount); ++ ++ } ++ ++ #endif ++ ++ break; ++ ++ } ++ + case tcDNGVersion: + { + +@@ -2682,7 +2709,7 @@ bool dng_shared::Parse_ifd0 (dng_stream &stream, + if (!CheckTagCount (parentCode, tagCode, tagCount, 16)) + return false; + +- stream.Get (fRawImageDigest.data, 16); ++ stream.Get (fRawImageDigest); + + #if qDNGValidate + +@@ -2712,7 +2739,7 @@ bool dng_shared::Parse_ifd0 (dng_stream &stream, + if (!CheckTagCount (parentCode, tagCode, tagCount, 16)) + return false; + +- stream.Get (fNewRawImageDigest.data, 16); ++ stream.Get (fNewRawImageDigest); + + #if qDNGValidate + +@@ -2742,7 +2769,7 @@ bool dng_shared::Parse_ifd0 (dng_stream &stream, + if (!CheckTagCount (parentCode, tagCode, tagCount, 16)) + return false; + +- stream.Get (fRawDataUniqueID.data, 16); ++ stream.Get (fRawDataUniqueID); + + #if qDNGValidate + +@@ -2830,7 +2857,7 @@ bool dng_shared::Parse_ifd0 (dng_stream &stream, + if (!CheckTagCount (parentCode, tagCode, tagCount, 16)) + return false; + +- stream.Get (fOriginalRawFileDigest.data, 16); ++ stream.Get (fOriginalRawFileDigest); + + #if qDNGValidate + +@@ -3386,7 +3413,7 @@ bool dng_shared::Parse_ifd0 (dng_stream &stream, + + dng_fingerprint fingerprint; + +- stream.Get (fingerprint.data, 16); ++ stream.Get (fingerprint); + + fBigTableDigests.push_back (fingerprint); + +@@ -3555,8 +3582,8 @@ bool dng_shared::Parse_ifd0 (dng_stream &stream, + dng_fingerprint groupDigest; + dng_fingerprint instanceDigest; + +- stream.Get (groupDigest .data, 16); +- stream.Get (instanceDigest.data, 16); ++ stream.Get (groupDigest); ++ stream.Get (instanceDigest); + + fBigTableGroupIndex.insert (std::make_pair (groupDigest, + instanceDigest)); +diff --git a/source/dng_shared.h b/source/dng_shared.h +index 296c37b..4d0e7f0 100644 +--- a/source/dng_shared.h ++++ b/source/dng_shared.h +@@ -282,6 +282,9 @@ class dng_shared + + dng_image_sequence_info fImageSequenceInfo; + ++ uint32 fC2PAManifestCount = 0; ++ uint64 fC2PAManifestOffset = 0; ++ + public: + + dng_shared (); +diff --git a/source/dng_stream.cpp b/source/dng_stream.cpp +index e6ad8c3..cfb7d20 100644 +--- a/source/dng_stream.cpp ++++ b/source/dng_stream.cpp +@@ -1,5 +1,5 @@ + /*****************************************************************************/ +-// Copyright 2006-2019 Adobe Systems Incorporated ++// Copyright 2006-2025 Adobe Systems Incorporated + // All Rights Reserved. + // + // NOTICE: Adobe permits you to use, modify, and distribute this file in +@@ -12,9 +12,11 @@ + #include "dng_auto_ptr.h" + #include "dng_bottlenecks.h" + #include "dng_exceptions.h" ++#include "dng_fingerprint.h" + #include "dng_globals.h" + #include "dng_flags.h" + #include "dng_memory.h" ++#include "dng_rect.h" + #include "dng_tag_types.h" + #include "dng_assertions.h" + +@@ -74,7 +76,18 @@ dng_stream::dng_stream (const void *data, + + dng_stream::~dng_stream () + { +- ++ ++ #if qDNGStreamCheckForUnflushedStreams ++ ++ if (fBufferDirty) ++ { ++ ++ fprintf (stderr, "*** Error: dng_stream not flushed ***\n"); ++ ++ } ++ ++ #endif ++ + } + + /*****************************************************************************/ +@@ -369,6 +382,16 @@ void dng_stream::Get (void *data, uint32 count, uint32 maxOverRead) + } + + } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Get (dng_fingerprint &digest) ++ { ++ ++ Get (digest.MutableData (), ++ uint32 (dng_fingerprint::kDNGFingerprintSize)); ++ ++ } + + /*****************************************************************************/ + +@@ -531,6 +554,168 @@ void dng_stream::Put (const void *data, + fLength = Max_uint64 (Length (), fPosition); + + } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put (const dng_rect &area) ++ { ++ ++ Put_int32 (area.t); ++ Put_int32 (area.l); ++ Put_int32 (area.b); ++ Put_int32 (area.r); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put (const dng_rect_real64 &area) ++ { ++ ++ Put_real64 (area.t); ++ Put_real64 (area.l); ++ Put_real64 (area.b); ++ Put_real64 (area.r); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put (const dng_point &pt) ++ { ++ ++ Put_int32 (pt.h); ++ Put_int32 (pt.v); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put (const dng_point_real64 &pt) ++ { ++ ++ Put_real64 (pt.h); ++ Put_real64 (pt.v); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put (const dng_srational &value) ++ { ++ ++ Put_int32 (value.n); ++ Put_int32 (value.d); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put (const dng_urational &value) ++ { ++ ++ Put_uint32 (value.n); ++ Put_uint32 (value.d); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put (const dng_fingerprint &digest) ++ { ++ ++ Put (digest.Data (), ++ uint32 (dng_fingerprint::kDNGFingerprintSize)); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put (const dng_string &str) ++ { ++ ++ Put (str.Get (), ++ str.Length ()); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put_swap4 (const void *data, ++ const uint32 countMul4) ++ { ++ ++ if (countMul4 == 0) ++ return; ++ ++ DNG_REQUIRE (RoundUp4 (countMul4) == countMul4, ++ "countMul4 must be a multiple 4"); ++ ++ if (SwapBytes ()) ++ { ++ ++ const uint32 elems = countMul4 / 4; ++ ++ const uint32 *ptr = (const uint32 *) data; ++ ++ for (uint32 i = 0; i < elems; i++) ++ { ++ ++ Put_uint32 (ptr [i]); ++ ++ } ++ ++ } ++ ++ else ++ { ++ ++ // Byte-swapping not needed, so just put directly. ++ ++ Put (data, countMul4); ++ ++ } ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_stream::Put_swap8 (const void *data, ++ const uint32 countMul8) ++ { ++ ++ if (countMul8 == 0) ++ return; ++ ++ DNG_REQUIRE (RoundUp8 (countMul8) == countMul8, ++ "countMul8 must be a multiple 8"); ++ ++ if (SwapBytes ()) ++ { ++ ++ const uint32 elems = countMul8 / 8; ++ ++ const uint64 *ptr = (const uint64 *) data; ++ ++ for (uint32 i = 0; i < elems; i++) ++ { ++ ++ Put_uint64 (ptr [i]); ++ ++ } ++ ++ } ++ ++ else ++ { ++ ++ // Byte-swapping not needed, so just put directly. ++ ++ Put (data, countMul8); ++ ++ } ++ ++ } + + /*****************************************************************************/ + +diff --git a/source/dng_stream.h b/source/dng_stream.h +index 3df465a..c8f37e2 100644 +--- a/source/dng_stream.h ++++ b/source/dng_stream.h +@@ -17,6 +17,8 @@ + + /*****************************************************************************/ + ++#include "dng_flags.h" ++ + #include "dng_auto_ptr.h" + #include "dng_classes.h" + #include "dng_types.h" +@@ -27,6 +29,12 @@ + + /*****************************************************************************/ + ++#ifndef qDNGStreamCheckForUnflushedStreams ++#define qDNGStreamCheckForUnflushedStreams (qDNGValidate) ++#endif ++ ++/*****************************************************************************/ ++ + // Constants for invalid offset in streams. + + const uint64 kDNGStreamInvalidOffset = (uint64) (int64) -1; +@@ -100,7 +108,20 @@ class dng_stream: private dng_uncopyable + virtual void DoWrite (const void *data, + uint32 count, + uint64 offset); +- ++ ++ #if qDNGStreamCheckForUnflushedStreams ++ ++ // Call this from dtor of derived class for derived classes ++ // which allow for destruction of an UNFLUSHED writable stream. ++ // This helps with fBufferDirty check in dtor. ++ ++ void DestructionOfUnflushedInstancesIsAllowed () ++ { ++ fBufferDirty = false; ++ } ++ ++ #endif ++ + public: + + /// Construct a stream with initial data. +@@ -252,6 +273,8 @@ class dng_stream: private dng_uncopyable + + void Get (void *data, uint32 count, uint32 maxOverRead=0); + ++ void Get (dng_fingerprint &digest); ++ + /// Seek to a new position in stream for writing. + + void SetWritePosition (uint64 offset); +@@ -271,6 +294,24 @@ class dng_stream: private dng_uncopyable + + void Put (const void *data, uint32 count); + ++ /// Write data to stream, performing 4-byte aligned swapping if ++ /// needed. If byte-swapping is not needed, this routine is equivalent ++ /// to Put. ++ /// \param data Buffer of data to write to stream. ++ /// \param countMul4 Length of data in bytes. Must be a multiple of 4. ++ ++ void Put_swap4 (const void *data, ++ uint32 countMul4); ++ ++ /// Write data to stream, performing 8-byte aligned swapping if ++ /// needed. If byte-swapping is not needed, this routine is equivalent ++ /// to Put. ++ /// \param data Buffer of data to write to stream. ++ /// \param countMul8 Length of data in bytes. Must be a multiple of 8. ++ ++ void Put_swap8 (const void *data, ++ uint32 countMul8); ++ + /// Get an unsigned 8-bit integer from stream and advance read position. + /// \retval One unsigned 8-bit integer. + /// \exception dng_exception with fErrorCode equal to dng_error_end_of_file +@@ -350,7 +391,7 @@ class dng_stream: private dng_uncopyable + /// \exception dng_exception with fErrorCode equal to dng_error_end_of_file + /// if not enough data in stream. + +- uint32 Get_uint32(); ++ uint32 Get_uint32 (); + + #if !qDNGBigEndian + inline // ep, enable compiler inlining +@@ -406,6 +447,21 @@ class dng_stream: private dng_uncopyable + Put_uint8 ((uint8) x); + } + ++ /// Put a Boolean as a single byte. ++ ++ void Put_bool (bool x) ++ { ++ Put_uint8 (x ? 1 : 0); ++ } ++ ++ /// Put a size_t as 8 bytes. ++ ++ void Put_size (size_t x) ++ { ++ static_assert (sizeof (size_t) <= 8, "size_t > 8 bytes"); ++ Put_uint64 (uint64 (x)); ++ } ++ + /// Get one 16-bit integer from stream and advance read position. + /// Byte swap if byte swapping is turned on. + /// \retval One 16-bit integer. +@@ -445,7 +501,31 @@ class dng_stream: private dng_uncopyable + { + Put_uint32 ((uint32) x); + } ++ ++ /// Put one dng_rect (as four sequential calls to Put_int32) and advance write position. ++ /// Byte swap if byte swapping is turned on. ++ /// \param x One dng_rect. + ++ void Put (const dng_rect &r); ++ ++ void Put (const dng_rect_real64 &r); ++ ++ /// Put one dng_fingerprint and advance write position. ++ /// No byte swapping. ++ /// \param x One dng_fingerprint. ++ ++ void Put (const dng_fingerprint &digest); ++ ++ void Put (const dng_point &pt); ++ ++ void Put (const dng_point_real64 &pt); ++ ++ void Put (const dng_srational &value); ++ ++ void Put (const dng_urational &value); ++ ++ void Put (const dng_string &value); ++ + /// Get one 64-bit integer from stream and advance read position. + /// Byte swap if byte swapping is turned on. + /// \retval One 64-bit integer. +diff --git a/source/dng_string.cpp b/source/dng_string.cpp +index 066b7ea..aca3405 100644 +--- a/source/dng_string.cpp ++++ b/source/dng_string.cpp +@@ -1239,7 +1239,7 @@ bool dng_string::TrimTrailingBlanks () + + bool didTrim = false; + +- if (fData.get () && fData->back () == ' ') ++ if (fData.get () && !fData->empty () && fData->back () == ' ') + { + + const char *s = fData->c_str (); +@@ -2173,8 +2173,8 @@ int32 dng_string::Compare (const dng_string &s, + + dng_lock_std_mutex lockMutex (gProtectUCCalls); + +- UCCollateOptions aOptions = kUCCollateStandardOptions | +- kUCCollatePunctuationSignificantMask; ++ UCCollateOptions aOptions = static_cast(kUCCollateStandardOptions) | ++ static_cast(kUCCollatePunctuationSignificantMask); + + if (digitsAsNumber) + { +@@ -2459,10 +2459,10 @@ int32 dng_string::Compare (const dng_string &s, + + size_t dng_string_hash::operator () (const dng_string &s) const + { +- +- dng_md5_printer printer; + +- printer.Process (s.Get ()); ++ dng_md5_direct_printer printer; ++ ++ printer.ProcessString (s.Get ()); + + auto digest = printer.Result (); + +diff --git a/source/dng_string.h b/source/dng_string.h +index 221f86c..4634a20 100644 +--- a/source/dng_string.h ++++ b/source/dng_string.h +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + + /*****************************************************************************/ + +@@ -190,6 +191,22 @@ typedef std::unordered_set dng_string_ordered_table; ++ ++/*****************************************************************************/ ++ + #endif // __dng_string__ + + /*****************************************************************************/ +diff --git a/source/dng_tag_codes.h b/source/dng_tag_codes.h +index b60789c..095f089 100644 +--- a/source/dng_tag_codes.h ++++ b/source/dng_tag_codes.h +@@ -215,6 +215,7 @@ enum + tcWaterDepth = 37891, + tcAcceleration = 37892, + tcCameraElevationAngle = 37893, ++ tcDJIPrivateEXIFKeywords = 40094, + tcFlashPixVersion = 40960, + tcColorSpace = 40961, + tcPixelXDimension = 40962, +@@ -369,6 +370,7 @@ enum + tcBigTableOffsets = 52541, + tcBigTableByteCounts = 52542, + tcProfileGainTableMap2 = 52544, ++ tcC2PAManifest = 52545, + tcColumnInterleaveFactor = 52547, + tcImageSequenceInfo = 52548, + tcProfileToneMethod = 52549, +@@ -379,6 +381,7 @@ enum + tcJXLEffort = 52554, + tcJXLDecodeSpeed = 52555, + tcBigTableGroupIndex = 52556, ++ tcGainMapMetadata_ISO_21496_1 = 52557, + tcKodakKDCPrivateIFD = 65024 + }; + +diff --git a/source/dng_tag_values.h b/source/dng_tag_values.h +index bccb29f..0e6df48 100644 +--- a/source/dng_tag_values.h ++++ b/source/dng_tag_values.h +@@ -52,6 +52,10 @@ enum + + sfGainMap = 32, + ++ // Preview (reduced resolution) Gain Map. ++ ++ sfPreviewGainMap = sfPreviewImage + sfGainMap, ++ + // Preview image for non-primary settings. + + sfAltPreviewImage = 0x10001, +diff --git a/source/dng_update_meta.cpp b/source/dng_update_meta.cpp +index ad85397..be1a09b 100644 +--- a/source/dng_update_meta.cpp ++++ b/source/dng_update_meta.cpp +@@ -562,7 +562,7 @@ bool dng_tag_updater::GetArray_Fingerprint (dng_file_updater &updater, + + dng_fingerprint fingerprint; + +- updater.Stream ().Get (fingerprint.data, 16); ++ updater.Stream ().Get (fingerprint); + + values.push_back (fingerprint); + +@@ -1976,7 +1976,7 @@ void dng_ifd_updater::UpdateAdobeData (dng_file_updater &updater, + + dng_fingerprint oldDigest; + +- stream.Get (oldDigest.data, 16); ++ stream.Get (oldDigest); + + if (iptcDigest != oldDigest) + { +@@ -1990,7 +1990,7 @@ void dng_ifd_updater::UpdateAdobeData (dng_file_updater &updater, + + #endif + +- stream.Put (iptcDigest.data, 16); ++ stream.Put (iptcDigest); + + } + +@@ -2966,7 +2966,7 @@ void DNGUpdateMetadata (dng_host &host, + tcRawDataUniqueID, + ttByte, + 16, +- rawDataUniqueID.data); ++ rawDataUniqueID.Data ()); + + } + +diff --git a/source/dng_validate.cpp b/source/dng_validate.cpp +index bfd3582..9843a37 100644 +--- a/source/dng_validate.cpp ++++ b/source/dng_validate.cpp +@@ -54,7 +54,7 @@ + + /*****************************************************************************/ + +-#define kDNGValidateVersion "1.7.1" ++#define kDNGValidateVersion (kDNGSDK_GetInfoVersion) + + /*****************************************************************************/ + +@@ -688,14 +688,14 @@ int main (int argc, char *argv []) + + fprintf (stderr, + "\n" +- "dng_validate, version " kDNGValidateVersion " " ++ "dng_validate, version %s " + #if qDNG64Bit + "(64-bit)" + #else + "(32-bit)" + #endif + "\n" +- "Copyright 2005-2023 Adobe Systems, Inc.\n" ++ "Copyright 2005-2026 Adobe Systems, Inc.\n" + "\n" + "Usage: %s [options] file1 file2 ...\n" + "\n" +@@ -730,6 +730,7 @@ int main (int argc, char *argv []) + "-tif Write TIF image to \".tif\"\n" + "-dng Write DNG image to \".dng\"\n" + "\n", ++ kDNGValidateVersion, + argv [0]); + + return 1; +diff --git a/source/dng_xmp.cpp b/source/dng_xmp.cpp +index 6af9885..746a38f 100644 +--- a/source/dng_xmp.cpp ++++ b/source/dng_xmp.cpp +@@ -5133,6 +5133,54 @@ void dng_xmp::DocOpsUpdateMetadata (const char *srcMIME) + + /*****************************************************************************/ + ++dng_string dng_xmp::GetProvenance () const ++ { ++ ++ dng_string s; ++ ++ if (GetString (XMP_NS_DC_TERMS, "provenance", s)) ++ { ++ ++ return s; ++ ++ } ++ ++ return dng_string (); ++ ++ } ++ ++/*****************************************************************************/ ++ ++void dng_xmp::SetProvenance (const char *s) ++ { ++ ++ if (s && s [0]) ++ { ++ ++ dng_string ss; ++ ++ ss.Set (s); ++ ++ SetString (XMP_NS_DC_TERMS, "provenance", ss); ++ ++ } ++ ++ else ++ { ++ ++ if (Exists (XMP_NS_DC_TERMS, "provenance")) ++ { ++ ++ Remove (XMP_NS_DC_TERMS, "provenance"); ++ ++ } ++ ++ } ++ ++ } ++ ++/*****************************************************************************/ ++ + #endif // qDNGUseXMP + + /*****************************************************************************/ +diff --git a/source/dng_xmp.h b/source/dng_xmp.h +index 8a071fd..1be0fe6 100644 +--- a/source/dng_xmp.h ++++ b/source/dng_xmp.h +@@ -348,6 +348,15 @@ class dng_xmp + + #endif + ++ // Other public XMP settings. ++ ++ // Provenance: ++ // https://www.dublincore.org/specifications/dublin-core/dcmi-terms/terms/provenance/ ++ ++ dng_string GetProvenance () const; ++ ++ void SetProvenance (const char *s); ++ + protected: + + static void TrimDecimal (char *s); +diff --git a/source/dng_xmp_sdk.cpp b/source/dng_xmp_sdk.cpp +index cc369c1..f4f765f 100644 +--- a/source/dng_xmp_sdk.cpp ++++ b/source/dng_xmp_sdk.cpp +@@ -45,10 +45,6 @@ + + #define XMP_StaticBuild 1 + +-#if qiPhone +-#undef UNIX_ENV +-#endif +- + #include "XMP.incl_cpp" + + /*****************************************************************************/ +@@ -60,6 +56,7 @@ const char *XMP_NS_PHOTOSHOP = "http://ns.adobe.com/photoshop/1.0/"; + const char *XMP_NS_XAP = "http://ns.adobe.com/xap/1.0/"; + const char *XMP_NS_XAP_RIGHTS = "http://ns.adobe.com/xap/1.0/rights/"; + const char *XMP_NS_DC = "http://purl.org/dc/elements/1.1/"; ++const char *XMP_NS_DC_TERMS = "http://purl.org/dc/terms/"; + const char *XMP_NS_XMP_NOTE = "http://ns.adobe.com/xmp/note/"; + const char *XMP_NS_MM = "http://ns.adobe.com/xap/1.0/mm/"; + +@@ -68,6 +65,8 @@ const char *XMP_NS_CRSS = "http://ns.adobe.com/camera-raw-saved-settings/1.0/ + const char *XMP_NS_CRD = "http://ns.adobe.com/camera-raw-defaults/1.0/"; + const char *XMP_NS_CRLCP = "http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/"; + ++const char *XMP_NS_VFS = "http://ns.adobe.com/video-foundation-settings/1.0/"; ++ + const char *XMP_NS_LR = "http://ns.adobe.com/lightroom/1.0/"; + + const char *XMP_NS_LCP = "http://ns.adobe.com/photoshop/1.0/camera-profile"; +@@ -77,6 +76,8 @@ const char *XMP_NS_AUX = "http://ns.adobe.com/exif/1.0/aux/"; + const char *XMP_NS_IPTC = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"; + const char *XMP_NS_IPTC_EXT = "http://iptc.org/std/Iptc4xmpExt/2008-02-29/"; + ++const char *XMP_NS_PLUS = "http://ns.useplus.org/ldf/xmp/1.0/"; ++ + const char *XMP_NS_CRX = "http://ns.adobe.com/lightroom-settings-experimental/1.0/"; + + const char *XMP_NS_DNG = "http://ns.adobe.com/dng/1.0/"; +@@ -300,6 +301,18 @@ void dng_xmp_sdk::InitializeSDK (dng_xmp_namespace * extraNamespaces, + &ss); + + } ++ ++ // Register VideoFoundation namespace ++ ++ { ++ ++ TXMP_STRING_TYPE ss (""); ++ ++ SXMPMeta::RegisterNamespace (XMP_NS_VFS, ++ "vfs", ++ &ss); ++ ++ } + + // Register LR namespace + +@@ -420,7 +433,23 @@ void dng_xmp_sdk::InitializeSDK (dng_xmp_namespace * extraNamespaces, + &ss); + + } ++ ++ // Note: IPTC EXT and plus namespaces already registered by the ++ // XMP SDK. + ++ // Register DC_TERMS namespace ++ // NOTE: Someday this may appear in a new XMP library drop and obviate the need to do it here? ++ ++ { ++ ++ TXMP_STRING_TYPE ss (""); ++ ++ SXMPMeta::RegisterNamespace (XMP_NS_DC_TERMS, ++ "dcterms", ++ &ss); ++ ++ } ++ + // Register extra namespaces. + + if (extraNamespaces != NULL) +diff --git a/source/dng_xmp_sdk.h b/source/dng_xmp_sdk.h +index fc485e0..bbb8f43 100644 +--- a/source/dng_xmp_sdk.h ++++ b/source/dng_xmp_sdk.h +@@ -29,6 +29,7 @@ extern const char *XMP_NS_PHOTOSHOP; + extern const char *XMP_NS_XAP; + extern const char *XMP_NS_XAP_RIGHTS; + extern const char *XMP_NS_DC; ++extern const char *XMP_NS_DC_TERMS; + extern const char *XMP_NS_XMP_NOTE; + extern const char *XMP_NS_MM; + +@@ -37,6 +38,8 @@ extern const char *XMP_NS_CRSS; + extern const char *XMP_NS_CRD; + extern const char *XMP_NS_CRLCP; + ++extern const char *XMP_NS_VFS; ++ + extern const char *XMP_NS_LR; + + extern const char *XMP_NS_LCP; +@@ -46,6 +49,8 @@ extern const char *XMP_NS_AUX; + extern const char *XMP_NS_IPTC; + extern const char *XMP_NS_IPTC_EXT; + ++extern const char *XMP_NS_PLUS; ++ + extern const char *XMP_NS_CRX; + + extern const char *XMP_NS_DNG; +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2025-10-10_14-16_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch b/asb/2026-03/frameworks/base/2025-10-10_14-16_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch new file mode 100644 index 0000000..ae42506 --- /dev/null +++ b/asb/2026-03/frameworks/base/2025-10-10_14-16_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch @@ -0,0 +1,3965 @@ +From d6df825fda3aa29cff7af05357005322152210fd Mon Sep 17 00:00:00 2001 +From: Ioana Alexandru +Date: Wed, 24 Sep 2025 17:52:01 +0200 +Subject: [PATCH] Enforce a hard limit for the size of images to be decoded +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Although standards like JPEG allow for sizes as large as +65,535×65,535 pixels, we realistically cannot fit such large images in +memory. + +Created a test gif since it's the smallest format I could find. It's +larger than strictly necessary for the test in case we want to increase +the hard limit in the future. + +Bug: 444671303 +Test: manually verified that a notification that causes a crashloop +without this change, doesn't anymore +Test: LocalImageResolverTest +Flag: EXEMPT CVE_FIX + +(cherry picked from commit 449f35b532d5f680b90c8f9d8150010e7f5f30df) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:4bceac8d45f07c272eced0b8da51513415d7d248 +Merged-In: I0c95cfeabe169630286e7af8577c4495c2b1015f +Change-Id: I0c95cfeabe169630286e7af8577c4495c2b1015f +--- + .../internal/widget/LocalImageResolver.java | 16 ++++++++++++++++ + .../coretests/res/drawable/test16000x16000.gif | Bin 0 -> 189995 bytes + .../widget/LocalImageResolverTest.java | 8 ++++++++ + 3 files changed, 24 insertions(+) + create mode 100644 core/tests/coretests/res/drawable/test16000x16000.gif + +diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java +index c5d74fc5abe4..6351c0e631ac 100644 +--- a/core/java/com/android/internal/widget/LocalImageResolver.java ++++ b/core/java/com/android/internal/widget/LocalImageResolver.java +@@ -46,6 +46,12 @@ public class LocalImageResolver { + @VisibleForTesting + static final int DEFAULT_MAX_SAFE_ICON_SIZE_PX = 480; + ++ /** ++ * If an image is larger than this, we won't even attempt to decode it, as we risk taking up all ++ * of the device memory. ++ */ ++ private static final int DEFAULT_DECODE_HARD_LIMIT_PX = 4096; ++ + /** + * Resolve an image from the given Uri using {@link ImageDecoder} if it contains a + * bitmap reference. +@@ -252,6 +258,16 @@ public class LocalImageResolver { + private static void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, + int maxWidth, int maxHeight) { + final Size size = info.getSize(); ++ ++ if (size.getWidth() > DEFAULT_DECODE_HARD_LIMIT_PX ++ || size.getHeight() > DEFAULT_DECODE_HARD_LIMIT_PX) { ++ // The image is larger than what we can reasonably expect to decode without filling up ++ // the device memory, so let's bail. ++ throw new RuntimeException( ++ "Image dimensions (" + size.getWidth() + "x" + size.getHeight() ++ + ") exceed the maximum allowed size."); ++ } ++ + final int originalSize = Math.max(size.getHeight(), size.getWidth()); + final int maxSize = Math.max(maxWidth, maxHeight); + final double ratio = (originalSize > maxSize) +diff --git a/core/tests/coretests/res/drawable/test16000x16000.gif b/core/tests/coretests/res/drawable/test16000x16000.gif +new file mode 100644 +index 0000000000000000000000000000000000000000..d62dded384135243c514253c600c1e30c931a780 +GIT binary patch +literal 189995 +zcmZ?wbh9u|Y_MyvYiD5iKMF=eU<8MN4g&)N11K*zaQtWZ&ne@vVZp&>4q>gB6B`yD +zZWmDYn&Yu?(a~-Rdwi{%g@huXy%gj+Op!}VvosMv8T4Iyu3VMvDaL$t*frCj@X=a_te(a*ViW;?vnN1 +zw&v#MjLWNHPj6d$dwap-Q**tyue-au;`6J!r?;=azrTT*Th3?4hKGkcgtg<&?AZAD +z_ylF|c|JQgJv}|cIQ!n2otvMZU*Oy==eujm%gZZ*SI3>*we|J&4aukH`R?BK_V$kA +z*Z0ou-v0jnfo5)bzdbuXK0Yy7JO13Bou8jySnNIDZ||eAWv;!(H3F^$JP +zCf70^_gXw#@wm_C+l$Bj4s4oFCb)=YKAGsDw(`j&AG4QFCI`4_KAjR0micsQMB2)y +z(_+eAKAoP>rul3}%CyX9Gc%U0d^Rg*+skLO3yx_%pHp%z^ZDG0XDgr2tNHfw`TPbp +ztrrVg#Ijy2>`+_vVo{ITs~3wWxM{syG9@hQ<LuU0IXmi21o +zie;-_ty;6~)vMJTj{Vnqy=Kd`tk-LIJX`g8-JWl+UavpEru}Ba5wYwy8&9aMezWO} +z+3Po(FSu#H-Et)?`|Z{nX{+CEyHoc1?e+(4+V6HenU?)-=Zj^l-|c#{?e)9eAC76i +z-}B{K_WQj*o~?er@6Wf_@AosX>wGxCBA)Z%Acy*z4~KZn-+VYM;I8xWh)8(O$Di1f8zuf>$V{dzs2UH99Kl-~DMWP0AOmn)X9`}Jzg_IJNtZ#b^^ +z`^}c?dB5N8c)srUyFK6E{eFLdUH{LABjWjgKAuot|L4;g^Y?!~UvSs|`{hb_{@<@R +z(%1j}cBlOP-|r9F_5b~NGClv_&lk(r|NHf3`}=>tKOEQp|L4o~{QrM{JYWC+-=FXA +z|Nm#;S-`;VaeztcLj#A;0w(bj2iUAWGz#=AU{Uut$QAXWN#e``HuDn)`KmrNEATAj +zaQ8SQH0wi)hR;H-@Dqo`c713y=vl~SJd>&mxiK9!IsJK6XW%StPdo#8JJfkKGA8izSYG95b5ru_wc4 +zvDEbw$INzp>@Db7Ec4vsxYezXeHCXG%Y8p_-0s)M{sx{U3hbUIoTNTY=(jIyXO`-fpFAD+>(lfDJj)E)J|< +z=h>{N&$DivS!TBVEdtGRf`m%t-cZHMqsS9mZUlt1Vu5eNJy4V%jFIF7{P@S**aj(!<^B(xh2mmT35{^a?+9Y1*zYOAUHg`lNeZo^|WXGK;e-{mM^W +zp7-m^atGd30qtH_7D;_w;o-X~X!@xu%dEbx4Cq}IvfS(Hs;IB4BF?S~+kWcmx~i|M +z6L?og9QV4mY1Y>@8NREduAjQLZP(Ye1-+|do_k&2b?fW8inFWZzMs0j@7LG$4ZLd- +z*u8HYlKQrx!*@-R_~{$RtiElW(7Pr@-TUULsBfEQoL!S(=*uKhCbN`+oZVzhB??Gw^L_VE1{zB>m$6hu?-K@iPzD +ztbZI7=-beu?(>i<`o|%Oa~s;s&phO-{&84=Z)1nM&m*DPKaOblZR`p^^GIy>kD~^C +z8++1y9!uT+am?b}#=i11kL7;^s#pES)%Uy(kuGcr5We8 +zEHgj*(y#j0W03pYtnsRN8SE)ZO6H->&nl* +zj{E)V`T@Re8`^!}BuW3ial&ugrs-$jq*?#Id7*FHmgT;0v!Z|Bx^ZsXw(V!%=2ic` +z{eW-#j^n=Xie~@5^TKcYuIp#tmF@n0_e0eFpv= +z2iW~SG)ez?z~R5+kodU|ZPtGt3iR(dqVD&xEBenPiSs*-nVM3+;Yi7D@ko +z;o-mQ()4p*mRbLO8PLD$%5uN2tD^tDia5XP+V*o_*H!<0oxs2Q#&N%In`Zxgli|Pn +z*7bAWw(b7=wxECao#%evcisN`uHyXed*9D}-}n3P`v(3!57_;G9FqR`p~HXABk}V; +zj#>ZvIH7;f6LtTer=tITnsI*5GxPI5&sG2Xynui23wQrtmuCO_vciAwtMK!`uI>K! +zbwmH&H|hSrZ{7a)ZO8e&@5;~rzW4jz_XGU%zaQ}L|NrB-|G!VO|NnX6zyH_u^Z&l>{{Qzw|NcME +z{r~^E{r}&O^ZWmOKmY&V@BjZ9BpR438dxG4*eV)0W;Af^XyCcg!1tp;K%!B|qERHG +zQLLg-Vn(CXjz*aqjdDL46(pLJESgjzn$#+qG-fnu?P$`u(WLjI$v~pn$fDUKqS>sX +z*aEeKUxzc+LA2VQX<;YD%vt;v}NsR%em2(_oJ;qqP@tXy(FT&tfIYQMtjwc +z_L>{*bwAo0Bs!WbI$9z++A2CaW^{Dz=;*o8(f9vH#{`MaNfw<`B08s4bk3O3IcrDf +zoEx3Vy;mZ7uT}Kkn9+M{NAH~* +zz4w0fK9J~pWYPB|qVHKn--{W2uXgmkxzYFTN8bmD{!bSDUn2UyRrLRu(f?~l|DPNE +z|9Uf`H^iA%egr%@8`4v$>~Lw(@P?!msL)$m^r;_=k%JJ)9Zdt +zZ;+hPWI3ZHaz#uC=RK>O_hRO}S3BpuxjFCM&v_pt=YO)C|0Qz%x61iHX3qb$bN-*3 +z^Z)&v&mgsc$!Y;h)B?7u1st;$aP3;ab87+LuLS~93x%u}ibO3Gt6C^AYoXMxg)+Am +z%KciXAhk%zYLQCRBDJbT8nYH@?OLRBYmwfsMFvufjjR@%L@hR}T5K_EvDL1{Hn$er +z{aWlGwZzG4iA&THx2h!`vzB=6TH86%uH`kime>7S +z-XOK2$!bMQ)QYyM6&(_ +z-ut!ofz-N3R_mTbt$S9r?!~NiuXe3_b8FqZU+X?dt^Z`T{!7&QZ&mAm%v%3z*ZM!V +z*8lspo5C=L3)#t^(K|*O={JfG-hwo+Pz8V_9ngGn+&8k8(D8QiQa7Xzk0L9?9EoYH{0Ca +zZ1;P!gY*_B>n$$PTimL*c+B47wR?-t?Ja)4w**LU4YJ-E61_F7dTYe&tx>zT#@yZ- +z_j_xC^tL4HZ7I>)(yF&*%-)u@dt1)!ZF#@96-aL{vff@2y}hh@d&TVSRlB#>+}>XI +zdwYZQjwb6JEzvvLs&{nE-qE#tN6+mYeZO~1kls1Tdgqkroztp!&X~P(*6y8iZttA; +zd*=e_U5l)DEs5T>ta{gq*}GQl-nHiTu64h6ZIIr*$$Iye=-u0@ckh_Jd)MyWdv5RE +z_j~sN={<+6_Z*4dbF6yLiP?Kj?cQ_d_MUUU_gs+Pd&zq5mFT_Ks`uWQz4zAcy?1W! +zz4v?Xga6X|9$D{u620$P^}ZLg_r2P^@6GLf?|$$5Aie*S_5Lr>`@dE1|1o?2uig9q +z+}{81_kISM157ptSYi&a)g0iMbAW5l0iHVt`2HLakU1!1b5JDapjgd8i8%+Q_8gSC +zb5QQjK?RvZN;Zd7Vh*X*9MYI`NNdj_ojZs0{v0xpIc#Ke*d*q#S}k=FG90GbiSpIko4^nLB6B{W)_%=IkY#vsYryUaL8KW6s%Id(Pgu +zbN1e!vkzp>J+e9XB<9?+nsYDaoO`wB+?zY+-u*fELFW7?oAX~{&VQ>p|6|ViUwh8~ +zxpV&CpYsf|7np1>u*6!$d)dhLvdRD0%VxEgE#_Xf+I!jN +z?q$2bmmOrUIN4rtiM`@hd&Oh!6|cQleC}TH`+Fro_G*yr)sWb$VYOEy=3b53do||n +z)wsV`6J)O?*Nwznq#NM1%dvnIzo3r-boOAc) +zyuUXW$lh9HduvJTt!1^hR?NM%YVWNzcWjYA-jhnaSz4n9!ku6D7EjQ +z%)N(l{~jvHJyNoJq!RZ?t?rS=yhmF59_ieBr1$TUf!t#wyT>MRkIm{HTg-cGwePXb +zy~lR{9y`cAak6{j68FTd?uo~|CtmxW_}qKq_wPx7+|wYtry+4q!|I+!%zGNO?`h1v +zr*Z$DCdfTYvU`>i_bjdMS;o9)S^J*l+Yi83dtSBgdCk4& +zb^o3>$h~N?d(jg2qOI;l$NzaRy7s;3x%Z;)--`)yFDKc(oD%nPTHVVT^Ip!{_j1m? +zm-GI;Tp;&qk=?5$aj%xuy;?Ev)vA53*4%rw?%%5oa<4bpz1|Y{dRyJ=9rIrA+V^_T +zz1RExy*?oK=8)Z+BXMtz)x9|}@6D-wZ_eC%bMD`p3vzES*}c6I_x4)d+Z*%V-rD!} +z&b_zy{=I!5_wJG1yC-q)p4Gj3G4I{0eed4fd-v|&yAN{jKiR$i68HXF-TNQ&-v8S7 +z{?EPl|NgyakpIAB|A8g`16%zEj`<(B_J82H|AFuS2LbtyLiQg;;y;Sjf0UU2QELB3 +znfo8*{(n@E|DwB1NqNJ|Ls4U#D6xc|7zn8>+FRTAvG5>qj{_i#S +zzt{c$-XQ;@$^J)6{ExQ!A06|5bnXAqbN@%*{~r_Ne@?RhIVJw*wECYj=Kq|v|L2_h +zKj;1bxj_EcBKu!U;(slx|FvTNuT}eht-1ee-Tz-3Vyu +z|NlSt|Nr~{pW&nb1BWJ7UOAT;0S}$pcxCNcBmy6~bcyQ59hnjM*sV|2wvI(I=!wT9 +zRo{6oGlQObP1B9t*CHAG%x9Kq?!6;3gP;4&v#tHdA{Fu?V3BLDoa?NRmqE*X=i0SO +zg}w?|6}mR==&aD!Ve4Y|*0D;5y@}YAdUl@c?69{{+j8&jYn2Xv7qhGM?Y*P3!{5j4 +ztNr_rRVLy?!l71PdAB(cACr#t%G$TdM1D#+HB~qM*qq4EY3JtJ*0ae*eaX1A)OWty +z+^DZv*Ve}FZd*$8d#r!OKc6P3PyIkzA +zl2=#P#vh*-`@8Jj-M#hf@^ODEK0Q4<-+g}E->PqK@9uAxkN;Qm>+9S5$LGiYulx7+ +z?|*g!2S#?44GzrwE(VUQ;wc*(+2vadoH*5&Y;fY%KVsm_YyM<|Grv8Hp^Koq%0?Gq +ze-}en(eRXwuHx}6hHjGSOE$Vm=N~b2mo0y?(Otft#mGakU1gJpa=(j_r|R^SO`huW +zTa3IkmoM4mrM>=$k+<&lC!4(W_p=!L7#>&I>|=c1#n{*Mddg;B^ZPBvewNRdZ1%H$ +zf5h0|_WP5~{`UV_OadI)Rks8<|L1o#33L@t-4f_7-)a)%slIeekhlI(lVD%-r(1&k +z?O9Dj0^L=&h6MY&nudmkr)~`mk8d>%i%eg-H7q*+sA+g?`O~f8@%5}`5sB@p+ai+t +zUCkm>r>Aa9(ls^+(O3bGJX;7M;JJ)jX!~xa#(p;`6TNv8C5jx5t*> +zZ#9pre7yKGwuigG^SN8h-Y}Pp&kE`v@ +z*?iv3I(O^!wB5Pe@3&d!?R>s$ci!&z$E@@Bet)(*fB%0rn}UPv>U#>N&H9xo9Pb6|9oW^n&nv%Ib1EAgr;3+ +zQFlq?3f0+q%P5uk2!XJWH~~Q42StX%~CaU6Q4)?r<~PcCk0V +zC0XXFg}c?Yi+$xslI6bcaJTz*vA>=rMS<1Q!%6JYgm#w{CDEN8Zf2Jz_P3;{s9Jh@ +zgk8MpJkCq?uiwdA=phWT~ZZR@nc`v#uXWGuyh; +zH?QpS?E5U~7Dp}pil$wj^V}uf>grCvvTc{=zHdpld1~ojb?x%J??=+@zV7s|`*wN$ +zf0hgfR;z#}u`3JMT{E0ScLlVWU0KNAn&F~q71$MaWs&&N3^&tVfqi9H7R$3{dbnBz +zO`3LPiMng1SLm*wY1^(W)o;!8Nwo@|b?wSB^P`!5rMrUXeY>*Uo;53=)hc9>*wq#8 +zu314-cZDo7ySmc9H7jJPRp_d)tE<9~W`%9t6}qnM>gssb?1-aQVVkC1U6byb9d&hA +z*tTs~*XFlo$2_$P-*xTky7HshabI_Z@B4OjeLZVV0;_ezA+c*4+Ff&!M0ZCVGrP9& +ze}8LEimG+wsjzFCrXS5oGu<6|uI$?8`K-AauGUePrd`{z+%-2Vba&LXZP&K0Z_Uj~ +zwT`}Z?b^2OM|1N^cSqm*c5VB9*1UpN>zGGk*LNIu%`2L^JLZ|$^_}Ni^GcRl$G!@? +zzU%tYyt1vkW8als-+iAozv8HM+^1>R_dIvaue!QB?%THOd*8R_*F3e3|8?#9zVAo# +z>%Q)e|M%_s{{O564Xic^OyV~Vu)7sBiS9{YGrw_=zpbD})h3ZE{Kg^iV+C!ddlLD| +zZyc6qE9`K!NfMfVRGu+H?UF>fwS)yv2=@owK()42`%S`uX`jy|hJfE#}g{y5=(DYkZmb;a%3f-F( +zw*A)C^=+kVQf;%NuHU-0{aER`(!JSn-)~*t&sMgf)ix(d{PvCGZe^RM?#)RvzkTz3 +zTiKSSwz*m1w{KlPR<>>H-rT(M+qdttmG3xen^!db_MPW$<-4x#%`4k}`|kU;@;y&& +z^Q*4kzW4oD`M$4v^XtCfzW<-C;=q4ayMiY1I}h01D-Mb7D`+#n^N_#2;)tqUVORK_ +zN8-mTj+yQ&>?^N`*E%6DDA`>ykuHXB%{dn!W(*6Id?|r}beLs8MhgSQVN8&NT9ZQWn{uKfP*`|R~Uj@s9KntuP!bNBjR +zSNGR_+kXG=`}X=jPwne}UBCbD`|B>AedLhNl|#Bu4(WY4q|b8LK;*EY%3&ju!^SR$O+pU;H%&Qg +zR&v<9<*>z+!Y>Y|nDULF9;|$`L1%BhD^ITtbexrW|oA +zIpW@O#AC`4&m~8^wjA+3a>VD#5#J|A{JtFVXE_=max_roXpqU#V3(sIAxA?~j)s*S +z4R1LbG399FlA}>ujz%9j8gu1n?31H$UyjDJ97_;6mZ)+p$>dnF%dwP@W2q^}(n^k{ +zw;aouax8Pnv8*k}vX30gxpFM`$+5gI$MRW@7l<4$R5@N`a=h5(cuC0d(v;(6CCAHK +zj#o@MUb*CW)t2McM~>HAIbQqZc-@!d^(-eEL{2oSoM3q(#WR5`WC#Z8WO915%jqp4r?;k@-d1vY +zd&}t^Q%>((a(dU6)4PwH-gD*j-Y2K`eL20K<;($*GY3`995Oj`*yYTTkTXY9&KxT_ +zbG+rui797JE;(~*%bC+h&YZb&=IoO*=f0dd&vN#H$k~f3XD^wYz3g)KO32x(DQB;h +zoW0(1_QsU6Hhy}+n?fywj&v+D(x&@K>7r2jJ;JJE%_vr<`uNU}PFA9iW6jZ$^WO`B9^`c1VMbXrY +zVxYU(Am +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 +zdH20<`^@)!Ui-fG^X~iq{+SYA#_pvj0&m*z@ +z7RQX&ee5mX^H}b`#R+GUFwJk_hWJd>>VdFJIk&&>8)o-1DW +zdG6;u&+YzOUTD_)vQT*M3pabKOOw}qS!%rZrC+_(mBo5rR|fBW6}I2%+U9j%*B0-6 +z9rxer#$mm08z=95lV)#y>+-s9TQ~20n^$jr=ds@RotO8%E8A~<@AJCvdq3}eU-#eo +z0ki&(gTnhhwAtG{5?=q~sPVp!ef2g^l=Xj}4F13G)3p6I&y3gqJX^f)^Su8yFP!y% +zU7Wn{%QAc0SHbIlUERFz>$-Z|H_7_HZ(iQ_ZQFj^cg5>}-~GJr`@a9SADZ?5JQUvl +z0`^IW~%m&N*jUk300b#1@hx6SMSzAfJW``&-MABXk-eVn}i&og`b +zUzgYa`?`7m-*@%)e;(`q|9N@;zi<2P|9xKn|L^Dh|Ns5BXJA{vz^A|{wt!Jifk|xv +zlb!;z*#c%e1s1mjEPe{CVGCH}6xh-hu;nSRmn~qgQ{ZS@z|p6`Ic)*wJO!?03%J%P +zaBo|{y-$JX*aDt&3cS}A@ZMA4d$xe@odW;21^oXM1lSe|@F@z4EfkdduPCIpP)JWv +z*leM&ouY`_LJ>bj(XfT0af)JT3&rvj#mg3o*C|T0EtKd}l$^Fua-O2pvV~IX6s5N< +zl-{Q(b8Ml^IYrrP3uW&q$~{{s_fAp%+d}z&iVAFt6!?@B#TF^bDJiKfQqogWHd~}@ +zr=;SxNX1V{HEfY;oRV7FBDFjv^|D3kbxIm-i!}O_G^Z`noTsFwU^L$Nn$2 +zIj3xUZL#e=WxHpK?cOQde_L$-PuYQOi36XCqu3HhITa_hB~E%O&Sp!T?NnUcmbmz- +zxP~opjZ<+;TjG|d;$F7Iy-vlWZHY&pis!T?p7T_^mM!sGr{cYBiT6GgpJPjW&Z+ob +zTjG09#qZe?zjrGB-h|md4&w +zjeE8wiY-f&Q%h1?mZYbaY_=@fPA$c4S&E-pYS^;WIJLC2 +zWodc;)zZtBrPrxtv@Of%Q_GyTEOVY(*0N<;>(sKhEz91gmUC=b&N;Q*Ys+%)spUOe +zmiJCA|J$UGbS*S%A( +z|F*pTpLzq^iUvN7MzIx*avDu)E1L8)n$1=;+iA48t!VMnXboG@8mG~gwxTUhqrGfJ +zd!0r{+lr1pjm~K+I_GJ0EnCsGPNRFiDLg(PL$J}q_%RBp5|n;m6Po>r?{=0;-@(^Y~|EA&1q>Xr{!r*FIzdi +zPIE@v${Br{GpDVbIZt!evX!&eY0loZa`ryWImcGcIj1@I+RC~2H0M2AIq#k3{BJAg +z|I=K+wrT;N)T@Lz1dECi`(ige%f2ZR&R~d-j=p{Tb}mzvenz` +zw0E?v-qEMMbK2^i^R#y@TfJ+Y_U>(~cklbJz315KJ?FIdUR%BQp7y?HtM|Rr-v4d& +z{(ssB*w!52(>W-%=AfL;A+AP@YcJdBUU6G{#ZUKY*xIXcy4TXyUdz+HUbgmn +zo$igcwKw{7Z%$i#bDr+4WovJ()4jcI?d^TKcaE*Sb58f}wY7Ke>E3&`_TD?)``^~y +z|EK$aZQTPty@z7!9?I!GQd{>(Pw%nWy2o~UPu$i$@zZ-6w(e=1-m|p->z?K5Juh4L +zyiV^$+qxHhdM~G~dpS?<)v|T3*6F?8w(j*ly*J0!y*a1%_S(9)_w?R9Tlemr-urLs +z-v874z_$JapZ-U&^&jQ*KdG(%q^JMcZ2f0D{V#6ozxe5Y4O{;;PXAll`fqvq-^r*8h8_|NqFpb(_Z!L_-zan5Q1<#p+53ia&o|1wH;kw*I_jeaA| +z>6?)hfB_r~_$H{1U=c3|J)z;EIxzQs}A +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 +zCvdKuz4NoD&5#CklH`6wRC{-Z@cnp7^ +z$|>F_r}(~{;?FrXP;+Xq=l`jpnN!0%r$(-v8hvtV?8~X~oYN9DrzLw%OU;~?-Z?FE +z<+SXR({f)<%jcY4s5!mZb9!m!^zzQ>l`E%LpPXL%a(X@Ij7H5F&7Lz_GiS7S&gfh@ +zqxrI*Tsd?0$(eIs&YaIVYoX?>#h$a4X3kpPIcw$0 +zS*uUZTKjU=dd}G!HD_=3oV_)3_V&)%J6F!$eRB5Rm$UbC&N-+#=dkCTqnUG#cg{Jv +za?a_KbI!h;bDneVMa{XFJ?CD{oO``{NE?%|9$y?{(sH|j9Lqry%w-$Enx3j +zz`1Gx_o)TEuNLrgEfmyRDD1URG;5)F*Fwov3#Csjlzp{Oo@r;zuUoE!hTH>g+#Mx_!Yt|C?t|gwU +zmUy39;`?feKiASgt);~!` +z*{7D}zFL;gwY*Skd9l~>(yZm>UCS$1Ew4Vcy!O@ddaf0XS}U5pR`P)yUv_nvG0N3Hdrz1DxtTK~Ok{m)hFf1g_a_tpCUTpJj*H!yo|V9nmZ-o1fy +z^#<dn@tH`~76Y|p*LQG1KC_ZHXeE$-c0JXde= +zKE1{F^%j5bt%2JAw+4G}4b9#f-n}()_15UqTVr2ujpyE$sJ$)Ods}Muw)F08nX9*D +zpWc@HdRspC_CoFL#opUXv$vObZ?9avz54X_+Sl9bxpy>b?`Zbk(VD%Zy?aOJ>K)yu +zcl5sA(a*heqV~?o-aDse@0{MfbLQ%uvrq4w`+Dbm?p+JDcP;kbwKRLz^6p(LSMOSV +zde_?5yVi5>-l)BMv-j?;*}J!Q@7}q3_wLiX_rBh}pL@?i?LCLR_Z-dMbG&=c$<=#K +zpWbu!^`7(GdoOD5z3jdBYWCji-Ft7Y-h2D>-n*~&-sj%;PU&Ou?HgQ7VH#d{7)t~n@u=Ai7GgYrCw6maZM_hA`xc40KTyw(EBj+f>fFYh^Cx#oEFnd7x@j@R>?Xw*5;>~o?u=R|wYiOw}Ay3d^G +zeRHCp=j244laqZ;PR%(vz31f2H794EIXU;u$@x5|7V4Z@>~m^q&i_-(drqxfb87XO +zQ)}OxTF-NOqt5BgKBu?loZjAZdgq$cyU(27`{wk1o-+q^&K&kRb2R77@t!j$*PJvT&AInH=RfM4|Lk-AYtH%aJ?DR}Isg02`M+<@|L3{DsC$9g_X2C~1@_(xoNF&| +zpS{5Q_5wfeMM2$*!oC+pb1#bbUX)yWQTpsf*|!(vc`qsIUQ+hGq?&t4z4wyl+DqDJ +zFX_I$q|bZVQ1`O2?`6~6%jUh8E!SSQK6}~r?PYu3D~`HXoPDpj=3a5{z2dp{iuc(o +zzHhJi^ZvgYsCzZo_iAYF)$rb{k!!C;pS>FU_G&!uwM5-($-dW8bFZcMUdvp2E&J@X +z+_%^Ad9N4hUN82&UYdKoy!U$L+UwP4uh+i4Ue9}@QTIl(?~T^n8|}R}I@jLlK6|70 +z?Tvokn-g_!PWHVyHTUN9-kUSm-kg2*=G?b8=kwlLsC#R%@2#b|x0d(bTDkVt>a(}j +zzP+`c_x48J+narFZ_T~Ez4!LcwYPVly}kGC?ftxW4(i@H?0e^E?w#YkcTTRobNcL^ +zvv2R5=e>JT_wHriyH|7XUhlnobM4*RXYby9d-p!?y@$H@9{b*VntShg@4c66@4Y^I +z@9o=r?|JWk)V=@N_x{)1``>%-|6Ke3{_nH*|GvHdpZ5Wy-UDX82dsGy*!v!Eu6w|J +z?g8(+2mE{w1@#^Z`#luRdnn%bP;%Wv>2nWd-#wJ)d!(rMNZId^YThIDzDJtt9%-L@ +zr2FoXKHpb;!o_i}39%jtbDXRdoW``pX9?_SR5d$myS)ndQ@ua@S$THg0+<+@j^&%Ijv +z?$vs}*BkX-Z}xk=HShKIzSld~z21H9_1<@{_w&6usQ2cu-2q(+ +zzI$_?@9jmsx0n6iUd?-Zz3=VKb#HHEx(^FOfnf8bpIf&2Ui +z-uEB)`9BKke-!rrD4PFKy#J%*`j67*Kgz!UD9`^%QU8;&|0mV_PwM@jG}nL9KL1Jg +z{U?3?&xZP+jr~8H=6^Qt|7^Mbv-SDUw(mdN^M7&F|KjZb#Wnwnd;b^D^i^#C|9flx@9q7+cdq}v`~2^{?|<*-|8r3P&td;RNAv$2@Bedh{h!n4|D1jQ +z=RE)4i~4^r`~SU~|MztO&`G0TU +z|9j8>|D*o@&;I|v=KufR|NsBb_5XjL|Nr;>|Nr~~|5^A{A}%~|Y~d8uOSy63p>v0z +zs^5}`i;rA;Bu(?4+_?DIeS)HEpGxGVC!SL@L)WF;y!6z2hGFWtC6Sk(`OdK{efQ+% +z<>&qj99#KRqprLNT;e%ZFZI@ym%%Fnm-;P@y80?~P2|?Rr?;-Y4&RV?v`;nq+MCEN +znOE1P-oExWdPm{Yb4#PIzl+^d`Ssn?+t=U6A82IdSBtsvA@NA3sD9d=8y}NTOjPw> +z7IX7c>Y16Q`Oog${G5Jap=-Zd?5!`ES5}6uPrG~TYxa$esppr)-u{+*XJ_gAXLoOZ +z&wp^Rm0vyX&X2+;C#UMC-@Eg(_{GJg{>$U;{wn?d=H}M?=lAaZF8}cGXuo>=y+4&- +zUS3_Fe*fOz>K`AUo?jk+|6lE&pI_fUzkmOKJp;2yfddn_$p;4(;gAAHHtCWNjvUHU +z3Y@sKw|sEoF}_mZ%xC@OgR_9MNTG|6x5-Brk>HR*SFz}lkFFBQQwrUrvbTJ6lPSJZ +z=q^|N<)gbovq+JLQn$$`50%LwMV@N2OFnsOES^&2rL}s?Coi4NSBkv#c7OTgZE#qm +z*vII!$!8yv%OSgf_#jxmIV7*fBh01;4E4i669_AHROLtaA;|0Saj*v(1_%z +zrD0LoTfc_I6kjb3kE{OrH9VnNv@9a2+w@yR%H+_p$h6s|-y$;>Pc4heTD|pKRL5MJ;v86lKZT`{YakRELNE^$=mE_%9P-+ +z%G7DmWj|AABu}eMo0YxoXWE?NYnAEqs=xhAU(hU8m9ePX>{rH;$zfHQ%VwAT%3QH{ +zT2u|Mcg~i}Vb!_YZkPSe-SK!@b>9D7uebfq +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 +z#W#9uZ1lF==a8*~0|%mv=qi?XqobYm~u#$NG_ +zy&4;PEjRXhZS0NS*qd`>Z>^2Jy*KvG+1R^xWAD9pZ%(+C_dx>)I66OCTD)1#K$|Wi3B`Mn_srV(S#wDrcC8^gX +zY4jy&&P&o-m!!QfN#|UW?!6?vcS-vHk_`Bg4ds%J^pcJ3l1==QP2-Zy@{-N#k}dj@ +zE$1a$txLAvmuzz`+4f$t-MeJ_f5{GfDUSc;Qk?Wsob6Iv{8C)wQrz-V-0M<2`cgdS +zrFgAN@!pr>b1udAUW(tl6#suI0eq=}a;ZUjslj%sA%3Z$aj9W>so{005q+tV^HQVM +zrAF^djX9SZdoMNaU26Ql)C9h?M7gviy|iSzv=qOz)VQ>?ytMSXw2Z#A%z0^9>(a9K +zrRAJU%e|MD_bx5}Us?fQdZAo;kzRVSU3!UMdTCsGSzda1U3x`ddgZ+Is&(nr`_gO9 +zrPtm|uX~qX|1Z6PFQZW|qe(BL*)F5SFQYXsqb)C^y)L7pFQao_M%TKG?tK|O=Q4Wl +zW%Rwv=>L~7fiH8ST;?Rb%*l3{Q~WZg#$`_XpO-nkE^|g-=FEASv({zK-j_M&T;|++ +zne*Od&i|LWfG=yIT-GAJti^U&OZ>8y#$_$b%UWKSwW2R;<-Dv_>#|nw%UW|TYwf+P +zb?>s)|I6CIm%ULgdy`)FX1nYye%V{&vbW`BZ?DVV(U-k*UiPka*}L~;?>U#f_g?nC +zciH>@WgpK`0_u><$u!4|7@54#V`MBT>iJb{O@)7Kl<{2&ddL`F8}ww{6FXN|K7|0_b&hc +zzkCM%0!H})CjA0t`vMmK0@nBfw)_J2`T~yr0?zpbTCWWZl+C|_)( +zUu~Dw)cze-WS{dFLwCPU*afV;-p{V +zY+vHyU*Z~H;+9|HUSHzTU*b8x#A|(t_x=)}^CiCbOZ?uK`2Q~n;4cl7FAdT!4Yn^0 +z@h=UHFAd8t4X-ba=r4_&UmCT(GnM$E9c&?ocF$R{{PAa{8bC(s}|{3Ew-;(;$O8izG_*1)$;nP +z75!B!=U1&-U$uIF)td8FYwuUBdtbHwf7J&5>W%W%oAj$U+gES#uihG8y)D0bdwune +z{_36ct9Pxh-o3wi&-v=T_pA54uipQ^`T&2;LHU|P`Zb5`YmWHW9F4CzmS1zczUD-K +z&B^&Sr`Fe;-d}U(e9hVWHRs;foc~{Qfxq^meC;Lu+ROH}SNvuYcCuf21=_U`@Kd+%%S|F3<(U-wYH?vZ}oWBa-%{&i2|>z?J;J+H5O(O>s+ +ze%-78>+4?cuX}U8?(O}$ckk=o|F8SNU;k0Q{*!+FXZ!ju{`Ftu>%ZmKf3L6q(O>^_ +ze*LfY^}qMm|2bd(_kR7q_x1n(*Ejr7FHktx#Ky1Wv!X!pa2ubzQ;$ZW($Ow4{iHK1 +z3YCxd$=NsYXcnoQoTTQz$Y*7d>gj2E@rQaei`CA~GRuE-W@WMZ`FVErOuSkp8W$J2 +z^(*O1`iLl@hkhSsW5zetWVy#Pp8u8>8WY@$>-Ko8b3cb +z&%T*Yx60(@rDgt$|NE`2GJSn*UHsub-DU9I)^clY);^Xu2y{QUIn{9^z0b+*61y}N(7U%%e& +z@2_v~AD>@eZ~y<_zyHh<3mTY(A{I2VE6rHY#BFqAK{LOT#KIQgpooR7;z=_Wwn-P= +zSlBM#B(bPNc~ZopPW44I7IkTFy0NHR|B%Gu9^*?9i+jx<%~;%L{prTyetRa#B@>*5 +zB9}~bSDLwGlDE;#C6oP~B$rMJ4vJhlH9TqN(rM8}Hh*`DR~*_dZ@Rs1_xnTA>-T)V6uo}$_eZnW@B97f_WJ$*nPfH` +zU>1(qaFAVj&W1zW#&6G^7JDX1HAC}pC#`to~=CkIH=WIS_{rS%3^Y+ZLTP`>Y$8Ndkt~~eumP_8oceh;j +zcb46HB{(>C>(%h&xm&MA7vJ4_J-%6X+l}POvDB;kUKAT;9 +zZ|C#*&2qb5ES?;<>*eyr^LD*jz4_j**Xs|+aJ%{{ciW?d%NH7XO`dd +z;jnQ0o{z_s=kNJ++W7vS&*z=x_kOt?9KZML_2l_`zuhjrzxVt7X8C)ph;-Xe+5?UjDy@s2F+4;6xgj_9OQ2@Xi<_;0PdFvr!rjSe(xf>ms@j<+y_1Y4&$^?cZvFD4 +zf0NOaMKY?I-kGO@7a2`m6{D&h{qj`!A){%V=BVmsXP%CJWHf!(9aa76m#5>IjAtB@ +zQ8Vn$Jd>jN6mC~=Gp8dKU(aN+fJs)vkvr=`vy#a| +zu2>Cc=~oxpolF)9&DC(#&brv0WU^T5u7war +zrRj%EmKn{}^v%w?Jo}N!a;v+V{?)H8&u21S;UudS*qwD{v6AUZuUM_%*{`lFcQRcS +zG*>Hhb=K9@Nv5l#?rMeaesy(ylj)iyS?$QvS=Tl%GF_V$s~vs&)wS)1OxG37)sB6g +zb$$0E)Adz%wc~%ky1t*uY(tZ*P9k^q{~L#u%rLg3QzH!{iY}2HkC*At>&GSuWTNcUcW_oAey1dA2>#A7Y?C96Gt{*bnwrQ?zZg%$V+mFn)@4Bm- +zU;X;_eJ1lAhh+5%yR+{+Rx;mtDps#}_Uk*(oy>P#nyXj3I{WVHB=g<3?&_8Aetq|S +zllh)Uvig;$v+sRgWWM)RtbX-+ziEDkWq +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 +z{wJ;XpN#T9TfP76l>f!+{gc+q`=5*Q +zf314|Yg7L3UGIM%%Kvlf{hv$ue{a42`zZh4tM~st<^TWn{y$Rz1NR38;Q~hK4~)tM +zOxhoqj0>2pKQKENuy}u92`*rb{=k}Cz?S`it+;@_`U87&0Y~=-j>!d_vp;YyF5p`I +zfopRC_wEnehYNU4f8e=XzBEIGr$5SEE|k6fQTB16-0P2Wp9|%Gf0Sn~QsDliAY7y<{Yg=|NJ;yXl5vr; +z^(STLA{FmXD#1mn(VtY4i`23|sTCKgSASA(F4E}!q%pZjbM`0A#YI}HKWS|)(%$_^ +z`*4xY=}$VBi*#>)(tTW{_xh9G=OX>zpY)lF4Y)rW2p1ble>PMuHq!oVWL#`){n^;L +z*u?v@NpP`g^k>uLVzcbeX2r$k)t}9qi!Hi8TTCvtoc-Byak16v&sLj@t#^O6K3r^b +z`m@dDV%yuFZ66ogz5Z92vxr9s+XgN#dqt-l64mxg$M4GAs{ +zjs6;%TpE`BHLSQay!vZ+b7@5P*NDlbk+Z)>E-sB){WWTHY4q-|(T7W8PJfNLTpD}( +zYwY9FxYu9fK9|P-{u<9*mcacjLAWeY`dgxMS(5g*B;&GV>u<@Ee!d7sPje}BtoE-&EzULagvDE+-qxx7gGdy#Q@vGw<2=kgNo?K|R3E4p|8=ssN0bNWZm<%-_hKYAZm^u7Mk_qn3~ +z_m6(&$_d;*CkR(gl>RwUxpI>B&q>CWldXSFcCMV_{c}oi<<#h(QRm77CToh@&2_WxN2$iucgUV%d&qhE3R5z +z{cCx1)r#(4D<)U1oc(L%;;L1vf34bFwR-oj)rYIroc^`ua@E?~zt%pkTKD?by3bYX +zfB#y~T)lz&_XgqWjncn2DpzmP{=Lb#db9QK&Cb*+KXaC;0xO&&>-@7(f@811;_u=Y2r+@FcT)p@9@4b(!_r3nT?{oG3 +z-@o@W*Bs#fb3nM}p!A>r2bF6MY5zH7Tyxm^&td19Bi?_G1lJsm{&O_B=2-ThW5qSc +ztN$Ett~t^D=fvcile7PvTwHT%^`BFlYfkU}bNX=2nbUvHT&_8L`_I|OHRoRcIrq8d +z{O>>KnQJd_|Ggkwdr|uDMdjK{+J7$@*Iu^%d)c}6iud0u!L?VT|6Wb5y_Ws=T5;|5 +z>c7{UYj1S_y)n7==Ip;W7uVie{rA@9+S|MT-acG==k(t@muv6d{(JXv?Y-B3?|rVl +z|NHNK=DG*m{~id}J(T|UP`U1r_Ppx2W|EOI5N&EjNTbtN;JnT>pFb|KEq}|D68+=W_ku+yDPQuK)M?|G&@m +z|9}7g&wRwbk(pD>q(UIksg+yOtmKD4l1rzsX4sYr!DP2yY0I)NKLk@eCMtVQGpQ6x +z^_r?3xvk`9kw=lXW7?ZqB#*8lTS`Ftrp9T+M0cHTj_7HyqKNEFRyK_7SI15x3~J|x39m& +z3la`CbBdeQNE9X=?Upnz`y)}5a&od}__i9!;LS*=uQ*45RK +z+spn+mF3*roOyj)t#o6zx`|75C4US3`qzP(Pi +zy6o-ko#o&E$<|bSe0*}cdA(e1)z{ZIx0nBytE>6>`Q`QP_44&~e}Dh{{{6rF0R~o? +z2?v;XZ3GUoh{jAf$R=AOaEL>7&V)l;x_bl;^O)Y5aG1~bkH8TDSDA@NgnVrTkBWrG +zOgt(UTO)W(B6ZHhV^X<$1dq#<-kEq@uJ(`M358afNhg$gZG=v$OpTdzQf+RH&?$|j +z|L06PrL}gC&}p5mcP5?I+xti8jKNWv$!Cnt+6bRDxf(P1tl8Zf;d2&G=S)6l^>&Z& +zd7H0yCZD(a`$zbK1FP(m3r@VYA{SjmW2annldToG}UH| +zsn~({5$0-79uGXY1W*xAXS?6}wY#RCfBEqO-Q*cT29uPQP1r +zw^saK#nZXd?^V6sD}KM`>)q-1>;C=~f6%}xH{(GQubsrh7SXsF58GtxBp!9B&YSV5 +zOLw2d;~vv{GamQZ{{JWOWP+>Q%qNq4?IfR035}cibXshkw +zGoR0^{U`ZiL95)X7mIrBq+TwW8aM0Zvbl9quU0IbH|y1^wfm%Aui1KU*6Ve9|4F^s +za8z#gn@wl!q~C728aMmxw!3xG?{++$H~ZbLxBH~u@A-Of_WON*|4Dy1z$!oI!y#UK +znU64fV1IiF7H?w9#|#`ON2&*yCa%Y3=uDnIwjC0~2luUA6j=YG8w +zTQB?VM(X^z-)`mZm;HXH^#0uM_iF#k{&>(TKkvt*UVFKpPo~Dt`}u5cz1*)COXtt~ +z^=j>Yx!-TL-k(i)Mr)cbq`8jYX9MILf^MmP!26KK^8dB_!c!%2LfK%3!|hdh}P&eHb;+AXg<S?NZ(ez3w)3WMex2}~ha%4{PDc6_&l8^eGUU0{&6|GZ`-JCxobue}Wu$-g +zJ>mI3uROQ=dDFlCpYQ@k(H9P!Q31_zA`3Y~UpPtL3TU?zStL01g^OlXV0WCzV#%v7 +z+$?Ve_ScClQ51dY;TaV)d7j8p&Cr)#k+*`T?-N;OIQ6AZW>oO(dm_s%ufFuFycInE +zpU4VF(N_VTQ6Y=vL|1x-z6zRoD`dHy=&Hb}uR>Nvg|3bhT^)J#RoKp3q3i4Zi>^r& +zeI0Q!Ds1yS(Y2YOucL0>3fsOjU0-?ib==Qe;rsuIZfF#JlfW4r +zaac}lV`u1_B+1(m$L+*6O`Q5BMKe0`be!1cnOEPWS>BF3UnjO@q3GKT&*-Sj^Tf8U +z41Jpwc{}R*KCx{Zr@qa}jE=s2Pi*_nt8eovZ%5z%C${6D=(~c>=$OZH;yX`xX<&%_r45$Uv=|#-1mLr +z`#w&6U-L3L{`Woc{Xeh1ulspB{{KJm1B_xH8aQJTnB^r7a)y0qlDw0^ZZC02aN36! +z&6q^)c!|URC9i#Gv%HhYUoUY)QS4)fXH1gte2Jr)VIR99?<9%umpEoP?PE`7OtSQS +ziQ|^nKK51KNtXXFal%pT(}d2L6lHnIlb&IpCe6H)qHZsFDsbATDJx@Awc{mEM_&6h +zZRed-{d&nWiDI8;oQz2`o-cVeGwk!Mn|IR8_e-8Doc4Ln%b0ZQ`;zA?uYI2P^G>?` +zf5{7tVqX?;#%4InOI_>?`?5&#Zic(P)TN2jzAVv<&Ge3!x;*pRmt~fBGyUtOt}GP$ +zy23LyD|o)t)sloN)!s@-h!O!+#u- +zyjRd}FY`!n`i~=;afRLSGLI#%|2SrOudu&f=82;C&l8?;MU&^tJk<>Uc`EW=(e(W? +z&kU#kJd+t$Jo~=PbIa>L&sE+lp8sFwg`@bd3!QN#i{)isdWQeHH1l4`a(mfVfzy9o +zSs7QlI$rj5?IcHS#pUoZP6QT+FflW}F6=gYp$4F7%W=Do7*`(@u1PXB%9WnB60 +z`?BvVum8UH^IrM>|FRz%#s56ujITH>FZZ!C{LiERlJ_f)+sl2LIQ`EP&G^dG@p7MM +zUjOsV@_yy{dbuwP#s9wWjIX*pU+(M5@V~Dj?^j*lFZXTZ^uKR1yz{O_CB|9#tezxI8- +z{GW&7|9_l}ulqb-{_o51|G#eDulv4V{@=&x|Np#afxY?yd;Na_j^+T4_6Z!_ +z7dZL_I41{iPM^Rz`vT{D0j|XXT+1hLt-ipuUVwXZ0QdF@+`BJu?-$@X9KdsY0?+9S +zJm&>?F9+~mpTK+j0`GkRzQ+N4&nNJ`zQFfhfd6v<|Mv;}zc29r7Z6|$6kwkyzWfnA1*JC!N^hSiz5Al{enFYTfilM@%ACF^b6!yP^8Y~D +z>l0;fUzEKsDEBx}?)gNy*B9m93(9{El>a_a{`W=s|AGq4K?>}X6u2)b@Czvl2PukA +zQk1@=C@-X>9HgW^NlE*XlD?3#ageh4BxUPM%JxDk&Os{flT^GfsrU=21_!BzPg0G( +zq#7@zmK>y(K1nV6l3KoydU23?`6Tt~OX~GP8qGl(?UOXRFKP4(X-*E(oIXi&_9e~v +zLRyQ1w3biOT75}ty^!|iAnomww0B?9-Y=wcI7sLCB%RZjbj}OuUJlZ|K1uiXCEfc% +zdXIzjo=?(yeM#@VkpAZ&{qK|Xe_zu7FJ!S +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 +zJ&nHiG+ypma@@1@dC#)%J9RE># +z{zvKm_dm+Ze^QSBq(1+X_We)#@}G_4Kbz10Y<>T;z5EyF_%H7Bzj)vO;xGR-IR0z+ +z{IAjXzsAddOOF4RKL1NmAo%)+G;_&%ZCP<~vB%{9TCu0Lth~HDV6oR+udS=Du8!E8b@$ZP)z{Z29PX0! +z-nQoE=8Vg$Voz^ddwYAq<5P3Jx39aqyW;b!yQjCWzrVkMnOn|h$A*W8JA}34&g|Iu +z`1k~6?|D8uH$6Q)!#MlinVp-TpI_kIE$6#y%gf6vf>+0#-L>`g^$p3V=lSm5_V)IU +z;@9`i?%w|X{()w0dA~h7K0ZD%Sv&sRo}HhcUs&ut-*4}(udi=x&c1(c@9yvK9~|zM +z_useY=jRufSI3{kcp|*dlJX{%l?n^X4cTCY|tnU?ix +z<%(siUaeZQ?bWN*8;)tcUbE#|*6X!9o~?SlZqK(@uh$=7(|)tzh*YpZn+Yc{dVh(wAF97-6?zhcKd@i?RPt#Ov`?^^To2&?{>Y}_WIrK5686M +z@A?1bTK41#e7lPQ1m@wh^}&ZiS9({nzZ)L6de(gjLG$!&u1;3ulan==KGt^ +z=N;H}zg%z;&;4@ILw)U+OFrgrzg!M**Zq1WBs}-))rj=9U$4cKzx{eWpQw20^Zc-W!7?#H7Z +z^LIZUPjJ`!`D99X-p{8q(%1cbHmCgE&*ux;^?torGCl9t%N5Jl{d%=#`@3JSHyqdd +z{btMcyx(tkJYVdq7 +zH`3St{dTAP{on5o+V%hacrrcz-_IAz*Z=$VX8Zepzdsz;|NrO9_5A;Te>`9R|KFeQ +z@Bja2;90=H?s0%g>O%vE&jKd#69?F=J~RsSEMQUhILHeh!gi!%%P%1<1Y`}LvSfoG9GyT=hF +zsgE5VK8u8=pE#ms^|3RcXOYNqkE2>qAG;#XEE3y(;;3HL$L<84#S+Iojv3AR*puP2 +zSnB$TV`jTP_7?OkmU-@R-0IfHzKS!8<-VUdZujfs|NaJ^B?|1GC!C}{P3Z7hq9lIu +zgqzi;i4%I3sHl6M^osg4X~vl)YUU?T`c-|Jyntt^hP&sfpjn@$tngW?6@Kzm*sf1g +zH}ovkN%uS*b?ei#9cPy6m7hEv_v_R213b$N+C9%CNqwGi!e^P$^pj`OtUk}Y(6h{B +zx#!uesL!)*oLOeJ{p8uas?W0@@GQ4D?s=|g*5^4de3n~XKY6Zf*XOw(dY0Qf_dH*9 +z>+`%HXO`Q2KY70H*XQ{Ryek~oy)HCKeObWayTVEQ)P**yFAD{FSGcHqUF?ebvPk0W +z3ODmp7yGKdELPxM>EZ5mY0|7OOEi2}dWE05G;PZ+)(t0K;>3fq3_>bk10 +zs}p!vM;!OMwrSSaH5tCEqpqL2wr$tfwFSMaW1f3m-*xNjx{9-_oY1=_Mcw=6si<$8W}ID5 +z*sOmX6zJQ~qVDsMEBePFiE|s;%+EaJtNw9Vfp24nyU!z`**}hG_-*V8Kl4az_m86n +zeH(kyeI85Q{&CFW+{V80Gmqtd|2Xczw`oGV&l4r-pC>&0Hcgs-=82m1&yxXto2D%H +zd8!rt^HjvSP1ClYd8$|a^K=5=<{8I*o*B*lc_zbe^Q`M9StNe;g`4%Sixc{`EK&D;=@tF!(u{Lk +zmYJV@=~w;h@&dlCE8Klw1*|KSt!vVKUq{{kb#2GFt?SCq +zzK;9->-qt{Z5!Hs-y})@zH!2D+otJf-=tapzImZ<+m_|NZ?mF*-@0*b+qUgz-{w{S +zzWso2`;Ozj?}}#szVpIw`>yL}-<9qDefLA(_C3#i-&fuKeecJ)?fbr;eP8$c_k9Nb +z9S7L`J~T=HdBEYn1nj+vkP*jN4Mu>$|j6YhSW +zCe8lyM8kjQsqk~3rtSXo)S!RonRLI;vu^)+W^sP!x$<+L=l%Zk+<|}Bg?7I$i=_X) +z@bKStY5KV@%dG#t4Cvo=Wx3ziRndQ6MV#MtZTq>e>#G00PT=2tx{r?t958Wkj(lq{N5BAV1HnlxrKY3*pzxzVKeqsc&`*~p^VB%;}@ +zqS<0bv(=7fn;Xq`KbjpRTAVCeTq0WBDq1{dw0P}k@ww6B_oF31qBY2(H6)@ntfDny +zMr+iL)|eZuaX(rUB-)ZJ+EOCg(kj|AX0&DPXv?|LmiMErK%%|KqP--dy{w|WVn%z_ +zj`o@x?R7ue8zefKEIL{uI@&5aI%afq?da&a(b4y#V}eBIB#X`|5uMX2I%mx2oVBBK +z&W+A_KROpkbS<*zS`yK{h=TstT5+?>GobAo{6L?O$GB9RltDknS +zaz7_3NKR6+oTL&tNv(2{#>`1tJ16PfoTT@2l7Zx8Bg@Gqk(13TCtJ*%Y_)T;&CSVn +zKPNj#PI0oF;u1N_jB6+0?Fw`meWfjr>>V>_@7g(g&&}EU +ze$GB1Ip>h&oFkEQj#bV%F>}tTopa9IoOAByoC}h3FImpL5;^x;<=h)H=ib^m_s-3^ +z_kPZOAUW@m<-8}6^PW}Cdogp~tDW=S+?@CB=e!S+^FLY6{}MU>Tjl&8Gw1)>Isebi +z`Tu^-XOLRJWVL`LY5`l-0*+Y=xOOezxwU}r*8%~lg+f*fMWPmpRV|d5weY{xu7xtU +z7Rvovs35gS$!d{G)FQR2MH;gfY3*91b8C^_uSEt@i;b)nn?x-(t6FR^Yq8a?#WuGV +z+x=SXAhpEFYKcqK61S=)9_@PgRGW@L@f=gS{gBHY1FQzF}Ie+ +z{aTtJwJgbMSxVHhw5nwpvzBG;T9$KbS>CT@1yak4td^HVEibEDUNLKV)vo0=x0cua +zTHYYFqRDDSOVo->(%Dq*hL{S~(?Z<+Q4mGiI%vwQJ>^TPx@N +zTDd@K)gr4^OQKdSt6H^U)~Z#zR;{_UYTd6@8>CimvRb_*YW23N)jMXb-nDD>o?EN; +z{aSrMYRw_5HUE!9tvOb;=ESTur*^G5b8F4HUu!N%t-WNm_Da;+YgKD+%vyVE*V;R` +z*53QI_JP#8M^@{eM6G*PweH2Nb+2}!HzTX=Jq&Es#Zxo5%C|12uV)jO<-5X_Y +zZlJzE)=uK+Xn>1!`(%QXA=k_MO-dy)EbVw*Ps*w-rclFS6cV61}~wdV9s}?Nz(C*WBJ- +z_j`MT^o}O$9WBv2+NyVS%-+$pdq>ah9euxdOpx9=$$ICM=$+H5cg~o-bJp&ib8hdP +z_j~68>0OJgcP)wDwXAyAirKqX?cTNK_O5lmcWsc~y~%p_mgwEvs(0_0y?fX0-Ft5D +z-uHX=0qH%5toIy=-gB&a&xzT4PVL@v=JuX*zxP~_-h0V<@0IAi*Q)p4n7#Mb?!9+z +z@4feX?*r+5kF57SiQf0Ddf$uL`(Ewd_vZG#cfa?2klz2vdjFT`{okth|Cqi1*Y5p) +zZtws1dq0EB0VbOREHMYzY7TJBIl#5&0MDHRe18rI$Q%^1IVciyP^{+Qe~CERALUP)g028b4Y8?A)Pyi^!^+&kU4B*bJ!&2uvyJvi#dm__8hjk +zbJ*_BVF#HbPBuqeVve}g9PyZQ#B0wHpF2nV{u~LAIT~bhG$iI|Sk2LhIY*=R9F4hi +zH15yQ1es$=Hpfz8j-}Nc%b0U4YtONqJIC_=94nAHUSxB;B<6To&GCvk$E)@nueo!) +z?$7ZCnG;PmCt6}owAGyGm~*0Q&xxKpC;I-Jm>_d=iE6t +z@6X8vGN%^VoLUldYFW*x6?0Cl+H-2nom1=noZ29BdXvrREitFJ)tufj=k%^Ur}x}B +zz3_xHKixP7$O6|QU +zbN8a$--`;emy~QTsl;AVtG%Q#_mbA$OFDNi>HWQAAbZ)!_OeOrWwYAL7IQCK?Y(Ss +z_p;sJ%MP+voNTYS#9nc$z2Y(Vir3yNK6kJ9{k;+(do{@RYDnzWu-dB;bFW72y&7}( +zYTVze39{FcY_FxnUQ4UJmNEBQ*4}IXbM9Wt`+KcG_Ii=+^^(}@WwqBU=3cMbd%foF +z^}4^;8)R=Z+1_Z0z0p>Cqhs!kuDv&U?%wG8dt-v^%}KU5r^MczR(o^C+?%uZ-kfvy +z=Dfc*7s%dPWP58#?5$n{soilguocnv{g6!Q(ws)_@-n~|P_r~12xAxw> +zbNBAOzjq(V-g{(w?@8>vXSMfU%)R$&@4Yv7@4fqb?}P09Pqz2J#NPi_d;iDW`@idKIKtS%HkljO(xQGA6>K;nWdnmQ< +zq0GI9a{nGG$URcBd!!QgNUiRX#=J*b`yT1sd!+a8k%8P}BfG~YagWXF9$UPs8e-M$CH}weM-n +zy{B>io+ijWOR{^G689{v?pemXXIcB6<=lIg_wQMO-18#4=OuB^%j%w2%zIw7?|IF= +z=XL*{H^{wcvU|}I_oA)tMaR4sUHe}2+SUagq-YSq41Ywo>T_wUsPx!0TQUT=wey{+!`j(M+l?R&lF +z-s^q;UjIKJ_vVn@n5UNQfB)&B1_ +z_rKTu|K1?~qsjh9OZ<pt&`+ra3|2?b!_hSCPSNs3Hx&QCo|9>Ck|9`Uo|0VwaxBCA-=Kue- +z|No!+|Ns5}&+yUzfkP83ubj(_fQL?Pys~yJ5`m9gx+G<%QQLCw?rW6}e;2c>^zFT)v%}xV +z?W_I!k5wk(L&Bj}UU|1U5g(I|^~&0}$wYoiIW<)`{@9$z&uQo8+SaqlMt#Y+wA6RL +z+uW$HS=ZLa?r)Qg{+4rVYwrDHbECiK-P>FHpG_|2N5P|`z4Gq!Vty7qJ3H6DT`u-l +z$*Zet;~7Z`HTAclWo;$N#JO_4V!jp2f&Rv0Y`8hjPD*k*DhPlue%M +z^IMF(G?y>g^GM_9vUX_4l(F`xqWq+3aI{-o@D0^m@u>U-SDd#(tL1mu&X4 +zet*Q+-}d{H&HncPSxf>P*;Tg$IP<%j1iFf+ZV7akZ#4<>RA0I!$XoxYNwBZ^(=EaN +z_N=BMf$pkXLxTNXO+!P&Q@4hO$G4h>MW!#^8Wx>@)HFP{{OQ*4_UkHi%g&2Y8I8beCf8R?Da>@qI0)D-4>m{pVd63@VM&snBw!U=CP&MQ@6*K +z-)}XKt9-t6dtCMVqvr9o-=A)eum8_#kj? +z`o}Did(EHiNba|1vrL)juC_B}vcH>U>eTSGovG8~+bq*&rZ3x>Haq{AW%}ImXFJp9 +z*Rxq=ENoZXm9e%jdUQWvyJkY**Im^~bET*KU8dD|`KZHtU>?$JKV{ +zY(DR1oxAmV+V0%#_uH)Vc0OOWJ8$>_kBX=7 +zDLgLUZc}tpefggMMW^+T+Z3NQf4-;qygj>Z$whbdy(O3Z-EB*+hNtf>y&m6gTXr*j +z`QEbI`NwU`@0LH`TYkTu-LB$cyZXM0$Nlbhl~1Rq@2h-1zum6tZeREF`SSgBzuzCXumAh~`TqL<|Jf55 +zSS=iw#4a?jyCg7)?r>l;yU@trlE9*B;m8$sp-KEm0-Na$N4~NP&GIaX9Ih5lLenm^ +zsJkR`h3;??+jgNbmly*>PWYh41@zb$vZ+P6DfS#38Y38`@oSl0)t)2?kpPCS<`qrd{XgcJ+4Y_0Tk}emTF1T$ +zyT0rC(Y&&)yJO##UEh75HNWDhb=;?E*Y`Yk&9Az;JMP=I>wDj~=GQ#6j{kM-`o8Z+ +z^XtCuj{o=V`u_i{1r4k=2~6TQ4zRlwG>Ps>U^BmQkiV^p +zO=z`AQ4+s-!riTC($qaEYUVdj`nMHLS!$E26@K$n__3mCTlb{umESxa&sIF+s7;#D +z^qXhW-HK;j-IHdv{pQ*Hw&FQYZPKl--#k}-ta#qnJ?VDeZ=SDbD_Ov5o8k0d{MLnb +zw~|Godo$e3Z(ZzfD_Npyo9PvP>(caNCCg0rX8M)ix;&q)bcL&JR?zfYSC+e#t_t0o +z6}J7>)%9(qYf^2qqpsh&w*6S?y3)Pbao=xU-_KUIq184gN&NPW<8Eb}rtZy2GrxWF +zd|TO;rM9_Q;kR#HKUTJF>)zbF^4quXvz6~SYMWOy{q~*bZsohK?#(OPe*5nGw(>ns +zZS$+H-@f<#SoyxMd-LnQ-@gB!t>OTyT|txhod@jh6^BIk6||Y(dC1>haYWUwuq*t| +zBk|)E$4vJX_LbjxEYDtf!qu*5()2q|)ZHsjh3+eww*Agi{r1W;sdmM)uHSiPe!TKr +z>AvE5-|zf?ZqHtIq1CQrk@(#g?(S8WrtT|QW`6gje|y!HrFNyO!tcHcKVEfh>%P); +z<#%7lvsd3ZYFD;t`rS9_?$x)h?kn52{qEcR_Ub!N?aFsuzx%HIc=f%n`^xuyzx%$P +zz2*U{eZ?X1dmq}}YaWU2uQ+CY?_+;^%@bAo%2VO@K21Mf^UQRA<+<{EpXamJzHqg# +zx-|XXm*wuYuR`}%UE6-|>-zTEH>vj3x31s&w*7eRyVCvD_rBlzzMsABL#utwBk}t` +zj=R@=n!3N{nfd*n=iBSPEVZwF6@LHM_2YHlw(hTeSAPHZefIhvNA2rAO~3!=xqJPu +ztNZJ|ZNLBbeS7_%r}p)~uHXOn{r~a$e_!|4|NDOb|9|!a3?c^@RSqzj9AI`iz!GwR +zHRS+X$pQA30~}Kha4tE(wdDZ!kpnze4)8uX!1v_2{|a5 +za!{<~pm@tci75vqmmHMZa!~rnL76KDWuF|B`*Kj8<&c8NAw`u#N+yStT@I;)98yg= +zq*iiBz2%U`ltY?J4ry&Uq-c5a@b5sU;z&mZtnawXEdS +z@|II8rkq;2s6DrYa5oW1OF_Daavt0`x% +zm7Klaa`wiQvp1KVy|v}+?IUOJTseF9$=Q2f&faG^_dw*_LzQ!nOwK)aIrk*w+|!hE +z&q~fcZ#nm3%DItdV$IG0<-G{me32V +zsTbHvFR-^>;Fx-WbLj=HtrxhDUf{WUf%oYJzONVfSuYBRUKCWlC}et3*!7}F=ta@g +zi(;i0#ak~*OuZ<%^rF<(i_%9g%3QrD`}CsR*NgJ3mlQ-VDXLylGQFhidPybpl4|ND +zwbD!Kt(P>WUea88No(sR?W31;u3pl8dP(o=C4JV*2BMb@RWBQvUN&~UY!Z6eH1)Ds +z>1FfQ%NA2FTQ0q9we_;~(aSbhFWWx7Z1?rDJ?j+*(JPLsSDZ|*IJ;hP3BBT)dd02u +zihJu7kEvHYmtOJOdd2(b6`!kDe4k$N`+CKn_5alX(W`-~SA$Hi2D@Gj3B4McdNr){ +zYIy6_h^bd2mtKw9dNum!)tIYSW1n7)`+7B=^;&}HwM5lxNv7A5U9Y8tUQ12AmR5Q# +zz4cng)N7ebuVrn$mVNYE&edzVPp{>Dy_U~h;>E*XzDsuV=l{AbO)w^+uEFjb_&yEulABQ*X4D-e_;V +z(J}Q#=h7QpTW@q9z0q^^M(@)beP3_%v)-H_dUK-c%}J&=C%fL95_)rL>dk4TH>bDW +zoH6z0%%wMHZM`}B=*>A-Z_a&sbKcjR^I2~#5WTff_0}TOTZ>(9EeZX9Yia7OWu>>4 +zx87PY_14Ozw^nVvwfg9-HCJz~eR^x%*IVmZZ*LI2y;1e{Cez!SU2ks*y}dQ{_O{a7 +z+goq%n0kBX(%ZYX-rjxm_MWS^_ddP7@9XXTtalEG-Z`jx=aA{0!>)IZgx)!tdgoZ_ +zo#U-{PE5UXa_OB@Tko7cdgsj5J7=HXIrsI>dDgoZMDJcyy?e>@?q%1zS3>VzO}%@q +z^zQZ6yEmrZy}9)6t*v)&AH93$>fO6f@80`*_de^r2cq{Ls@{8KdhfC8y(gjfo~GV= +zR(kJw>%A9K@4Z}l@730OuaDk)bM@Zar}y4{z4xB={s+_{eS=0*89JY-v4v;{@;{TTH79JAA6*8?UC-YM|$5L>9ajH5PNK>_Snemv9a4@ld#98X^+jy +z9-Fs4wwU(Va@k|6ZI7*wJ+`^_*!J0DyKj%}*`7FvJ#kcf;$-&3+3kr-*b~>ZCvIg= +z+}oabOnc(F?1|U5C*H@N_*{G9`|OF|xBpN4*`5Z7Jq=WQ8f5k~*zIXZ*wfIor(tDJ +z!`q%lOnVx+>}k}tr_slr#$0|@Vzu06|r_AKw)vwXJa1!B(&)t(obJuh~9UJ~}aH0^m=+4J(Y +z=M~eQS1x;Awe5NJvFA0{p4UEmUia;JJ==>0u@{YMFPh9=G`qcM3476+_M)xqMSI(e +zj%hDCm%ZrP_M-dPi=JyQdY`@M`}U%r?d1fqmlM@qPBMEr+3n?&u$NQQUQR1}Ilb-W +zjA<`tE_*p^+soOOjcTtqnZ4fZ_IgX$>#b?8x0Su# +z-u8OOwAVYAz23F$_3mS@_gs6u_u1=x-(K%$dvieS%|W#{hs@p_c6)Oq?9I`%H^<7} +z9B+GbV%nRN%if&Y_U81lH)pQBIs5F*xo>aIv%S3__V%LM+e>C|FT1_H6883L+S_Yo +zZ?Ct#y)o_W&1G+IZF_tB*xNhT-rjxo_TIO*_u1Y(5PSDf?cF1@caPoPJqdgFH0|B9 +zvUkth-o2Ri?&Y#~ueQB=eeB(vYwzAZd-v|!yZ3DGKZw2msP_Jo+569K@4tk-|C;vx +zTiN^XZSQ|f`~UvuviHBXz5jje{hw>^|2}*F@7w$TY#$iJKQO9)U^4%}?EZlz`~z$H +z2e$GL?Cl>orhnjE{()=z2kzq^c&>loeg1*(`v-pZj{@Q!1=T+anST^^|0ojvQ8fLd +zSoufs_KyYtR%KPkI^QVIX0n*K?x +z{F8e7CynW!G?#zU+WtxV_$Qs~pLCyp()<2NpZ&9e_-8})&qn5-jom++gnu?o|7=$N +z*}VO;#q`gX%RgIf|7?Byv(5F-w$DG?egACF{>4H3i=+A%C-X1P?q6KOzqqDegERm{xv}SYoPkqAoH)m?q5T~zlNrN4J-c| +z-u^XW`q#+iU!%5vjXwT0=K9yz=U?N#e~oAVmLUEuQTEFxBzn8av +zubBS5a{2eF?cb}9f3Lazz4rO{y6@lX*?%;M|7cYI(PaLk+5JaL_>b1~A8qA7+S`A0 +zO#jij{72XJAKk})^j!bZ`}{}W_aFW2KPQO)oT&bDlKIcc?mwr5|D2lsb6WY&>Fqyf +zO#eA^`OjI~f6hMsbI$djbD#g5_xe~zaAIadDX +zc>A9d)Bl`Y{^!*8Kc|oXIdlEb+2?=GegAWw{qF_wzZcd2UNZlC+5PX8@V{5n|6VKq +zd%gYdjp=`HF8_OL``_Eg|K7R&_wMt*_rCwV&;IX$_`iqh{~nqDd+h%2N%+5~>HnUU +z|9js4@5S_gFPHy&wf*1g|0VqY*YyA2 +z%Kv|F|NsBT^#4DX|Npi9|L^1f|6Kq7_xb;S-~az-@2F>D7t+c2pm3;#OWY{u#RtVB +z9YX3sYcf769qW-YFM9LhqwS4t!rzdwims9^;P@EhNR<@bhE$d +z+}e_LeN*o1Z@PDO6g|JRHv7BYy**XmKfQhZUH`#>CU#-HoF4{{j&z9|=e_x1_~gVS +z_26|mKaHN9nPp!5?#)l*7Z(<}Pu9!*W%BCEs_@NuZ-1G-xv?qz^19sLX7BFoD*ycM +z?QioB4-T~p>*xKk`1It|bmRR0@BUbRd2wlZ@cO*JR^Q&-+Ftzr-Cye;A08c_te^kS +z=GT{3*Ei?C|7ZK>$EW9)*XRGY`}gP9_s{R&|F>^oI)eprE#V!k_PSsfzN?n?(b|`i0?)stBV|dC!x!3eog>s+e +zs~yVyw!eNTPjKY2RGH{1RjD$`Q){QnWM8YFDpLZzELEq5Mpdd#i_F@oIz6`Pr|OKv +zE=#qUsk188W@WD0sWv-z*H5)Mg{Lgl=a$~8RG(M*YNz`A+Fw7_7c_ENX)J7&s?u20 +zskKXEaj(@cjU^MktTdNSjjGaIHZyCN=JL5!|9@$&SlDHywQ}jKDy>y3SMAbTy>{0x +ztu-4@S!u7`daFu%-Og9LwAb(b^-FuhK`v{ZjYp-bbvB*U+O4zstkrLwEf>A4b+=xP +zs@C0hGi$f*_PbTTb$2}Mvew)AbXK+Au9vHJ>+OEK>$l#XkEg8l_kO)qt-tT*tKIth +z|Ni=|e}IwO#^4~UbdA9wPVGGghk3347#tDwwlO>^8eL;}Ofq|q;c?mOKZYk1yKRh4 +zs?M%4I;FXKkI`w}-G7YE7@oEpjNjZGZnUzTn7hYjV+5y4K{9r}kcx +z%f8lsO|Arb+nQbtjjlDl7MZ=*^m=UdU(*|j-L_^oQ)kzj-O613f3Ml?+}(f8?i8N3 +zHNRVWyVm?(gCeebylxduHI+$dhPCiR&O?*wzGb_^>&^0yPdE1S-;==`=9lP +zgWUEuACF4c+k85yz2D~ZS?m8cUoLvv+kU+oU2psCX7+yD?{};J+x~diZEyGU>Fj#D +zUoThhxBLBe_kX)TA5Yuc|NVNq-u~au*Zb}N|NZ^no`G580He@`1~wZ7CgBwaSdBI` +z^3^D?C~F+#4BF5nwnu@@c*Q~9q7BV*e-t>JH4X_*+R&nAqsSG!;*jX34Xt`Liag2x +zH4aN&+R$dUN0G01#bMb`8`|yuC<-)d98nb7*x_cQBs6)&5mlp&oqjb+B8xSSY6flW +z3frS3wt2---J*@%aetH~4r?4UoV2kg%|==3@`_`on>P05)hNq6);Ml?X=7j69%Z@D +zD~{WK+Sp(BM_GYc^Ms?&rU`8}DoVmDPq-Rwn%GyPqN1#M(lcn&q-lFp)Qnf2^ex&n +zdEOrt4QI_$fs-~(S!ScE6}<9P=%!6m*VU-%Bx|0IytHZBwmqtP#Vb$8e%dsB-ycNpVSEFw8So3`4rOorc?NPV;yz+eQr_J;K{ZV&d*1FIrv}FOC +zt%j5Estc_~TNd)wYPcwCUF;0nvPf*NhMV!Ki@il#7R&wB@Nm|;G;z|FC2F>sUcsv_ +zP2IF*sa~z7PqNnKnU}UKGux}_SG?--+)rDU+x^uHXx6&2P-yE4H(RZs$*Zm`HQKt; +zuU0E$vDVdY3tg&TJ4y} +zTGw}8+Pbc6uXfz$RoC}^+Pc2(uXX~n_Kkx=+cvb>>LdxTzH!uO+s3|HofKv5n*A#U+qNvT)y)cCee3F`ZClsX>gFVC-@bWi+qP|c +zb@Pf>-@f~4+xC5bbqkuc?>rRRzT=p!UeV;$cb*z;-+8W9uVk_I-IqbzcU{}7SGIZe +z-M2;Cci;P~S8-VT-p5JX_dK)Jue!YY-q%gr_r9yuuX(I}|L3Lc`@Zefulv0E{@+jA +z_y7B=-@vT%fKhnI0X92>CgC*?SdDiai~&afw0=dtAF9mmY}8TJ*gc`W;R$8o!Vh7+1~o+t|M +zJmF?%G->jhC#uFfPx{pvOAYps$0DC^#8bjMl%lUJTsiU^GuqZ +z@vO^ho|$gmc{Z=kc+O*;=a!duo-5mDJn!?G=eD1Bp0E37yntEvg`@DU3vG5Li-gy{ +za5dg_v9Hc#iL&lX&){8`rtLFXX1w;LZ}G0n^ZuEvaMpblICg&Ui&t;c=zpl|IBtA)_qqvdH0=XcILY-uYFg#dH3CSb>@2>>%OnN +zy!+m_edhZwRbx-t&OX-r|t(x(}_!dmi%DTO3i=``8)0=aJa{ +z{}#uL*M00Q-t$=Qzr_h>y-yP-?|GtTZ+R+s-KVLW_dM0Bw>*=q_j%^!Je}XYU)L7z +zeI57T>c(NcZyP7?eUoNyee3eNZ(BF-eVbQredn>>_nnvbzAM{reed(S?|VP*eP8$A +z`T?{4kAuSdKD61}JQ802> +zE>7O}WtqM0tKjv&u5RA%m2mte_sag|8;G@-M7u_|Gq8W|NGv5yB~-3|9zai|Iagf +z`(KyW|NFXm|KE4@_J1Dh|NnV;|G#hh?f-pV|Nrmj{r~^{w`X8mz`&=#D7Ju6PJu~n +z0h68rv)KY>I|UZE1uT9FtYHgS;}qD^7O>?hu$L`huT$V?Tfot$z&ULJ=R5_jWed30 +zDR6IFz`aj_=hyl7v07E1IfN={oSIZsh)*+QvxiqhK_ +zO7ByYIkr&doTBWtg|hb)<(@5+d#5P>ZK3@Ce~Johixl{j6vY-P$|)(SEmG1`QZ`$p +zY^S8+wn)WKNi}SdYMhc<+9I_)CH1mJ>UByQZHqMelr*O;(wwKHwQP~rIwkFGi?sJC +z=^R_6b52S3+9KV1N_x*0>Ah3Z|F%f~pOOLFVgo*9L$Sq%a>_<(i;eV@jm;Jt+bNs4 +zEjIB}HVs>B8mDZQw%9CB*}QDAd7ZLF+hU78Wy@)cE$1m)En950PT6|fV(Wd%Hpdp* +zoKv>Fw%GQbvfZ=AcJGw!zb&@^r|iJC#DP!6QEZ8$oQjj$5+^+sXR{^Fb}BAzOI-X^ +zT*H>Q#;LfaEpf|JaW7lqUZ>*Gw#1`P#dF#c&v`0d|CcTCTBqW@ZHf0j6`x~Ee9o!( +zUR&aOPsQ)q62EsU{@<4P|5FKITN=Qp8Ys3jP);>SZE296YOvYTU^~?ix1}L|s-a;^ +zL*rD#(w2thsfL#=4X;y;Xj>Z5ry4nJY2-ZBsAWr|)~QBsTN=GjHRjmTm~*PJ*OtcK +zQ;mDJH13^h{I{j?|5Ov$mL>41C5kOelv7JmTb87!mTa~x*-kCRZCQ$+T58y`)Ht=Y +zv}I{|YUyRm((BYR+LmSXsbx-EmN`!?YuU1_b!yq$mSyi#%Q?0z=bT#ZwPm^Y)bgG! +z%X_Dm|7}_RKeYn3hti6>ZBa`qV3@Ew7xXUbSp_)jIX+ZOg0osn;A^UUN>p_S*8=d+K%1me;*g +zum85Z{-1gS+lmH0jYhE*jdB`IYAc%bG@8v;G}~#kxUFdM(`XG_(Hf`GmbRiTPouqT +zMSGn_N85^yK8?<4D>~BW^qkY^y|$wFo<`rZ6@BkC`oFE{ +z|EDp5ZRG?$&52?wC(3C~Qd>DmPjj-_%E@+`Q`}Zg@zb0dwsLBm=Crhx)ABT@m#v&$ +zr#Yi-<%~YfnbTIzoToW!*~(e#G-q#HIeVYxoMS8JoYS0pZROm1n)9BmocB(1{{L?) +z=l|1Oz_w}upVmULRSV^`7OAaTq^GsmY}H~rttD=&miTEc4O_J|PHS1(s%3dv%ga_R +zuhUx5wrWM6*2-zCR?gE}wQSX@by};pty;ZLYt6A$YtCt{y|!xYJ*{=mR;_!dwf@_x +z_5ZXsu&v&}r@c{Z^+q}EO=_z*>1l5^TfNy%dyCuZEq>Zt!&YyN)83Z0dRw0M_OjL6 +z>$G>Yt=`e6y>r^?o%6JJEnB^7o%Zf+t9S3y-g9jAo^#rJudUvDPkZ09)%)IQ@Bg-X +z|3B>mY-6|&X=FB;rv)9&~y{B{T*_w0jbk2WU +zbN-*s1-7*p_;fFdt-UCxdr58WB|Y8CW@|6o>0WVLd&N)pYS`MVak|&i)?Ulgym|Y?zOdd@9Exqw)WmT-TU9x +z-v6ijfNk9aKD~!x>mJJKJyKiuNKfyv*}BJedQaTeJ@M0f8n*6foZhpvbZQYALy_eJ0y_~1_YT3G1>-1i4TladO-kW3V-kj5Wdu`p@dwTDlt$X)Q@BO!R +z@Bit2U|auzPyeIX`j2w@pVZcW($oKJw*Ir7{+Iu5>%aKve+^szHBSFq+WK#K`rpge +zf3MU3(YF3apZ?Ej>wnJE|FvxWuXXyrx2^xZPyf%c^?%Ok|Gl>U?>+s0&({BYr~m)k +z`v3p*8Q3>4@Eb6SZ(x)+U{c?}q;J4%zJb}^fW>_Si@yPD_y*Q^1Ge-HZ21Q4FE%zJcew0q^wF@aHzT&Qt>xZ4d0|1Z={yKNiE+_BYk6I^UcQg#wPBYP5g~b!#A788=IwXHp@3QFW+olZ*0-N*`nXra{6Y= +z`NmeuH(RYYw%)$kdcU#F@y#~pjcu=Qw!Lp`_k6S6dt>|Wo9+J_JFstY;5Ts;-{L55 +z;-tRCN#DfTe2cTaiHrLd7k?Ai@GY+KCT{6l-11G_%eT1Kn|QQu@#r`4{6Bq*=X?{d +zE;=SL*=lB+%^CrI6xA@*S@q50-@4bot_bvYaO#;}r2Jo8(if;{+Hw{wX +z8l-O;Y`!(v-ZaF0Yly#TX!zF9c+;@-tzr45;pJPy>rErtw?_1vMo!-vIo~vD`PQiQ +zrqSECM(;O`IleXKylL$9t+Dq_;@cAC&63o&CFz?b +zn{P|DH%oEfmf~-g8on(x-YhMBTUx$Zdil2Wdb5o7Z5jP$nbWsr&Ns_izAbCLS@!mA +z+563Mj&I93Z`}T@{^UCSlE9aY6E#F?X-n@GI_UirS +zHOIHtoHwt%zPqYcTTEczIf37DqWI2<@|Kg-cTUo`oNT^xvc2UL_nlMx +zEvJU>oEmRAEq&*-e9P(OJEzxM&S>8`qu+Ao^qn*3Th3a(bJlvx+1q!{-fubQ_|7@! +zE$3d}IrqNhy#LR4&Ubn-{TP-%)umBX8cl2BDoW6VKeCu7yckfzny?gua-TSTg9N)d? +zy!GDeyZ7F=-uHa>zW3Jqzwh4v-}(Uio&)?g2gUasl(#vgzUPp>&0+IBhwW{SxbHdQ +zZ*w$!&(V0BW9fU2<=Y%D-*dd)=0y9R6aV{dPEOx*a=y)}<$F%8w>iCi&*}X(XO8bV +zbKd6c^*v|r+njs8=iGan^WXQJ|8H}FeeVT++l%6RFUs3qQr~+?-}bWk-pls3SKRkr +z@wdGizV~Xp?X~p1*Ya(zm+!q^Z+oMC?~Q)jo74B+oNs$;`QBUWZEtVidwaj_o#T7& +zoVUGueed1-w)dXzz4zYs{`bB2|Jy!b-}ivu?xFa;hw^rh)b~Bow|i{9@3Fnz6Zd^j +z{Oz8G?|T|=_bh$ivwXYf<@=u3+r4Pt_oCnK<@9|o=i9wnzVFp~yVu+Iz20y4=J>uh +z=k4BJ-}m;u-Mi=e-o3Yb|9#*4|8^hP_kZBG|0uryqrCkm_5Gjp?LYrF-~ZX({)_wm +zFaGvl!}ouUxBr&D|69KO_wxPU>+OHE@Bh(n|8x5OpY!d1E#LoZz5Va)`+x7Z|8so* +zpY!&AukZhR-~Qk8{r}$E|Np-K|9^XjKf)Fh5*(Y^1=T`!Oh|NY=T~%_VlgqvwOiaU +z?aGdc$?pB~j%^~AlTth05(|l)}7e2ePb8@=>eEUW=QL8B# +zfs5TIs)g>Fk{P_*f1%q{tEpL`tHU>@UEMV`JA8fo!8TFrX*rRb(=RRy-90TgdVBuE +zV^giC=f&7gHU)wXY +zIQ{?m`Ht;kwzEnyFE0;V9=3N@Y4-K?iN~ke&MwQny}j`HwY{^;^Y8C(WEZ!aQ&IT% +z_(b*aeRC>{pPygoKHY9^Rq5;N8`H1vn_FG}{{F#sar=2Sm7kwqTpqrEUTyXF_YaRx +zx1V2E`}_OH=hyeoudo0ApOGiv03)Z)g#*ljJ^=?=C37wuWLNA7IK-*B=E5Ov!!rSg +zc`e^uILz_>Q|ICl;Xt3jqoR>H7mtc3_5>c2%v^Kvm~`Qpz~i!&Z!R8}Z{!I& +zq1dT&>4fq`pP-YfGjlGTRA1N=bV_sOnoFm&H=YSPt-JHhrPKNcd4kUvp47Q~#`vO7 +z@LAKFIhW6xKkNxUXZio-n#<>`Kb{FbZ~ODj<@5H8ydf7HId!jGa2E6px#%jHd*!0L +zVsFSLPtCPgE_oZC4Y}-V`S!|Xe@EWXD}kQ6SFZ#I`i5Q&jm*7zH9WC5^jc)*+N;;1 +z3(tmLkF9)r^?H0GZ`h5*PTgxak|+9x-AtXCd+lcW!rri3nJd>`yOq81Y}oDGoo}z* +z&OgW-ey8xH?)5vx7k$I;mfp<0ez*K#Z}`2+mus)ztNwU4{C@4vx7Y92Gx9||Xynwp +z@t|4IFXCaVWZsR3?TUR7k2*Ei-FVb(crM~`ujRWNkNX|@BA-n3)Vuj)a-d)2)2Wep +zH=j;V?2CLhGjrX|XR`~>MLwTf`R?ZP`TraFqFyZQ)VuX!@kGCfL^``J!L++pRbAZol3BurK=E&X?+gQMU3fnB``ya-cfa3nQ_siJiVBkAxRWxN7=M>2 +zSKYjc-LYRDi64}3Gwhq#lWOu<^5P6P({&Sjb4wmeKa_B{JU6kgbjxGek2BnD-%aeV +z{qk6zQPRVaZ_FMb=Y0}g!PgD(Odiv%~nmqT*6Lm*P +zufV=ZQ-NKpPvF|2L-}~jMexsyMBH!fy +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 +z$C-rn5z_U^y85AEJPt$X)!-@CW} +z-hH%t|F!P@&wcOz{(H}8|ADps1Lyt^y#GH4+J6+S|0uctqwN2WiuRvW>pyAk|D^l> +zlcD`*)B4Yr`#;~|1J0b +zw?g~xrS;z{_kXYb|Gm-vM{E6$&iy}n|Noe1|8r{n&zbvw&i(&$q5ZF=^}kl`|F!o2 +zuZ{MP2km9eh+ +z!(p`K#6%VESvH+UOHWPJiQZMiX}s*rOq1+edpeDmpPOq_{p$~>$%+dLUAm=gyG&MI +zTIw^~s+P-i)s>YYtE2XInXbOJHfDF#UoNvXH#Vl6o@Lu@w)WQ6oZGwp*K(V$yR)<8 +z^{u_#=Iih6t@-`yFSo^p2M1fYrR{nwHa$TeY>gt;4-F3Xy+uq#Vl6`w$ul4qKclT8P{>N*xS=aJYd7T1y3CY5(0v0bi? +zBdJ5F&m*Z*ZC*!Gm)5!yN!@z;IFfse&UqyFn%(P2?z4J#BDvr0A4kdrCqB=ViEeV8 +zDgP&V>77iO>}SWBIwi=@Gj(cMTxaUEsJxS@)8pzm(`F>~d8W-wo7b5(D{I}!wAp$4 +zIMe48o%2keTXwHAeO}eOlj-y8{&8k3XyWtASlA}lm9eNx?^MR(K0B_=C6oNTGM7$^ +z>&jdbr~wcGA>Wv|=y?o{^r +zegC*}HXP#f&e?cOt~+PbDZSG2Xpn|x2fAujzh +z1&8_Uc?ypR`TG?9KPndAQ+P}&|4iX=xq6x9nEd`m<%X^Y-(W-zhrpTYk6fesB4`s`qEh@7Mk3t$5JH +z?^p4#O}?+iMc(Eb8~Gdbw(5oaUbmmG`pu^Ee${Wc-S4Y@x9k16>i7Hp^VNJf#P478@tAyn&8JiP=W9Nnv*)k< +za>?Jn_UpCy{@QQ1^3T_PzgN#+_y5PEe*e0k&*t~n{d%?jeBJMN`}yntd^+!6|M%Pd +z{`!Bv-k-1k|L;Hl0S2yu1{R$KjQkx3ShNZnd2$vo%X1v$@G58$S+jsuzvCcJRzb7O +zn+5Fl9ESwD3R+Zj7IOM`91>Yo(5jQOkUO5^u*9i?Hj_0AdGk9C%e*RRw|TRWzn`BpC +zEP1};n8~Wb-khAp()T%zTbwHFD_OHx_I<~3n^%SXHE$Nn|K~X2z*RJ%MQ4d3f9DAo +zt)huNIZKq~IZt|c6-}D5W{IkP=SiQeqRDgK{9mGO&v`1Kt7ys+ou!)oou@)p6-`}} +zvs62t^K`_iqG?;!EY;2LJRS3@X!@QvOZDqH&m?da&p4v9%&@=nOo~?V%riO5jOTNn +z&G0Iob!E*m)AgNabFzwO-+8mld_U*8g0A8@Pjr@Bp6@(YvZ{FQo1Ep=_c_m3oGPC8 +zWzBNi_nqf!UKP*(^JcmIf6faHTqO%wbXPd?cU@@FDp|;ryTVzX>tcsj$s&=pD_r%v +zF7{-VES7n@!rh+h(uA&(B`UfrJ^i~bO<7g4R3~?(cRbhS8K+8?nXFyuo8NVL&a0B; +zHg8w@*K=K2z*V}!MR!$Tf7g{ITBR#}a#sb<=eoMWt8`V!+EtzdrP+40=BZ=5RIwq@r}^LAbNe(rl8y2|%F(OqA8zWd&nRpoo% +zI8-+lkjtMdJS-mb6z&;5XbyW#+g-iAj0o(C-26$g3pHZ;rg +z{C~*dU2#Zc-G)~Eo`*cy6^CWsZD_aWc_h$XaYRLLW2b-5Bazh=M|JWxcE|HPmN;E; +z%w*lh-u#}&GOsI++q~P@U(fSIfxGgAi{7S*{XI`qv@1{g3m5IGi#>T;7R&R#^zg2_G-chErTV=ueX^@A&w00H +zxjpZzfbOa*OZ2v`^zVHYvbyT(n!K&6<9T04oUXdIW!=`b`Ms}WURPb;^KR?}%DQb^*Z027$*#VA=iRpL`+46LbXVVb +zqPKnL`QCRWtE=z6$=kmBKJWXA)7AIBtlPf#eee64*VXs`yxYG2KktVI?wSWI`a2Ht +z_kC#5u6f9lzvHkx-^UK`nnxn*cO2F4``D9R^H}Enj^p-xpC)wIJW +z%KAM|_4|LG$*%i6=l!1N_WZvtbk}`ZqQCd0fB&y5tLwh5$=~}rp8xla({ct@k;ibm-Zjj}Hq +zXj*^@(QN7tQt@ +zEsh#3&K@nU87=M|EuJe{yic_FzG(61Xbse84fbda&1enpXpLOa8hxTQ_C;$vM_Zys +zTe3%6YDQanM_cBKw(Jvaxi8xCIob;~+KWBfOEcQbJK8H(v{#>KuYJ*8&(YDS(b4SD +z(VEfG-qF#yqNDpnNAHV{evZzG8l96pI;UoIPVeZPxuSFSiO#t%I_GnAE!5~*?9sI} +zqicCb*UA-Lt50;TebKd^qkE%9_hygotr^|hJGytS=-z#zd+&?x{Tw|9HF^$v^c>CT +zIo{E8az)SS6Fp~N^qlADy{OT9*`xPrM(_2G-kU3WZ=dMB`=a+gN8iK$8hwvF`krR= +zJ@4pyxuWm&iN3cl`rdQ&f7Iyz?9u-jR=<|Osb +zNt!DsX`h^=`*M;#=VU|8$;O_OO*1E(cTTokIobN;WZRdM?K!77YEE(XoZ^}}#l3Ti +z=gKMGC#U$noZ`QzKVSjXpUw_T|)g&S{C7(~>=>rDje`@0^yo +za$5GuX}K?_<#SFi)SO=IIlVMW7O;0M +z;9RwU`_uy7R}1*L77A)D6!uyunzc~8YoX+-h0>=M%D!4C&$UQVYmu_oBGs%#>RpR8 +zS1r;$wMh5XB7LsKhFXh_y%wAP&suEWwb*jiV(U|jZC@?6=UU>ZwZz$LiEGvp_pT+L +ztCo15TH^a^i9grUK&_?0UQ0u>mWFpNja;=f`qa|cS4-o$mL+N}OZHlpnzbywYgy*1 +zW!a~e<-S^$&$YZzYk9HP^3ts3J#@_Mcnjan<3y;ihlt!VFB(Yb0x +z_o)@VuU7PPt(>T}awlkG|M%7U|6CgwwKp((Z(z;dz}~%q +zbM*%9(;IkSZ{X+ND5$+r*n6XB_D1pUjgqT3N}t{+`+B21_a;T{P0HSzRI@j!cW=^M +zy-EA@Cf(PY^tm@1YHv37-fWt^*}QwR@DuyTRc~9 +z@jku9_w^Qk?yZ5^TZ6r~hGuUK@7@}@dTaFQt+B7S#&d5=)ZUity)89+TYC4l%+=en +zPjAb8y)B=6d!hFBV(;yx+1tyzw^y#-UVVCd?d$FJ+&dbzcQkwNXwBZy-o2yq|LPsx +zr+4(e-qFv!bE5Xn$=*AsX78Ndy>sU3owHBxocns`eC}NfwRbJ{-nBG)*YfUND_8GY +zeR|j0*Spqp@7}1rd$afMt=YS`ckkZ0diU)m^AuHJk5^xnI#_ul8;_fUJ^WAA-Wv-dsk-uH6# +zzSpPsy?wp!J@@{P+WS9y@Bf;;|9kiTpR4!(KE40%>;3<^4>0N+VD>q{nsb1?=K$xL +z1Kej0@V+^~&vQ^v=b*69LD8Ip;yni?*Bq2Sb5QopL3y4-iaLjseGaMS98&K&q`Bsh +z_L)PvZw~4695&QBZ2aHnuxZX=^Pa<&YYtnVIc)ppuszQaN1Y?iK1W=0j=1+6@mzDn +z`^*vFH%I(=jt1%+4fZ)2nsYR~=V;`bqtR!M#=bck&vPtM=UB4OvDBPn={?6X*Br|} +zb1e7Gv3#E6g*wNJeU6vr953%VUb*IY^_k-ztQRnn#pVM1&PH*oyy>rd!-DghkeRFz0&zXZdXAb+EIhu3kc+Z)WYtEcL +zbLQ-uGv|5EUeq~z+2`!loU_+^&fZ*e_V$^xcmKaRd!OgrL!EPvea=12IrqHh+{-oR +zUY|Ml_RYEXJm){^od4`|{%g+p?>*;#t~vkv%=y1>&j07Rz^HqH+4ll#?gjSV3!G~& +zaG$-v`}P7q??pk~i^9GaMRPBT_g<7-dr|uAMcKC(<#{hD>RwXzy`-9ZNxk=y=Gsfz +zXD{i#y`;~3*--bgvF~Nm+{@;@mo3*`wmy5=_U&bR-Ybr}SDbyXxaMAQ@4e!=_KNq} +zE52{9`14*3)V&()do?uoYIyI}$hB9a&t8pvdo`Z-TB7c?WZ!G4x!2NruVt>imVNeG +z?%Qkmyw?kLuNV7XFU`GP-g~`r?e*%j*K6NiujjqdsC%Q?_eN{(jrRY&H#*ne=stU+ +z_w9{--kTG3Z%+2TIW_m@^xm5@*WR3c_U7ETH|O)-TBv(#vG1*=xwn@0-def#*6Opj +z*1o;9p7-`f-P@aeZ*R@Ly}kGL&b7C9pS`{J?d|=%cMj^_IqZAqXzrcky?0Koy>t5P +zowIN6oaeoJQTOg;-@8|H?_Te{dvopG+h_0IeS7yl@4bh*_a6J+dzyRidGEcKYwx{2 +zd++Vrd+&Mgf7HGI+4ug}-22~q@BdtT|M%JZf8XB!&-;K;?*X&l1J=9;?0pY7*FE4q +z_kj1^1Ae}Tf_e{y{T_y3J^H~YQbn)iBp-|L<0Uhh8ldhff}`}y7+)O&N-@6FM?H^=+loLu+j^tm@@ +z-@Q4{_x7UR+sl4$ujakI-uL$Ay0^FgpL=`v-P`+o?;h&Cd+hh_Y2Lf%eeYhbd-wX> +zySMM&z2|%XQSbd{zxQAB-hc0V|8w2@-{;={efR!9-v>th56u1_So1%y_kZAA|AG7b +z2j2G|`1wBy>VFjW|0tUOQM~`7rrpETEh(mwx5 +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 +zNXJpw#!)EYHl#u;BhvTaW>L%Hnwp# +z@o_edaW>0wHm`BE=yA53<7~CY*?Nz&%^7FgJI;1*obCTOJMg$T%D6b`xH#LmxcIoZ +z#<;lUxVYE2c=Wh<&T;WtnAt#OUs;~I0uHTI5c+#A>b@qb(sc-#_Y+>&(Ml5N~leB4rF+|qK~ +z(rer@dfYPSxMi(z%iiObbH*+Aj$7UvxBNeD1w8JBGVVn>?!`9lB|h$@G45qK?&US^ +z6+P~ibKI-exL5CSuQ}shd&j-*jeGqc_XZx1Mj4ML9gk)kj}{+~))#(PJP_s%)q +zyViK`-s8RJjQ8F<-uvEo@Bia{fXC;cjL#t*pTjmjM|^ya#`qk|@i|`ObE3!R +zD0??h?ros_-#`W4AVt|ACEXxp+aML+Al29)wcH@}+8~YIAkDc!T5E%}_Xg>l4br_E +zr1v&R|8I~1Z?K_ku#s-Cv2C!4Z?I`>uvu=fd2O&oZ?NUuV5_yk)_a3(&Ia4w4Yqq5 +zZ2vddfj7iaHpEFc#Mw5)#W%z?HpDGA#Jx7eqc_BJZiv^~5bwPqK4(LG?}qrj4e|dQ +z62Kc8C>t828yajI8sZxo8XFpx8ya348qpgXIX5(FZD{n~(3rEKv3EoN$Gr`W{~MaX +z8=mZTe&Y#Wy188Q8+^FTXQ7d|*R?dxDwKi(?-l#Qaqt@PyTK6_;{okk!ywMwFqc`bB|KDsI +zy~Q_rYi#tk-01DK(K~vhcg~I8wKjV9-snANqxas8-uE_o|KI2XyfFu5V-D%Y9JY-) +z;u~``Hs)Av%<XYaRikjsIR7|D!kl=iK;TYvX_KjsJ5t{_oxRe{bXe|BYwhOJI~sVA4xqwo72~OJI#l +zV9QHjuS@vP(U-tEFM(@a0{6ZIo^uJj_Y(NtCGh`C5a3G`luH!SOBA+C6!A+GjY|~E +zOBAn5l;}&8oR=uIE>U`4qRhEO*?Wm{?-J$zB`WYGDas`&=_M)KC8_u&sm3L#4~FG=TIlJ31Ey?06a|B?*&k`3jOjr5X@?UGIWl1<~1&GM4X>yj<{ +zk}c;YTdhmB-j{51F4^{8vfaC6`+vy}d?}7{DNcGR&UPs-ekrbTDQfiI&`E~7~=quDN_#V?~ZE~70kqrEPpqc5X#UPjltjP89IJ?Aod +z?`8D8%jo}?F@Z00qFm-Az0ApWnN$2Sr^aPY%gdZzmpP*^bLPCvS?e-q@5`KXE_3d^ +z%z5uJ=l{!Gz?Zd9E^Coq)?&M?C4N~;WsvC`O<=&i^duv_p?R~j-&gI^{mwWGB +z?)`tc5BTyP%H=)M%X@5>_rx#nXc@;~Y2f40m2;+Ow5F8^Cz{`b24AAR{h=jH!em;ZZT{-1OCfA8i0dzb(J +zUp@nW0i%2YlYRlSeF2Mq0c(5#TYkZR_WA;j{sPYV1zhV3xc3+EoG;+LU%>aifd7Ai +z0DqyNe4&tjp|E|Sh<~AIe4$u=p?H0vM1P^={6eYqh0^;AWzHAM-Y=AUUnu{-P=UWl +zQNBn?zew4>NX5TMHNHqKzev5lNTa_yV2HTg0 +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 +z%SlF=ldWD(cFLUM^>Rv3=G3T{Q*dTvnX^{C +zoV6))_O6$+4`t3d^>WUo%(=H-&V7_Q@72qBpEBqFdO4pdYXR4*1wvU1rCu#m%37rL +zYLQXaVyjn+owAmAy;>5KwKVG0(xm@c%d%cAE6Q44^=f%j){3rID<);Fob_tuqO4V` +zUai`cwR+d9)rYdyoO-q9Qr6mAuhu@wTKDSJx=&f_f4y4Il)ZuL^#-BrjZ&{SDrIld +zdcDagd$ZN+%}&`{yk2h!%HA6FdTUblwyf9Nin6y?z24rGy`$^(j!D@&XT9FJD0|nc +z*Sj`l@80!#_o3`Pr(W;5l)d-X>%EV%_q}?(?^E{vU$6Hw_ +z&8bZ}r+2+MeJJP5sW)dX<($3s=Io=KbFbc<`;>G3*PHW9xfi(pzr7%odr|7`MWx(J +zT5m5I-d;`0y_WU%T2b!xs<+pha&L6Ky)h~G=B&3j7v|Lg62ro0DS?;Z%{J(PO)P$}<` +z*1JbWd5^8$J$A}_;`Q!HP~Ow1cTbb@o@KpzR+RU=>fQ6Eycb>XUQEh+IqTiaMR~7Q +zy?eDO@Aa;CuMg$DIrZ+%rM$Pd-o1U4_wLoZcc1d!|9bbHDgOi4`wv3-AEn-ZRLcLP +z_5PDl{%5QApPll*c)kA;l>asA{nw=YZ&~lZ73F`gdjGvC|3}yRACvNb&U*iIQU0%0 +z?|*IjpZ|N;``?H1|D1aN=TiRPTkroq%K!K3{l8E7|9`#z&s4y`{eeNafKmDbqjCY0 +z_6H{80%q$E%+3WY-XBTZbbpkXTqrsFqvYa3sns8)HWy0o +z{wRI8Q0DYUnahQ;w?E20E|h!yQSNi0{O^zQ%tZ>^pA>|P6s12YDi|CVc{YfRbNHzNZC)MO4wd_x7#YO7XpVXU+G`c@&OfJ%#{Yi6ik=E)@TAPcscYo48 +zT%>dQlg{NL-P@mZ9~bGp{-pQ0NdNaIedb~V?#~9o#fH+K4V8Ec>%raj|*zXY=M_i|)@BlZ!29f3{p)Y_UpyC=c&+~8wYkK5_ZRQOB|fLW_*^dW +zz5T`aaf#pSFMgj({C|J(XZ~Lr!2LBqxHM4uYoKyzkoMOgLm@f=ffA +zzlJ84hGl;ZD=rPM{uE=!dDmZ)5or2Q?)xGdTFTe5Rmiubpa;Ih={Z>h;; +zY1!Y>ip$cgzoj>qWpsbbm|T`Q`&;JXvaHqLvNo4x@BWs3xGd-Nx17smxwpUNJ}%39 +z{VnfvS^n>D`OM`7+}{g?%L}Ey7b=$*X@4&=E-$wJUhG_6;{ClOxV$v_dueicS@!p` +z;_~w9@8!+q72V$}CYM*v{$9DbylVCT?^T=2t9O5|K3raN`g_gg^4iz|XIE2nt>oDy6)HTvh&!%F>|e`@tCm;) +zTHai>qWjm1$yF<7|5~}YYSrprt2S4y-u-L!;i@&Kf33M(wf6R}wU4XTz5ccCbJhCa +zzt%HXZ{YsDLAZLO^zV(z)tj_`Z!)glZ2fz)bM+SQ-&=yKw?_Zonq0jt`}elu>h0CP +zw>MYs=>EN9a`n#HzjrRK-nIJouFciEcmLjfxO&g&-+L}s@4fwd@8jxyuYd3RT)qGI +z@BPd*2e|(n5Ux2W{pX-^%^~eShm30uTmL!iTywp$l{*PQ?T +z|Ic~m+6&x&F9_FOl>U2Bx%QIw-%G}|m#zO^cCNkR{r5_6?bYbNSCeb6W&gcaTzkFx +z@Ac-|8{L0zOs>5-`|r)gwYOIPy|uaa_U^y857*u~{rAr0+Pk;^-hEtq@Acn%pKI^` +z{(GOf?g96|2f}p^rT;xtu6v~Y?~!rcW9xs9o$H=>|9cW#_cZ$7)8x8m+5esu*FCTP +z_q@68MfblKlj~m2{`Ycm-K*9AUTv;>z5Czm!*y>?|9f+}?(OY=Zy(pad;Ra-=eqa5 +z|Gj6f|G@qKgK+&v>Hi;<>pyA#|72YM+4}!y=lU<+|Gxy+e~teCHM#y<_Wy6i_1~-i +ze{Zh;(f$9&tm>|G$sx|GobI?{oeC +z-~azJAF*#_<`grj5J+@t<(4!n`5}yCq&%3+3^82^H(iH^{{~zuYH?NbaEP8r+rg`~4nW~bPmsf^wuam7V +zdwY9l`S*XaH5DHppPX)9FIQXj_4Uo|<^SdCYJPrxd3}4me0|;D-#@>9|1W=lfmLS0 +z0VZA>frBifF%u55$<_!Q;!vG4;SiVZ9)ZI=rgtVB=Cl1Ha74gWX5tYcUmL-rBB3!8 +zkBY_C2p*G2oip*6RPG+Z<1(dpCLWio{Udlnp;czm38h{ep_3|8V><4cl!OhzkkIaG_cCec+kXaC-JaFG;YSjHrYCfM;)s3W<2WB +z-6!$5$MoKe$9=Z{B%Vxgm7DowlCPcQ(qoAqK*ubtG(B~#;Oy<9f8PU_W)rSoRJTD5kc)ax}{@6CF>Ztp*-H~%*r +zm7D!$(^)&|w_C2p&3?P>Zk_bI9Z%=Yez)uGKI!*+zTTVte&64J(jN}6%Fp?5h}T}` +z;}OyLIUkS7*2{c4p*nxgr&GH7Wj>!Vy+7ykIotm-UoN=H&;4@A*IxGPmC*RPU$4d1 +z%YM6&I)CoBTe`PiBO(^gV%g%PSB0DsMQ;{}bqN6nP}j +z8R4QVC)nv3@ktYhAksi)+LK8bfo+wG) +z^l-Nmnly3B6BW%!PwzOP$uqA!QM0`1>0c)_WueGZ4bMog;CVt*SB5;*ioEF+zE5b{ +z#wkyAG9$gC?+H!cdF82I0fd2T&+UHR^soOXyns>kg#%|)K(n04Le9_^PLj6*+U-OZ2~K_Cq8Sy~9VfC_^6CpW +z%Ugl{bs|d?MPGV&Mg>itC$dyC^rctit)S`qM3xy&ed&`K6+HW%$a2f8Fa0WS1<(H{ +zvcggHRX}G{$YMFsm7bxmf@a +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 +zbMAe~d5 +z)#JmiS984USqGK5J?8 +zt)=l&%aWs(rO#THeQR01)biq}<>j-MSKnG*FSVjMYDN3372UU1^h>Rr9JO-#td+BG +zt(-5lYH`%6<+E0;zO`z-)auPqtGCZuz5CYc{ZeZVN3A(NYt89fYtBopy&Sdn`mD9L +zZ>_yAweE4$y63ajy}q^Xz0~^8QR~0YTL1gj`u|cJn4>qa&)&d&djr4pM&am<;Z!eDCUOsz!_3iET(mR@?ceKym(S3Wz|9XKgXQ^KIi=JJLmt)Twsp9z&`f^_uUKpvKNJ8FN)8- +zD1G;$yzC|A*h}hjFKOSsq%V8fIQFvn+{@N?FWbvragM#>KKF|E-7EgGSA%1(hR?kk +zefMg-?6u_BYw2^ZW#7G)FMGW>_Iml;>(zI!*UR2$j=j-7_eS^K8~w63C&%8LKKJJA +zyEo^{-dY@cYx&$;tMA@gFME4)?CtGyZ|}Z)d%x_R!?Aac&%JZ{?w#|pcQ41@y*~Hu +z?YnpH%ien&d++(&d#~@_doO$cbL{=^bMODYd;h=e1Ln8~?DHOQ-+RC>_fROq +z()S+9%RN$#d!#<^k@meu`f`tr;~xJvpZD1M-eY^YC(dzC+~+;a`R)91aM +zeedObxmSzhUM-*ZYW2NW>*Zc=j(fd*-s|1>UhkKCb2#qJ@p*4f-+Oaj?(OBcx7X*r +zy?yWPeYtm!{P*(t->dI`ub2PP9RH(z{*V9N_kZ-u|C}8EbNc+B +zv+w_$FaK+C{IBKnf33d%YrXvM&GEmt&;Pyq{_p+re-6k0IX?f->HB}q%m2L`|M&X* +zzqjxIy)Xaoas0pM^Z&iR|L?v0|IhLNzt8{w`~Ls`@(TYrWjr=4IM~b~tQB)&!@|Su +z0?J-$>vfkU) +z+}xaTc~$J`ZEJ6DFL-=vuJ`tJcXwBOes%Zs_VxGoH!yR{`Rv&6@NkE)cHEgA8y_E^ +zpzJ;Gzt7H1PfyP<&c1hM=jP|<7dUsz`R>~C^74w{)p2KcZGC-xL-Of)zPq=*y}hIO +z^}VyZx4*xCpqX3VZ_kd8k55e2jz70&=jZ1a7JJY4+q>)Q>l>T1@1NVd`}_L`hr8wd +z_wD)l`Niec@#pvL{r&yJQI@M@u*W{*@{P9I@?}6>NYs0@wmt2 +zTE^pEi)Sky_t|`V@wnfCP4meF7qQGI6Ft;cKAGfW_VUT(05{F2Q$oTrpH7WPTlsWa +zOxeq)(-Yb>pUp^_micUE#&1c=v8)#hJJeRaSkzc!#-ZdxywObN?+xpYR_s+Y^=l)ZYnd_kMms})P8WxZOt +zV%e%!tJZ9L^=kEoV_L7*Y`K>8dhL#9t6s0$^X=8^^#|Cr-)uM{mi=bq3ANR4Hk~nh +z{but8H|@7uu7qX3-FhQ!_1kTC%3iIvfu4|v269bU2nF%ez*I>G41zz +zzFfR3d%N +z$73?(Z$2JZXxI64LS=f+r;{4X*L*sqv;EDd(+0k{q48g1;=&2-zmAC`~7ak^R?ga)qH>Z{eAXMa_uC!M*ZqFC=li?g?+>u+|M_r4Jpa$f6YA^#d^%(P{?F$N?)rbfTnW$r`}Icp +z`oG`ql)wM`{Xx6_zaLMg=l}cpV)^=izus(r|L^yQ3I>;M1z^Zot* +z{|r0}{xh(99AJ|A(7@rdfJyws0XC}-jRHLjSkyfZaz%Y;k~p(~&HThczN!z+3Ooxr +z+&vBn&HB)y;j@q{{KO%#T_0KvdKU7edmNU!^`Xt;%tF5M6Nlw~eQ0;!StQWzaYRY# +zV~2;&BBALgj;L9E>>#I~O}s#o=~JAr4h#Bq;fMzcQlWcVzW +zx_;uA*{+Yh1wD&po_id(y7jTI;>=>X?q$EY)!LJQXzS)07oHOSQsJo(kLbY3hca +zr8?=Jr=xCtnzrN2QoZv3Cr`)y`ZWCj&oYB{&ofCcLV?~DF6v$vyQ01^Zhcv1adxF&`KinE +zetlW)z`H7--RsICsjn+Md{+fcKXql9)z_5)y{kf&dtF@>^>tOm*;QfNPhDMC^>uXu +z@9K!-Ue`9w`no2=cXibDQ`ffb`ntBDcXiBjuj~JJ-TJz&;_T|U@29Ts`}K8w1Mivy +zcJCX9q`qzF@LiK6e)`5St8W`8^sY%!_r7^5>f5FnXV;{epT2pn>f7c8ylXSuy>DHb +z^=-=v-?drcr*B=`^=<2h-nBXD-nVbv`nGMy*|mA)r*Gf;^=-&BNz6}lRJ`b3re;nZO+t4I_<^h}a +zkAnhz8(P$T9&$zhI3#gyL!0@ThkVsP4lD3&>~QyaBsBZS5e>hMUEyaQiS7RX)CUwC!h}>Q(-w2zX1jl$E$G`k=ef^wtJ^=% +zRh-*A@B5kOcE5j~Z{XXqfZg|nlk~3(9e!IDiJyJpX8r5pguX3H)O}xiMgO`qy6@|#+rO^uIJb3O`PtWT +zzkgjnz_)EfyYHJM>EAa__-)%X{p_1G>)$so^ljU+-1lu(^zU0Y&TZSa{p{Pk>fg5? +z@NM65-1lA4?B93(zwq0>>-yPuWxIdh{m{34&vW1RRkwfN`*CjjzVBz>*Zux|pMihJ +z0d~I+P11iJaQN>yB!2EgoAsZE0{uIVsQZ2FivIIR;{1+d=I1{4RsVUcz`yf^yWgit +zv;REN@ZWhV{M@H$yZ<~j=-+uJ-S6|P+kc)}oZoq_{M_exzyCaU;NNwj-S5jH>Ax>L +z{C8cNe(uXM>%T7p`gdJf?)P<7^xszz=XYJ(e(vkK>c6iO_;=qp?)Poe?7wd^{CD5F +ze(u}0-GAQ}^zXj&-0%CY+kf9xoZo%#`?>G?e*b;nz`y4KyZ?_v(*Hhm`0sfne*VWX +z>wh07^zV71?*H>t^uJFt&hL3VKaX@c-ZY!rlMZrP=?!tnlCaD*XJfYrFq_ +z-O#`HO}hW@Tetsx+i`yHyYlnD@BRMw{Q&>I5AFVc9!dZIal(J!r|IYaJhT4)^Fsf= +zFU$S^zKZ_;>&E$g-?pFs`>y)`?+5(*e;oJ!_i6V3KQH|E|GIwu-?!cW|9Xq4K~D08Dx?nk47M3a(5lS)LBT1At_j3%ueO*%K4^nNrMNHiN+G@C>;n^iPh +z%xJdS(QI?0+3rWPgG7syMT<*Bi(5sD$BY)Q9W6dLTKs;r1W2?7S^RGeiD(V0XpNZB +z8nvS}=088bR(?dY6y +zqjTPm&IJ-(i!8d9M072y=vpzOYt@dfH8;A}{pi{t(Y?u{drL(3wu)Q+ArH+s(f=(!-#drN<{CqiryPDdT;IMy>p}Y +z-jCh~5`B*>`kqAeJ*((@F{AI*j=ncH`riHM`ykQ($)f*DME|#n{vR{?f9>f1bK`&i +zzaRY!k`tILC$L0LV5^+KF>?af&Ivp>C-D88ARswW$a11cO6{B|b918H +z&xs0>lawqcsYFgvtDK}UbCTB1Njf(t>HVB!AUWB{aNair3C5J~yZM{hSgYIW@>~YDnbNu*#_sGp9!FoEmd;YTVDM +z36j&2ET^SJPD`ttmN9c$*3M};H>c(OoK_$?y~uKUN#yjh%IOs|r&sNqUUPGL-OuR_ +zk~5ktXS77lXsevjF>^-O&KW&7XY~D?F+pF +zUkeqa7AaXRQi)omR<%fD)*`K4i*#-+()+c@Kx(m()nb#V#b#BDEoLpY+O^o`)?&L~ +ziyfqvI9V-miCW@TwZvoA60coLd~PlA`?VxM>i^OptEC}POT(&`M$B3owQFh2t)+3l +zmL^CoOR`#)616O?YFWmtWm&tH<=k49_iI^!)bb*$WJu61v2t$X)t-3O`lpRCq@iCX`yYWwoR~zy8my +z_5XgYXOP~&WW9kUdIMYa29DVqxOQ*gxxIn!_XYv!jY8HNMWQ!~Rd1A-y-{lSMw#0i +z<$iBeklv(Zy-6i{lUnsAjoF*Dc5l+Ty-Dx)CIjisM%J56qBomWZ?>4d*=qM@o7}iOw}w@3jhMYPYWLQd+gsy) +zZ%vTimSnvxC3;(0^|p-J+p>0V%elQR@AtL>>Fq_<+e@OimsM}Cn7zGf_x76G+v|RB +zZ;;;6WWA#$dPiIJj*i(ox_0mAxxJ(B_l^nDJ11H1oD#irTJ_Euvvrg(o%4S0 +zTp+z`k@c=6(f@ZXtKPL@_O4aCcdfa-Yu)c%8>Dw{vfjNVdiS>K-8*LQ-nD!8p4+?k +z{oZ{*de0&2Jx8MV9IM`QV)mX>yZ4;Az31HTJr|_+Ub5bMC3^3*>b*B+@4dBq@15Iw +z@BQBUKziRJ>wQn6_dToL_hRsn{%mKEV0~~V>aP2w3bLRlxp92Ci2Zd}7io_fgt2rn!=b+S{gEDsx +z%KbU0Aah8`=8#IvA+?%A8gmY5?Kz}#=aAl?Lk2R3jcg8^#2hxOIczcKu+^T!Hg^u& +z{WlmY5T5H77deoaowfqUX+uzCR}>$ef&Hb8;qsYN!Ymc*P|R_oKvgzoLY0|)Ve>XHprabWOI5;%;{}4r+3Uby=%|uJ$Fv; +z`*Zq$%$Y+rXO6_2IaYJ##GEsy_MAC$=ghf3XD-N`y<~ItO3c}7HD_hQ_G-l3t5JKe#@xLc +z_xEao?6oA@YbmkU(rT||%)OSi_gc=~Yk7aK706yMvb|msd%djodd1x9ReP`3+`V4+ +z_j-fujV9Y0EwMM+YHxJRz0tMzM$g?FeSdFEki9v{_U4q>o6~A<&X{|1*4~?Q?%tgD +z_vQlGTZ?S}-&zuTYgz5B6?1Q`+Iwrw-COJa-r690dz0<$EwQ(^)!yDQ_x7&6xA)w= +zz3=bs1G0Ax+1@!4d*@i~ofC8KoZ5Tm%-uWZ{@%GDd-sy<-7B$ouhrhYG57ARy?5{2 +zy?gKP-3PMw9@*Y|5_|7i?Y$Rs@4ec4@6FwN@BZHVAbbCl?foyY_rKNN|1tOeuf6yG +z+`a$r?|lZj2TXPkSmGYA)ji;t_ke5P1D<;i`2IZ*kb5X(_fRD6p;+BRiFpsD_C1uj +z_fYQNLj}1NNe9CoqLb;{yj2~du(L)*d*?;S>0ocd5^93J+`^` +z*zVtB2e~Isc28X5p19RL@tF6-Yu^)}d;g#K{d*E1_cX}vX-M4Du)3!a^PWcSdm3}^ +zY23f333AVp?4G5>Jxi;5mND;H*1l&s_nzhbdsZO#yvXi(N!;_Yy5|-1o>%RAUUTnx +z-M{Azaxa?fUbMu$XsdhCG4Dm!z85|BUiAHYF+uL-B)gYW;$BXxdpTp?%USzg&bjw; +z-oKX%-hZq6|Nh6k_rLbN +z|8wvCzklx;W@sA0_60l-mDM +z=Ke>y{~s0PKPlONQi=bhR{u$3{wJ;dpLFhj()<6(K>o9l{b!T-&t~{Xcr{|LFVwV}kt8N%lXd#Q&UD|8vItpR@M=oOA!@ +zy#GHJ$p8Pf$o|)o_+QKFf32ARYt{Z=YwrJA_y5-h`QMxDe{YHZy{-QDj`_cL?f<>! +z{_lPNe;<(lbIAVBk@!Ey>i?XW|L4^HKWFa$Irsn11^K_1?EhYg|9h?e?~VC?Z|(nk +z=lTBXC^#q275d++G%@b_{1 +zYXAOYm5KO}aHy46-fd3A$E0Juvi5Bgw9~!rvWrHKTe2ak-r}~l&PTcxO44iq*pKNgEw`VbQ5p-AC=pyXz +zV(2Owp0d$ZJif)yO)`DSMmOpFBZls>}&pXOR&E^ +zt7%A}yXw}EV1HNB(9rPIt)b!Zt)^j-=}WhUMdu$i4Ua8hSuGM8+0}L=H1oSzB({qGPur2$F5hO6)TzE~M^d-` +zF^lA0^JhDf`|a5*Qzp8r?M#{M?`D}gH9T!+>h$<`E6ENE0-_Zm9={PF{|vg+n?>qUcaBsI%nf?wcRDBP`y`|UV+ilBkrZ3-H +zc02#LZTa2u=X=ZV*R$JIJZx9rSMj*t-LCTK^z?m|&*!(>RlQukd|%b;_5Y9CRlnW- +zd|&nZ{p|KNACIf=ulao5-M;qg_4NI<-|x5E*Zq9He1F~V_s8w)|9*eIzyAM!_5=o2 +z3kN2#3k~cp2~46p9N5e*H1fA3u&7!%a)n)J5ntA&%$vdzsfDXn*u}2!BS~Ugcev`6UF?o$NtQTj;bt`LVo$nDveeZbZf4sq_U5-F +z%RIGkx4L$*ulz`|+}9oMcHb`c*R!N3uv&UJiCvn|?vkP;y3@nW?9#;kmJ}6LOHZ%= +zVV5RNKa!$my3^CI?9$}>AE=^hPlByND(<^M-rK#&%Qgu=-y`!#OnzsE& +zs$S_%@3?Q5rtfD-GibH+NfNs}n^&@F!TX*{A +zm0g~FpC#SmsHI=gw99jzyQEuP-RW1h?eg6BE$KE-E&Z#mU7q*-NV?tEo&I&-F38BbU0G&+G}EtiSMa=VSC-qeW(Blbg)9=gy29Nx +zD`@JjkY#39|F86K%?ep+6}l?y>ZuKZ|r+}B;<`@UUWU(cG8z-k?FNbK5%cGsLF(cKZp%&u+h +zZ_Pi8IEml7(C${U +zNOW(8oB6Ga{cR;nRBbc8!f#!geyn7f>E2Ag@>`eZvz4xJwap5ee(TC|x6)Oid$Yo} +z-@3ZKt#nPQZFbc4Ti3Q9D_vK*H#_e8t?T>Q%KmR?warNqzkTDlTiK?mdvnsvZ{IxM +zR<>oSZEjZh?OWH6m2KO)H#e{R_U-#@?|na3zVGYa{JQVA@Be43IKXOG&?J860lRy}A<=yWZRU3#^0!wUQMD`V3cvG6 +z{CLGN(|v_~<#!&-vsa#QwJVx5{mv70_sUbD`--M*zw=bTz4A<|UGc2zcb=IauRK?} +zuXx_~JJ0Ret1h(Kl`Im!`@-G5>eAGGCCkk3zVvUey0X-+bXEA>SK-I2u5H~{x~}~0 +z>v;C+8%OQRHch|#Cf&XI*42Gw+qU0*o8Ml2=c!%!uIqQ-l^?IZ_x1n2@_pa$zOQGm +zdBAF4aY+2$hj#axN22>Hj+x*4*xz3BMAg3XRQSD5(~s9YGu>Z#uKeET`RuhXT-WBGKVJK;bbs}|@Atm%XRrIvYG3n6{Qi&Q +z?scE0?yq@fe*fqB_PQ@i?Q36!-~V;}c-^9ZU*5IJn9a@fe^u(8WwlaRxvDTmEU +z4x6_ewwQ9*a>-$#94~e`UJ`P=H05|%$?@`* +z;}uhmS1vhTwdHvAk>fR2j@LdpUiamAJUmBlM_`=PBJ+;+2!PvkdsqWPEIR1IlblN +zj43B)E;%`C%gNbCPR_YaOE0>&FwdK_6Bd69}Ikoo5sdZmYt!FvCLFDvCmD8I{PH%QOy(Q%I)|AuRN=|QY +zIlW`b>77eX@7i*D_mR_kuAJWc}U$k{tr&fa};_THDX_gT(85IOfy<=i8abB|rlJqbDYH09j0 +zl5@{n&b^p&?&XqmueO|fedOGmE9c%mIrr|%x%VvRKZuS%)(ad{FK{lsz_s-P_t6VHS1<5By}){7ETFG?=GD7E#X^wEnlS1-ywy(stfqCD#*1<^~2s+W{ZFDbiTQVG4JntDmC +z^pbk(C5@?J`tWSG=}f@jiOR=js*Tr&s*GUh!wW8X$T#Q1xn%>D6G@t0AFRLsPGYm0k^R +zy&5s~YUI+ZQCqJ@AH5oL^=juAH9}y^;+)JYk6O<<+ENd5WQZgdcDZ>da>*ElF;j=sn^R&ua~!8 +zub6tha_RM|t=FrMUaz@&z4qz#y06#kS#LCm-e^?4(PVm~+4V+C=#AFY8*QaG+FNgQ +zOuf;$^hVd#8{J25^jy8s`}9WN*Bkw;Hz$bRoTz$plIhLKt~aNI-kh3xb6V-m>8&?s +zOuadC>CIVNZ_Yk?bI#S9bD!Rv_x0v{)>{iiZ!J{4waE0=V%J+sLT@ciy|t|L*7DX{ +zE2iFBx%Aemt+!Smy|w1*t+h{Yt^0awJ?rfaqPI7y-ri(-d$a59Eupu!rrzFGdV728 +z?HyBZ?_7F&*Vfy+kKW#M_4eMUxA%R$y`S~Yf&Zd+4yxWcWP0bY>zyN^caEmsIaYe- +zcS*{65TeZ6y@_3j1HyBAgOUNXIV+4b&~(7RVt?_Mjt +zd%gAUjj4BUF1>qe>)qQ&@7}q3_wLiX_rBh}&wB5H=)H%k_a2$vd+d7eN$9<&srR0h +z-h1A9@5R)6FPGkXwe{ZXqxariz4!L%y?0;ly=T4uLG=Dd)%%}J?|*i^|0VSP*VOyp +zO7DMfz5iqC{hv$k|Jr*0_tE=*uHOIq^!~rE_y4m#U=Vx2sP=%#>;bde1D3D{tZ5I} +z${w({J>Z!3fOFXcu5Ayvk3HbI_JH@<1HNw$_}Lx`h&>ckdnjb~P}uFENZ3Qs|7j1! +z${vchJ(QUCP;%KrscjFXk3E#R_E7fOL%DAc<=Gx7h&@tNd!%IcNZIX?O4uXSv`1=X +zkJQ^9X-s>hx$KeFwny5>9_d_rr2FiV-nU2kY>y4Z9viAXHZpr`?Dp6s?6GOuW3#fy +z=53EHraiV?_SkCMW9ws&ZLU4GefHSy+hcpSCk|py9MzsUnLTlKd*TxI#5L`STiFx$ +zwkICbo_H>M;}h!0 +z(}-zLBbPmm+V(X1*wdJ6Ph+1wjr;aAp6ywJ*t0~nXGvzylHH!Aggr}5dzM!AEWPbn +z#Z+kgo +z+RK^CUe4O~a`v&8bFRId`|RbsZ!hPwy;>mlYN6VzMP{!SyS-Wx_G)R`t7T=cmbblH +zG40jLWv^Ckd$s!5t2NhNt$p@t-M3fk*hjvp0v`-W&;gb2RPEv9dSE +z+uod*_U7cWH>b9}IeqNSnQL#(K6`WS+ne)jZ!d_wy{Pu~lG)qKZf~!Iy}g?D_FCE7 +z>uqmuOnZBC+1p#&-rhd;_Rh7pcb~nz_wDU{ws#N2-aS-%_sHzsW4Cut!rnbid-tsD +z-Sf70FQ&bFx$NDmZSP(md-vwrySLBYz5Dj=J=^;aV(&kyz5itP{kCnfVw%I=?3!au2|e^M*| +zq~88XWBMn}<)5^+f6_kwN$2_}-RGb5zJJnZ|7;-s*--tnk@;t1_s=HbpH0(0o0Wey +zZ~tsD{j=rr&sN(%TOa>ybN#dJ^UrqQKiji^aS;FFsQ$&t{EM^u7nkrauIXRg%D=d` +zfAN_9#dG-=ukByFkALyG{>AtC7r*ac{Mo+-h<^=K{~BceHQ4=YNch*#^siy%U&GtK +zMoj-2x%_L?_OH>$zs6ku8vFcf-1o2X?B5c^za^@FOEUkK?EWn!{99`Jx3uza>FwV# +zrhm))zx-R)_HWt8zvW#2mizo$-uG|$?B5H-zZa^1FEamL?EbwZ{CjEo_p6g$R{nE(`_CEE +zf6iR~bJq5svycCrbN%Ps=RfCt|2d!i*8=fh3)O!uGXJ&M{nwK4UrW<}Ei3=Ey#3dT +z>AzMk|FvrSuhqwYt-1bd?ekyjzW-X!{(FP??~UreH<|z5?EZU8`0uUhzqghD-roLu +z$MoMjm;c_i{rB$UzxQ1Kz4!U=egD7z-p~H$fcT$->VFQI|2gdb=ScXUqv?N+mH#>3 +z{^!K>KPQ*}Iko-I>EnOST>o?S`JZ#&|D0$4dqMo~MfJaz%>Q0?|9d6;@746b*UJB1 +zZ~uE^`rn(&|K8gE_xAC>cdq}v`~2^{?|<*J|9c?*@1gp?N9O+?yZ?I<{_kn}zh~wD +zp11#dG5z1m<^Nu7|M&X%zc<(ay?y@g-S>a*+5dkK|Nl|_|0nbRpWXj|3IG2!{r|V} +z|KHpH|Cs*&=kou*w*UWq{QsZp|NlP!|L^<%|Lh(0Ozc8B86Ol5wQz|W<-GWyc%(x} +zJ!nnFN2OyuQszZ(UVKzOF+s_Fl1}C)l~Yr+!Z+o-{G@tjhEe*Z|7$WotDT!;RsQMC +z%g^c;7C5yF>1KV=xU|G;x>4?{FPc|Y1T7C*oAp)e+M1~CMQ>ky)xNPI>G&kw>~A`^ +zwq#x3l>7Rd?wuV)&o8ab{;qd#Pu2HNZ(o1ce{i6QU05&Yhry#GUE;=hZ+;j)IWb8+ +zcwNpTliyt=X~d~@F0U#4$vY)ZepF88_)v2LTm8#Psvv#UZkFENtIwP^m +zQf+4HtV*?6nX7iH&CcERQ*BP+DNFUarMD{8=T*MisXo8<*H85Yja*h53tOeCG!}Jg +z?b2A>YxPTG$wV(J&81VLsx+6)%-W^7d~Ve*%@qr~th81xomHi^YUQe3TC3OY`lYpI +z<0&icwOemhX|LP)YM1ubLHW{|~#Y^>#j;Rjs${<*MC!yWj5mt+(gnDQo?`UvE|G@B8^`xBmXW +zzkcf5q| +zvulh_X|CR5bXs@!AEPsdr)`YSn%=H4K4ZDR*Zh9%@4w~`8oBK(9=1x?Sv>00-e>W+*ZQBulZoDTmQSZf*I7QBnZ3{Q +z`P}M%mM<1|+gZI_I=jy5)ymcTtX}_LyZfKjn~kUKtlw_EU1$An=j(me@Av-xXZ_(I +zx4q5Bqtf*@pH6D;xA}b5`oGPWi{AFOU#~{j+kU&5z2Elx-Rl3gKOT15+x>hxyWZ~C +z%hmhse!t!Q-|o-H)Asg%zuvC5|M&Cte*6D_fB&~lJLqCu11?C_SLAUC~Kbd4B9kl+8z}(yL0cAy?bUEIUUjjzXv<=`zZxFST9+nH+OkB=_P?fA@TyBw +zH*Hy}SF7ohtaW+jr7g?M_GDS|wXQ4_+PcEcRx4=osw+#4wyyN6 +z)e2dxb#-OX)>UD9wZb;9y1KS#>*~0_S`mk}u5FyObxoSBcGTrn*S2ojx;C#?JLa+0 +z^_`ctt}ENC9rtI_Hp3&!EkC%|qVe9f#%q8FV=7 +zJQAF|`B&nEO~jyF|&P!eZ^}Y%YNQ*-0q*@gl3&5io!cj +zxY-#^n!M(Ts`1W~esxAu7VA9K4BmMvY@gAz&1;_O7VkVA_s?j?VV!4&lXsp;vooG` +zdCfD^%{$NL)fvxutn=LR^3HQ*`;6y(Uh~}c^Um{i|BM$f>%MRl-gTkP&Sa7B+83_I +zyDs+CnJiJ(ed!sz>(aD+Cd-W1{(tFPyzBD3eso%q}f~Fy1ee&*3En0=G9x@d93$+=jFZc%Jy5|`@HV^-p_mA*ZsGC +zz^wn{pzyvAZT2>ggxCK#YP|1bU%kx}W&NKggZF)!w%_KN@%o=S-doA-TPS8w|!S^xLV%lp1<+i&}>c>V9YpZ9&=_uuwIv;Lol!ux+5 +zv$y*+dHtWK#`}MstGD~ISpVQY~llT95W^e!N +z^7?;YH}C)ZuHOF7WBva>FYo{NZNL4$&+GsH{k;GGzyJ0OYzr9p6d1)8Fv=+~sV!jA +zQ~1wpwt(4AfyHeBi=P5(*aFr#1-7&WYer@;Sh0slV*0k(w#e2Ri%3kBsAh13=b=_v}E +zEfls>6meT9;-@GYwoo)qQ7mntSe~MI*+TI;MTxeB5`Bu2(-un3Ql`^tOf4 +z`xIr4EtEN@D0^+8>^()fXA9-tDawCaDF07Ufo+ijpOT{3B1JhRCACFLdP>S>i0Vp(U-zDp-m^t|@09evEzBg-ii_J47e5u( +zuqCc>DsE{@-11c1%a*v;sd%(4@#s_WoVLVso{HD9C0^@Pytgg!-lyVoY>Ce~72j)1 +zeDA6FJzL`UPR0M*690cH0c=YH_*4VMmIlhH2B|F#(o+pKTN-Sq8sfGz#7{LeY-wnm +zYFOIRusqf9vZdj5s{bR}mPYicMowEAIZrie+0v+Ws?pn)M(34CgaV#^Zc)RNSeCF!Xpn=MPWQ%iAMmg1+D8n!GoPAx5M +zSz4Z2dfBq{I<<_pWf^^HnbVeK&Qr@;wk&I%TK2YO+56OTjxEbMrDNZ(v)|z^BnDwxUr^qe*SW|0X?+X0sK|b{Z{iD_ZH~wxXj?qjTDd&UqSL%T{!))9Bu|qI;i4&#@If=QMh+t?0d{(f4dc-#d-|Z!7x$ +zX-r^SIe|}eqS(rba+;IWR!-8>oNTsovYqA>x0O@;G^d8GoEoP&Ep6qrJk9B4E2r0K +z&S+aXqfc|@w3RdGY0g@$oYo?>Rg3hr7MrbFY^SxvZPgM#t)*eBmd0r3_i3#;wrb5et^aGUty+6eYu&R|>)vUt|F&xV +zKdlXHt2gjzZxma-QBHf4+UiYu+MCT*Z?@Ck;?$RTXVcl +z=S16@6MZ@-r>!|TPv_LKHK*3;oZhzP^gf+4$JU%Vr*rn&nzQ$G&OKXm?w!v0Z)?u~ +z)49O5_5z>oMX|LP<#aEpt-YkDd)aL5Wjoy~Zfmdj>0S+6do@n?TH4xcdAirj*8aa< +zr+cGq?TtR&o72|boTqzh+1gv{bZ>84dwZYmonve7oYTE~ZSCEAy7!)~z4uP{{Ah%M_o7el<+OD#=jpv#w(iwBz1Q2;z22wy=GeM7=k(rQTle;!-n(b(-o4X%|83p- +ze|jI-)_>sB|0uTpqn!RHwe_F$^go-e|7@rK#cll;KmD&^>%Ye7e@k2cEl>Y@+4}Ew +z`ajy%|LD{IIc@#VdHTPWt^c)7|M#}_zxV0?Ikx`KIsL!a*8jby|L@uQfA94Fe_Q|m +zpFRWo1_piuM)3`d@&^Bz)Hg8c8!(%1V7514ao@n=Z@?P9fi>QMEqw!9z5#pr2KIUb +zj`j^4{RW)VH*n53;99 +zeWL)sp`iFiL3u+V^^HRMhQj6>h3ySR+&7B&8;XW+6pc3&OW!D#Zzx{AQM}$zqJ5)8 +zzoF#xjgs>XrIv4$T5l-5eWUb#Lz&|nWzHMQUf(Et-%#%PM!ENf^4~Yg|2I@%-=x5A +zq$s{gQQk;NeUp;Dk+S(FWqTtP_f0DPMylbPRO5}*(l@E)8>yFXQm;4CXy2sKZ=^YW +zljeLQt>v4v)*ESW-=w|YNay&b|2pT5bgysHy>F!Ve3RaLBmM82^#2~e`C||&8G3jX6c*F@{P^QH=EZRTeNSs=r^{UzS(lV +zvDNa;R_l$ew{N!IZ)|gXv(0&9+v}Tc?;G1a-)#5Z*#7%w`~Suc>{}f8O&rCyILezi +zsc&)8H*q%K;%sl?;=aYj-^4Y1i)*}zTlyBad=vNbE$;Ou9_?E^`b|8iZ}FUO; +z*LoB0?OVL}oA?~x;&a}__xcv!`zC(RxA?s`@&CTX|G!B9`_=${(?Id9f%2w7>RW^K +zO@qz12HTs4xNi;dHw_Kn8X9jJmcBJC-}HZY`PT4y(}?!15&fo-)3-*>H;r1pHEO+S +z^!BaM`%PnxZ;d%`8hd?f?0wU?=Ue06o5p|N8voxkfqh#7zgeRAwnTZeB=v1c`ew=I +z+mh|gQrx$t_?xAMZ%d6gOH1FDmT#6`zAe4pETer}M!#9+^lh2*&9atn%UW-iy?tBu +zezTn8+j7pE-fz)!d`HiD +zi{9%ydhc8GJ>Svy-lG5ej{g4^6WDi7;J2J8zH_3yHH-#IPca(emB>GhT~+IPB`1`ObOoE$4sVIsd=q0`^@C_^lR-?^-BtwMc!}B7LjH=DQZ#TP<T_k7p7_g3q_?^^%gY6JW34gA&{#dmL%x89__dy~HPX7k;f?X9=C@805X +zy)}II)_Ch}>ASb(TW>Gly}jOgNBiy_{nk6D@7_7zde`#ZyVhIp-oAVHe(OEQckelG +zz4!X=z4xv6J>R|Wz4iX@yZ8UMKES@`0Kd&a@jVCSZ4RmLIizoM*nH1ndz&Nfdye?q +z91Y)dG~VV|`krI?Hpk2N9Iv-I(Z1(IzsT<-{#EmJ!j6_ +zoV~v1?0uVa&-a{rZ*%_pp7Z~0F0k*tz;Am|eD6hh+e_+uFX`J}Hs5>M-u8<7-YfpL +zSHt&Sjkmp)zW4vNeB0~gd#~5q-e})@qu=)C^u0Id+umBf_ttvb+uQfv-fw&7_})9` +zZSP*+d-uNWz2|%Hy|=ypeeeDMwh!3%J>a){D8BEZyxk-9eUJ3*9-HrbY;X6(ecuy* +zyQks%p2pifOW*e_-|l(&zUTFJFWUFL=(l@0ec#LZcCVK2d$r!~_4a+Q_uIWWzVFR> +zySLZ(y}fVu?)koV@9o}y-}nB%-3RvlANcJ*itqm@Z~sYs|0jL>&*u9-+uMI}-~Ywm +z{%iRDukrTZ()WMMxBp(g|9idtkM{jP`t5&C-~V&I{jcTwf33Iwy?y`h{q}#3@Bee& +z{_pkufA8D>d%pkQd;9<2_y7NI@9;<1VnV`y$7XgxwU8YX5}n)m72T#-OiXg^7B@`0 +zvSVVhd%wJ6n~3G46wk@(fy+X6PD=HjuAg{pisj@q-`VDc&#vs8obEs0zL8DTYDz}n +zV)uz^p}VGJ1~2zt=r+}AYF6m#@QrC#cTLR>Umt(4P1Jf?PUPnFi_1cHPs@$op8xRJ +zRO{(^vAfGZKD)YmdVc)=dPa6Jn;8X(hua0!!}iQ5Og`SP=swM6W>M4xdo_RK6! +zKR@5GUCee?N#^C{fy=}8&MM8mzCQ8zG~3x_xwp3$KEJkic6t8&{f+G6c5^BUA0MBn +z9=>l*W%2X#3*D#N&8;eZeSKs4^?h@z%irHW*e-5Aucq?z^Zyr@hwq;L~}acZu)aEROROu%7Y +z%QqJe^E>hc9uf4^xp+i4&?oSyXk^aCqvDA@fyX2>*IYa%U3ez&xNPN{i^t^~d4f(T +zcIsR@p*+zi=%nh*oJ%Lw7xo05(p-z9AP~C3CM_bXV*R +zx#X$2_R1x1!?PimeJ$T!x$N)A8+s+sQ}^nX;6UHdtD%v(SFeUA_J&@I%v^i*|F!7C +zv!T~xE8kwd9^c3tb|bM<_u7r*iN0YsQ)lL0yP3YQH|$pC%C*;SWp6wib~|_H+iSP; +z5AufJDLkor{Z8>k-|)MoH*>GwEq~Y>ey{T7+UxhKKb{T0U;Fdz_51aVd=U>CIrVNl +zXcqK~c-ShLcjIBZVqe6gPR(^U9(5a@i+J2?`R>N!en-B@ClfvOZa$eD=ok5PYGmHc +zr_&SrBA?C7TzB)??80-A&*xUYyZL;6BVW{ug`Il0UM!yI7xi-K%)DDKmoMy#dbM)p +zx?8VSZ#)^d^>;pfitK`J#XP->*0G +z@BjV&us{Ca&zI}(|NH&%eEk2vKi}W~|DRF9fst=Q1FOjcM!^{lta=j~c}pHJD@r(W +z`b}sO-SU9daE2pq-h^h^FAvxqC7cBNCbX!UJmd_V;Uv0lLaT1cL+(TgXUTID+Dx}R +zurc40qdi6Z>nw +zJeFsa^l;>xG@;exiK5_44_CcO6MIXZC@V^OdiqV8GJTpHi=~vh{dCpUl=av^| +z`jxJmJojzMbL)qa{*~t@&-=RNx$Vc9{$>TC-% +zRe$D5-?C4W7s{k+`p-NSy6w}{jdN0U^Jkup{q|}4L76ne{+VY|%|6e(I48|?{miqu +zWuIq1lu5Td|9|GW(rusTew>qT`+ny6+HardGsKwl7N!=Vtom&$>MK+n42zvRQ%sv#u;P`?@l4ZdT~}Sy$JVeO;X>n;m(6 +z*0rtMzOF5tn;rXp*7d#LzOHYS%}L~+edDOvw~Z6$=A`P+zInFn+vbI`xtad6Z(ZH? +zZR^Ilxw-kXZ{Pj)ZTmslyu$w3cb=Mk-+6IvUg`STci)zM-~CWFzw-R-dtbMG-}`ZH +ze(n3&_y2zTzMoO9ppk#h16K1N2L*qZEuUr1}bfR4G%=2@enQs4iws2nY-1l>y+kXFf +zzEQ4ZA^+SLuI9fkPMlY=RDbSE-|}CV7s{2c^q>1Gbo;NX8|Rg-&7b=^_WQ5v2j$8( +z_RoEjYX1A?#d&30*Ux>MTmJj@L%H&u=jXmF-TwRT$9d&@-_Lzt`~CNQM)`_^{PRAv +zn*VtyIKSek{=AR9<$oS4%2%HBpZ96%_CHSz=U1N1pZ9t0_dm}a<*P3C&-=2}{O`-a +z`Bhif&-=Qz{O{{T`RbeJ=Y89{{qNht`PFyd&-=dj```DC@-+|n=l?ir{_o?&`87}V +z=l?ug{_pcb`P!HM^M764{_pF?`L%EJ=l{O@{onV4@^v5k=l^+X{{R2ai}UNguAl$+ +zZTbJ-59RBBo}d5k>-PVDKhCfJ`+olazu*7=XEb17Enwi>z`*-~LC}Cvw181^1EcH* +zMnwZA)dD8X4NSTpm<$b=O$(SUH!$0NV0JWMaV=o++`!`dfhEv@HMD>=aszAZ2i8Ob +zw$uW)%nfY0AJ_^F*h>r8D>ty$eqe7j;Ak!2=-j~3`+;Mk0q4{L&Y2rH=YHT^Xu!3! +zfNSLjuC*VyHX3kmE#Tg{fqU-qmZGYuxX*NVZ?fUzM!HvvbZ>6bz57Y`p^@IxBE6TJ^xl5b`)H*9wMhTx +zCjGyk^cjr}Sc?rfHyiMNHV`y66fHKC+-xZO*-+8gNVV8VbF-1|XCp&nW7A?|%gx5N +zpN$=jOEjl+_^nSLOXlyyP*mCA(%ekK|7aChFEw);@*=p@)tBuCiTZ^rCZnob0+4`Wd +z&Cz0;lbdbM{{L)q(b)ECvF**xws$|M`S{o-V3;%r*tY`MkR_KUNliHmEA +zi{};>-!CqKCa$3+u8~_@W52j2nz*HwxMgl}%l+b3XyRU4;$FGMz4nWHqlrgriAU!a +zkKQjH6HPp)mUzzG;yL$==Ry;&r6pb~w|K4n;;`=ou +z&@?o(G&FK+XzbU}MANX;(y+{}VYy$!3QfaHOT#O-hSz=#Z#0c)Esf~h8qxbTVxno} +z)Y8bATO;Rwja+CNwX`&9<<_XRU!yjfMsF>R-nlh;@7L&qrZGoLV@__3Ir}x{qG{~a +z(%73@WAA>AeP|l@v^4JJ*0{G{<35_ke=Uvwxi$Xp*LX&=1lFYR5?YCq{vlQ2|6whrbzTZ*;%~C_lQX{ve +z#(qmpG)qe@OUvArmisNO&@8?5e_49vw)EO>>5XO?tz{XV+cJ8;WlS{7oLZJSb6e)z +zZr{I6yCKey%o{g%&YUcg#jz`4DE_j`e$d7)@|q2%^L+3$sl +z=0&RIMVi}-biWrFnirdv7h7&Gw*6l0XkOx4UgEjE#P@qipm}L%d1>VK(%A2%iRNXg +znUb)b`YH4}Z +z%I#HazgKNEuijc-y>ol@-tW~1&1;U9*PQ&nz2@xqnv3SOSIcW}Zm+%jz4oDb-P7{A +zm)q;!ey{szUjMbc{^$1kzu)T_EgD!W8aQ_}@cw8Jv}hEqXq4R1DEp&P(V|JUqDgZ{ +zlkSfuLyKn9ie}3l&9*<99W7d1D_T5vwD|sL3AAVpt!Rzh(Hi@sHPNCiwW2L^M_cZX +zwnB^c(u(%V9qqM0+8Zr8S}Qs_cXagr=$L5HIklp5=8n#}KROp$bS4 +zODm^W?wnrxb9$rYjMmB-ojYgr{+uz^nKO6JocnX;Ld#i8D`&0TIcx3DSsN{9 +zZ>^lYbLZ^6KW87boO85t&dHr~&isMfs}@S`S}6N#p`z6y)v86ByB6vGT4ZRo +z*tBY~<*voHzZN@MEpe?{;<;;y@2@3+R!c*xmPYPc8vAQ$qSdn0s%4qGmgW9hR`}m) +zd1=-1%3aHAe=TpcTG3jyqI1`Z-d`&wTCJQ~wQ}aJm2-csTxhjwY1OKgyH>6JwQ8f) +z>aA6)ckWuf_t)x!R%?z{tvR`C&DmdTE?TXwoTA|M%B=M(Yi%)f+f>Z{YpCLC|`mX!Sn*O;TReAf@%_Ce(0XfV_14JUTVsE3O|;&YTD>iE_qN>M+X}6> +zmsW4D+`YZ__x48X9j(zN_0Fl)J7@0RIrsO@h1R>4R_|K5d)M0EyEaOCiS?>YN>&qeFKSF88l+`aei@4XML_dTuN_j32Xx4-v& +zwBG-Bffu*1lk-8tvMRG=V(m3xlY{yE-gbE388MCYCpy?;(jv^hDo=H$#hC+Ge-xzOg+(wb8%_ncb$=hQ}< +z(_3p!@7!~G@1N5LZO$C6IdgK)nX`Y+T(miRwdU;2J!kL!Is4G&+|!zKFZY~#`{&$8 +zoAY05&i~wV{_mgjjJ6k8YcFu_y}-y>jpM+P~KuZEv*J-ss$WqxbKPiMBVV*4~`C_vYNcHy7I8T3UN+<=$Is|K8eY +zdwXl`?VWpX@BMrGpzWQbwRcYLy>s^Oor|`2uh!nZx%ckfzjq(n-g{bm@8#ZmZ~xx= +zXnX%_?fsv7@BjUKpV96CYuy9ReGhp5JrJ~eC|dVWa^FMQe-9Py9;wzn(%kn*_unH! +zyT_(=k1h8-w*B|m(e8%UBUi%IS8AGCXOwC>HxeQ(bGdvnq5?bW)sH}}20`|s^TyLV6P-o4!S?(M&K +zAMM_Mt$Y7--}}G+-ZR>NV6Feax&H(2{||!pA4TgwO78zC`~Rb&{U_D>Pn!Eb>Hhy@ +zX#d%?{vp|GirO_vZe;cmMx=X#ekN{lAy{|GoYH +z@1yA7%V*0$|G%6!(q7SNT-N))SeE*#m9POtgHTT +z7%e$5QN??fO{dY)Q&V-KchztjFFP~SB>UE$PUGe0=Gs*M`on3m;=)3gZYkR?la-g2 +z`pmYf;<)y|p#x_O4oP^L2N2mb|{T +zx7&REy}dQRfBogQ*zn+B3%9ggkHyAEM|-5L>v$|TJvli=J9=M_<>qH+=U7+&=5FTVGvW6TQ2R*LvHVn_IGP@9X_QL(QNa|FZ*OAnvweCbxx86RE2u5Ob*9g&dUrB?e%(LLj0H`6UKtD9&jZSYTc=<)$8_gWv|(E&MSNEwtHRK>vp|6mA!u7 +zKdzh&hxoj6HXf7f&e?QI?{v=Qb9UUhTQ2!|=We|g*PXlVR^I8{?f2@q^L9My^UmA( +zY+iTXu2<_$=k0#Ck2`2xL+WelPQ(EiK6rI-F&r^KH=)6zy +zS+n~+#pkTvpD8|X_n)Waf)l@Q$wfE$-jYjR`e#cn``PoBUJ3H|Exj5R|G&5NT2%ho +z((7^cyk$3%`hCl8rp@myyOp*6Y}xI+{k-LOiq89%-z~e}TYj(V{n_&Sb^m!Q9yIa$ +zRXl8y@2hy!rGKvCai2Y3<&#PNew9zB#rIV{o0WgA^7*`azN#0C`u(b2E}P$1^=j4n +zb5*a`?dPk0v+2BF_1kUt`>NmVdVj9^{l5QvH6IS~``3ItCf{H4>6HHYn$PF#`D?#i +z^7pU(dM&=c_S>!e^R?ga)$`Z=c+~G-_w(8O{<>eU)}OEY{cb;h{hv?g{p%mU**) +z-JawBA%U)f78RX^ocf|isj^{WmajKxrWX(d}{EoviuL|02-Yn#==QyIk +zRoLO8vq-SNV_(yF%715?$YMR41#jJLb(I@%1#}fnS)#L4)4%gn$f}~LYjT!q +z$8(;JI8`)l%bKOS`JJa@UKLH>^Jb}jJ?EJOuHqR-be0+Rcb-YnDxP^JXPNPQ&a)X_ +z#j~!gS!TMv^K8!ltm4^s-YhfU&v~w(t9Z^6o#mG2JI|G@DxUi$XSwx#&hr(gisyY< +zv)uN5=lPmf#q&uH7UBQBhPnT+p?;3ZBFj$==)sPcbqC+SF(0>?E9|kdtQ~UuX(#V{y*1^16*Yr +zT6EVW@^{}jqE)uBCwEPc%S-Unjzx(!`S7qDxyj`1L&wb|sSNV=3y6X!2yYD>F +zD&Kh~cU|#(?z=C%%6DB^yRLM7_uV&H<-6~^U01%J``(AH@;y&<*H@nJzV~HS`QA6V +z>#Of`-~Vx{eBYO~>ucY4-~aQfeE*-f>+An>KVaalIKZN}p^?Am0gHCUL7uz~&GI}C +zIlLVvSu4LWjx$k?P+q|wk +zU-NGB{Qo>J9Js44wCHVF$lv?IMZ4-^Pu`Zr^1LrSysIuvS+`}We(y`4?5fLi-fdZK +z&-*H%yXwjky{#+#dtZgDuDZG=Z|mxK-q#VQtFCQXw{>lP@9UV?RoC~t+q%A<_e}zK +z^^GHX+cx(1zDd!pzIi5Z+vfSaZ!^5BZ(UipZR`5pw>jC>x9_~$wtYYEyMpfOJ5Ths +z?>yi8u4Hxf-8XsLci-oIUvawn-j{XT_rC9aU-P>9{-1Z-_y6bp(7;{ufJJ}D|AYK} +zA6m3)9`fYxI4sZivBSIOk;wWTNA>$Y_GH&QmU+M9xIN#e3EeeMRP=YA^zZvLWp&L{ +zo&24rD1Wc|*w`F)?~ysmj}^M2>~dcH3UxNBdy=a^W2{Q*M;u7FH7|IzVz?^b!Bzk*ERWjU&r(RzHz$l+m`iv-{$xK +zzVo{7`=0lE-`D@=|MP&m{>KsheINV#|2)yI|9K{V-{<-Ke_wdl|GKh%-`Dm1f8S)+ +z|Gx8n-}n9e|2}lr|9PUn|L6Jse_vMD|9z9c|Mz|V|36OG|NF9j|KIoh|Np$M|NrOx +z{{R2^8yGbjm^~U;GaA@C8aP)paGz-4ebK-b2J-jG#h&~n`ShdcQjkBXtqAlZ2O|wo}Tcc19q`=Wb4N6$fxp2HqJM>Bek +zcl4ZG(R2Dl&)F9}=Q(;WYV=4 +z^~qUlU(Q<3IeVk#?9HCDw`R`X-Z^{c%GtY5&ffcS_I}Pe2Q}yXKkPZ@Xy%;bopVmE +zoOAl*oUnEa%=zCt=l@(e|M$uHe_zi3&$)n6YXP&@0@kbr>|F~u +zS1sT^wSf240)DQAf?5lOy%vgQEfnusD7k8(^r?liuNKO4EmG84r0lgwHEWT2*CNeT +zi?mNI(tWi^pKGz9)?#C?#im({&AS#`u3Bt;YO(FB#r9lF9JQ7>do6LzTH@Zd#BBUoG+HS{kUeG}votXx7s3uBDNymPVgi8vAN#JlC>Bt!2qx%TlwJrFSjMT(vCw +z)UyA%ua@O=EicqsUhK8JG;4Wz*Ye6$%d1Z75!W*Cu*&n?6q=g*2?K!D`&1+Is4Sgxvy5v=UTN;Yt>?}RZFv0E$>>ja@DHU +zr&g_fwQ4=r>Wx~fH+!w#nzed+*Xo_CR_{Kwdhe^%`?=N})LL`cYt7NDHOITwoLsf$ +z^r`m(3n>1H% +z(muUO_w^=y?#+hUn~l9Un`Un|@7`>=db9QE&9<*M+jDPm)ZXIky~QSv-n}hz^|tKO +z+j3uT%je!+sJ*?|dwXg2_VVuSm8-W`pWa^kdV4+hjz;Yr&E7j&vv;(2@912;qx78?5@0`!QYoYe8#ooJ?X75_wy=&#_U8_&; +zTKjs}dhXpDwRdm!-n}(@_xA4HJ6G@CeR}ua*Sq&~@A-dFd(UC-Jx83{?Fd~zh>|M-o5|l>ixe@@BjOH|9|cSj5-IHeGahZ9ANJ`z`5oC +z_n8B{Zw~PD92C?!DC~1kH0Pjr&q2vG2c^#(lznqhp68IF&LL%=L#jE4)O!wTt~sQA +z=8*22L;5_24RsD1`y4jSIc(l@*mBKb>obRK-yF8*IpU~u#M$SFYt9k(o+F-Xj(DFr +z;``=^KhM!Xouk1%M?-UthW8weTyr$~%+c64N8@>pCF&eY_BocCb1c2*Smv5z|Fh2= +z%YAb!pXYd?&hcWORYh&B@tkPR@OEaz4+gg*vAe`7G97bVwTlsGNJT)V*x%d)YMivU%@i%e9xS&tA5Dd)c1%ilgooXWuKXxmVnKuXwJ#;(hjt +z@7pW>yjKHtuLk>G4b8n8-g`B2?bYbBS7YB^jpx0VsCzBh_gZT1we;R=nQO0QpS_m* +z_F6vg^+Mh2#lF`|bFY{8Uawqxz549++PBy1d2cl8-e~r{(VBasz4u1v+8fefIv}xA*_^K48>)!0h*cHSYm?-viEd54g`g +z;C=UipYNfd-a}!(hoX58#rqygu6rnb?xF0vhw^-n6!jh{`#n<4d!*j?NORpI?Q@TG +z-#yajdu*up*x2u}Y2IV=zQ>m99$TM#Z2Ru9J>L^Yy(i9oPh9h!xc5ErT=&HL+!Nn- +zPyG3w2I@Tx_Inzd_cXlkY2>=6(dVAVzIz(a_bgHGS+d`=)Vycueb4@9u6ve!?pf}; +zXZd{33-z8C`#mqsdtTo6ymH<1>T}O)-#xGAd(o)(qS^07Yu=0Yz89VAUUZ*((fjU2 +zKi|uVdM_vYy_}l&a(ds(nd@H8KKF9&yO;C%UM`-qu%Syey_LYz24sUdgr>=yU)Ge`|kCAzBdQ;-W>LOb2RVG@xC`F*S$G??#UH|4B9flY0Lr&GnzO&wtW= +z|4El`Jc`EKU=QQ-?fcL6{9hdPzc~AUan1kY-v7mO{TJ`^Uwq$x +z@#p^hr(WzW=qJ +z|My1y-<$n^Z_WR`z5n;l^}lzY|NVdO```Qd{~Xl+bJ+jS(fmKh`~RF=|L64iKWE?n +zInV$1qW<5@{(rCL|GnP-_vZS)x6l8*`~L5J{(lek|2_8q_cZ_C^ZtJ?*Z+Hc{@>g8 +z|K9We|ET}}v;Y6E`TxK7|NpuE|L^nv|GxkKpI_iV3!h5Fg$Ir;oT7RuH!eJM?hsV< +zTM}{ck!z2nY2K3?7azM%P;~86iM;g0bBbo@x|Exjo_fzPOg*%E1zoAl^20aJg4fV-n#NKctzk+zok)EUxluT+?x0F*45YH8xoK9sYYLW6S*bx +z>blh1*WO0&D13TuY4r7Xv3n}NzI%H6`uq3;jja4?F*iOW9{JxXs-Je}#>eCn6IK0} +z#oYXqdS<3+{iK1{x4-4y*;)Gj+1=aU +z^B){+-*>T@Bgo7U=}HGVB$9U;J_jrQsBrYUGl+^LwQPp +z6PNau4^BMBR|=f@tiOD47H}3RbP@74`RF1N98%~i7G3huRU&yxp_^3pmXB^S#a9a5 +z<*L7YbXRB=De_S2Hu>bCGC8EkQ*Cz1Cr^#VQ;NK_R&V*_rL*};k+({vQ@8 +z_Axqb^4Z7aa!9eS+3k|gz7~(C6#H4d-tyVc=JSqHw|xkE{87wLHG=_t)?74a{N{ +z2~FH)KN4Dm!zvQnq|1IJb|_D)Nb1tw_9Lms_*zABpY^vN$rGH#DpMwToBd3g5*${U +zI_-aS+0WD&$Tf^O7c`4iWi0A8`<1a|a#&U7ve{+7GFL2~ +zR+Y7C^|oJGYc^l2%3ino+pp{mhsCOMHk~&6owMa~Sat5U+hxCVcRZd}oww`tw%>Vs +zK3}WO-}n35@B9PI;xz?_xXu3*91#w$DLf`!{-^MS^7NXbQ`+1A6rC}?UQ>L|`um^a +z3(n%TC6~O-|CU?{4zDe}7G3_g^hWaZ+Ok{O+y9o`DZXA?ey{rb-|`2|;&l~|y3PMp +zJeeF`SNUvq`M=5+i>KFBy;{BfU)7t<*Xyd^?f(9+`om%I`kGIt&HvYYxg1_!`|Wo5 +z|Jomqr`Om0dcFOB-Jk!Tuh-ZA`~CfYJp)@n1G7p3BX5NRi&{V<Vy0*eqCoQl$`bm=b +z-XE?8ZGk<>D#?;(E8I+$1@>mABun4@;bw6xu&;PYvh3RmcbjK{{nbyB<^TS0cVG*e +z(5#Z8$Xn^*q82o<=(=(tgXv$)hR88MX +zuaIRyQ&*>?YRCTciZ~WD?f>Q_sk*t9-Z9UDrtf}|s$culJAo~D#$lB-!`@1t6t&=) +zr&H35=l=A`a0{Mwc}be-+DhM?wBXsdpQM@Z{pnlK7Ch&%O1kCQO23k2!E;}yq+8$p +z=~r9%hx{cD~D&;R`--Tv=S{|2^@19xP>ed +zUYg;mTNT)o7P46SX@8ZD>}_N#w1LIHDG|u{$*Q+adNekON`)N+P?eEA7ZDCs$ +ztLA3DubpH)-L!Z$Hf|-}^iELtFTs$Ex|2XRG7BEDPWJ +zIyJxg?(euC$HMo0UYcL~wmSaLv+(`DpXS&9{TnF_S*@`1zi&;F$nuDz+G&N|v44^zjz=6b +zURKzfTazsFJmR?Zv%>z`KgkO0ktdwhiYE5fq^PJzp7c&DnmqSUiiUgSso-TrQ`gp{ +z>ZC`Wj(%1&eea)CgZ9WX$!f(j&(@@wERQ^!omM>i?w>S^e^huApJo{P6a@)U| +z0qs#&7OR!6^sUVbSsrzDbz14_*uPm3$D^)oURJs`w>CTGdDQjY&q~+V{>@HckG^qO +zt!!g&ZBB}M^v%<0Wt->z&B<_&zIAz7+19nSxjE_4w{Jfy+rIbj|J;K1=sS*{_kTYt-~ab-egk{V17`J#gS>SGE$T53 +zxzj5S%l<3qaF2N;yu9M5Ze3wdddy?#=M~3o{}oPXk9nf3UU|~Du4u~gn5Ww5m8WC> +z70oyv^UQd8<=Nc2;yKS_o?Ab!JYV~-cmaFt3upDJi@kLvOVndudZ$-ip8Kz4g?sF) +z;N?|U*VdJ;NsoOU{k-b>-hZVV+GF1&t5@GVTUWMadF(}W$G)$AUVZ=Xzw!g@aUYu1Yaa5}R~%7~``Ddc^H}zO#R>PgPm`C|Jk_nQJo7(2 +z?(^*DHP3DTS6*n3`?6TQ_N8xq)s^LOUstEszK;E0b>n#4x6R9I-{#g=-+3PQefRU) +z_qG44AF#*&IILdxvA4eFiF*9c)9H1e=l-vG;U53%^76W`YwK&@q{sih{k-n`-v6~9 +z+T;H`RKT5%j^HXt*`&{JpTXh=k@>p{;y}?abT2j +zVA645wsBzbabS&cV9Rk}uW{h$ap0Wez_rGKdyfOp83*1w4t#GM`2RQv@Hh&}I11@F +z3fnk}_&AEjIEv*siq|+w^f*e+agx +zRD7INWBxm-$4TdmlkOcSy*Ez!f1C_>oDF51jdYxiZJbSf +zoK0h#&2pU0Yn&~5oGs@#Tdi@n-s5a@#@Y6cv)vnK`#;VOJT8thE>1cw&NePCJ}#~? +zE^aw4?lmqRJuaSeT)ft}c<*uXIpgAc$HniBi~k>&03O#s8P^~k*I*mh5Fgjj7}u~I +z*YFzGh#uF-Ij&J_T%-56#+-4Dz2h49#x?$rYXXm3qKsRTj$5*gTZ)fcYK&W2j$3+- +zTSkvt<{Y=IHE!8^+;Yyi<=%12d*hb>$E|?Jy->!zNXNa{#=XSHy)?$XEXTdP#=WA) +zy>gCw)tdkA)qC7)&bZg!aj$#hUjN6vfybj!#-mBcquIuz#mA#H#-lCAqrJwXqsOCj +zjz`xTkM2DlJ!d?6?|AgR@#z2KF@eW(qKxMx9nZ-&o>P20r^a|r%ki9E<2j?pbLJe+ +zS!+CJ@9~^-#&hl+&v|b==l}6sz~i-0#%qy|*J2y5B|ctDW4xB-crCB-TG8XRa*o%k +zHD0Utc&$0(wf2tJx;I|y|9EZS@!lxoy-CM=vyJx_AMdR(-rI7#x7T>@=<(h;$9vZr +z@7;U6_nh(Gd&hg<8}I#pybtjB9F*}nq~mkg#^;ES&(Rp4V>v#@YkW@h_?(>Mb83yx +z={-JY&iI_Y<8$tf&-p+9eJ=3$UX<~@q~m+p#`lVk@6{OJYdOBxYkY6?_}-l3duxsF +z?LEGC&iLNF<9qLo@BKf%4|x0@%J@Cf@q29J_r%BVX^h{q9KYu^elL3bUe57*wZ`xD +z9=|td{NCR2d-ulg{U5&%JpLbL{6FdVf41@e;^Y4{#{XN6|MwdIA3gp*=lK6xAit6X9H#L +z2Fkq+l>Zy3z#F6}8>FNgq--0c;`={HH8w~sH%PrUNTWAMb8e8<+92({K{{uHbngb~ +zy$#a;8)U#6Y$zLSq#JB(8*JhmY#JMEmK$td8*I@VY&kdBYHhIf-e8-v!M1mU?cN64 +z{|$EF4RMqWancQOwheLd4RMVPamx*HuMP3&4e^{C;)_=bkYhKA*ahS!Eh^oB;x4UJkG8of6(=4@!}-O#wVq49r16L`ZC +zWy6wm!;)>oQhdWwW5d#N!_sTRGJ3-@=Z0mi4a?pemUA{N_ik9;+pzq}xSFH`N-Wy(XHoW$3c-`CZ`oG}~yb+DE5ly-g +z&9)IOz7egl5pB5g6u%Z)i+8*`#J=H%R%Q)^>R?~OTgHsiK20dVtI+; +zb%_#viIVdYrPd`%?@N?9mneHLQSM!${J%s6z9dDtBqhBhWxM}LDt<|-aY<@yoYaCEJ`!w!N2Z_b%D~U$O&VilbbLlU|CmU5bleifdeoTV9HLU5ZCvis!r( +zuXQQj`%-+)rTE@U@q3rz|1TwgFEvmuHApWt*e*51FEunSH7qYRye>7OFEw&rYSg;a +z=zXa%=Tc+urN+HWjsKULz?YUNmzJcLmTZ@n;+K{hmzI{7mR^^Z(U+DvFD+|bTK2xQ +zoO5Zp_tNs-rRD!iE8t5nluIwtOE0!dFY!w+jY}`fOE0fWuju=qUO6wlYF&EuzVw=N +z>9zOL>)xf;|4VP+%V?C#Xwu7Qw##Vo%V>?uXv@oJugmD@%jle!(X}q4dtXM+xs2X> +z8GY|E`u}B2;LDsSmpMr^G +z-n-2C|1uZwWi6D;TBMh?*e+{{U)IvNtYvvw%j>dM^kuD_m$hnL*6Mv(YtCh@y_dD_ +zUDo=4SsVDWH_Byi(#zg#m%YU=duv?ww!G}^b=f=mvUkqQ-nA}!_rB~s=d$8EwZ2e#f1%9zLfQL;a_2y +z7aQ9boA?)-#uuCA7n|1?Tl5!O&M&rFUu?a<*yen(?fqiA_r>=Aiyin&9OX-#^h=!W +zOI-X*T;ogJ@=M(7OFa5ZJm;5qtuOK3U*dDV#P@!Q-}@5(|0MzZrGfILLHeb^_N5{I +zrJ?bqVfm%u^`#O0rIGVXqt=&3?=OuxUmAPAH12(A{QuGf{<1{*vLyYoWc#ud|FYEh +zvb6lN^!l=l{<6&ZWm)UXviFzeoG;70UzYd2EdPI50e^X+e0h<6d9i(YiGO)%e0f=Z +zd3pW+@{0cQ%K7D0>&vV6m)D#xuf1Ph_Z}qPz+cfQU(uvr(QIGQ;$P7kU(uFd(OzHC +z(O=OyzoKh>Mfd)Sp7RyG_bd9|SM>j{n805-QND7Le&uBQ$|?SpQ{yYAa;ICRJU$scTYO#IQ691~D@m0(6tCrVSt>~{> +zIlpSv`l{9YtJa*aT6@20-TSKb|Eo6eS8tTB-lSi>*}i&TUVe+v}@$^jGhk +zU%hL6_3r)Ed(Kzyyx)=R*FXz|2 +zT3`2if8Cq&b#L$2y?bBx{(s#E{`!yd^`G?XKik)T@vr|HU;iz?{(F7>kN*0f^Xq@D +zum8Qj{?GaPzxV6^y|4fOzrNvzdV#{hCN_R0pA`j)huiq%oq9A1m5z3a=_j38QK)>p +zPtLxHN3%%f1Wq$LWdbLWmuC5AOpLF*Bs#5Li>*Dq|@oJap+}xCQev$9$GTqzT^6nq%)h^e& +zyQ}Q|qqD2a_3!Vi`_IIuQ(^G%P#eFp-#Grk{LnO{MYkbMx$* +z`E;vHUS3+}zu0eWmFeqi>*5dh=~kP)y|pd>@wv6t=I`(At7qodtFid_=vcq9|GFB> +z&(F@yckb7#wfg$%+WO@4>uRmPzq_};nP0!o=I5tp=NJ30ue1IA?cM#u{rdHGe}8>@ +z|M>j+di($X{{3f`SkS;M6tSR@U1`RGCT^n}3!3?zBo?*^2SqGw6;GP6uuZz?#=>^_ +zCW%EI%9A1%b*e9#v8YRX(~U*l`iCSI_ZVM_Sp2`&{LzfXeb%3DEbg~wl3X&uStxSJ +zM0cf`OD1_6-CQ!+-$`=ml;EJqrBlO`W-gr;U37El^!O&pWiyf|MJ}6}zG&vMS=pOz +zE}NZyNOJj{;!Bas=axU3xqM#rr<=>?*E312SkNpKwPInr(ySGWx{YqFSlsU7@GNIh#&t +zZ@#nXwEkh4&1Z}+$80`p{&>#jbJm~lY(8(#EW71`vvBN|i|)#Ew_NfzzPshJzq9Pt +zE5X6BTd#&E&)s@0y7=za>+#L9+ioOJj@@=Meev9Fx3V|i-F7?wudp6dy0 +z_k29AJb%xp)5iDr{QrF3S$^-A%faz`zg|zCzxUhi;`@8Q-*1-R_v7*8_l;^ty(dP+|e%%i61g(Gn%8w +zo1Jl3`jJ7q)g49t>KBLQnG8FeWRwKEGma=L8FqTbC<)JgaYWt8uq$YelIZG;quNP^ +z-BEXx#CN|qs^4VTlO&@oc{<~m@gl?CtQckK+b@opA2RGKnxicHI^($YBg6ixJIeCE +zUmUk*GMdmNqoT;2dBR!AXku54%710)mnYnvj3!N*qoS&vdD1({X!5K(D(coRPx?0* +zO<5$Ps_C71DtM96)KxL6+R-mhgSwrP&4Zg%GB=toA=cimCduYP$tp2>K|AsIEp +z?#wgEO2#u!#i$w2et9O{$#~YKIclb>GtXux8PC3TN6mcq%d`1S#&aIYs9T=SJXgHP +zcZ&m +zOTW6%?qsq^Xs(8Tb!qYc`E=@mV +zvdn0%rf+uE<=Kx+mRsG`^sjz(c|Mcr3MW~u!0!K9R~9RouJnr43ZDJy%5o>uRY7yL +zLRV*9U7cjQI_j=g`0iI%*EgB2Ns`r$Je_rI^CHu=S+UyDw_jb`e#mrP(Om7=*ICzh +zKQdikbyqw7_p9ssnanmc$?7C>XWuxiWVW#@Rwr5d^^N0BW}7C>)k)RPzIi&yZ1b$U +zI_cK0Z=P>5+p5);;_^`gLdmT5BZxdjws0)c6#SL5?*X^ +zR4dM~JNnHd@xvC!jOH2kX6HPXer$2v>YibL^_$1?%$6sd1zB +zxuSW-b6@8?w|;DSzUrRw{NHb$+cR5TXp%Eo$esJbS=s7hSDeXW>9;T3ovkiSnrE_9 +zJNKn`veo5T_e_>szkTW7Y;|RkoasvM+*iSit*)+$GhH41_Eq>{t81I)nXb*weI5Py +zzt#0!_e|GUzkMCgY<=U9oY}_i+&9U});CYZnQfl^_D#C8^{q?u%(kx1eVd(Zef!ou +zv+cXzzRhp8zVk@VeCO%hcg2gX@4kvN-+lY-yYj=<_dd-t-}^fEef4AO`@im)@BjVw +zeLb_y115QkgWP!^nw4!Ha>ZL5mVWo4-Pz`m(0q%d+Ib(llWiVL-M2Vy{qAFbv&|DF +zdCQaDd7maPwt1=*Z+SZU-KXh?ZJrs;w>+Dj_j&eXo99;dEzeiK`#hi7_JxzY)y3|- +zFN>9JUwXw`U7r2!%W`MiS3&cwuCC7ex;okRb<};U>$~55UEgf`CQ07<=IOj|n-|-@ +z&5F0aef!R6GCY>14alv+mnGw|@Wge6!t`Me?>Uz4L!vUTpVuRlM!%==Z;_ +zAGZ6pX};~-?EK%iAKQK3b>H@V_50uVneBfZlDGTVo&V>tvi;9f@phkQzyI^x+5Xq1 +z`F3Ad=l^}3Z2$Y#eY@|w-~WBzZ2#wxy#3G9`TssIw*UJo-v0OP_y4{hw*U8OzWv|V +z`Tu`Ew*UX@zWx8-@Bja2PGI1Az#x>sDD{9*DS=7r0h3Vzv(*D;rvw(S2P{DetWghG +zlM>jn9jC$n1fEk5crGRI-g?0M +zD1qsFWzA^-#ztQP}FCuv4Ol*F%w@MA4{+qDhHj +zSr5gE62+??iZ>-nbUl=qlqfmtq2!`OsZ|f9HYG~$dMJG;QRdV`nM;YXw;swqN|bx` +zQ0`Nr{I7@dOi2n{j}(NG6r~<1DkUjtJyJ4CQnq@e?3AS9^++WsNj2(`YEqI~)+4o| +zB=xFC>P<-+U5_*-C27ujq`4?bYt}8OlaeiGJ+@qwY_;mK)uv?YU5~8~CEJ{OY;!5u_SR$DN6B`t9@~9Nw*U3m +zo+-tF>xqLrOrMN~taZO5b%X;Egl;U3X#JwrS +zqw9&sq!iCtPdpc;c&&QkwJF7W*AwqUDL$v3_*_cyz4gTRQHtNICw`w&{C_?1XG#s= +zdKw^<8YuNNP$@M?>uHctYOvMQV5ig&ucskFsi9F%Lz7a&vYv(&rG{5M4R1<~=z1D4 +zDK&D|)5t}sQLCOtZAy*a^)&iWYRsvpF_%(fZ#|8Dlp6QyY25!$sqw#_#xtcQa6L;9 +zN=uY_mZ+4Lr1dPxC@tCQS+Y}Fir2H0ptRJeXQ@ePX<5(Giqg`no~1XXWpq8un3R?| +z>sjWaw5(OnvNokK1$1b^(^mGTK=zR`Aq2rT+a)H(hH@Y +z7b>L}X+1A8N-ws0UhI@!;`O{FD7`f5d1+F5S=RHiqV)2r=jBc56~qMs>q0@uq4LYWh#UQSfXoTT+~ +zl2PVltCy3VGN*XGoD!5dHR|Qmq|9kqFQ*k{POo}7y(x1>*UK4`GH1?uIdf6wtW_^( +zZOWXz>*ef2nR8CPoO3C2?yZ+|A7##a^>W^)%=y1w&S%P6!1Zc@P}V}JR|}Q07HPd& +zWR$ho>eXVWtR-HrmIP%jje4~-DQj8Qt7S!5%d1{3Z^~NH^=ieWtd+A~tz49~YSpV% +zo3d8#dbRpc)|ykV)?CV3d+XKOM_KD$y;}DvYyGcR>zT4QaJ}9jl)X{v^+u)aO-Dyx?Cn*rw>M?)=z6_lQufYSuXirW-nHuW +zu1(pycfH-|hQ2e{rG5Xw0y_2!^b&LOQg +zhm3L#TfI5#lyk)E&5@v-qfu{;CgmK{o0E%jPOW-# +zYE#bXU2jew$~klD&6!I%XK%eZ`zYt!t2gI9<(&WZ<~&pG1+KRjgmN!Ry}hWEdr9l< +zC8ONSR&OsmA6f!Op +zw*DyWTqxrGQ6#ufH2R}xa-mrEN3r5U@#>G_&4m)(A0;LiO3wZ$xwuej^+&1Ah0?n} +zN*^wiIsH-Qa-r<)kFt*oqt^=}(HvMM~PAl#Gj%tv@L{ +z7pZuEQVA|njsB#XT%?x$Nv*g@z50`SbCE{(CymKPnzKJ?E-unq{Yh(ck@oIS+J}pD +zPJhz5T%>#ZlkVdpz1N@gJ{Rf#{-n=bY{325K)BdY`m>>Ov61#?BjaM@|JI+4or_Jp +zKbr&>n?`>&O)fUe{%lrUY+n7@yt&w-`?JO5V$0c|Ef*JCt^RDax!8L5XY0eoHm5(^ +zTrRe~{n_?$vEA#>cAtywe}A@TE^*-g;vih&DE-Azxx`8Pi<5DQv-KBe=Moq1FD}6) +zuF+pylS|yPzql2bxL1F1Z!YoZ{^BvY#B=r+&&4HPtG{?{F7e*|#rtrH&*?8dmrHza +zfAM`>;`jQC-{%tl-(UQhO9QyS1_+l1N`DPhE)CND8f07=Z2dLZxirN4Ye;ZuX!O_6 +zaS6oOQUyxjXqo&bNXw{<|B=O{VgT9EH(ODYI0dx +z_P4a+vh?b2>CI&s-QO}Mmu1fWmbthrYxTFR&1KoUzhxgT%Q^im=W7c-;0dPi><#GJC~Pue=i9xFOB|Qnp|F%{k^QX +zyuA8*d2@M1_xFm)<(0F)S1vBETK&Chb9wdd@70IPYfgW!xm;d*`+M!<^19dG>pqv) +z|NdUjT+zV&qd~Z$QTj)taz&H&k0#@aX6ql#&J``*KU#t-TBCooCRem&|7a_&Xs`a! +z-dxe~zxzkW! +zuAI^RbH?P#nX`Y+TwFP8_0L(GD`)TiIs0(soYOz&T&|pZ`{&%pmGfTzocFnM{_mgj +znX49X|5_kiwNU!kLglJO+P@YVS1q>wwb;38iTAH1!BtD6e=SX}T9*B5S#j0!>R-#7 +zt5$UXS~0n5s^Oor|k?t^U1hbM@}szjq(5-gEl*p3BvHZ~xx=xO(5~-}^pS@BjUK +zKXc6i?mq{FYYs~PIjCH7Nc+zr9P$2hB)H~i^q-^2HOI3594oFlUj65I +zbIpnFKPM*FoSgmVrBtN)zZTyuK&pVNnH&Yb>p=5o#1+kehJt~vMm&$-Vv=YRh> +z&s=+f`|kze+KbYEFDlnw(*AqNxc0L3-^c6)(*WTX!_x9o1JE#BNxmy`rl*cx+mWMo&?uDjsEvEx$ar^zh}jD&#V7E +zZ?1dM{qM!(x|g&6ynw}|3~HePul-K8P|Wd{{Pvz{)_kjFTwR+qyK+RuK$+(|66hW_v-)O +zo9lmc|Nk+${^#ufKNr{kTK)gm=K9~e|NlN*|L64oKbPzO-v0mhas9v7|Nnii|Nr~{ +zf950hjm(^4CKUpSPOaRMW+gual3Y54HN&=42qwGrN?VqF`5~C%F;Urbnn|Tls@MOi +z+L7BzehQ`e%rwrtwxv=y-EXdS<+m?Cg);&cI(LehR*7T=E%lyhR{BdMD`aKx%CN0f +zqS;|9k7P;4#l@b}&1$7ev#zd=++Oxq +zsx0T`=FIEcYNgBb?(VMq{_U@HMZv?vo#N(oGL=P7PtPRzxxM_qTwTr2&o8fUua~c{`}_On_wWDZ4=}LGOgO;A +zYa?)wMKtFBgoA9dH3Ek?ROd`M#HG7O;4qKroe78eZ2t%x5pb26ctpt8M)0UeXw1Z; +zVzD)X$0SncOgtu)yGQW2OzEA8$K`7O2%b=Am6>!xsn|D)<*cO$<>(2XU*=`2%ocfI%o1ZtG9cE&)a;x +zGx@yT-#@|^99U(iTyWyG6}jjl8aw5pn{2JfB@flPQ!aVw?iIQ0V|sVWWk1`$B3A-j +zWv5;V^0gJc8WI{i^=epbt?0Fg)VWiyMdj`ly&h9~ck1=H+P|VV5?W=a-AL-S6}y=- +zHFny~w7Ionw=$N_opvj0?cV=lw{y1Mopw8K?_aSy1xID4-zhq4D}J}+YV7p8Wp``E +z?^QgVJN;hO+r8rVYrfu{e!uSTU-1VGta39RH1XO=JZuq-oAIzswoc+vhw8i;kGgdC +zNj&Z`y*J}=pY1=1Clg%dWVPbXC8&-rvpcfZW%Gp6_Fd_HIUU*^jNSNZ>Qzg+UQm;HJrG=A>aYq9mR +z-)^MNpZo1r?taOe!hu;%ppi4=0h8nn2X;GwCc!BWSTrLXx#I+yC9gbSv%KNRUnkI_ +zDDsfQGr~!Do-f-2g6YNP8c`R`<;=h~mJi*?~ +zkjGLtZ@8K76YMLT@>u3&guC@U!T!oCkL7;eaJT;_IH6JGi2`S&hqIi}#Lkc>N|HA{ +z-0g%WO`P&XMKjXVJ5Ffw%qvgSEN^=H*9lEoDDqUpGtw(~p3u~lAy2g;Z+eCA6PmVh +z%2S=pNbl%-LeqC%d8$`=(>wm3(2Rp3&kQ;veUjybXPyjsW;FArPr9A(tcz2gnXHWT +z&5jeEee=pQvz<46^Xr7?JQR6uaWc}cc%Jawmm$xsZr=1O-zPlpXIZ$Pz`-mmZ!`L6hf+EY%Es=@oe^X!<^pWrkB<`ea50&%P(J-16#6zsg&|^Z$vg +za1?zN&>0o7SWa}MXXvY-nYTig+lj6UocbzcWmM?uIMLOSS6_wgycN2>PIOJ8=ifE%x8wi+6Fc_&GHzr-=aX&-wsW0IxsOB}bn +z_OY+>PO|)ei4%@upC)w1q$tZvp7ad+G->9Y6m@&aQ-RYyO<5U}svR$RI`Z15X*=(v +z>eox2Nfi4$<77;l@qEd%nPH!2-Mo`#zF+cO;k3_lUdE(b-c&B_ZyP#ebCTty +zZ=MYMwrS?woOFBXTNkH&+p;n?H#=VX_RVYGw(Y!|n_n+|=b_m59VcV+iswt;eHr$B +z*Uh_m<@=@YeVq1v&&$~S>ig37e_s2(@8{k8`v1}o7{z}a;EXG1mX~?R8UEvt +zmiH^q*UNobDE{|_XMEM=`Ep-ZhW~vPdB5uVez|WOr~iGE8DD+-zTEen*Z;n&ykC9) +zzub?5;{QH$#@9TSm;ZS({NJaU_iLWp%m2DK{oj|B@wKnx<$vG2{_oq)`?c@u<^Mbs +z|NrA;eBI~y@_%23|NnLKe%<%|^8Y?g|NrM@eEskH^8bHc|Nrmj{rdm^Mbqr3oXNlMz6l=Ov^jf0fUCn;NBQnnXTaSl>(pQPe_NyT4CH8@B$e3EMPCDnK# +zwd5eR^hs*jm(=ow)Qf}E%O|NxKSnZw}JlK1qA`CGGt}I){UFj!)7#eM#rMknZIm-RqNdZ(q{AFQoT4 +zNbmV1z1NrY-V5n}4$}WVN&ojH{r^G+%)tiilMT2p8}JJo3I`jCPd1dkY$z{mq#SId +zKG{h7vXQ>9v2n1m`DA13%f|M?CeFbo?vqWtFPr!an+6A)hEFz)zHAyVY?d5smOj}m +z`?6WSuz7K?dHH1X>dWT!!WPZJ7VVQQx-VPw3tLVOwwylMa`t7*`NCFYmu=1q+g=W~y*}CY_GR1q!gh~??VeAzdwtpN +zy|De~VEgZr?SEgk|1a#o9P;0ReToD36$gG1N8u1h@hOhdR~+Rc#Kk$p#eIs4_Z1g^5!c`l*YGK>(N|pKMck4@+|sAGWnXd27jZ8R +zaW9|ZUVX*AUc{q0#G`$RNB0$vei6^fA)eExc+S4!IbXzUafsLQDPF6uc&!)l-W=k+ +zeTw()E8hD>d=7{B9G~KI`ijqa5#P%pzSpPt-oD~{U&QZmh~M)mey^|iy%+KS9OD0d +zivRB`{{KY+m_q~Drv`9e4d53I6b=m(pBgBAHBeqONI5h}eQJ>Q)gXP*VB^qW^Qpns +zSA*?EL!3iH+^2?kUk&jW4Gj(r4WIfyH2P|2yl7Z*XjuBxug}` +z*Na9pheotdjp)7_(JvY~IW%(m)X3RaBj<}oEe?%ZJ~e9f)u{EN(VIh~w@;1UeKmT& +zXw2cznB!ApPG5~VFB*F}H1_(`*xOfQ?~BGg4vl+0HSYD*xc8#*pF`unPmTY5HU7V7 +z0&`db`?Lh^YYF^fiNaxt;?okPuO-ThB`Jp`sZUGNzLumfmTVlBY(6d7`dYHRSc-F4 +ziu<$_?`tXkVyVGlso~R7qpzjLi=`!prKL|x%f6PDFP2^$mR>$Bz4}^uy;w$bSVsG_ +zjP7e0{bHGu!!oB&%ba~JbG}&C;;^jc)3R1y`=7O5EPHcU_V#JnyRT*M7t1*umUDbs +z&gpA8=f!d_hvi7cX%RFL9q<;(fiu +zU%WIpyfl1zY4r8dc=592@UryjW!cxu^2N)G!^_L3msejeuNSXq4zFmRUeSHMqF=mn +za(LzR>6NpuSI!r&S{z=re0tUD>s9N;t2c*NZ=YVh`+D_$@tVWoHOHsdoW5RjUcB~l +zcXq?l)lj@FVUnN(WE}3N&7~VzC^QeM6>yfX6qZx_7W}55iRaBTD)(x_)D|~N3@2| +zXpO$n8ZXh79MP6Oqb>VJTfRhlaYTFhjP~jq?e!8J%@G~#Gdj9&bo5JfPLAlDKBIH? +zjn4TJU5g{Sme1%~eWPo=MEB;1?(H+Wci-sVFVS;2qUZRGp3^sa&P()Oj_AEUqxbfW +z-un`Lk0bh?&**!7qwl>$|L2JQ?=$*;-{}7@F@ZU90{hGf+&3riOHLGyoG3nXqV&y) +z@{*I3BPXfPoTPnolD_0*dbZP7RLyKQ(;j)aaX2 +z<0YphM@~zhIW7CLdwmz;Y!a_;q+b8p|A +zdtY+iXU=YFsinbDOT%X^jlQ)s +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 +zbD0_R)N7h5H(X+F2?c3#If0ewt +zx;Fm!yx8Am@9yrcXP1xrQ}OBP+4=7CZJj +zx{8LUY;+ZmZ!vU}Okc9mO*;RGp}TDPla223^(;mnitQ?!Je2!gj67ARr)=_6pWkBS +zrMY~`CNJ&vM~u96w?EnBt-qhe*vIg=%4Q$q^Df4|rq@$8`~GI% +z8WQNPx-}%&-_9*+n{jBCOg~wI5#}uD;HIFU5p1M7@{C=x> +zT;=nn+vBR=A2pA!{r+@&eEok`i-bmYwH*n~{B9PBt>S4r65Hk5ERs6am+eUE)<0&E +z+-v@9M{>VCn`O#GceR}TWf?M$5>-)5OMGkw|4wAuN`EYs(fKiio;zn;x1 +zV`016u8hU~ZdRE~r>E`8{J(sDn^o4z<;!+utzLi3DtqnrXS=f3?`N~l*?3%Sch2VX +zZq~V5ucz(K-G0B#I&bImWxMlszdvT3zxVsI-TC|fv)L3JWLMu)aG2lSrtqkE`kun$ +z^6fT7C)JnlDLSox+@|=f`SU%+=k3{TOD?*r?=89P?`~UqH9UQ9>Gk+_+p?SK%lDSu +z&OdHjez*Mj-tznP>~<9o+tv3~Jnnb5t9&{=eP8AC`R#U9FPAUhSM_@Ral7ib+n?{N +ze!rjHzUJd`_5C%U&%4{#e!ZT)zxMn6cKf=Y&zJA7`~Cj7ef{6>&-d5=|IePlz-r;Z +zBzB>J-6erZbcX|**@Z^_mIM}63rDW73r*txk0h{}?r`KQyU;AplE~p|;UqNeLW{af +zB3I}RC$Vi8TJ>8Jc~UK$rLJ9QGe45ZSGvPl?%RcSdzK`DRtpy;v5Otb%(p%w~PJtEGY`CmL5)GmnO8kq$r8*^l&q~G_k)WMMc%p(<|)K +zr0GXe)J%7J`juUpJf9_1!`0F&XxgPI%Ux2nLU($FZM!seeM_oNs-<_-wM)~sA4%0K +z-RT|o?b7u9ENKRhmuDPzNi&+d(F`ju_FJokM|y3JEd|Eg=3=Y2ntZufPkf8Dps^Z&DC +zIIvm;G>KhV!0wviB)Ti0&FsoT{?-f^Rja_Ruq%tik7l@;?h5QHyRulGHPge@DrnNQ +zD@)W}GrdB01x?#_WvPB^rcbI>@T_ZBmYE;T^ef#JJn!3;<@T&u0j*Xci^Q(3aCglL +znz}1wnc3Bq{;gRdORYjzg#opsWmi|nvt~yewF=ub?dqCz*X*dPyTZ0@ +zySg^NH9O|1Rrs!JSJ#yv&5rxJD}3L#tLy7oa}rpsBMymO+tBWslO+0ocf>KXYa9Dp +zb5c~TBTt20+cf=XPMYcN$a7`aHqU3x&2Y7jx-{+DmgTOwS)se5u5G)vb$x4YPO5eE +zt!vk|Z9kfuSGqg;-nVPp_p{~|v|7hJ61%?RxNBa~)ZH=9%&zY|-ZpTyT0eSYkt+$-ErTxUElk@HNWPmb^Nbu*Y|xtnqT*I +zcl^I^*Z2QtEofl1NnjGcae&>ephLht4)&7^czRi-3q%x_aupJzj0K*t*|H6CRytGjbrA=3j0d;B+GrjaonD*XhN$^ +z%6}#CnVnznUMs$Tic)A4M@GmhG%8BM== +zCf%)g*3~^}X4`L`&2KB7^VBBY>iW%d<;RNWech98_xu4Xw60N#eI}9Cs_*G<9!In)&UU=iAD* +zEVa$e3cr2p`mwTYTlePXmEXR7pRIhyQQN$t>9_AZcPrm@b#GqT_S<*gx0UaCYMWnm +z{r0`@$IAD8-J4(c{r~Oz|Jf=Iu-X+giQjp^?p|?7bYDT6`JIRS?G;BrJe +zUUAHHUtwSQoyYR*l_y;7iY86J^F-af@>J-)qG{XjJk@WnJd@^Qq +z?JEw6-}}(+Uh_zFf5kELdmsDTYo4guSDp&L_i6g^nrEi_E6e}{uU)Q(SzDc#OzIFZHxBuIZ*S;&=Uw!ZUz3=pryF*E|xx|KqrO +z-KVMhYo3|k|9QT>?#oj9+E?NCe_cOb_igL`+IQvmf8S@X|8dm5?$h-9f1bP7|GK)r +z?%VeJf8V#)|9NU(|Lgkwf8USS|NFYX{@?fe|NpZeU=TULsB(bG_*o7Lh#VADIVfauP}t?5NXS9al!Ia= +z2gO?sN=!K@x#XbKmV?qq4$53PDEs7~+?Rv$EQb_C4k@Y}QZhND>~csYMe&frX12-a!6~-A?+iFbgmrIeR4?e%OQQ1!v-RU4OI>snH>Ia>~h#7#5LuJTgegk +zmLnchj(9FP;dIlWQk^d^(jn_W(C2|2wr<@C0a)7x84 +z@0fCW=aSRAww&I5o{(~oJ%ioZN0#K^a9V-3%pM+@O{0&&w5cn^rE2ZMIqCR!mbxZLNAJ@UKA_6 +zDBgNeV(LZ7r5B~PUX(t1QReDJ*{2ufzFw4Py`&&|Nm2EZlIbO7*Gnp)msC?Psg+(* +zZ@r{3^^)e&OIlkmX&=3$bM=z$(@T0^FX^*hHW0mRsCxOok?Cb)*UKiMmrYYIo0VQR +zZ@p|W^|IyC%T`-2TOYk_bM>8;l? +zre4cjdM#_~wd|wUa;{#>eR?hL>$QB=>jk3M3stWdnO-k;y5SVR(f-K>&+Qc +zZ_ZqLbJo_Ivya}KbM@xjr#I()y*Z!t)&kL63sr9|GQG9f_12QmTT4@KEi1jXy!FXVAbR(r>fKAGcQ3o%y%Ku&YU>Ocdp*O`}FR;uXpdW-g_W=@1g3wN2d24yWV>edhcoKy=SHO +zp10n6G4pIz^N3BCU{_5Qcg +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 +zdn$g@&C5O|DQ?#+tL6&)j+YOfpV%rYD +zmbNr3Pc^)3X?UG#MBCDcKGn!+OC#s0MlD+!wN5p9+tTQLsxilw#+*})y|y&=o@(5) +zrE%|6dv}Kv|)UuW>%UY+Fy=__cKDC@<%W}@C}BF(f@5l|38fh +zZ2wnI;M1HawsNAJ<|MV1lk_wvo2{H|r#Z!K2;bj +z+E&i!)0{bN<;;1SvzD!#wN7*Pww1H@Y0f#ea?Ux;xz|?Cy{9?v*~)qEH0OU?Isc#L +z0=88P__P*^ty(CjwMcE%B0a6eW~&z4X)SSEwZu;7x+-nM%8KJ7inR_{5dz4zMcz4x^DJzKr+o%a53tM~uY +zKESr-0H4l5u{8(fbPlPlIi#m^*lf*VJDnqLYmWHo91UA@G*0JO+L~i|I>*b_9Iw+k +z(YEG9pU%l?YfjG7IkjxfsdYN1x2-w7Pv^|BHD}K0oV~W@>^+@x&(@rKr*r<>n)Cm3 +zF0ie=z^8jrZ0$uk-Aig~FX`!CHd}kyPWOu2+ADs#SHsp`jnloBw)R?{?)9>@*Xwj| +zw5`3-r+ahS+MDxqZ!KGUYn|@xZEJ7u)4g+S?VWSFcdxCzdr$Y?v$gl$>E8dg_WnQJ +z2W;ye@aa7iTlY{-?~&TNM|ygX&DK4((|h8!?unn?)3E>Rp2q1tOI!CWPw#ozy61Iz +zFWT0<=+k>SZQaXxdastPd$msQ^|p1d_vyVkw(iY2y|>ray}hUR?%BF`@ATe(TlfB- +z-UqhzANce?imm@Br~gT9{U<&B&t~gC+v$ICTmQvR|7+O#uW|a{($;^=)Bj$!{(GJN +zkGAzc`t*NJTmN&O{;y^0f34I1y>0#Pefocnt^adQ|L?W+fA8u4d$#`HJN^IP*8l&f +z&%nNcf!}~pd;_Dr0h9U$CVc~D^9{`Q1}yFySo{rG!#A+T8?dEsV9PgPFWt;NHH0d%pqC@eMrZ4S26_;Jt6a_k082djtOO8~FeKHxOXo +zD8O$hD85lp-cU$=qmaI#u=z$|dqWZTjUxVrqTw4w;|;~qH;UyOikEK`uQ!xv-zd>< +zC^>zj_l@%Z4HeioDexO9 +zif>YsH&RmHq@-`8Y`#g^-blrLlZwBQYWODAcq6s+O=|f@>gAi%>y0$pH)-@6X-?my +zIp0WY`6jLPM%vpqY411EIlf8fypitpO}h7u^qz0hdvB!weUtuwBLnu$2K>f`;+qZS +zjg8bd8|fPxn{PI@H#TwKY~pWh8ot>y-qg4KLpsUT+%FzBQuXG;;dZ$oZyG%eO|YH;vxD +zHG02k%<-);=S^d;Z;icg8uxr_+`jcQol+G@I{ewzp_;-_hc4 +z(Hg#^HQu5veMeiqMSJ;<_Iits_8lGl7M;^~bk4WvTE3%ey+!x-9o_padXDetId9Q> +zeMj$oi@xVO`rcdgfB(Ou|G&iq_MH>>EhmcaoG5QONqy%eeap$_J15&)PI2Ek#ouyj +z_|B>EmebOAPRqBPUcPgBz2%Jdoiq9^XHMTabH3%Qm-)gb>uEq9NOWb!Y@wZwU +zzH4c`)w1+m%kr(3m+x9$Z?&R**NT3tmD6{voNu*i`L0##tyXW}wR*qRn&Z3HoVQwg +zeb?IiR_mVcTKC>+{r6q#|66Tf-@Sp~dZYO6jq=u;)OT;vx87{Nd$Yau7Wds-{H?c! +z@7@}3y)AwBwtVaD<-51nTkmM!y`$fH=k(n>=l{3fwS4!k_13$$@7}%Nde8CQd(K<$ +zy}o|z5n~}{r{~GuDwGO-*edB=7{^ABmOo= +z!}lDGw>g%+=UBeY@$x;#>upZ7?>W(Lb8`Bglk;s(E#Gr$z0K+Edrt4SIdgo^ne#Sh +zukSf~-{#!&J?Gxrod3S({C}GZ?0YZp+g=pkdr{u@lKS3D`nH$N_g=QQz2d(2iofmE +z@V!^#ZLg*8y_Ro#y?pQWdfOZAdvEmH-kiSo=6u^*%lF<|Z+mwE9sx4rj#@4ff7_rLGG|KIij`@RSKb`Qn(J(Rb5q`vQwzTIQ_I)q+OGU-~W5R{h#Ce|D3n~ +zdwu`k`}Y5y@BjDS{{Q#=|Nq-N{1LX8kl@(NE~pl=V?v^HJHMja6pM*TuHE8>X;*ek +zOm^>=cWe`}oRs1@Sv_!B$j(Wr-qZCHk4>?hoaQ^*yztqTos-l3=i4{3iCRs`2wdzw +zQ7v@Wl+57e|NR%bO|_bu6}mcnW7^eSQ?tX@#~*AHwVsv}xjFsfve4bra-+BBKRhlzMu)VfwW_GmF#D +z&v$GWvz=9vd3kx@^02+LO0%!8Pdq-&c6M3r?d^rnukD>(o_~LTBfGfWoQlH7$0w?X +z@0(Lu{QUev_vvx>;_~qQ^J=TVzkhgqy8ZmR +z+TY(lKEJ+yetrG_|BO5V2N*eZE*xMM^a(h~Dw%WPAiH8uz#&e}H5U$X8=eU`%xn4P +z!eM?#p1>o5o;nwg2nYHE9u5mjNwV0%V&%)`UIaf +zy_s|Qtog&9;B%HQ*IYhl{qaojdE1|FE}yq&?89Y{+F_%ePl9`#bW6UJ3Nny?P}$&^PpIXk_lytKo^gq1Pfa*IvCAU3fP1 +zdTiy}tJmWjdBbibcIsZckv!2i>}KlB+-o<}7xsqT%3Qhj+O6!3XTxsi?tFXgcK$)$ +z@H>Skb+6wkzUUi%xAbQ2^}FQ{d&BQlzFd3#UiHWSXT$H;{(O7=emx^!#Dhjoy&Dgj +z1^pr(wo2yRc-XGk7xAc5bKQ+c-G=8P9`{izoU;y<9po@7BxZ3;Uv8tz5b8 +z)~nSU&qcjnyYt5 +z{rT?p`~8gkF&_?c>fiZrSkOP_<59`{J0FiL_Q!lWsk#2nr_+Y#V?Lj?e1GTjc}M=( +zFBd)a?|!))=pXy_YGnT1uh$d%W53@^&&QMc_y2sp=pX<0>&^W8 +zf4@KMkN@}c<@)>oet$e4|NrmL_xJz*XOwVYY^^k{4&VnXa4In_Kc&`k{oo<++J{rCT1$ew^WM`)*=??U%>$ +zjFKLXeE%m+Xf=7FC^*x@Rd3S7-jXNEijtn5ev>9m-SR}$aHgkk-lWNMzdTWQl=KSh +zn>1yq$y3e1nO>pmCQV&i@>Dxf(mV3pq-k5XJk>3n=^guS()7Jwp6WMB`XusAo^jOV +znc>8lKB;<>XPzy2X1q|+H`8zOtgBm|nQolvo0~Uz_T4Yf%nwTX74}V@^VHkC)IS%JNIQx?m9ec|pX6*RGL$`V!6m!5&Mf~KyUvQ)S9rFWuK +z@XT{lmYHsS>03A}c<#F?%m3Sced*sQ6|#_T>IzrWSAi2}g)G&Zy3)7wRq#To(3O5u +zSA}kU6}oX&=-Rxgt7E^u3O^_nwy|&OnpD%*kr!u$ZCy8YZEor7=!a6_JI_sBSGx6e +z?8jN*d*4l6U;FiSJfn2PLB44lT20?13eJu=syA(8Z|R$4Md`?se$zHh-TEfgaCYR` +zylI=~etnbfC>?dNZ`zinrf)L?XGdLKH*M?M(zn@(($P21P20A0>)YJI+0l33P20Zr +z>)ZTB>6nLn({~&-eOEYfcFa?~={wJszAIiR9sAO6`mU>6-<58h9s4$K`tG}5-<2Pf +zj{Dd*ea}C(0yCo}Y2dblblrvL0)SGRrJ +zx^Zr9ZvO1scfWnxeo!{Auz&WQr)J-GUYwg(x_?M +z`+oNQzu&&^XOt^w+(Xm(v|*mUxjY}b#>#s(zW?>U&ns`b^V}R*~b34 +zZ&J;F-@G{Qf7#acbKmBc|GxcDu6*bDx$jE1|GxWiUiselbKlo~|9ziPzTzPNybrDB +ze;x|XuQ;kd?_+QIpT~;wl_&k@eVV%c&r`$sm1pzkeV+UM&vQrls*C;ezAQEW`!aBT +z)z$U$zOF6*`#Mp+`sVq0-?nc5`?hd?_1*XLzVH41_kE*$%|rhAKaQIJ`#5oa%~SpP +zKhKu``@B%T_ND*)Ust#P`?_&{?c4nMzwdtk_x+%J-N*jb|Nr;<|No2z46FqVoEsQ;KQIUyFp3s1N^W43{lKVb +zz@%Ehq`84f_XCrm0kdfVv*iY6+YikD9SvAq3s^iiu=svp2{d30Entn@z#993HPL`A +zwSX;i16%F~wn78;(gOC%4eYfa*c%NvS_?QjH*oZR;FxH@IkkXu<_6BWA2=5pa4jw1 +zTDgI1?FX)n2HaZ3$S4G!!;16t>(bZ2M8z(NM&-P{ebii0?;{Kts{cLea>LqOl)E6Ai^u3&k=wisgP3 +zD>M`@EflZZC|>(fywOmiwNRpSqeSmViHU}i|ECs8&fF+D_oL)OL#d^OQY$w~t^Fvq +z(NKD8q4ds;(tAHjA2gIXS}1dJqs-ZlG8YYHuNKPQ+$ekZqwGUNxu=D4FE`4){V4a* +zQ2uM7{LhW@e?Q7I8Y!?ADR6F5;Qgc^Xrw4wq$s&bQTCIfqLGqnk&@;nCEZU-hDOS! +zMaq_&lx;sLI~u9D7O8k{Qt|zy5@@6vTBI7eNj3J9YNC-^YLQyzCbis8YK2DXrA6wM +zo78JRsW%#Fv=(V}Zqn%eq%qM*b83<1%uSkeKWQ#B(pp-iwQ`fz+D}>=jkLEGY46;m +zz4w#$K_i``MLH)p>74zfbJ0lmYLV{EO}ckK={_{lds?LT|K%pVx1aPr8tH#6(*LlkVgt_22E3mQ1dRwAk2kv$5@G +zV@G2X*J2aT%_hE|O#+QgLyJu#H=D+OHcd1(OD#6b+-#Qn*{smmytLT7awe;?Y{-(YeK= +z_lw6w6VIt7o-?<2&i&%K(8OzLiPy?4UTeR2Z8Y)TTH?KPi}&6y-Um&5j+XeG+~RZg +zi_b+9->W6QH@EoS{o?!3#P4Z|-^(q2Z@>6`H1Yph;{S7t|KBhEjHUssr2(8<19-m% +z2$}|pmIg|04V3*FsAw9bS{kIeHAweskfCX?X=$+K)?nMO!H%XOuB9QKTSI)mh6I|1 +zhL(m#ZViq78k%StmRcH?xiu{JYgnOacxh>P<<{`pui=fR5v`>Wom(S%zefC@Xc{@S +zG;-$F$hlu57n(*bEsa{aHEQkGsEwx4TT7#NZjIjiHTs}w%+b=AlUrlXevP?k8hf=g +z_U6{uyI*4;n#MgXjeEH@?(NsOkEZcoOXGiTjsN>Kp3y9UwJd>iTLSO51VOVz(XvFz +zZHcnq5*5voRLhbywpVGG}hfock?v +zp;^|_vaFTcvetgf+Gv)&wJdw*w(Py%vJaZ&94*T^xh?1Hx15V+xmU|_Z*I%I`z`mO +zS>FGrWqB{R<-Pru_t7l>Ygzu!ZTWw{1+X(Y$hMdF9OQm2E>UjO%dJ)=bfYefU+jt1Tz4T2Vpq7{vjI~rwwG%8v&sa7;;?r75e +z(PU`RY+BL$-*QK@?T=S|fL~#{Otcv}jAMXv^Humiwcv +z(4xJxqP=oQd+m?*MvIQtijK}59lbv~CR%h(t>~P&qjT<$&V?3TODnoo?&w+cQvxlghE`6E+&MM&=hQ^YX{nXdGIvhP{W-1Ba(Zdy +z^va#nYky8}w4BjeIiqvujQ_nqXH2x5Ikj@;%$+mm{+zkca@NwySu1zWTKjX>M$6e- +zD`)TAIeYKV*#|A>9Ic#la_5}0Kj&PuoO`u$?#-QZ@BW%6Tt$&U^cF-bc&% +zUn}ST+&Ta6&-sj23s|cbaPC^b`)h%q)k4v#g_64#%KloYXthYSYLVuyMY_Kh8Coqi +zty*lkYq9OG#g0}>T&tFN?poseYe}Hh($K1~=~xxbbbS}iZF +zT3)$pdF`*|jaDmKt5$UGTG9J!#YC%>Q>#|a+_iGKv1(dLk9%^}S_hjjlOGPF7T +z-?Zki<(|W~e-1m^9C58V;<@LD@1G-qHb+Bijz;b|8vEyHqRp|?nq!%Jj^+M2R%mm) +zwB~r_p5wKDjyKwzXstQXx#vXhpA!>pPEM^kIdjj+xqnVBv^ll3=G4kPr`G;CwbADE +z)|%5h_nhAQ=k!6FGe>LAoZNHf?4L6iZO&e;IeT-@*}H$vKD0UawC3E)J?Gy3Irq`# +z{MVZEKlhyf`{z8P?FH7_3!Hl|@cz9ZXnRq#_M+t8i?V+&D%xIBt-YkV_mb}4ONO?W +zO=~Y(?!9dL_p+ny71!D;o_nwO{=E`tdo{H7YUJLlv45{7+Fnboy_UK6TJGO#g|^pA +zYp+-CyIrs0)g|@er*4|pV_tx6Kw>H|| +z-dcNm=ib|U|K2`md*^8Fos)a-oc(*}qV3(QwRdmsy?giX-G{dKp4Q%bx%b}NzxO`c +z-v3&A|L5NOfB)WRw0po>_keTX1KxiR1nnM*);*Nm_fYoVLq)qss&$Vv_dU}6_sG!h +zv1#37%YBb+|2=lJd*WL6#B<*h-+xa6?Vg6#J&oM=H1^-qM7w9HbpyVr|G@kIgP{FK(fW^)`#;M5|EOsHNwxly=KfE*|34Yp +ze>ScEY`Ool?f=h?_Fr7;K-||M%|yzYp#IJ+1%ua{s@#|Nnio +z|NpiA|IhvZ|Nj5a_-OutMiy={FVYv8MuZ(ropZ^?2 +zOHNEw@t$SVX|(jzRGsKuHJrxF&dfB)zO|>*c=@@xHr2oWaGI>Ru+XJj%C^g7<)x)Q +zv#n~mOjli58L~QRZ6W(dv)TFS>6zKq +z^?bIwzP!A$I(mPf?e1@H@9eJr&u6#i$Hyn9XWRGN?fv!j&F$Uw{Pz3){QUCz_WpkR +z{r~>{`ThGpe?r55CO(gZMmD*QgeES%6A8_Hb{vTNt`* +zl=?i9I@RWNBz0-6JCW3_w~r&a$LO3#aGP`IolKu!_m4AUK@*=>#=U9e3`QOMc$DTd&1+=We@|cRF|by*lo^9gq6F^L9R) +z*PXZP)w*We;Zd>pp2A~N`DY4`%hmG~olxrcDLSb(zo+Pw*7`F=r}g&p6rV9V?^Ar% +z?0!%2Iji?)iqG5q=P9}1#P3^j(M`U$
E*^KU;b| +zuAaB-MpD0T+0C^1y=Awu)}JlAowuL2{7%t%-}1X<_j}9lRlPr3e!uQNZ^eTqe!q%` +zZSs8;kGk~FRXpyq=c{}&$=|Q?>9qL1%4h#)<)5p3KChmy>cygdzp9tZ=J!>-TDAUM +z)$4Wp`KsS+I`3EgcH8~F>UX=|pR0bq?>}G7heQ1SH6M@3_t$(nrGLKW^ErF|+Ao*< +z{cFEoi|?=fb}RpU?e}~2{B=Jb_50WTd^W$o?$@jJ=j(pI+s|MB=hJ!r`oG`q_t*dX +z_5OVQ|9}7a4=`{QG_dF_VC3&Oz@k;q$dj{xS)St{hgU(9$eIPL`W**(vI?4I-Yj6Z +z=Qt$LRnVfMvyjujxcJh1~HRhb2xGw3)0~$eZ7BSmsqhyUm-0{Pi416u1gI +zTyz!*_IDgn(JJip$yp>kpW~>8S7BGknnj}PJC5pP6?Vt`f3rw@KgThHuEL%aoyC&p +zJC2#GD(ub4SuA~@UW;>$ts#W=gkszd(KkrT?x1+!fC9To*gMN*0N%UGd*lzw2U8 +zR>@+Sw=3N3xh_rUDp{hUyVBFY>(Z1}B};X3S9-^DU7m5OWSPm@mA?61m*>1HS#I-o +zrGGuwl?7a-D_nF}1@?DcS)x_C(kFLS@O-YTE4)fqg{)l_y1wh`nyk{*F>hCe@8`O< +zp{sOFitg&j^Ig}rtSVible;?lKG*dfr%KnAtX&=ZzU%s)SEcJ~-mZ@S&voMfSJ{RZ +z-8G5)-8YVCm2K?FU6U-ⅇA@*`_IL*QDxq-#n95wt3FmHR<-;w=Q&*ZCRqbHq*cR +z)|FLdTi4{S&5q~3edAQwwk>Pd=H_?bzVoVV`<}OJ^Xs|qJm4zdaYT1rVSo3XCtBq@ +z&*ZKvp8ucw?hCK-U02qwD_!4x_f1y$?mKVSmG9@i_o1tN&lBDCmFK(feOXn$_f78l +z>igXHf1E1c_hs$++V|b}|GX;S|L5)c`v2Sy7`Q7Au;^`Qo&CN_dMjut~f07ZbQ30&m)2EiX$p|8$11b9*L~3II5Gku{)mUvBc?$V0@}&-2WnyYftm-sYL-d!CuBt~{HQw|Vw`p63>)E6hhd-TbA4N +zz6$8By0S!X>q`ILS0SsbuCB@3x;mcsb;RkaYg^WBU7O$gI_7oN^*!&luCM2PlfYel +z+1TxuWPbvU&p-Pb$vhIw+-F3Z&LJk-#p*kN{Z{O$pzTiWNL^7sF~&;S3&>H2?P*6;uOzW@K9*Y*GZ|9QXv +z|9}1lMvVq$j|SF^2KJ5y&J_*ZCmMKPH1KmY3TiY8do+q>G>UgLO0H;>KG7)qqEVis +zNl~Lo*`rA{qe;D^NpnS$_K7Cl7ft#c&4wDy#vaY48O`P$&6X>gtxq)DzG$}RXmQkN +zarS6&&1iA&Xz^Up;(emU_eG08M{A%)Yp_RaXhv&zM{DGY*60(hu`gQVIoc96+LArm +zQZw4pJK8c=v}K=Y%YD(7&(U6}(O&G)UYgNf-qBvUqP_Y=d+m$%dXA1pjgDrIj@FEh +z_KuFu6&>9tI(lDp^mBAh)aab-(K$7vb9zVT%oUxpPjt?G(K(-^YoSKhVvnw+8UMSM +zcXX{>(Y5+S*V-3d>p8kNYIJY*=-!&qy}hG*=Zfy#C%X5(=-$uKb5Nt_ut(3)jGp5i +zJttT6oIcTW_C?Qmj^2wJy_Y?DuV(aK@94d`qWAWR-n%b)?{oA$)aZNc(f2f??|Dbx +z%N2dEPxQTg(f6LC|D#6#XOI4`8U5co`hTwI|9ztW?~DHb91|EdCop?XV9lJs-Z_DD +zW9Y|lBxQFDs3=M>k>Dej$9JXcQf +zJ~_qr_W(V97mwA${F1! +zXY{_D(a$+^qUOxWo-?Or&Ya#kbLPsKvro>P`*P-d&RGjJXD#-ewKQ|q^3GW+SI$~} +za@N|Hv(|IY-l#cyv*+xsnX|Wd&fd9l_U@Cj_r9FHpL5PZ%{hlX=N!$PbG&oT$(3_X +zpPX~{<(%`Jb1!Pnz3e&nYUbSQopW!loO}D^+`BL5-shb6P;=g6&v{QX=RNP7_j2XD +z*C*$_eL3$v=lqYF^FMpe|C%}fd*}S0EC0{`eRBTam-GK~E@0GJ!0fevHERKT*8T(25K!0_F5X6wKTkIY2>P<(WjQizFHd3wJcF +zeYK*WYvn|(m6N?zPR&|5y=&#nRV!zoS~>UC%K2QY7HX|p?Dc=u(yUd>yH>4SwQBXL +zRcl|ZTF)*SX)b2Mws@vb!|SFJgH +zYR%bKYtD16y{NVJve(+HS!=I%t-ZNw?d?-*@4i}lpKIMit#yyR);-Ny_q=P}%T?=M +zpIZ0!)w=gw>pyC(|LnE?Yu5VjUF(0YTL1gh`oFK%|L5AksJ(&Ndjo6s2KMd^oU1o* +zpWeXxdILZAMnUb3!rmK2vp0%&ZP_0G +zH|f6Kq|d$CP0KE2uY^=5nSEsok-oV~ZWW^Zxt-r~7>i}&d* +zzOVmp@#o$esJ%7VduwR+*6{AFk*l{xpWYh#dTTuQwnXi1$==&iv$v&pZ_8Z0E&KGg +z+}GRkxwjW;Z!h-VUYfnVynB1)>h0C1x7WVjUeCRwQF}+T_m0-=9qrvaI#=)LKE0#& +z^^Sh-ofEZpPWIk8HGAju?wvDN@0@*l=iJvj=X38`sJ&~k_pYVcyOwwFTDf}H>eIW{ +zzTUN-d-q1|-J894Z_VDly?gi0)w_3}-o5wr?)}_*4r=c??7in`_MYS2drq$2bNck2 +zv#Z +zzB%I0b2L!rXt2-G(43>;Jx3$g9F0D6H1^HWc%Ea4I>(ZIj-}=tOYb?Bx#n2*nPa(c +zj^*~p*{=XiO~@ya#FtIr&-eRI5?=R~8YSYHb8>3V$>}{OXRbLp`^?F?Z%)qVIkiyd|Ea}3r~rpE&bjA3=U%Qk_xjAa +zw{On9=Q;mT=lo}%^Ivn$fA2Z}bItkRXU_k9bN)Zi1xDQq%)S>`b1$&>Uf^7Nf&1(Q +z-nSR{c`pj;UKIAdD4Kgwy!WEy+KbX>FUr2XD9?LIQTLLv?` +z?InHQ%Z9p_jeReh=3X}My==Mmvh~@^wr?-n^Imb(z2fY9#WnYed+!y`wO72){=ee; +z_KH96)j-{=!M;~RbFYT?UX5IPHTvw;*tb{Xd9NkvUQ719mYRDlz4uz?+H2WoujRhI +zmd|^=Q1^PV@AcB$>*c-IE7xAHK6}0P?e%)z8;!a*ntgAy=H6)Uz0tY$M)%npy>D;y +z^WL1Odvmhy&8fLJr}y5Rx%TGlvp46yy*Z!v)fYY$dwXl{?d`p{cdos?`|Rz#Z*TACy>n3a&SBp>M|1BS@4a(!?VZzS@0@*m +z=REJ-i@J9&``*2pd-rBdzK4?Q9!j5kDEscAJl`Wly+_J^k5uy>srNn7T=z)(+#}t0 +zkM#K-8|pna_Iqra_t?DevE{nQ*5@AEzI$xX_ry`}iL>7m*Ssh0eNQ~sJ@G#G#P{73 +zf4--IdQXG>o`&W<4exszx$bH7xu>!3p2qV%OVoRo?Ds4+?^$}^v&?nRvd=xsefKP% +z?|Gr#^J2f}rFqZG`<_>>dtQC+dF{LB^?WZH^bKQ&Xb1!<|z3AtA +zIZ^NBWWSeF^IlHxdpUF6%h~5%&VBcCKHvXW3-w+t_ItH7@740YS1Z@OT7B--+IO$k +z^S$1v_j#cdOxA(o?x$gDubFcTld%d6U%|X34hyC6h&3kja@6E||Z%&_kbN1bv +z^L%eF>bI-c=6_P}|D?J8llJ*fy6->f +z^M5wf|7`64*);#NdH-k2^`EWJf3|)9*`EK4qy86X|1YljU)=k@c&`8d#rymh-}hhq +z`M(D0e+~Bk8k+w#y#H(D`mfRFzsA1*8qfbNQU6=A|F_iqZ|VKtGS`30KL0KE{kMGn +z?}hr`i~YZs=6^5m|6aNNd-eJ6weP>z^Z#hn|IzIKqc#6Wd;gEl^*_4L|LA@Hqo4oh +zME#$W{eMo)|2e(?=gjp#XP^H$_x;cL{J$3J|61(-Yia(k<^8`_uK%_A{I9j|f34^L +zy;1-7X8+$?^M7ye|Gjhl@7?Er?|uJ!KmVVD`hO1l|2dlf=Xn30lk5MSKL6+J`#S@rxJ1Dfny7&s9wsA3lE(;1XcZ( +zL|lC2+9PS2_vFUK$LsZQ-ZKnS&n=0({LFWbW$C*oH!nZ; +zU*Oovry6zTMc@+8sd}lmuDlFh5xCTEY1GwMp=%z4$#G`$x(bwKYZppm5 +zF7@`cx6wNapPpM9ef?eRp31N9p5DIxKK?)>E5BOIjSq=OIz{!BXd~oDYo>Jh% +zrM=~Y6OZwg0%tz!FCUx*oJ9&MtMN6`DngJe0akK6$824k_|fn_cqBQ)BUzA}_7gTRwT|Y`#+Dt+)HjCvStpBE>#N +zr%gWlm|PAi_BFd*^4Zto@swgetJhmT``LWHQtWT{`^#s42WHWd04HwKF99yXp(TNC +z(xqPlJ(Q=G1bJz1{SxG3e6=Ll&-&|^-~eaQ(*Ger-lkteLV`m}L&Ks=zlKI6Pc03L +z%HH}lET;HsX?R@q*RSCT&7x%yN!_O3B2p%YmPMw`F8vmnv3P1(RMzUP-=cChUoDHy +z+x_)hbirZK@|dF2rr%>qE{B%KmfbG>9$WEvYI$7M>#g78YCc~rkFWdv^?Q5+vsgt! +z6Svuqgcjkjio`bQvLA^Z%F`;6y0o|bNa``ZR*~Fi{q0Bc1ZT0zlu6!ZKU1azhgGId +zi!S?_IwN^nW!kLlZ9miI6kn@MpI80uXZnI>v8s$k-DbZsmP`(-%3L

{sTB#nY;? +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 +zx7_&ewedfC22EGJFxdbM?1ZKMg7QY16xCFMm +z1opZFj=luWc?n$W61evz@SIEFy_dlEE`k4Ff&gElpj@JmUZSvFqKIFjXk4OLUZQwi +zqC{V!EiXyEE=i*= +zNpoJ3*19C^eMvg!l63DS>Ag$R|CeOImux7PY^0ZLY?o}}muwoBY?ha7UYBgqmuxvN +z*=k*~^}b}AbIG>%lI`9l+yDQU?7)}eD3{`-m*Q-f;^LR$8kgdhm*QTR;?bAlIWNU) +zU5fX<6rXb`zV}l6-lh2eO9|jh4U|g_(n}4tOAYZ$4UJ0;%S#QfOO5DDjhvSnwJtS! +zUuw*`)YyBeaqm*&|D`7Ir6tOxCF!Ll+oh%WrKQHDrRAli*QI6jrDe`b%UYL~y)P~2 +zTw3nEw7hp|`Tx=i_|gmI(u?%ci|x`&{L)L~(#!JF%j?oB`qC@srB|&>uilqlb1uF1 +zUV7cT^!k744SX4mav4o}8O?SXEq)oTaT#rS8SQl$9eo*{^D?^DWpwY$=sB0sdoQE! +zT}J=Ej0t?16Xh}|>19r~%bfDxFLP>K=Cr)b>2;Yi`Z8zE%bc|?bN0T>Ip;Fx-pic# +zE_43B%msW|3+1vF>18dp%Ua@>wKOhkSzgxix~vs_Su5vdty-70dSBL>b6IQeWvzRc +zwfno%6DHt;^oMFMH3q?7jE0_r1&B +z|1bLhU(P|foI`p!hwXBX_~jgp%Q=>pbG$C+L|@Lyc{!)n<(%G^bLL#m*?T$X-sPPC +zmvezH_o7_xCB59scDYyla<9haUdzk9UYC2LFZbrW+*|8%Z|}>!b1wJpz1(~6a_|4k +zeZZIZP%iJ0UfyH7yeEEnPvi2Q<>fuE%X`t6_wxU|yjSb;Uhm6$b1v`gy}Wnt^4|Z; +z`@om~Q7-?JUjAph{4aj_U*qz><>h~`%m2}r|8rjcuXXvq_vQaNm;d)({=awm|NrGP +z@E0)37cl7;FxwZf_!qFo7qI0Qu-6xG^cQf>FW_2Vz`ehK=X?S0{Q|!C1^oXD1o#UD +zA@n)8da))#5-FVZ<*qWQRuUdP*YTf&)_5Z6j@K)!XZDOMiuf5`5do{lHT7K>I`q~@)wKwP2 +z-dbOKdw=bn^R;*H*WP)@^z2&>mJ+JJ@K!58ejJ;zwUW`-HZSIbuZ`F +zy;@)QdVk%U^L20U*S&jR_x^v~2mbnx^7Wte>p$DqfAO#X8ejh{zy5oD{g3|opY!W~ +zt*`&Rzy8np`oH(<|GlsO|G&QBhkAj+!6r6-C7%@qiig|yVwa|3yA4i&Rff(~CdUqgkwWc9vQGqcbau)z8nft7qcXD$%&O$gN+= +zcU6hzGk_~=-_vj4go +z%g@iw&3EqCtF`+2>e~9`^XqD@zrVY;znNdZ&gSQ*XXh9DudlQH{q5cT!~Od8c7K0; +zd;j?S`g;5S|Ni}FmRQihEEKVzkzHxVf+lXG8w;BGog@~v2nR(hY!y$Mv9L|L=*Gf! +z`6h`)9m067WdmTNiLb-EEKt9 +zqPx<}C6m03ZZ4VZ?;stn5uU +zm(9*UB)NP}@ukS+bITw7pSgTq^{1Q5=hrhytys`36t!YuyV9%`i@J?&tytXeB(-wM +zCPq)|a|IZ||;Q+I6 +z%!Y&P%5ydx;x@js;V{3m%*G?a!7&?;iYL$6cuczZ&c@^N%`%%#C{K>rbW(lsoK2^+ +zH{aQGTK}-j<}=2ZV>X{Pe>`XNIqT1NHlMd=mfdo}S@?hKmW%GnbGKabHom*%vcI$J +z)+@omv0JZ(C(qq_ExP#b*6Z=jvfFMXPmbMoGkx*gZMU*F-`#dQ|FG=#JH?k{x8E&) +zJa_xO>d$w#->+wu+wq`TIBv(ocIA0H9(5bv+wr*HS#IZ($-!|upH5GnxAWQT;(I%v +z&u^C7^5&6neLzuo?L-tKq1Ki}K^em}GPo)3qG +z2_eU#}<6-}~)$@%_Eu?>Ecu`|)^k{Jx*h7ti1K +z>-FaQ`+mPaEWiKH=gaZ?|9*cwfB(PVpYQMg|DV~Qfk{Szkvro6vywq0SBwI)^os-k +z*_{lUgytx)YG)kePBLhgx}(5u{o){hlR=A;j3TFZ#v$QF2CZ5#irmpJ4v8N!Xfv9l +z$eW#USo)DcyVV^<{^}Qp<(UjSoMe;)yEBd`D;air#V85SesM(I$*?PEj*{r=jHB8~ +zhTTzjl*D(xII7=d*pnoqEO|QPnDHXR-mDm9>Dw=knIAIjE1IJ$`#R&e^&`XnsyoW^ +zzh4};XEK`5B%`9poq57p$!KC%jEb`K%M-Jn5ZeGZ%x3?dX@M!Veiu+cZa2H#_rm^dqC`yY8s!SHCzhp1B*|(=p3b_qd6DVbtXS>n+pn%|KV-VDXs&kb>#Xa$ +zADOPNx~m=k`_=XROlBMYH_7TGa%bN-tYo&aD^@32`t^Sjm3zIFYO*|tq{b#t?`Z{L1owtd%K-Tdm; +zx9>BV?>Ho@SJ<6>=dqIc&Qq~^#j{`EdG2Js>(X4k($(2_UniOGzI9ixeD~|S@0-l` +zJd)L~Je_^-^CI)TuVVG9Z@<3x{gC;-PjmHaUuWO{{m6X(uezk+I%r?({`zGDl`qrg+W?NV1zRgayzJ2SS +z+4kLU-{v=4-+3fwzVmeMyW+*xcVESs@4o%^UHM__d!Oc+@BRNe_kHza>-)d%neYGo +z_I*9G%>yQRi-X*GADWeI9&*K79F~6fq21Z$koVu`rW7Lhi#r2&9^+8o%eb6W1Htz_bty?zxzC&+4hB#yw%0- +zyf2HDZC`rDTV0<0?#ps#+gCyJt*)-l`?@;W_I1>KtLwYpeO=#d`zA@=`sV4pZ<`m} +zzRilazJ2@Mx9x{*-xbZbzWX}w`|iiK@2l=x-~avY`+jD-4^8qm54rPy99Fjb*cET{ +zSo-~sW^!xw6 +zt{=AhwrRfY+wA<`w;$Vm-*w;ief9g__nGZ~9Fn*D*q#69v9kToQ}K46XTSgR+}Zxu +zrTKPWSLgqIooxU6)_uG0yWjtP-)#Tqk-YuS)A|2CFSh^tD&GG0?f3t_AGZJZX}>%RT}-|zqbXHHAQ7M5*>j9Hd0<+ZvW~T%euLmqa39L~M +zSd$XivL3J%C9qdLU~fv`=z730DS>m=1I|SWT&o^%ZA#$Y^?>_O0?(-jJeLx9Z$03B +zl)(4u0pF(t{$CIHnGyxK9tsF03Q9c`R7w=mdMIR+C~Wmm*eOxO>!C!IYLM5$E|r8Xr>?|LYGC{gCrLzzp7vbP?}K1!5( +z^-%6pqWrIi@=QqzT#polk`$#LDJmr?X+2UhN>a9Zr0kTW;`K-+C`mQyk!n(sTGk`A +zq9paIN9s*U8eNYxCM9XkdZf81No&<3txZYVyB=vDO42#?Nas?L?yX0 +zr2p%YK2x#**JA^rWJ9UPhDymsT91v4l8vn%8#^VNcs(`=N;ZvpY?_p8mi5@IDA~N~ +zv3XOnMb~4CNy(P89$PL-wp#VrYE!cHuE*Agl5I{swz-sSd+V|7qhz~RkL^As+y8oO +z&y?c8^~6Ca#Zl^sqf*L$C#@$=Mk&r#Pn?}nT)dvR1f{q}J#kG+am#w*R+Qpi^~Aj? +z#iQ$q$D|a`Sx-C{rFgA+;l!@xArL_fd-9t0#V+Qv82C@n=d6 +z;CdP$lo}}YG*BruNb6~kQEIT&(_p965U-~pL8+lpPeYSZ!?K=+6{Ut(Jq>S4jp%wB +zF)1~2*3-yEsZpz*Mr}%s-t{#4P-@Jnr!kjOV{bi;eUuva>S^4k)c9Xdm7im2&GDUn8WdRf-P34~Mn~6+j!7AvvtD#A%II44 +zqH9w|_pTS+hcbFjz391=(R=Gf@1u;qS19*y`0{r>rGjua*R5Esc7$G%0IY)~jVjS<9OwH*me)Ae6mP>h(sY>`hv) +zHyLGbwtBtUDSM08>n%aqTcciYP0HSu^?F-T_V%jR+ncg?biLj&DSPLv*E<(w?^^YG +z*QV^phpU_uhKF_fht~SFiVd%HIF$^?s(D16*$o2<04&=m%oTE{1jwa0NJ5AIdp%>dl!;IcIOZIr}K*+^aX|KINSM_2xWN?gg&57ld*zO1-_PlzU0* +z?Iok!%T{kMJLO*SdV3`(_iEJJt4X=nvff@R%DrCo_Igw9jjp#hCgt9o_4ekX+*_;O +z-rAIVd)M3BhjQ@@C_v-DvPr3Jhy}i$r_kio&1EIW!Qtuurckh +z`(K;#fA4z#`%wO$Q}6#=%Kv-o{ohCV|6aZS_bLDXulN6%3K+OQFbEegN`GKfE@0CB +zz+_y&Z2f`Rxq!v{150oLYxD=!JMC- +z3%GZG;67ZybNU0%XuY(jNtt3x%{l3K1yL|NEmnbCCk~Ck5dmMd?q9%0)`rpOlP?l&wE0 +zI~S>Ve^Lo9QjPwknp~uo{YkC3NWJ=#dUKIR_a}|XMVhleX)Z3(TK!3DbCLG$Puhoz +zbWVTLxm=`s`;+eDBE8q2^gb8q|Nf-UTx`Jo*+971Q2Mi>aN*FHn09{-dt?a{n=u2vE}T~mWzw6R)4nITx`Ajv-RO(o710d +zE*IP0{%rfW*zWabyU)e;zdzeEmpE{LaS$%~?<=XXg?Z?=LRF +zC9ct5T$4-OvcI?$m$+Adac?g1=>Fm{xx{n!7th5dUaP-&Z7%WN{l)umiO=aTK9@^; +zZ-4Q9T;lioi{IxG|KDHynM(t>zXk}G21iPGN^mCKT}za<%$C0l<>b}mct{+1G4mKyynHMuM; +z`&(LZS$g%i^yadR?r#~B%Q9zw%UoQRwfbAu=CbVF-~MMGF3UOnE$4Ds?(J{6kIV92 +zf6Mz^mjC-(K67~i_xA$f@o)^zgI3UuUh@RYIAw@?(fxy%WF=5uen@ad;5FsK{i8v+qEY%sqjE))_Kzmxie~E{&CV4q-alG`D_Wy}v?f=yW&da^u4u3R +z(cWCq(fy-iaz*FtADxRUx>o<_+Fa4S`$zZTik{OydM;P=-u}`1xT5d%kG{_p{l9g6z`u?f-9#+|D2j!`F~pW&uPV#)2n|@ +zZ?2rt{d30T%9*o&&RkqMYxU1rn=5DU{yF<_<($($=UlFwd;90y$CdM5|D5-^a{lk1 +z^O>s_aQ|8$T(wa8*Fxp0McTg>8CNZ~{YT@&*kd9w}0<_T)pr0?|q-E_y7LA +zpSk7$_n-d*3E)_)E=*BtTwb0oOtX!M_>$u-Ba{~Rl>IbQwe +zcyrB(?ms6c*PNXF=j7s=Q>*`++FWyb_n*^;YtEehbLMi*+1r24KCU_U`p>z~HRpf- +zInP{sf&1?T;o6JRe=jQ6Uef-1$+-5i_20|RwO73VUJ0(f8vXZba_zP3zt@UuuUG%Q +z-dua5`|pj(wKr$~y}7vd*6P2vHrL+X{rC3a+B>KJ-nm?R_x9hrk8AI}{(J9p?fu_> +z?=#mu;QseOxbC6!zlX|okF@_iGOl}U{qM1J-4pMBPlD^7M*n-7T=y*d-?QSn=hgq7 +zH`l%B{`X>X-OJhkUM{YCwff(y&2|4@@Ba7taNV2J|K41#dwcue+sAe9UjKXdx$gb% +zfA5*=KXCv5AYA`Z`u|7e`cK;bKN;75w*LRwx&Djy|1ZJyU!(tjO|Jiz{r_8W{rBqs +z-<#`ybpQV`x&G(u|34Sk|62Y3*XH`)yZ`?_T>t0v|38=O|K9%p_i_Ed*Z=>0uK)l0 +z|9|Eq_KnP(VkQ*=iB7HDl4d181d?1jg*C&rR0t-!^-5coefc4n;xSR#bDBw|P^#Bd +z?Z|B1em4dD$O{qLh=9HN&^nNEWA^oo!kE?T=(h#>K^+)6Ht7O0%x6j@(}M +zSE?-M=H|@n+iIoD^X~4h{Qm8)bVb3#!=2*hbuyJjPfyP@FaIY~Rr2!k%JA)Vvejj8 +zZ|^Ms{!g~1;^X6!)6MJUYOB7!zPY{pzg%6-&(ANfZ?BiHulxJ^=lAdb()(D@ocsghDIjgsOgwNZ2 +zy)*f|-QPdL7aUk+r(AI2wH3MOA{sm8qMK~3$R!Wexl=BA>FyP|>|=U&%4I*>zam!x +zTxF+T3G%fSy&4i4JN0THxgQ9r`<^EwH3RW +zGBtME&9u3-Vz)Au&YgBEYwcdK+c{hBPP?7A_pjKUf}^t2?-ZT26~9|@HFo;lvb(k7 +z_bQ&woqn(C?OyTwHDB*ezhC$FulR!oR=F7unt1Ib9=3?a&3M=*TPN|TLv`MaM_s!6 +zBp&ztH@!FGai8r!i6;|Wm;AeNS!zH*{s}slF#Rq-kbS+ +zUhO~07YkbDX1!R{YbW(`$<(-6FPF`&lX|sc>AYF5R;}G9^?J?Ld$V4z+xt)I&4#0L +zv)^nwYbX77%hkBqZ@1m8lYY13>AczRcD>yv{eI8ad$Zr~`}x8B({4erU!!yz=c%IPIl_5{HB5!(y?-QD~ +zamrJj%t-I(dqUH9UU{lldDA=opU{khBF_vuBYl$PglC=%d1f^8rcb(^@T`kdo|&wS +z^v#YFo_+JmGqasHee>&t=R6d7ZgDcwuXvvD+?OHGt#01*E8izP@8gu`HZLRntM3WV +z|9R!P-Oro;_5XwyFp9o#;EW1rmJ?aX8T!IW@>W2*oya1=sV`hKqXN6*L>5b4ec@(# +zE3m&#WQn5aOApVepvm(@mTHE+^oqO{G<~1QGQ+7aeKMnhXWtW9Zh7^kU*)af`Ts;# +zIEuas=!^1%-b=~ +z?ZkIoocgY0WpwQ8IPu*#uf8kWc{}!fo%o)IqVFqCM#p`gC%*S(==-Xhx8uI=6W{l7 +z>ie3P(ec0UiSPe;^?lvX+wuSZi63AT`_RA{lfW!5aga0YLzCp41a^CgLxR&jv}pd1 +zN#u@~I4pVXL!0HDME-h-BZ^`lJ3M2Ogy%~f)eQUC6?rE~e80po!)YITGGmgZ?@JuF +zy!NrL@=mh+e~A;0VxJ~-#-u3AOP=%$`!s3hofLI@$y0&TK22E}ld2src{=jir)fLy +zr0Um8o=Ft@JmX|cn(=(evzcL^XWhJ$X1-tYT;a6Ob6&=zTi=&FUwQ5Gyq|Z{?f*+& +zXcYUhfHOA3SzhX5XV{lTl6N!Q?WHbFoc3jjW^AT+ywv5H*S;*XyqoD?FLh<1*w+=F +zv01_MrLL|F`?@OfZdUkyscRdjeO;3on;m^$>iW)WU)NRM&5r*sb>pDew+)@KImz01}4ecQ4!Ha9z7`u5Fh-?r_%o10%RednRr_Z=r=^NQz7-+dYO +zeb>#qdFA`1?|q#1eb39-{ObGC_kUjdzVGMV{QCdW4;aON9N>&AXqK0G$Ql0QkmS9B +zc6*seg42H-(TpqXj+c2XdHu&R%X@|W^)gQs#ebggj4PTvU*@T1_|H?3_llIOWu9AJ|9P(RUh(|@GA|s(e_iN|D_JZr`_eP~*QJ^FN|xKpz6zZF>&nWw +z($(>@uOqMjy0-IP>H2!vH;LlEZ=8%P+dNjz$lNn!q`@Y=wo!9@qtGr)* +z|G(UigW~@_bjH^_mY4r|GW_4CnfGg++spsDIQ`$3mGQN&*fDE +z6#xI@WPIJ{`SO2XhX4O{^M2j;{qp}lPXGVsWqkeb`||&PUjP5^=l%Ns|K%B&0~pvR +zFmPXB;1^&N4qz0Yz$krzQC@&aIeKx4&Yiofot^zuJr=kn*+GFPvG8tfqTCI&*1=`;}dvJU*I_} +zz_xc3h+ZTB63-CP-;CnuS@AU<~_X7N%1NgsB;QxJr|G$6$bD#kGL;>!L0{nu4 +z!hwR~69uI&3d#!#DF+IvPZZL=D5NhaY#bWUOrL0`l5KfphR<^MEgXE?u!!rf|8R1C8tl6 +zoPAMpzM$0NK&j;urB+{*S}!QQIZ%50MCsiZrS}WU91fH@K2hfM|BEu`1!XS>%3hx+ +zd;6m7eL=a$fpX6$%DuiQ_g+x`bD;e9iSoZM%KsNsU=C7XpQONjNr7KTQ8-9Ze3GK{ +zB}I85CFLL`^+`(Fmz4B{l#PRw%_k{aUsARgQgIGaai65(eM!Y%NHsV}HGGn4^d;4J +zA+_Wnwe(49*_YJvh183K)XOKSS6@=E7t&}B(rBNg(S1pyUr2LukmmGBnzJuy&KJ^J +z9Hg~;lGf@=TI+?hHwS5NpQOF}lJ&wRW!Y0naChn6>yf2&h3!4T9n}$y|jlOIeFKm_^Y?eOR +zEc>!qzOZ?5uzC4p^Xkjy^}-g-!4~b4ExIpT^b1=~4z`>=*>d(}%lX1qi-WC}Pqtcp +z*=oJ8_2yvf?USu{U$)*aY;!o+=J;fr)0b_|3)@}}w!J>t_V#7l`@(jQgYBMAwtIcq +z?!B=6=V1HqlkIQkJwuQ=(8 +zI2(sJn@@4JzT#{z;^G|Q;y%U2`-+Rdh-+|&Yxoq`=qs-AB5uhcZs}9pvah)1i?|p6 +z4{nA_};$adtb!wafsjZDSof7_`MhL{~Y4~ +zeTx6@EB^mQ0+>Ss*rx_?Uk%_F4HOOy6rUO>eKk;CG)Or#NPTLM_SGPL(O~1yVDqWL +z)>nh=MMIoJL)@o^cwY_i7Yz*#4Go_f8htf1UNkH@G%S5;SoYPhe9`dY(D3r9;ni2e +z>qR4)LnGR!Ms#0|=ogKg92z-&YUJ#zk@H2P7KcVHpBlCLYSens=*^+g+owkFz8bw> +zH0E$<%<=zIV@_X}|rLQH*izO+CC8#*3vThoz-YOUu5NmM@lG9F|@_Exr0$dc9aib67_E +zw2ba+8U13JlfyEnPs^NrEpxtD*5a_N<#f!;8(Q7h7L1wihpP4li+^UgCYd +z#9zELIJ`7`dTI3a(s=Q*1Em1%kss`i^I#yrFRvG`Xb!JvpI*^@y`o>d +za&ma(^y!teuUF0&uUZ^lwS0Qj>g!eO#j7`mS8tzQz59Cge({>a;Wfvn*POmyb6&jm +za(M0a>9x17*WMSedmLW(e0tsM>viwN>pzFrf1h6e`+EI<@doCI2KE^Z+&3EdB^rez +z8pUTcO5bRdmuOOsXi}fiqc +zjo$kbeUBshp3mreeWUNaME~cA{_ivTf8Xf;FEN2RasvCz3EVd)@JmhhXcGpASIoL($@!ln=YO9$|M$)L|0NeNM=fBVwSfE90)DB5!chywXDyVz +zwNPGak#f``^;wIwZ!OZ7T5KG(*nHMv>syQMrIt8HEpeZ<#QW9~f2pOxQA@*TEsegl +zG+t_1a@4Z)SnTg&UERy0SgXrHyB`__tnsg;wXR!*O_a`vs2 +z^QBfTj#{;R)~eOFR;`y>y*X<2_F1cU-&(z2YR%!OHOFVIIelx*d8xIRqt;%Zwf6R{ +zwfCjgJ&s!UeAc?xx7NLvTK_p}{r6exf8SdFUupw$^al3X8@O+8;FsPg9KBKe|Ll#@ +zw>Qd5Z&Hrlq&|C-_U%pj(wmK=H=EDiY<+vPz4R96=q>KEw|L*);xD~5IC^XN?5)wa +zx5i6vOOD=_K6_jC?QQwe+l!;Om(Si_eS3So^p57}9qqGsbl=|5FTHbe^v>zCch0`O +zbH4Ph#nHQ#&)&8A_OA8PyEjMg-adQx?%TWfOYb=xz32GsJ*RK)IWN8Ua`fKov-jS< +zz4yNKzQ@t~p3mO*`u4u}()&M0@Bcn~|L@!T|4ScWjyb?S=K%Me1N<@vg<}qi&p9Z4 +z=b*gIA?27u>T?ch-#Mf&bJ#fMu=$+B)^`ru%N%izIpRL&i1(c%{xU~{V~&Q;IU0TE +zXuQm^4UT&nKJRJt +zy{GYV&ywSwrO$hoeeYSm-1FkN=jHRBSKoVHFZZH3?nV2&7v1+>^vk`R9QShiyqB}@ +zy__%iYH{4F*($Gtf|@6G9ZZ_dlTy&U)U`njE +zzW+5|{#$bVxAghnvhRP(m;YWI|Gj+v_v-uK>*aqm$Ny-b|D*f +z_Wi&2<^Mg7|Mz_Uzt{Kwy_f(0IsX6m`Tu|4|NmcJ;UA}r$A$$5n>mEFVoq#Wc(`3a +z*=vr+#zjZFC5*G~oY=Vdc)x;kmyGA8B_}6q1h0xYxoPR?=?2NC=6G&ic6PQ!@vA#0 +zH!nXw-=UdH*6aV46&DwKOxB7$wPoeyib#-;b=B&G?wywUuKH+eetoODx +zH#cWoUKM+K+uGaP3m%`E>%D#5-Q5+RU)?>uef|CY4b0qfK07u%Jlr9y9d~BO#>dAe +zD0|QI*}3WI=^4h^_s;Cx{QUd^=WaRQU0YsWUJ<-H?(D9uudi=NK0VKO_qMmUcND+A +zcXs#o_xBGpbIbef+41r5iOJgW=l1OU{QSaV@A-avcYS?*V{`WXb9;AxfB)ccx4i$p +zJwHFcxV$?4{Jy=vzkhgqdcObueSd%d`270*`ThI<|7T#+c+kKimhqsGLv6)_CLXgF +z51IwsG#<8ygk?Nzl}KChuuZ1y#lv=mHjPLBJ5;7+JnGa~w&GEj&bAkix($wLJnk{M +zmhrgP;@OJFeKy}-Jnna3(|j_)MJ)5lL=Uxpkv25kDSvlKYKAT-|O!N7il53gI=T*bOuVOcMi&PZGJa@m}+S1*?@Xw!PNV#&0uS1VU6 +zTlH$ynr*LMt=@1<>-Cx~*Ro!(-SKSI>vem+y?VX=0Gsxk4M)VX-)uagw))McGiI;f +zY`);8{dUWhu$lqL?{_p;9O#A(w +zFW0i)@BQ&?_4|E)zP*0GpMhQH!vPlYoDT;%)Yp7C#AE*E!(jn;osUOE!gD?zl}KOn +z@t92cn~%p8+I2piP??_d>7>T;HJ?uDY=86Vw83$m&u2`o=X^eE@qEqab2i`Kd_M2M +zuKVSJi+JvriyrE0zg+S$fBWTffV=M3DzU#~`_ul;&0ru^;K>j~|;-)^K#&;52Y +zWBJ-|w{o_>{dT+HxbF8mCD(Jm->rDQ_WQk>?{B}~Z(!H^@t{RK@5jRq^>sfU^_ai= +z@pyu}-p?mf!t;JUosquo=d(HG?|wdC(60CE#ggfHzh16bzV6qnHQV3)dcEPe-tRYC +zuIK%JyW{z~-~Zq3`Tp+r`vdIye?A-$&;RrBg!=kFpU#-S|MU5RyZ+xVSHkoEe!Y>t +z{_nRtuQ%J@|NH&nxc>h?U#{o>|NGUEmU|r4iu%|Uab}U&_7g|-sy=on@GO=%?s3d$*2kU8M+urtLVhRImKx>9}8?rXS#0X3*|=CQ0h^j1xY~jHaJFlVqx3v+XC(=2d;3{eWk=#c|JbMYBH7dEv9%>iWrZWxGDl{m`@A=DFwjs#~Au +z{W!DS?)%B}b-zB(XW(7o!0vUSN$Sf24&N0{;-@aOS$$b3(7VD#-Rojk)R#pPXIHqH +zpSsvr^<}XF?@AAMuS=6=eOaR6yV5KC)TL>=zAQE9UFnnV_5bp$TVIx0oL%Wxe(LhP +zUtg9x@U9AI_qwu3>gx&*-&H}=PhDAN^>t-H@2ZgHURPH|eO(oCc2(H+Q&-njeO;Zv +zyE@{y*R@TvzOKpeT^)7()U|E9zOF6kT^;k>>-w%+U)NQfT^;xR)b)M8zOHZJU6a7> +zedCbSw+$V>Ym&rI-#BLVZR3RAH7V-eH%~=<+ce|snl$s%H_ugl+q{5xZHBw|txL1M +zZCT;FHY@z}t!ul!ZQanjHYeTt_N`mrw(U5(Hn05j?R&qzZ9l-fuAtrf&LgSsJ5Knn +zE1G`#&NHj;J1_LED_QP+_f^#QT{q6IE8Bkh?z^h*yC3kbuQ={~@6)XBdtUgiueyHv +z-naj|zVH3eyT0bR_x)eDzVG{Sc75IV)A#@V`o5omZ$ksS&jTju9|t)6HZ+N!dBA4< +zh_Og7Uwqhm7jSm_xs0j2fj@c+I^lVN&h_I;kRkh^fOP?tbd*i=-V`9xzAIr +z=%1$|&TX2u{mfIn>Yt|*_%_ct?(@uO_RljJew$}qKl99N_s_EheVgYz_jzu0`{%ie +zbDQUVKl9w~_s{bUd|MW<`@V3J{&k_lZ_6U_voGANe_fo=w`GaC?@O=fUzcW_+p^63 +z>`TAuUzZo~ZC&B+`zmPmum4w8_-$Pke)d(^?q63o^le>}?)y6G_OEL@&TU;+e)e_T +z?_bvs@NL`B?)xT5`uB|!e%m%pKl>)l`uEKXecQGy_kEic{rlF9bKACUKl?VX`uFVz +zeA{;%_kCA1`}ds}e%p6lKl`q1_wTzO`nKJI|#1eV%pu&ohhjJI|G$`#kUWpXUzzyDqf*eOV;^_l1Z5 +zu1nL;eOYGx_hmr;t}Dy^zOIV?`zqr6u4~)ReO*`m_jSU5{@pi@`+eIq`|q0!|J}E) +zpZm6L_usb#{k!ix_xrx<_TP6E=Xc-xe(w9e-+$jX@b7uR?*HSE^uG@s{(ByYpZ{^p +z`rpS1{d=CM`~N%@{qNI^^Lw6|pZ|HT`rqdT{Ci)x`~SK$``?!p{(E1ApZ|4j_rI?j +z`uDy`_y2wC_P=jC&hLF!e*X8p-~YZJ;NSP5-T%)c>Hj}Y`0x8P{rsP2*8hKA=->Bc +zx&Pl+(f@zlIKS`P_Va(=Rsa9}fPepwZ9Joo?q +z>-PVDKhE#}_x=3;f4~3#XOL)MvS?t5Xke>o;F!_CwWEROMg!lE1_6miA&W+lh(@uB +z#{UvC8l`qL%G_v_`_ZT%(WGS2q!Q7jR?(y}qe*K=lg^DMy&p{m63s>y%_b4eW);mA +zGn%b-G~3*0w)@fSAkpGv(c%)(;#Se(F{8z6M~lym7QY`Y0TQi27Of!>tzi|d5i?q& +zcC^OaXpQ^Pnjq1ZWYLxq(Uw-xmNBC(Ye!qojkdfWZ3PnTMHcNP5$$Ca?G-cHt9G>4 +z+-R@+(cU1@(PYul64B9C(a|xZqiaV;&y9}09~~1UIwx6lPKoH8R?#_QM(3;@opWw< +z&im21K%#4rMc0yuu4NTnD`s@9+R?SD~zMm5WBqs`4P85lpC{{U9V&+7tofBnlPL%sO +zQ9*K&lI0|o$VqCIlQd>d(%Lym=jJ56pOXwECmUH#Hi?{URyo;X=47j#lWlHJw);8R +zL2`>8bJot8b8gO@_jBd~ +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 +ze7 +z4GDBt-5L_??`j$v8lJi}G(5i5G%PZG>DI95{G+DfvE@&k=*ZU +z7MVIdbz5Zm{8qE5%;ihBMP;u)Y8IWl{pq&o{Qa!vF@?ufx5pHpcQubKy`H)~w)}pp +zd0gf5rQ74G-yb!Pul@dXdwl(WR*Qs2cC{S|&HQc_iLK&kI}+RF+bohg)tBu^>efGI +zk=$$kY)5jxJ)339M0d5FDUKEIyL +zDq~^0+OCYn{ccv7OQ)yp%3MCb%_?i<@@2cSREJ7@EG +zH|yN3*VA_AZol7VowxJ(vfX*R-ygHi-~0X9?)?4#*=!09va9bYILz;EQ+QN7eNW+W +z`S$-dMJLsl?>C5+) +z-OfL5TYk6v`QGyT_3U;P58KuERXpx@x2t?QJ$+y0^ZD&|RWFw>-&gf|{c*eMx7(lZ +ztA4+q-M;4IarON*pU=D7*M7a8zQ6YS{dW7hpU;=?ulxP}xPATK@6Y$w|NqaPz`$zZ +zz$A8|f!!s6Npyz;o7sg%{+0w5RSQS1unSG%M-tdfcR2EuU1*kPN#t;~a1xq!p+(&# +zkt=kEli0Qkt@xEW2m*pu#(EOm8Abub$v^!PO7DM)U`{~wjW8= +zE8XcG_wCa3{VZt)t(HDXVwYzecS$pvy3;4k?DEX>Eoml8Eq$}XF3-AtB+YE=PT#z; +z%d_vZq+1-d^edWndCqf}bgQd7{mQmop8LKf-R7yKf7P|i^S&QRxBI%&zwX=R`Ttom +z99XRan#8UwV0X=M65SQhX7>NeLjKka7gejkuCObM#E)jUneGbgE4#8-o;B0M)hcMx +zv@1*0T{FExcLhz`c4eu4Yo<@CRq(89SC*L{&GakX6+G|TmF4!VSplt9A&bPWu5fqF +z3YxkrWSQC3mHw?+Axo`7SA|_&6@D};Z0oMjb!Asq$FpWf9JLDDH0|n|bl2>ttGmLs +zZM(WQzcoANsa5!{YggBmAI*;Yx+{F&x2x;xS#uItts@SJUE9#^nv*2DJK~txwT=C) +zIVq~vk*C70ZJK^GC(U$sgpDN3G*NO}oD5xodva)!lL5wq4)*zBRw*sdfCXYuEREKbl|n +zb$9%~Z`b$#XDw)8wMk$Szj1)wt)NMCPXe3yjf4Dc1ud#JiCp0~4v8NtXfxfD$X9;j +zusmB~hpSDJ(DWNe)ZGfZLiZ$zZNG6;zpbz*)h1c$`i*1e#|ryO_aw`Gzj552t!P54 +zO^TBE%@gi!MU$rPNl`PudD6eFXv$KXRITuvr^1gFP20LBRj>T!>3Fu{8Aol>jHcf_ +zlkQeL>*}5~v+Xy}=C>8kd1{kxb^YeK@?*vGzV1o4`+oC$J=_111+2CiPU5#Nw7Zoo +z65X5OW`65pe_P2CRohIj@LQLrA1hgAx;N9W{MP09Y^5t)ZL@-=-@3Bgt#noB-mI|g +zx2~>lD_xUnn;mui*0t@&O4pU{&5rwi>-v7SvJI`aIZ5KTZya|k+cb4=PMZ1co9Elg +zwk);H%?iJL>-w>>ZCm%|=9S;ReV?s-$5Gq7qUpEqJa;SKb#-rE+4kFa-?x?Td1{+q +zb^Z3e@5jpbechX1_x<+$|7;ZpSnUd$#P2*{cds}ky04(k{LVxE_KG8_c7=l+-O +zE1viL&U1VAstc`lC5y!GzHoQ1x-@lP$ujf1Fa6uAt}L}HT@`-!Rrv9$Yg_k~t}DO$ +zI-b4y#!-yby<;SbQ +z_ig+Azwg`Y|2(z-U;pd+{eRz&*Z=#vzy9C%`~Uy5A7BtUz^HP7$>adD%K?^<1FR_r +z*h&ttw;bS@a)5Km0j@0vxQ`s*xpIK_$pOAE2l!bI3WyvOR5>VQa!}aiph(C;(UgN? +zB?rY@4oXZpD7oaI)Ru$NM-IwdIVk(&pxl>(@+^lGL=Gvc98xklr0jA?CFGE5$|1Fq +zL+UMuG^QNVTyjWj%OUL}hjgwS(tUDB@5>>5mcs@jhYeK@8<`w7b~$Vka@aKGuvy7r +z^OnOFQx01$Ic&A%u=SC{HdhYYJ~?dn<*+@=5eJbYjw(l-OpZ9a9B~Ob;+k^At>lP% +z%Mp($M?9At@!E34`^XWWD@S~v9P#_||A;@!(EyR7fhtFXOpXS-91RIM8k%x6tmJ5T +z%h8A_M!oFH;?qRPohCMPGmoSYJJa%#%SX(cD8x15|Y +z<>bsICueOrIs3@TIaf~3eR6W%my`2ZPAw2QwNT~MB9l{#UH+e15^`#3%Bf`~rsd~35IMb3<@6?#)0E2sB9Ilb@8>HREc4v3sNsB-3z$(h40XO4uNIhu0jSjm~= +zEoV+lIdgK!nNwTNoIY~q%#|}|pPV`O<;;1Ovlm3pUQ{`I$>i*1m$O$w&R$J9d#&W` +z^_H_YrkuUGUrWcr9FR+AOU`@Ti +zR(gTG^#aG#3!FO~>bi^8rKMM5u%rd|{) +zy(r##QDW*v$)y*iwqBGzdQs-;McJnp<-T5&XT78#dP!0Bl9K5qW!Fn8p_f!sFR7JY +zQg6MaG4+z>(o0%fFKHjWq;vI>?$b+pUoYviUN#WDY^ZwK$n>(Y>t&PB%ciN9%}Otu +zw_diGdf9U6Wvi{1t&d)|xq8|5>1DgGm+e`vIEY?xRK4P4dd1oGic9Df*VHR+rB~cr +zuXs$o;<@yS*VZfEN3Zx?z2f`y{}sQlSNvJ828dn_RJ|HxdNtVfYDnnS(A2A8rB}mS +zuSQJ08oBgp)YhxfN3X_Qy&C)UYTVbW@vPSpM6V^PUQ05)mh5^hCG=Wq>b11eYw4}m +zGNxY3TzV~Q>$U8o*K)32%YAw+@9VXE*6RhL*9%pz7nxo!cD-H_dc8FDdRgi9^49AW +zQ?FMpyy3`7 +zH#(Qz=-PUt`{<3Ht2cU|-st;!qo4KW1ksxlRc}r*y*b(S=9JKzQ&VqFE4?|r_2!JJ +zH)k%rIcw|9*+*~Axq5T%)0^|Y-ki^RYk}ykg{rp}nf|}E*!9+u&|6DWZ!IgmwY>G# +zimA6&F1@vC>#fyCZ>_m{Ywgop>%QJv&w6`<=O#I-t2mNOX%&bskgV4-rnAN +zd&ktnvfos&!NoZ5Ql^wB$KuHHHO^v=1jch0lky&!t`qUzmCrgty9-n|lf_iF0hYo&Lu +zx8A)m_3q83cW-UId;93!J6G@CeR}ua*Sq&w?>!K`_fYlTBh!12UGF^!z4tWr-m}ts +z&s*=kn0oK!(tEGA-g|xY-kYoU-afte?(4nxtoJ{N-v6k2|C8zc&#w2sgx>#}djDJL +z{qL>+@Bf&3|L4;Czqa20ef0jHtM~stz5nm){r{{F7{nehsy$#bd%*1WfF+bh;9T~AYuf|vV-I+)J>Y%zfbZJ_ezu1KVh;t?9txQ~6n1+k682Cu?V(uN +zL-DqU64M?^E_*1o?V__z +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 +z_S44xxHGeu8Z>L3 +zNfg>VEHZC-gcw`lY1dwwC|L>2w1GCnJMxiYW*labN +zgjZc?HQKU}uU5lFS?gkF(3VAFdo|pQS6%Ea+Ok;guZD-S)}@J)wk%Pz)$|Hpb!qCR +zElc%kHGPt`F3-HQWtrJtO~2w*m*;-kvfS>kWv~AP0 +zy*g>et8bny+O~P#U!DIM&f2#wPTIC*nXPVC@akJvH*MRxu2wfES^M_QOWU?>+pC*b +zy!!UtPusTd`>R{ftbOO9(Doh2Z1svJufFrtX#37{wR$CsweP+R+P>@BUcIu-tM9%o +z+P?eVU%iUM+V?(A+P>$Rt$x+z)%U(`+P?Q)t$xj8?fXA3ZQu88uYTR<)%X8?+P?qa +zU;PGVod=AC5?$XmSQu-rd`4riT5 +zf|GX~QL{7b3SRR_bn}j*dUb|9$vTfEFYh>Jw$HGyc+F$k&pVFW{WF} +zJEKXH*E~@*-g(lm&S=VFou`_?J5Pn}Gn%&f|C*<|#XC>O{WF?zSm&AHpZu-yz^YyKI3_x*F3lVyz_kBKjQ_=x-T4scU@?+Gg&0O_JynQ +zu8VzjCQFocUwQ`bx-@N{$ui@$FMW%5U7q*PWQDWttH8;-t}L@NT@}3cRp{njSJ%~< +zu1VH?9eH`zwQc)M*A=gQ9s7CL^?mlS? +zzU?#L_j&F6+RwZ1|NCctfLZTDqwt;wZ1xt1gx7s&HQw`(uioN_vfju4oxyt^iS4&I +zX1wlWZ}FbTa{nz(IO~0yIC;+#HG9ib!RtOv-Mr_iUcKd+WWCQbFYkF~w%_tx@w(4* +zKks>N_uukDv)-45!h2u1*;`$jyza|VaDIU*893Lc<-yQ{Z`jDulu^Tc<<}D +z|5i5+>wViedGDJvd+S@5*L~Z%dGFi2dh0un^}g@Cy!Tz%e(QUm*L~mndGGtW|JDzf +z^?w``-uI!+-sX|;`X5J)_kHZEw|Szh|MO(g~QP*8lr5c>k|!`|ZAMUjO%P@&4cU{@eXHtpD%h-C|;*1(Y8>cPf>E(LdkiGQp*-fty7fVworPX +zqRg>{GUpU!uPv0lrzrPqq1^v>it^tU%KuYTU|XcXr=%#hNKsBnNo|pmo|3ZJB4s-z +z6}LqyeoCrgi&W#3)Y2BIATN)at +z8kV*+EKfDOY-xC%YDC-8h(6WGX-gyLsYWeZ8nsR}dfU?IeX23Xmd2b@jlH%s_MU3o +zv!!wGRO7!bjsK^bz_u)bPc2bwS)!a;lG?H)J+)-BWyyAGDQ?SB{M1szmZiq2rKK%P +z%Tr4)Tb5p@meIB>qfae!+Oo`fYFW#cWvx@o-nJ}zpIXkbWjW{6a<47Ry{DGb2LF*WOdFd$zppoqGMZ +z<@Nv68`xGf@M$!Pt!R|fXi{6zq^HqrwxZciqs47Si=Reo*oxLTjkdHEZFw5)Wh>h2 +zG&__+-oc6-qW1-|JllU?=U?ny-81dv)Ss+cG_FqR&Vjs-Ws-gYn=ABwAI`4w6~Y7-d?A@ +zqiyw$KJA^;R_~mry=&R(UF)=WZ(F^4pZ1<(tM{DK-g|BJ-h0~no~_>ZPJ92i)%*Wx +zA7EQ^fKTV3*qVcKI)~KO9MaP{Y_{gGoz4-rHAnn(j)tu{8mDtCZOySfo#SO|j@Rj& +zXj^mQf1l3DX=_f-(>b+l&8c-dr?;&+y-(-Nu{CGT>72c`=IlM4bI;bCd#7{$+nV$L +zbS|*1y}+k?QEcr+Io(TYYcJ{PUN&2M*-rP0+uAFBx>v*2UX9bembUgOswb$!( +zZ?vtw(WiTJ+S;4*bZ;$NduyHU?QLss@6)|=Z0((Mx_7Uwy?amh-m|s$-s#@|w)Xx% +z-3M&z9`NZs6kGRDPVbT0x<`6?kImLSw$pp!w(g0a-qWykPvi8SrLB9Gr}w;U-SaxV +z7j5fa^y$5vw(jLTy;sZDy;`UDdfU3!`}E!%TleOi-rH;I-rm!D_iWv}cY5!?t$Y7Z +z?*rTV4}AI`#nyk6)BmKl{*#{m=l^EwKilblaa;ezPycJ!`mb^N-_q89%hUf}w*Gsa +z{*SixKl=24PFw$Tp8l_8>wm4&|GjPf?|u4zj;;T5PXF(<^?&c_|9iIn-#h*P-`4;C +zr_aE?fq~zEQG5fVyaAK?1}1$2X7dfq_698O8(91eSi?84#v8DuZ(z$eU@zamUT?tB +zzJa6PfOGl=&iMvh%QtYXH{jmBfqTCJ&+!dB=M8wTZ{WRe!1sIu-+Kf8?;H648wjv( +z6yP@$6yGQ)Zz!a`QApoV*nFd~y`hNvMiGBQ(eRC;@rGjQ8^!Vs#mhH}*BeT-Zjr5*x(tB^D|9zAGeTRi8RcrD-J +zwcf;g`xfv0CO*fv_?$QKy}rfwzKP%SEq?D!{J(GU|8Ek&zBPc~G*EnNpuB02`qm(Q +z(_r(h!S<#h?ps6rO+&-ChQ^zQrEd+(Hw`b}8eVT2(Y`gJ-!yXi*2wv$QOmbRtv8L{ +zzBPKkY0UAhG3QNVuWyaLZyNV}YutO&`0rce|C=VTZ%g1eOBCOhC~uaezAZ`LEZKZp +zvb|Y~`?eH+v()fysqtoM>D$us&C<)arPrHfv~SDkH_M#9EpxtE*79vx>&>#aZ_D0q +zmUDbt&Uv%k>)Uego8>* +Date: Tue, 7 Oct 2025 14:03:49 -0700 +Subject: [PATCH] Ensure sandboxed UIDs are treated as untrusted in Appops + +They should not be considered "system" app for the purposes of +attribution tag vaildation + +Bug: 443742082 +Test: atest AppOpsMemoryUsageTest +Flag: EXEMPT CVE_FIX +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:1bc6b146137f76589146dff5cd82363de7ccfb7d +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:066eff80abf013531320b8280637d1d00dd553a1 +Merged-In: I0c4ac8eaa8966027ad01375dde58b05febec3ffb +Change-Id: I0c4ac8eaa8966027ad01375dde58b05febec3ffb +--- + services/core/java/com/android/server/appop/AppOpsService.java | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java +index ae8a8d943ccc..9c9d338099dd 100644 +--- a/services/core/java/com/android/server/appop/AppOpsService.java ++++ b/services/core/java/com/android/server/appop/AppOpsService.java +@@ -5043,6 +5043,9 @@ public class AppOpsService extends IAppOpsService.Stub { + if (packageName == null) { + return true; + } ++ if (Process.isSdkSandboxUid(uid)) { ++ return false; ++ } + int appId = UserHandle.getAppId(uid); + if (appId > 0 && appId < Process.FIRST_APPLICATION_UID) { + return true; +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch new file mode 100644 index 0000000..b0ec2bc --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-37_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch @@ -0,0 +1,222 @@ +From cea235f00865ff73344f1efa9494e47beecc3fd5 Mon Sep 17 00:00:00 2001 +From: Shawn Lin +Date: Wed, 15 Oct 2025 12:18:38 +0000 +Subject: [PATCH] Fixed "Unlock your phone" unexpectedlly turned ON after OTA + +If the new setting key is not set, we should use the value of the old +ones as the default value. + +Bug: 444673089 +Test: atest BiometricServiceTest +Flag: EXEMPT BUGFIX +(cherry picked from commit a3799e443e9fdbbcbc96824b8d2fc1e4c683bb7e) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:2b8bc2b15f2aceb4e2a379209997afce16427e76 +Merged-In: I902c7fd9781037ba05b30821b4fa22aa1509f3fd +Change-Id: I902c7fd9781037ba05b30821b4fa22aa1509f3fd +--- + .../server/biometrics/BiometricService.java | 45 ++++++++- + .../biometrics/BiometricServiceTest.java | 91 +++++++++++++++++++ + 2 files changed, 132 insertions(+), 4 deletions(-) + +diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java +index cf5fa9699ca9..7485929147e5 100644 +--- a/services/core/java/com/android/server/biometrics/BiometricService.java ++++ b/services/core/java/com/android/server/biometrics/BiometricService.java +@@ -412,20 +412,39 @@ public class BiometricService extends SystemService { + notifyEnabledOnKeyguardCallbacks(userId, TYPE_ANY_BIOMETRIC); + } + } else if (FACE_KEYGUARD_ENABLED.equals(uri)) { ++ final int biometricKeyguardEnabled = Settings.Secure.getIntForUser( ++ mContentResolver, ++ Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED, ++ -1 /* default */, ++ userId); ++ // For OTA case: if FACE_KEYGUARD_ENABLED is not set and BIOMETRIC_APP_ENABLED is ++ // set, set the default value of the former to that of the latter. ++ final boolean defaultValue = biometricKeyguardEnabled == -1 ++ ? DEFAULT_KEYGUARD_ENABLED : biometricKeyguardEnabled == 1; + mFaceEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.FACE_KEYGUARD_ENABLED, +- DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */, ++ defaultValue ? 1 : 0 /* default */, + userId) != 0); + + if (userId == ActivityManager.getCurrentUser() && !selfChange) { + notifyEnabledOnKeyguardCallbacks(userId, TYPE_FACE); + } + } else if (FINGERPRINT_KEYGUARD_ENABLED.equals(uri)) { ++ final int biometricKeyguardEnabled = Settings.Secure.getIntForUser( ++ mContentResolver, ++ Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED, ++ -1 /* default */, ++ userId); ++ // For OTA case: if FINGERPRINT_KEYGUARD_ENABLED is not set and ++ // BIOMETRIC_APP_ENABLED is set, set the default value of the former to that of the ++ // latter. ++ final boolean defaultValue = biometricKeyguardEnabled == -1 ++ ? DEFAULT_KEYGUARD_ENABLED : biometricKeyguardEnabled == 1; + mFingerprintEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED, +- DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */, ++ defaultValue ? 1 : 0 /* default */, + userId) != 0); + + if (userId == ActivityManager.getCurrentUser() && !selfChange) { +@@ -438,16 +457,34 @@ public class BiometricService extends SystemService { + DEFAULT_APP_ENABLED ? 1 : 0 /* default */, + userId) != 0); + } else if (FACE_APP_ENABLED.equals(uri)) { ++ final int biometricAppEnabled = Settings.Secure.getIntForUser( ++ mContentResolver, ++ Settings.Secure.BIOMETRIC_APP_ENABLED, ++ -1 /* default */, ++ userId); ++ // For OTA case: if FACE_APP_ENABLED is not set and BIOMETRIC_APP_ENABLED is set, ++ // set the default value of the former to that of the latter. ++ final boolean defaultValue = biometricAppEnabled == -1 ++ ? DEFAULT_APP_ENABLED : biometricAppEnabled == 1; + mFaceEnabledForApps.put(userId, Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.FACE_APP_ENABLED, +- DEFAULT_APP_ENABLED ? 1 : 0 /* default */, ++ defaultValue ? 1 : 0 /* default */, + userId) != 0); + } else if (FINGERPRINT_APP_ENABLED.equals(uri)) { ++ final int biometricAppEnabled = Settings.Secure.getIntForUser( ++ mContentResolver, ++ Settings.Secure.BIOMETRIC_APP_ENABLED, ++ -1 /* default */, ++ userId); ++ // For OTA case: if FINGERPRINT_APP_ENABLED is not set and BIOMETRIC_APP_ENABLED is ++ // set, set the default value of the former to that of the latter. ++ final boolean defaultValue = biometricAppEnabled == -1 ++ ? DEFAULT_APP_ENABLED : biometricAppEnabled == 1; + mFingerprintEnabledForApps.put(userId, Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.FINGERPRINT_APP_ENABLED, +- DEFAULT_APP_ENABLED ? 1 : 0 /* default */, ++ defaultValue ? 1 : 0 /* default */, + userId) != 0); + } else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) { + updateMandatoryBiometricsForAllProfiles(userId); +diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +index 9918a9a35c33..3061bab24518 100644 +--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java ++++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +@@ -21,6 +21,12 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL + import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; + import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; + import static android.hardware.biometrics.BiometricManager.Authenticators; ++import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED; ++import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED; ++import static android.provider.Settings.Secure.FACE_APP_ENABLED; ++import static android.provider.Settings.Secure.FACE_KEYGUARD_ENABLED; ++import static android.provider.Settings.Secure.FINGERPRINT_APP_ENABLED; ++import static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED; + import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; + + import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI; +@@ -40,6 +46,7 @@ import static junit.framework.TestCase.assertNotNull; + + import static org.junit.Assert.assertNotEquals; + import static org.junit.Assert.assertNull; ++import static org.junit.Assume.assumeTrue; + import static org.mockito.ArgumentMatchers.any; + import static org.mockito.ArgumentMatchers.anyBoolean; + import static org.mockito.ArgumentMatchers.anyInt; +@@ -2147,6 +2154,90 @@ public class BiometricServiceTest { + invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG)); + } + ++ @Test ++ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) ++ public void ++ testEnabledForApps_biometricAppEnableOff_fpAppEnabledNotSet_returnFalse() ++ throws Exception { ++ final Context context = ApplicationProvider.getApplicationContext(); ++ final int value = Settings.Secure.getIntForUser(context.getContentResolver(), ++ FINGERPRINT_APP_ENABLED, -1, context.getUserId()); ++ assumeTrue("FINGERPRINT_APP_ENABLED is set. Skipped", value == -1); ++ ++ Settings.Secure.putIntForUser(context.getContentResolver(), ++ BIOMETRIC_APP_ENABLED, 0, context.getUserId()); ++ ++ final BiometricService.SettingObserver settingObserver = ++ new BiometricService.SettingObserver( ++ context, mBiometricHandlerProvider.getBiometricCallbackHandler(), ++ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager); ++ ++ assertFalse(settingObserver.getEnabledForApps(context.getUserId(), TYPE_FINGERPRINT)); ++ } ++ ++ @Test ++ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) ++ public void ++ testKeyguardEnabled_biometricKeyguardEnableOff_fpKeyguardEnabledNotSet_returnFalse() ++ throws Exception { ++ final Context context = ApplicationProvider.getApplicationContext(); ++ final int value = Settings.Secure.getIntForUser(context.getContentResolver(), ++ FINGERPRINT_KEYGUARD_ENABLED, -1, context.getUserId()); ++ assumeTrue("FINGERPRINT_KEYGUARD_ENABLED is set. Skipped", value == -1); ++ ++ Settings.Secure.putIntForUser(context.getContentResolver(), ++ BIOMETRIC_KEYGUARD_ENABLED, 0, context.getUserId()); ++ ++ final BiometricService.SettingObserver settingObserver = ++ new BiometricService.SettingObserver( ++ context, mBiometricHandlerProvider.getBiometricCallbackHandler(), ++ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager); ++ ++ assertFalse(settingObserver.getEnabledOnKeyguard(context.getUserId(), TYPE_FINGERPRINT)); ++ } ++ ++ @Test ++ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) ++ public void ++ testEnabledForApps_biometricAppEnableOff_faceAppEnabledNotSet_returnFalse() ++ throws Exception { ++ final Context context = ApplicationProvider.getApplicationContext(); ++ final int value = Settings.Secure.getIntForUser(context.getContentResolver(), ++ FACE_APP_ENABLED, -1, context.getUserId()); ++ assumeTrue("FACE_APP_ENABLED is set. Skipped", value == -1); ++ ++ Settings.Secure.putIntForUser(context.getContentResolver(), ++ BIOMETRIC_APP_ENABLED, 0, context.getUserId()); ++ ++ final BiometricService.SettingObserver settingObserver = ++ new BiometricService.SettingObserver( ++ context, mBiometricHandlerProvider.getBiometricCallbackHandler(), ++ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager); ++ ++ assertFalse(settingObserver.getEnabledForApps(context.getUserId(), TYPE_FACE)); ++ } ++ ++ @Test ++ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) ++ public void ++ testKeyguardEnabled_biometricKeyguardEnableOff_faceKeyguardEnabledNotSet_returnFalse() ++ throws Exception { ++ final Context context = ApplicationProvider.getApplicationContext(); ++ final int value = Settings.Secure.getIntForUser(context.getContentResolver(), ++ FACE_KEYGUARD_ENABLED, -1, context.getUserId()); ++ assumeTrue("FACE_KEYGUARD_ENABLED is set. Skipped", value == -1); ++ ++ Settings.Secure.putIntForUser(context.getContentResolver(), ++ BIOMETRIC_KEYGUARD_ENABLED, 0, context.getUserId()); ++ ++ final BiometricService.SettingObserver settingObserver = ++ new BiometricService.SettingObserver( ++ context, mBiometricHandlerProvider.getBiometricCallbackHandler(), ++ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager); ++ ++ assertFalse(settingObserver.getEnabledOnKeyguard(context.getUserId(), TYPE_FACE)); ++ } ++ + // Helper methods + + private int invokeCanAuthenticate(BiometricService service, int authenticators) +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch new file mode 100644 index 0000000..ebaf486 --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-37_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch @@ -0,0 +1,503 @@ +From 2bca2265ff3e26b09f9b31c31063147a94e4c5aa Mon Sep 17 00:00:00 2001 +From: Hiroki Sato +Date: Mon, 20 Oct 2025 19:13:15 +0900 +Subject: [PATCH] Harden InputMethodInfo parsing against large metadata + +An IME's metadata can reference arbitrarily large strings (e.g., +@string/large_text), which can lead to OOM or large Binder transactions +during parsing. The previous check only validated the raw XML file +size, failing to account for the size of these resolved string +references. + +This patch hardens the InputMethodInfo constructor by enforcing a 200KB +cumulative limit on all resolved metadata attributes. A new +MetadataReadBytesTracker now sums the actual size of all read +attributes, including the full length of any strings, and parsing is +aborted if this 200KB limit is exceeded. + +Bug: 449416164 +Bug: 449181366 +Bug: 449393786 +Bug: 449227003 +Test: CtsInputMethodTestCases:{InputMethodRegistrationTest,InputMethodInfoTest} +Test: InputMethodCoreTests:{InputMethodSubtypeArrayTest,InputMethodInfoTest} +Flag: EXEMPT BUGFIX +(cherry picked from commit 7afc13faace7cfafd0353482db33504c5e269d69) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:311c7f2c2b8b927571884765c7322a21f8115383 +Merged-In: I43f7be8eb80abeb39863a3b01d3a606beb90120c +Change-Id: I43f7be8eb80abeb39863a3b01d3a606beb90120c +--- + .../view/inputmethod/InputMethodInfo.java | 284 ++++++++++++++---- + .../view/inputmethod/InputMethodInfoTest.java | 98 ++++++ + 2 files changed, 323 insertions(+), 59 deletions(-) + +diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java +index 31c7b7b20ef7..f81e2fab44aa 100644 +--- a/core/java/android/view/inputmethod/InputMethodInfo.java ++++ b/core/java/android/view/inputmethod/InputMethodInfo.java +@@ -50,6 +50,8 @@ import android.util.Slog; + import android.util.Xml; + import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; + ++import com.android.internal.annotations.VisibleForTesting; ++ + import org.xmlpull.v1.XmlPullParser; + import org.xmlpull.v1.XmlPullParserException; + +@@ -314,70 +316,72 @@ public final class InputMethodInfo implements Parcelable { + "Meta-data does not start with input-method tag"); + } + +- TypedArray sa = res.obtainAttributes(attrs, +- com.android.internal.R.styleable.InputMethod); +- settingsActivityComponent = sa.getString( +- com.android.internal.R.styleable.InputMethod_settingsActivity); +- if (Flags.imeSwitcherRevampApi()) { +- languageSettingsActivityComponent = sa.getString( +- com.android.internal.R.styleable.InputMethod_languageSettingsActivity); +- } +- if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) +- || (settingsActivityComponent != null +- && settingsActivityComponent.length() +- > COMPONENT_NAME_MAX_LENGTH) +- || (languageSettingsActivityComponent != null +- && languageSettingsActivityComponent.length() +- > COMPONENT_NAME_MAX_LENGTH)) { +- throw new XmlPullParserException( +- "Activity name exceeds maximum of 1000 characters"); ++ final MetadataReadBytesTracker readTracker = new MetadataReadBytesTracker(); ++ try (TypedArrayWrapper sa = TypedArrayWrapper.createForMethod( ++ res.obtainAttributes(attrs, com.android.internal.R.styleable.InputMethod), ++ readTracker)) { ++ settingsActivityComponent = sa.getString( ++ com.android.internal.R.styleable.InputMethod_settingsActivity); ++ if (Flags.imeSwitcherRevampApi()) { ++ languageSettingsActivityComponent = sa.getString( ++ com.android.internal.R.styleable.InputMethod_languageSettingsActivity); ++ } ++ isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, ++ false); ++ isVirtualDeviceOnly = sa.getBoolean( ++ com.android.internal.R.styleable.InputMethod_isVirtualDeviceOnly, false); ++ isDefaultResId = sa.getResourceId( ++ com.android.internal.R.styleable.InputMethod_isDefault, 0); ++ supportsSwitchingToNextInputMethod = sa.getBoolean( ++ com.android.internal.R.styleable ++ .InputMethod_supportsSwitchingToNextInputMethod, ++ false); ++ inlineSuggestionsEnabled = sa.getBoolean( ++ com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, ++ false); ++ supportsInlineSuggestionsWithTouchExploration = sa.getBoolean( ++ com.android.internal.R.styleable ++ .InputMethod_supportsInlineSuggestionsWithTouchExploration, false); ++ suppressesSpellChecker = sa.getBoolean( ++ com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false); ++ showInInputMethodPicker = sa.getBoolean( ++ com.android.internal.R.styleable.InputMethod_showInInputMethodPicker, true); ++ mHandledConfigChanges = sa.getInt( ++ com.android.internal.R.styleable.InputMethod_configChanges, 0); ++ mSupportsStylusHandwriting = sa.getBoolean( ++ com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, ++ false); ++ mSupportsConnectionlessStylusHandwriting = sa.getBoolean( ++ com.android.internal.R.styleable ++ .InputMethod_supportsConnectionlessStylusHandwriting, false); ++ stylusHandwritingSettingsActivity = sa.getString( ++ com.android.internal.R.styleable ++ .InputMethod_stylusHandwritingSettingsActivity); + } + +- isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false); +- isVirtualDeviceOnly = sa.getBoolean( +- com.android.internal.R.styleable.InputMethod_isVirtualDeviceOnly, false); +- isDefaultResId = sa.getResourceId( +- com.android.internal.R.styleable.InputMethod_isDefault, 0); +- supportsSwitchingToNextInputMethod = sa.getBoolean( +- com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod, +- false); +- inlineSuggestionsEnabled = sa.getBoolean( +- com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false); +- supportsInlineSuggestionsWithTouchExploration = sa.getBoolean( +- com.android.internal.R.styleable +- .InputMethod_supportsInlineSuggestionsWithTouchExploration, false); +- suppressesSpellChecker = sa.getBoolean( +- com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false); +- showInInputMethodPicker = sa.getBoolean( +- com.android.internal.R.styleable.InputMethod_showInInputMethodPicker, true); +- mHandledConfigChanges = sa.getInt( +- com.android.internal.R.styleable.InputMethod_configChanges, 0); +- mSupportsStylusHandwriting = sa.getBoolean( +- com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false); +- mSupportsConnectionlessStylusHandwriting = sa.getBoolean( +- com.android.internal.R.styleable +- .InputMethod_supportsConnectionlessStylusHandwriting, false); +- stylusHandwritingSettingsActivity = sa.getString( +- com.android.internal.R.styleable.InputMethod_stylusHandwritingSettingsActivity); +- sa.recycle(); +- + final int depth = parser.getDepth(); + // Parse all subtypes + while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { +- if (type == XmlPullParser.START_TAG) { +- nodeName = parser.getName(); +- if (!"subtype".equals(nodeName)) { +- throw new XmlPullParserException( +- "Meta-data in input-method does not start with subtype tag"); +- } +- final TypedArray a = res.obtainAttributes( +- attrs, com.android.internal.R.styleable.InputMethod_Subtype); ++ if (type != XmlPullParser.START_TAG) { ++ continue; ++ } ++ nodeName = parser.getName(); ++ if (!"subtype".equals(nodeName)) { ++ throw new XmlPullParserException( ++ "Meta-data in input-method does not start with subtype tag"); ++ } ++ ++ final InputMethodSubtype subtype; ++ try (TypedArrayWrapper a = TypedArrayWrapper.createForSubtype( ++ res.obtainAttributes(attrs, ++ com.android.internal.R.styleable.InputMethod_Subtype), ++ readTracker)) { + String pkLanguageTag = a.getString(com.android.internal.R.styleable + .InputMethod_Subtype_physicalKeyboardHintLanguageTag); + String pkLayoutType = a.getString(com.android.internal.R.styleable + .InputMethod_Subtype_physicalKeyboardHintLayoutType); +- final InputMethodSubtype subtype = new InputMethodSubtypeBuilder() ++ subtype = new InputMethodSubtypeBuilder() + .setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable + .InputMethod_Subtype_label, 0)) + .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable +@@ -402,12 +406,11 @@ public final class InputMethodInfo implements Parcelable { + .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)) + .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable + .InputMethod_Subtype_isAsciiCapable, false)).build(); +- a.recycle(); +- if (!subtype.isAuxiliary()) { +- isAuxIme = false; +- } +- subtypes.add(subtype); + } ++ if (!subtype.isAuxiliary()) { ++ isAuxIme = false; ++ } ++ subtypes.add(subtype); + } + } catch (NameNotFoundException | IndexOutOfBoundsException | NumberFormatException e) { + throw new XmlPullParserException( +@@ -471,6 +474,11 @@ public final class InputMethodInfo implements Parcelable { + return; + } + ++ if (si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) { ++ throw new XmlPullParserException( ++ "Input method name exceeds " + COMPONENT_NAME_MAX_LENGTH + " characters"); ++ } ++ + // Validate file size using InputStream.skip() + long totalBytesSkipped = 0; + // Loop to ensure we skip the required number of bytes, as a single +@@ -1132,4 +1140,162 @@ public final class InputMethodInfo implements Parcelable { + public int describeContents() { + return 0; + } ++ ++ /** ++ * A wrapper class for {@link TypedArray} that enforces limits on the size of the metadata ++ * read from the XML. Methods throw an {@link XmlPullParserException} if the limit is surpassed. ++ * ++ *

This class works in conjunction with {@link MetadataReadBytesTracker} to: ++ *

    ++ *
  • Limit the length of individual string attributes. For ++ * {@code settingsActivity} and {@code languageSettingsActivity}, the maximum length is ++ * {@link #COMPONENT_NAME_MAX_LENGTH}. For other string attributes, the maximum length is ++ * {@link #STRING_ATTRIBUTES_MAX_CHAR_LENGTH}.
  • ++ *
  • Track the total amount of data read from the metadata XML. The ++ * {@link MetadataReadBytesTracker} ensures that the cumulative size of all attributes ++ * does not exceed {@link #MAX_METADATA_SIZE_BYTES}. ++ *
++ * ++ * @hide ++ */ ++ @VisibleForTesting ++ public static final class TypedArrayWrapper implements AutoCloseable { ++ /** The underlying {@link TypedArray} to read from. */ ++ @NonNull ++ private final TypedArray mTypedArray; ++ /** Tracker for enforcing metadata size limits. */ ++ @NonNull ++ private final MetadataReadBytesTracker mReadTracker; ++ /** {@code true} if parsing a {@code } tag, {@code false} otherwise. */ ++ private final boolean mIsReadingSubtype; ++ ++ /** ++ * Creates a {@link TypedArrayWrapper} for parsing attributes of the main ++ * {@code } tag. ++ * ++ * @param wrapped The {@link TypedArray} obtained for the {@code } tag. ++ * @param readTracker The tracker for monitoring data size. ++ * @return A new {@link TypedArrayWrapper} instance. ++ */ ++ @NonNull ++ @VisibleForTesting ++ public static TypedArrayWrapper createForMethod( ++ @NonNull TypedArray wrapped, @NonNull MetadataReadBytesTracker readTracker) { ++ return new TypedArrayWrapper(wrapped, readTracker, false); ++ } ++ ++ /** ++ * Creates a {@link TypedArrayWrapper} for parsing attributes of a {@code } tag. ++ * ++ * @param wrapped The {@link TypedArray} obtained for the {@code } tag. ++ * @param readTracker The tracker for monitoring data size. ++ * @return A new {@link TypedArrayWrapper} instance. ++ */ ++ @NonNull ++ @VisibleForTesting ++ public static TypedArrayWrapper createForSubtype( ++ @NonNull TypedArray wrapped, @NonNull MetadataReadBytesTracker readTracker) { ++ return new TypedArrayWrapper(wrapped, readTracker, true); ++ } ++ ++ /** ++ * Constructs a new wrapper. ++ */ ++ private TypedArrayWrapper(@NonNull TypedArray wrapped, ++ @NonNull MetadataReadBytesTracker readTracker, boolean isReadingSubtype) { ++ mTypedArray = wrapped; ++ mReadTracker = readTracker; ++ mIsReadingSubtype = isReadingSubtype; ++ } ++ ++ /** Retrieves an integer value for the attribute at {@code index}. */ ++ @VisibleForTesting ++ public int getInt(int index, int defaultValue) throws XmlPullParserException { ++ if (!mTypedArray.hasValue(index)) { ++ return defaultValue; ++ } ++ final int ret = mTypedArray.getInt(index, defaultValue); ++ mReadTracker.onReadBytes(Integer.BYTES); ++ return ret; ++ } ++ ++ /** Retrieves the string value for the attribute at {@code index}. */ ++ @VisibleForTesting ++ public String getString(int index) throws XmlPullParserException { ++ final String ret = mTypedArray.getString(index); ++ final int maxLen = getMaxLength(index); ++ if (ret != null && ret.length() > maxLen) { ++ throw new XmlPullParserException( ++ "String resources in input method exceed the length limit of " ++ + maxLen + " characters"); ++ } ++ mReadTracker.onReadBytes(ret == null ? 0 : ret.length() * Character.BYTES); ++ return ret; ++ } ++ ++ /** Retrieves a boolean value for the attribute at {@code index}. */ ++ @VisibleForTesting ++ public boolean getBoolean(int index, boolean defaultValue) throws XmlPullParserException { ++ if (!mTypedArray.hasValue(index)) { ++ return defaultValue; ++ } ++ final boolean ret = mTypedArray.getBoolean(index, defaultValue); ++ mReadTracker.onReadBytes(1); ++ return ret; ++ } ++ ++ /** Retrieves a resource identifier for the attribute at {@code index}. */ ++ @VisibleForTesting ++ public int getResourceId(int index, int defaultValue) throws XmlPullParserException { ++ if (!mTypedArray.hasValue(index)) { ++ return defaultValue; ++ } ++ final int ret = mTypedArray.getResourceId(index, defaultValue); ++ mReadTracker.onReadBytes(Integer.BYTES); ++ return ret; ++ } ++ ++ @Override ++ public void close() { ++ mTypedArray.recycle(); ++ } ++ ++ private int getMaxLength(int index) { ++ // Note that the Android resource has limit DEFAULT_MAX_STRING_ATTR_LENGTH = 32_768. ++ if (mIsReadingSubtype) { ++ // No limits for strings in subtype for now. ++ return Integer.MAX_VALUE; ++ } else { ++ return switch (index) { ++ // TODO(b/456008595): Consider to add ++ // InputMethod_stylusHandwritingSettingsActivity ++ case com.android.internal.R.styleable.InputMethod_settingsActivity, ++ com.android.internal.R.styleable.InputMethod_languageSettingsActivity -> ++ COMPONENT_NAME_MAX_LENGTH; ++ default -> ++ // TODO(b/456008595): Consider to introduce limits. ++ Integer.MAX_VALUE; ++ }; ++ } ++ } ++ } ++ ++ /** @hide */ ++ @VisibleForTesting ++ public static final class MetadataReadBytesTracker { ++ private int mRemainingBytes = MAX_METADATA_SIZE_BYTES; ++ ++ @VisibleForTesting ++ public MetadataReadBytesTracker() { ++ } ++ ++ private void onReadBytes(int bytes) throws XmlPullParserException { ++ mRemainingBytes -= bytes; ++ if (mRemainingBytes < 0) { ++ throw new XmlPullParserException( ++ "The input method service has metadata exceeds the " ++ + MAX_METADATA_SIZE_BYTES + " byte limit"); ++ } ++ } ++ } + } +diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java +index dfe7d0306905..a7b2bba045dc 100644 +--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java ++++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java +@@ -16,18 +16,27 @@ + + package android.view.inputmethod; + ++import static android.view.inputmethod.InputMethodInfo.COMPONENT_NAME_MAX_LENGTH; ++ + import static org.hamcrest.CoreMatchers.equalTo; + import static org.hamcrest.CoreMatchers.is; + import static org.junit.Assert.assertThat; ++import static org.junit.Assert.assertThrows; ++import static org.mockito.Mockito.mock; ++import static org.mockito.Mockito.verify; ++import static org.mockito.Mockito.when; + + import android.annotation.XmlRes; + import android.content.Context; + import android.content.pm.ResolveInfo; + import android.content.pm.ServiceInfo; ++import android.content.res.TypedArray; + import android.os.Bundle; + import android.os.Parcel; + import android.platform.test.flag.junit.DeviceFlagsValueProvider; + import android.platform.test.flag.junit.SetFlagsRule; ++import android.view.inputmethod.InputMethodInfo.MetadataReadBytesTracker; ++import android.view.inputmethod.InputMethodInfo.TypedArrayWrapper; + + import androidx.test.ext.junit.runners.AndroidJUnit4; + import androidx.test.filters.SmallTest; +@@ -38,6 +47,7 @@ import com.android.frameworks.inputmethodcoretests.R; + import org.junit.Rule; + import org.junit.Test; + import org.junit.runner.RunWith; ++import org.xmlpull.v1.XmlPullParserException; + + @SmallTest + @RunWith(AndroidJUnit4.class) +@@ -133,6 +143,94 @@ public class InputMethodInfoTest { + assertThat(clone.isVirtualDeviceOnly(), is(true)); + } + ++ @Test ++ public void testTypedArrayWrapper() throws Exception { ++ final TypedArray mockTypedArray = mock(TypedArray.class); ++ when(mockTypedArray.hasValue(0)).thenReturn(true); ++ when(mockTypedArray.getInt(0, 0)).thenReturn(123); ++ when(mockTypedArray.getString(1)).thenReturn("hello"); ++ when(mockTypedArray.hasValue(2)).thenReturn(true); ++ when(mockTypedArray.getBoolean(2, false)).thenReturn(true); ++ when(mockTypedArray.hasValue(3)).thenReturn(true); ++ when(mockTypedArray.getResourceId(3, 0)).thenReturn(456); ++ ++ try (TypedArrayWrapper wrapper = TypedArrayWrapper.createForMethod(mockTypedArray, ++ new MetadataReadBytesTracker())) { ++ assertThat(wrapper.getInt(0, 0), is(123)); ++ assertThat(wrapper.getString(1), is("hello")); ++ assertThat(wrapper.getBoolean(2, false), is(true)); ++ assertThat(wrapper.getResourceId(3, 0), is(456)); ++ } ++ } ++ ++ @Test ++ public void testTypedArrayWrapper_getString_throwsExceptionWhenStringTooLong() ++ throws Exception { ++ final TypedArray mockTypedArray = mock(TypedArray.class); ++ final String longStringA = "a".repeat(COMPONENT_NAME_MAX_LENGTH + 1); ++ final String longStringB = "b".repeat(COMPONENT_NAME_MAX_LENGTH + 1); ++ when(mockTypedArray.getString( ++ com.android.internal.R.styleable.InputMethod_settingsActivity)) ++ .thenReturn(longStringA); ++ when(mockTypedArray.getString( ++ com.android.internal.R.styleable.InputMethod_languageSettingsActivity)) ++ .thenReturn(longStringB); ++ ++ try (TypedArrayWrapper wrapper = TypedArrayWrapper.createForMethod(mockTypedArray, ++ new MetadataReadBytesTracker())) { ++ assertThrows( ++ XmlPullParserException.class, ++ () -> wrapper.getString( ++ com.android.internal.R.styleable.InputMethod_settingsActivity)); ++ assertThrows( ++ XmlPullParserException.class, ++ () -> wrapper.getString( ++ com.android.internal.R.styleable.InputMethod_languageSettingsActivity)); ++ } ++ ++ // The same index can be used for method and subtype for different attributes. ++ // This verifies the same index returns the correct string for subtypes. ++ try (TypedArrayWrapper wrapper = TypedArrayWrapper.createForSubtype(mockTypedArray, ++ new MetadataReadBytesTracker())) { ++ assertThat(wrapper.getString( ++ com.android.internal.R.styleable.InputMethod_settingsActivity), ++ is(longStringA)); ++ assertThat(wrapper.getString( ++ com.android.internal.R.styleable.InputMethod_languageSettingsActivity), ++ is(longStringB)); ++ } ++ } ++ ++ @Test ++ public void testTypedArrayWrapper_closeRecyclesTypedArray() { ++ final TypedArray mockTypedArray = mock(TypedArray.class); ++ final TypedArrayWrapper wrapper = TypedArrayWrapper.createForMethod(mockTypedArray, ++ new MetadataReadBytesTracker()); ++ ++ wrapper.close(); ++ ++ verify(mockTypedArray).recycle(); ++ } ++ ++ @Test ++ public void testTypedArrayWrapper_metadataReadBytesTracker_throwsExceptionWhenLimitExceeded() { ++ final TypedArray mockTypedArray = mock(TypedArray.class); ++ final String longString = "a".repeat(1000); ++ when(mockTypedArray.getString(0)).thenReturn(longString); ++ ++ try (TypedArrayWrapper wrapper = TypedArrayWrapper.createForMethod(mockTypedArray, ++ new MetadataReadBytesTracker())) { ++ assertThrows(XmlPullParserException.class, () -> { ++ // Each character is 2 bytes. 1000 chars * 2 = 2000 bytes per call. ++ // Limit is 200 * 1024 = 204800 bytes. ++ // 204800 / 2000 = 102.4. So 103 calls will exceed the limit. ++ for (int i = 0; i < 103; ++i) { ++ wrapper.getString(0); ++ } ++ }); ++ } ++ } ++ + private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes) + throws Exception { + final Context context = InstrumentationRegistry.getInstrumentation().getContext(); +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch new file mode 100644 index 0000000..532e701 --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-37_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch @@ -0,0 +1,313 @@ +From a4523e227733ae20eafe4ec3e85474a5b7ebf7c6 Mon Sep 17 00:00:00 2001 +From: Nate Myren +Date: Wed, 15 Oct 2025 15:48:49 -0700 +Subject: [PATCH] Prohibit untrusted proxys from specifying proxied attribution + tags + +Only trusted proxies should be allowed to specify tags. Also prevents +startOperationDryRun from editing the know attribution tags, as the dry +run should change no state. + +Bug: 445917646 +Test: atest AttributionTest +Flag: EXEMPT CVE_FIX +(cherry picked from commit 110db0acb84cbd21f9da9391ab242d141ebf390c) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:94d32dce4639ba16b321667719678d2a6b42d9c3 +Merged-In: I14ab1389384fd28009edd9cceceaacdb97fb96e5 +Change-Id: I14ab1389384fd28009edd9cceceaacdb97fb96e5 +--- + .../android/server/appop/AppOpsService.java | 112 ++++++++++++++---- + .../android/server/appop/AttributedOp.java | 14 +++ + 2 files changed, 105 insertions(+), 21 deletions(-) + +diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java +index 9c9d338099dd..85a5ac8bf3ca 100644 +--- a/services/core/java/com/android/server/appop/AppOpsService.java ++++ b/services/core/java/com/android/server/appop/AppOpsService.java +@@ -39,6 +39,7 @@ import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED; + import static android.app.AppOpsManager.OP_FLAGS_ALL; + import static android.app.AppOpsManager.OP_FLAG_SELF; + import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; ++import static android.app.AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED; + import static android.app.AppOpsManager.OP_NONE; + import static android.app.AppOpsManager.OP_PLAY_AUDIO; + import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; +@@ -3189,7 +3190,7 @@ public class AppOpsService extends IAppOpsService.Stub { + public int checkPackage(int uid, String packageName) { + Objects.requireNonNull(packageName); + try { +- verifyAndGetBypass(uid, packageName, null, Process.INVALID_UID, null, true); ++ verifyAndGetBypass(uid, packageName, null, Process.INVALID_UID, null, true, true); + // When the caller is the system, it's possible that the packageName is the special + // one (e.g., "root") which isn't actually existed. + if (resolveNonAppUid(packageName) == uid +@@ -3407,8 +3408,10 @@ public class AppOpsService extends IAppOpsService.Stub { + @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, int notedCount) { + PackageVerificationResult pvr; ++ boolean proxyTrusted = (flags & OP_FLAG_UNTRUSTED_PROXIED) == 0; + try { +- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); ++ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName, ++ proxyTrusted); + if (!pvr.isAttributionTagValid) { + attributionTag = null; + } +@@ -4072,8 +4075,10 @@ public class AppOpsService extends IAppOpsService.Stub { + boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId) { + PackageVerificationResult pvr; ++ boolean proxyTrusted = (flags & OP_FLAG_UNTRUSTED_PROXIED) == 0; + try { +- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); ++ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName, ++ proxyTrusted); + if (!pvr.isAttributionTagValid) { + attributionTag = null; + } +@@ -4206,8 +4211,10 @@ public class AppOpsService extends IAppOpsService.Stub { + int proxyUid, String proxyPackageName, @OpFlags int flags, + boolean startIfModeDefault) { + PackageVerificationResult pvr; ++ boolean proxyTrusted = (flags & OP_FLAG_UNTRUSTED_PROXIED) == 0; + try { +- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); ++ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName, ++ proxyTrusted); + if (!pvr.isAttributionTagValid) { + attributionTag = null; + } +@@ -4223,7 +4230,9 @@ public class AppOpsService extends IAppOpsService.Stub { + + boolean isRestricted = false; + synchronized (this) { +- final Ops ops = getOpsLocked(uid, packageName, attributionTag, ++ // Edit is true (so we create the Ops object if needed), but attribution tag is given as ++ // null, so we don't cache any information about it. ++ final Ops ops = getOpsLocked(uid, packageName, null, + pvr.isAttributionTagValid, pvr.bypass, /* edit */ true); + if (ops == null) { + if (DEBUG) { +@@ -4402,8 +4411,11 @@ public class AppOpsService extends IAppOpsService.Stub { + int virtualDeviceId) { + PackageVerificationResult pvr; + try { ++ // assume the proxy is trusted, since we aren't sure. We'll search for the attribution ++ // tag with trusted flags, and if we don't find it, search for a null tag with ++ // untrusted flags + pvr = verifyAndGetBypass(proxiedUid, proxiedPackageName, attributionTag, +- proxyUid, proxyPackageName); ++ proxyUid, proxyPackageName, /* isProxyTrusted */ true); + if (!pvr.isAttributionTagValid) { + attributionTag = null; + } +@@ -4413,8 +4425,9 @@ public class AppOpsService extends IAppOpsService.Stub { + } + + synchronized (this) { ++ boolean hasProxy = proxyUid != Process.INVALID_UID; + Op op = getOpLocked(code, proxiedUid, proxiedPackageName, attributionTag, +- pvr.isAttributionTagValid, pvr.bypass, /* edit */ true); ++ pvr.isAttributionTagValid, pvr.bypass, /* edit */ false); + if (op == null) { + Slog.e(TAG, "Operation not found: uid=" + proxiedUid + " pkg=" + proxiedPackageName + + "(" +@@ -4422,9 +4435,8 @@ public class AppOpsService extends IAppOpsService.Stub { + return; + } + final AttributedOp attributedOp = +- op.mDeviceAttributedOps.getOrDefault( +- getPersistentDeviceIdForOp(virtualDeviceId, code), +- new ArrayMap<>()).get(attributionTag); ++ getAttributedOpWithClientId(op, clientId, attributionTag, virtualDeviceId, ++ hasProxy); + if (attributedOp == null) { + Slog.e(TAG, "Attribution not found: uid=" + proxiedUid + + " pkg=" + proxiedPackageName + "(" +@@ -4442,6 +4454,54 @@ public class AppOpsService extends IAppOpsService.Stub { + } + } + ++ private AttributedOp getAttributedOpWithClientId(Op op, IBinder clientId, ++ String attributionTag, int virtualDeviceId, boolean hasProxy) { ++ AttributedOp attributedOp = ++ op.mDeviceAttributedOps.getOrDefault( ++ getPersistentDeviceIdForOp(virtualDeviceId, op.op), ++ new ArrayMap<>()).get(attributionTag); ++ if (!hasProxy) { ++ return attributedOp; ++ } ++ boolean hasTrustedInProgressEvent = attributedOp != null ++ && attributedOp.hasInProgressEvent((event -> event.getClientId() == clientId ++ && (event.getFlags() & OP_FLAG_UNTRUSTED_PROXIED) == 0)); ++ if (hasTrustedInProgressEvent) { ++ return attributedOp; ++ } ++ ++ // We failed to find a trusted in progress event that matches the clientId. Check if the ++ // tag is valid in the package, and look for an untrusted access matching that tag, if so ++ boolean tagValid = false; ++ try { ++ tagValid = verifyAndGetBypass(op.uid, op.packageName, attributionTag) ++ .isAttributionTagValid; ++ } catch (SecurityException e) { ++ // assume tag is invalid ++ } ++ if (tagValid) { ++ boolean hasUntrustedInProgressEvent = attributedOp != null ++ && attributedOp.hasInProgressEvent((event -> event.getClientId() == clientId ++ && (event.getFlags() & OP_FLAG_UNTRUSTED_PROXIED) != 0)); ++ if (hasUntrustedInProgressEvent) { ++ return attributedOp; ++ } ++ } ++ ++ // The tag was not valid, or we failed to find an untrusted event. Look for an untrusted ++ // event with the null attribution tag ++ attributedOp = op.mDeviceAttributedOps.getOrDefault( ++ getPersistentDeviceIdForOp(virtualDeviceId, op.op), ++ new ArrayMap<>()).get(null); ++ boolean hasUntrustedNullEvent = attributedOp != null ++ && attributedOp.hasInProgressEvent((event -> event.getClientId() == clientId ++ && (event.getFlags() & OP_FLAG_UNTRUSTED_PROXIED) != 0)); ++ if (hasUntrustedNullEvent) { ++ return attributedOp; ++ } ++ return null; ++ } ++ + void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull + String packageName, @Nullable String attributionTag, int virtualDeviceId, + boolean active, @AttributionFlags int attributionFlags, int attributionChainId) { +@@ -4881,20 +4941,22 @@ public class AppOpsService extends IAppOpsService.Stub { + } + + /** +- * @see #verifyAndGetBypass(int, String, String, int, String, boolean) ++ * @see #verifyAndGetBypass(int, String, String, int, String, boolean, boolean) + */ + private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, + @Nullable String attributionTag) { +- return verifyAndGetBypass(uid, packageName, attributionTag, Process.INVALID_UID, null); ++ return verifyAndGetBypass(uid, packageName, attributionTag, Process.INVALID_UID, null, ++ true); + } + + /** +- * @see #verifyAndGetBypass(int, String, String, int, String, boolean) ++ * @see #verifyAndGetBypass(int, String, String, int, String, boolean, boolean) + */ + private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, +- @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName) { ++ @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName, ++ boolean isProxyTrusted) { + return verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName, +- false); ++ isProxyTrusted, false); + } + + /** +@@ -4907,6 +4969,8 @@ public class AppOpsService extends IAppOpsService.Stub { + * @param attributionTag attribution tag or {@code null} if no need to verify + * @param proxyUid The proxy uid, from which the attribution tag is to be pulled + * @param proxyPackageName The proxy package, from which the attribution tag may be pulled ++ * @param isProxyTrusted Whether or not the proxy package is trusted. If it isn't, then the ++ * proxy attribution tag will not be used + * @param suppressErrorLogs Whether to print to logcat about nonmatching parameters + * + * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the +@@ -4914,7 +4978,7 @@ public class AppOpsService extends IAppOpsService.Stub { + */ + private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, + @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName, +- boolean suppressErrorLogs) { ++ boolean isProxyTrusted, boolean suppressErrorLogs) { + if (uid == Process.ROOT_UID) { + // For backwards compatibility, don't check package name for root UID, unless someone + // is claiming to be a proxy for root, which should never happen in normal usage. +@@ -4922,7 +4986,8 @@ public class AppOpsService extends IAppOpsService.Stub { + // system app (or is null), in order to prevent abusive apps clogging the appops + // system with unlimited attribution tags via proxy calls. + return new PackageVerificationResult(null, +- /* isAttributionTagValid */ isPackageNullOrSystem(proxyPackageName, proxyUid)); ++ /* isAttributionTagValid */ isProxyTrusted ++ && isPackageNullOrSystem(proxyPackageName, proxyUid)); + } + if (Process.isSdkSandboxUid(uid)) { + // SDK sandbox processes run in their own UID range, but their associated +@@ -4986,7 +5051,8 @@ public class AppOpsService extends IAppOpsService.Stub { + // system app (or is null), in order to prevent abusive apps clogging the appops + // system with unlimited attribution tags via proxy calls. + return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED, +- /* isAttributionTagValid */ isPackageNullOrSystem(proxyPackageName, proxyUid)); ++ /* isAttributionTagValid */ isProxyTrusted ++ && isPackageNullOrSystem(proxyPackageName, proxyUid)); + } + + int userId = UserHandle.getUserId(uid); +@@ -5007,8 +5073,9 @@ public class AppOpsService extends IAppOpsService.Stub { + if (!isAttributionTagValid) { + AndroidPackage proxyPkg = proxyPackageName != null + ? pmInt.getPackage(proxyPackageName) : null; +- // Re-check in proxy. +- isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag); ++ // Re-check in proxy, if trusted. ++ isAttributionTagValid = ++ isProxyTrusted && isAttributionInPackage(proxyPkg, attributionTag); + String msg; + if (pkg != null && isAttributionTagValid) { + msg = "attributionTag " + attributionTag + " declared in manifest of the proxy" +@@ -5035,7 +5102,6 @@ public class AppOpsService extends IAppOpsService.Stub { + throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid + + otherUidMessage); + } +- + return new PackageVerificationResult(bypass, isAttributionTagValid); + } + +@@ -5050,6 +5116,10 @@ public class AppOpsService extends IAppOpsService.Stub { + if (appId > 0 && appId < Process.FIRST_APPLICATION_UID) { + return true; + } ++ if (mPackageManagerInternal.getPackageUid(packageName, PackageManager.MATCH_ALL, ++ UserHandle.getUserId(uid)) != uid) { ++ return false; ++ } + return mPackageManagerInternal.isSystemPackage(packageName); + } + +diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java +index b9bc27d9c696..73f9fede9ed6 100644 +--- a/services/core/java/com/android/server/appop/AttributedOp.java ++++ b/services/core/java/com/android/server/appop/AttributedOp.java +@@ -40,6 +40,7 @@ import java.util.ArrayList; + import java.util.List; + import java.util.NoSuchElementException; + import java.util.function.Consumer; ++import java.util.function.Predicate; + + final class AttributedOp { + private final @NonNull AppOpsService mAppOpsService; +@@ -620,6 +621,19 @@ final class AttributedOp { + return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty(); + } + ++ public boolean hasInProgressEvent(Predicate predicate) { ++ ArrayMap events = ++ isPaused() ? mPausedInProgressEvents : mInProgressEvents; ++ if (events == null || events.isEmpty()) { ++ return false; ++ } ++ for (int i = 0; i < events.size(); i++) { ++ if (predicate.test(events.valueAt(i))) { ++ return true; ++ } ++ } ++ return false; ++ } + boolean hasAnyTime() { + return (mAccessEvents != null && mAccessEvents.size() > 0) + || (mRejectEvents != null && mRejectEvents.size() > 0); +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37_Trim_permission_permission_group_names_e770e9f02341.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37_Trim_permission_permission_group_names_e770e9f02341.patch new file mode 100644 index 0000000..7863fbe --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-37_Trim_permission_permission_group_names_e770e9f02341.patch @@ -0,0 +1,51 @@ +From e770e9f0234158f4631c7147b64a1d70e0843d0b Mon Sep 17 00:00:00 2001 +From: Nate Myren +Date: Wed, 5 Nov 2025 14:36:49 -0800 +Subject: [PATCH] Trim permission, permission group names + +Bug: 453649815 +Test: atest AppSecurityTests +Flag: EXEMPT CVE_FIX +(cherry picked from commit 595cf99ecd42927eebf804638a4623313f3f14db) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:08ea2a452c271ccf258d63efc0126c7fa13d3312 +Merged-In: I673ad83d05c9825177967e4f0a960e8841610b71 +Change-Id: I673ad83d05c9825177967e4f0a960e8841610b71 +--- + .../internal/pm/pkg/component/ParsedPermissionUtils.java | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java +index d4dabf51d4c7..0dd0969ae35e 100644 +--- a/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java ++++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java +@@ -152,6 +152,8 @@ public class ParsedPermissionUtils { + } + } + ++ permission.setName(permission.getName().trim()); ++ + permission.setProtectionLevel( + PermissionInfo.fixProtectionLevel(permission.getProtectionLevel())); + +@@ -199,6 +201,8 @@ public class ParsedPermissionUtils { + sa.recycle(); + } + ++ permission.setName(permission.getName().trim()); ++ + int index = permission.getName().indexOf('.'); + if (index > 0) { + index = permission.getName().indexOf('.', index + 1); +@@ -248,7 +252,8 @@ public class ParsedPermissionUtils { + .setBackgroundRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0)) + .setRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0)) + .setPriority(sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0)) +- .setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0)); ++ .setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0)) ++ .setName(permissionGroup.getName().trim()); + // @formatter:on + } finally { + sa.recycle(); +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch new file mode 100644 index 0000000..c132917 --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-38_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch @@ -0,0 +1,35 @@ +From 09055276288a68cf35b0f84ba32e28822f74ecf9 Mon Sep 17 00:00:00 2001 +From: Sanjana Sunil +Date: Wed, 26 Nov 2025 14:40:09 +0000 +Subject: [PATCH] Explicitly unset INSTALL_FROM_MANAGED_USER_OR_PROFILE flag + +If the flag is not explicitly unset, an app could set this flag, leading +to unexpected behaviour if the install is not actually from a managed +user or profile. + +Bug: 459461121 +Test: atest PackageManagerShellCommandInstallTest#testSessionCreationWithManagedUserOrProfileFlag_notFromManagedProfile +Flag: EXEMPT BUGFIX +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:990428772d4718853382ec4c5feda2b7bd6f923f +Merged-In: I21bbbf628e97244d469eb23ce0558dbf560b7618 +Change-Id: I21bbbf628e97244d469eb23ce0558dbf560b7618 +--- + .../java/com/android/server/pm/PackageInstallerService.java | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java +index 2d0bb258e89f..a0ad5d1aaa30 100644 +--- a/services/core/java/com/android/server/pm/PackageInstallerService.java ++++ b/services/core/java/com/android/server/pm/PackageInstallerService.java +@@ -1027,6 +1027,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements + } + + final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); ++ // Only the system should be able to set this flag - so ensure it is unset when not needed. ++ params.installFlags &= ~PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE; + if (dpmi != null && dpmi.isUserOrganizationManaged(userId)) { + params.installFlags |= PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE; + } +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch new file mode 100644 index 0000000..601ae55 --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-38_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch @@ -0,0 +1,41 @@ +From cee45869c491d4e39877918ee881eb60dec7d6e5 Mon Sep 17 00:00:00 2001 +From: Iustin Ventaniuc +Date: Thu, 13 Nov 2025 09:48:16 +0000 +Subject: [PATCH] Handle loadDescription OutOfMemoryError in DeviceAdminInfo + +loadDescription was potentially vulnerable to an attack which causes a +DoS exploit by injecting a maliciously large string into the Receiver's +label. + +Bug: 443062265 +Test: manual +Flag: EXEMPT BUGFIX +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:06a5b2327caa3aa8843496458e98b9bb070df6e5 +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:660101e3cdd2f8e8bc627517691dfb885a5c8302 +Merged-In: Icab26c4b77e73f0fcb9a560e3211482ebe2f37bf +Change-Id: Icab26c4b77e73f0fcb9a560e3211482ebe2f37bf +--- + core/java/android/app/admin/DeviceAdminInfo.java | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java +index 7b46db16f80a..7616d805c31c 100644 +--- a/core/java/android/app/admin/DeviceAdminInfo.java ++++ b/core/java/android/app/admin/DeviceAdminInfo.java +@@ -473,8 +473,12 @@ public final class DeviceAdminInfo implements Parcelable { + */ + public CharSequence loadDescription(PackageManager pm) throws NotFoundException { + if (mActivityInfo.descriptionRes != 0) { +- return pm.getText(mActivityInfo.packageName, ++ try { ++ return pm.getText(mActivityInfo.packageName, + mActivityInfo.descriptionRes, mActivityInfo.applicationInfo); ++ } catch (OutOfMemoryError e) { ++ throw new NotFoundException(); ++ } + } + throw new NotFoundException(); + } +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch new file mode 100644 index 0000000..e51fde4 --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-38_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch @@ -0,0 +1,838 @@ +From a438ce172b441c8297eadde8d990ab292f5aa7d1 Mon Sep 17 00:00:00 2001 +From: Hiroki Sato +Date: Tue, 4 Nov 2025 17:29:42 +0900 +Subject: [PATCH] Introduce InputMethodSubtypeSafeList + +IMM#getEnabledInputMethodSubtypeList() can return a large list of +subtypes, which may cause a TransactionTooLargeException. + +This patch introduces InputMethodSubtypeSafeList to wrap the list as a +byte array, avoiding the exception. This mirrors the existing +InputMethodInfoSafeList pattern introduced in [1]. + +Additionally, this change extracts the common marshalling logic from +InputMethodInfoSafeList into a new AbstractSafeList and refactors both +SafeList classes to extend it. + +[1] I0a7667070fcdf17d34b248a5988c38064588718a + +Bug: 449416164 +Bug: 449181366 +Bug: 449393786 +Bug: 449227003 +Test: CtsInputMethodTestCases:{InputMethodRegistrationTest,InputMethodInfoTest} +Test: InputMethodCoreTests +Flag: EXEMPT BUGFIX +(cherry picked from commit 1d68a1099be2b99e8410dad01822851287994682) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:e99cb1a4f240988380e43592d845c64f78e1a6d7 +Merged-In: Ied64a9f018fd3e79cfc51ccd82d361b43e5f29dc +Change-Id: Ied64a9f018fd3e79cfc51ccd82d361b43e5f29dc +--- + .../IInputMethodManagerGlobalInvoker.java | 6 +- + .../inputmethod/AbstractSafeList.java | 127 +++++++++++++++++ + .../inputmethod/InputMethodInfoSafeList.java | 105 +++----------- + .../InputMethodSubtypeSafeList.aidl | 19 +++ + .../InputMethodSubtypeSafeList.java | 87 ++++++++++++ + .../internal/view/IInputMethodManager.aidl | 3 +- + .../inputmethod/AbstractSafeListTest.java | 98 ++++++++++++++ + .../InputMethodSubtypeSafeListTest.java | 128 ++++++++++++++++++ + .../inputmethod/IInputMethodManagerImpl.java | 7 +- + .../InputMethodManagerService.java | 13 +- + .../server/inputmethod/ZeroJankProxy.java | 4 +- + 11 files changed, 497 insertions(+), 100 deletions(-) + create mode 100644 core/java/com/android/internal/inputmethod/AbstractSafeList.java + create mode 100644 core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl + create mode 100644 core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java + create mode 100644 core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java + create mode 100644 core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java + +diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +index 290885593ee6..6fbb76577fb8 100644 +--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java ++++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +@@ -43,6 +43,7 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; + import com.android.internal.inputmethod.IRemoteInputConnection; + import com.android.internal.inputmethod.InputBindResult; + import com.android.internal.inputmethod.InputMethodInfoSafeList; ++import com.android.internal.inputmethod.InputMethodSubtypeSafeList; + import com.android.internal.inputmethod.SoftInputShowHideReason; + import com.android.internal.inputmethod.StartInputFlags; + import com.android.internal.inputmethod.StartInputReason; +@@ -297,8 +298,9 @@ final class IInputMethodManagerGlobalInvoker { + return new ArrayList<>(); + } + try { +- return service.getEnabledInputMethodSubtypeList(imiId, +- allowsImplicitlyEnabledSubtypes, userId); ++ return InputMethodSubtypeSafeList.extractFrom( ++ service.getEnabledInputMethodSubtypeList(imiId, ++ allowsImplicitlyEnabledSubtypes, userId)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } +diff --git a/core/java/com/android/internal/inputmethod/AbstractSafeList.java b/core/java/com/android/internal/inputmethod/AbstractSafeList.java +new file mode 100644 +index 000000000000..697b153afecf +--- /dev/null ++++ b/core/java/com/android/internal/inputmethod/AbstractSafeList.java +@@ -0,0 +1,127 @@ ++/* ++ * Copyright 2025 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.internal.inputmethod; ++ ++import android.annotation.NonNull; ++import android.annotation.Nullable; ++import android.os.Parcel; ++import android.os.Parcelable; ++ ++import com.android.internal.annotations.VisibleForTesting; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++/** ++ * An abstract base class for creating a {@link Parcelable} container that can hold an arbitrary ++ * number of {@link Parcelable} objects without worrying about ++ * {@link android.os.TransactionTooLargeException}. ++ * ++ * @see Parcel#readBlob() ++ * @see Parcel#writeBlob(byte[]) ++ * ++ * @param The type of the {@link Parcelable} objects. ++ */ ++public abstract class AbstractSafeList implements Parcelable { ++ @Nullable ++ private byte[] mBuffer; ++ ++ protected AbstractSafeList(@Nullable List list) { ++ if (list != null && !list.isEmpty()) { ++ mBuffer = marshall(list); ++ } ++ } ++ ++ protected AbstractSafeList(@Nullable byte[] buffer) { ++ mBuffer = buffer; ++ } ++ ++ /** ++ * Extracts the list of {@link Parcelable} objects from a {@link AbstractSafeList}, and ++ * clears the internal buffer of the list. ++ * ++ * @param from The {@link AbstractSafeList} to extract from. ++ * @param creator The {@link Parcelable.Creator} for the {@link Parcelable} objects. ++ * @param The type of the {@link Parcelable} objects. ++ * @return The list of {@link Parcelable} objects. ++ */ ++ @NonNull ++ protected static List extractFrom( ++ @Nullable AbstractSafeList from, @NonNull Parcelable.Creator creator) { ++ if (from == null) { ++ return new ArrayList<>(); ++ } ++ final byte[] buf = from.mBuffer; ++ from.mBuffer = null; ++ if (buf != null) { ++ final List list = unmarshall(buf, creator); ++ if (list != null) { ++ return list; ++ } ++ } ++ return new ArrayList<>(); ++ } ++ ++ @Override ++ public int describeContents() { ++ // As long as the parcelled classes return 0, we can also return 0 here. ++ return 0; ++ } ++ ++ @Override ++ public void writeToParcel(@NonNull Parcel dest, int flags) { ++ dest.writeBlob(mBuffer); ++ } ++ ++ /** ++ * Marshalls a list of {@link Parcelable} objects into a byte array. ++ */ ++ @Nullable ++ @VisibleForTesting ++ public static byte[] marshall(@NonNull List list) { ++ Parcel parcel = null; ++ try { ++ parcel = Parcel.obtain(); ++ parcel.writeTypedList(list); ++ return parcel.marshall(); ++ } finally { ++ if (parcel != null) { ++ parcel.recycle(); ++ } ++ } ++ } ++ ++ /** ++ * Unmarshalls a byte array into a list of {@link Parcelable} objects. ++ */ ++ @Nullable ++ @VisibleForTesting ++ public static List unmarshall( ++ @NonNull byte[] data, @NonNull Parcelable.Creator creator) { ++ Parcel parcel = null; ++ try { ++ parcel = Parcel.obtain(); ++ parcel.unmarshall(data, 0, data.length); ++ parcel.setDataPosition(0); ++ return parcel.createTypedArrayList(creator); ++ } finally { ++ if (parcel != null) { ++ parcel.recycle(); ++ } ++ } ++ } ++} +diff --git a/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java b/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java +index 9e720fb6ccee..a2ea5b08f13f 100644 +--- a/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java ++++ b/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java +@@ -19,24 +19,24 @@ package com.android.internal.inputmethod; + import android.annotation.NonNull; + import android.annotation.Nullable; + import android.os.Parcel; +-import android.os.Parcelable; + import android.view.inputmethod.InputMethodInfo; + +-import java.util.ArrayList; +-import java.util.Arrays; + import java.util.List; + + /** +- * A {@link Parcelable} container that can holds an arbitrary number of {@link InputMethodInfo} +- * without worrying about {@link android.os.TransactionTooLargeException} when passing across +- * process boundary. +- * +- * @see Parcel#readBlob() +- * @see Parcel#writeBlob(byte[]) ++ * A {@link android.os.Parcelable} container that can hold an arbitrary number of ++ * {@link InputMethodInfo} without worrying about ++ * {@link android.os.TransactionTooLargeException} when passing across process boundary. + */ +-public final class InputMethodInfoSafeList implements Parcelable { +- @Nullable +- private byte[] mBuffer; ++public final class InputMethodInfoSafeList extends AbstractSafeList { ++ ++ private InputMethodInfoSafeList(@Nullable byte[] buffer) { ++ super(buffer); ++ } ++ ++ private InputMethodInfoSafeList(@Nullable List list) { ++ super(list); ++ } + + /** + * Instantiates a list of {@link InputMethodInfo} from the given {@link InputMethodInfoSafeList} +@@ -53,81 +53,20 @@ public final class InputMethodInfoSafeList implements Parcelable { + */ + @NonNull + public static List extractFrom(@Nullable InputMethodInfoSafeList from) { +- final byte[] buf = from.mBuffer; +- from.mBuffer = null; +- if (buf != null) { +- final InputMethodInfo[] array = unmarshall(buf); +- if (array != null) { +- return new ArrayList<>(Arrays.asList(array)); +- } +- } +- return new ArrayList<>(); +- } +- +- @NonNull +- private static InputMethodInfo[] toArray(@Nullable List original) { +- if (original == null) { +- return new InputMethodInfo[0]; +- } +- return original.toArray(new InputMethodInfo[0]); +- } +- +- @Nullable +- private static byte[] marshall(@NonNull InputMethodInfo[] array) { +- Parcel parcel = null; +- try { +- parcel = Parcel.obtain(); +- parcel.writeTypedArray(array, 0); +- return parcel.marshall(); +- } finally { +- if (parcel != null) { +- parcel.recycle(); +- } +- } +- } +- +- @Nullable +- private static InputMethodInfo[] unmarshall(byte[] data) { +- Parcel parcel = null; +- try { +- parcel = Parcel.obtain(); +- parcel.unmarshall(data, 0, data.length); +- parcel.setDataPosition(0); +- return parcel.createTypedArray(InputMethodInfo.CREATOR); +- } finally { +- if (parcel != null) { +- parcel.recycle(); +- } +- } +- } +- +- private InputMethodInfoSafeList(@Nullable byte[] blob) { +- mBuffer = blob; ++ return AbstractSafeList.extractFrom(from, InputMethodInfo.CREATOR); + } + + /** + * Instantiates {@link InputMethodInfoSafeList} from the given list of {@link InputMethodInfo}. + * + * @param list list of {@link InputMethodInfo} from which {@link InputMethodInfoSafeList} will +- * be created ++ * be created. Giving {@code null} will result in an empty ++ * {@link InputMethodInfoSafeList}. + * @return {@link InputMethodInfoSafeList} that stores the given list of {@link InputMethodInfo} + */ + @NonNull + public static InputMethodInfoSafeList create(@Nullable List list) { +- if (list == null || list.isEmpty()) { +- return empty(); +- } +- return new InputMethodInfoSafeList(marshall(toArray(list))); +- } +- +- /** +- * Creates an empty {@link InputMethodInfoSafeList}. +- * +- * @return {@link InputMethodInfoSafeList} that is empty +- */ +- @NonNull +- public static InputMethodInfoSafeList empty() { +- return new InputMethodInfoSafeList(null); ++ return new InputMethodInfoSafeList(list); + } + + public static final Creator CREATOR = new Creator<>() { +@@ -141,16 +80,4 @@ public final class InputMethodInfoSafeList implements Parcelable { + return new InputMethodInfoSafeList[size]; + } + }; +- +- @Override +- public int describeContents() { +- // As long as InputMethodInfo#describeContents() is guaranteed to return 0, we can always +- // return 0 here. +- return 0; +- } +- +- @Override +- public void writeToParcel(Parcel dest, int flags) { +- dest.writeBlob(mBuffer); +- } + } +diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl +new file mode 100644 +index 000000000000..11000632eba5 +--- /dev/null ++++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl +@@ -0,0 +1,19 @@ ++/* ++ * Copyright 2025 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.internal.inputmethod; ++ ++parcelable InputMethodSubtypeSafeList; +diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java +new file mode 100644 +index 000000000000..cd95088f5cf0 +--- /dev/null ++++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java +@@ -0,0 +1,87 @@ ++/* ++ * Copyright 2025 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.internal.inputmethod; ++ ++import android.annotation.NonNull; ++import android.annotation.Nullable; ++import android.os.Parcel; ++import android.view.inputmethod.InputMethodSubtype; ++ ++import java.util.List; ++ ++/** ++ * A {@link android.os.Parcelable} container that can hold an arbitrary number of ++ * {@link InputMethodSubtype} without worrying about ++ * {@link android.os.TransactionTooLargeException} when passing across process boundary. ++ */ ++public final class InputMethodSubtypeSafeList extends AbstractSafeList { ++ ++ private InputMethodSubtypeSafeList(@Nullable byte[] buffer) { ++ super(buffer); ++ } ++ ++ private InputMethodSubtypeSafeList(@Nullable List list) { ++ super(list); ++ } ++ ++ /** ++ * Instantiates a list of {@link InputMethodSubtype} from the given ++ * {@link InputMethodSubtypeSafeList} then clears the internal buffer of ++ * {@link InputMethodSubtypeSafeList}. ++ * ++ *

Note that each {@link InputMethodSubtype} item is guaranteed to be a copy of the original ++ * {@link InputMethodSubtype} object.

++ * ++ *

Any subsequent call will return an empty list.

++ * ++ * @param from {@link InputMethodSubtypeSafeList} from which the list of ++ * {@link InputMethodSubtype} will be extracted ++ * @return list of {@link InputMethodSubtype} stored in the given ++ * {@link InputMethodSubtypeSafeList} ++ */ ++ @NonNull ++ public static List extractFrom(@Nullable InputMethodSubtypeSafeList from) { ++ return AbstractSafeList.extractFrom(from, InputMethodSubtype.CREATOR); ++ } ++ ++ /** ++ * Instantiates {@link InputMethodSubtypeSafeList} from the given list of ++ * {@link InputMethodSubtype}. ++ * ++ * @param list list of {@link InputMethodSubtype} from which ++ * {@link InputMethodSubtypeSafeList} will be created. Giving {@code null} will ++ * result in an empty {@link InputMethodSubtypeSafeList}. ++ * @return {@link InputMethodSubtypeSafeList} that stores the given list of ++ * {@link InputMethodSubtype} ++ */ ++ @NonNull ++ public static InputMethodSubtypeSafeList create(@Nullable List list) { ++ return new InputMethodSubtypeSafeList(list); ++ } ++ ++ public static final Creator CREATOR = new Creator<>() { ++ @Override ++ public InputMethodSubtypeSafeList createFromParcel(Parcel in) { ++ return new InputMethodSubtypeSafeList(in.readBlob()); ++ } ++ ++ @Override ++ public InputMethodSubtypeSafeList[] newArray(int size) { ++ return new InputMethodSubtypeSafeList[size]; ++ } ++ }; ++} +diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl +index 0791612fa0e8..d9b52404724e 100644 +--- a/core/java/com/android/internal/view/IInputMethodManager.aidl ++++ b/core/java/com/android/internal/view/IInputMethodManager.aidl +@@ -32,6 +32,7 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; + import com.android.internal.inputmethod.IRemoteInputConnection; + import com.android.internal.inputmethod.InputBindResult; + import com.android.internal.inputmethod.InputMethodInfoSafeList; ++import com.android.internal.inputmethod.InputMethodSubtypeSafeList; + + /** + * Public interface to the global input method manager, used by all client applications. +@@ -67,7 +68,7 @@ interface IInputMethodManager { + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)") +- List getEnabledInputMethodSubtypeList(in @nullable String imiId, ++ InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(in @nullable String imiId, + boolean allowsImplicitlyEnabledSubtypes, int userId); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " +diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java +new file mode 100644 +index 000000000000..0f72f095dbe3 +--- /dev/null ++++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java +@@ -0,0 +1,98 @@ ++/* ++ * Copyright 2025 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.internal.inputmethod; ++ ++import static org.junit.Assert.assertEquals; ++import static org.junit.Assert.assertNotNull; ++ ++import android.os.Parcel; ++import android.os.Parcelable; ++import android.platform.test.annotations.Presubmit; ++ ++import androidx.test.ext.junit.runners.AndroidJUnit4; ++import androidx.test.filters.SmallTest; ++ ++import org.junit.Test; ++import org.junit.runner.RunWith; ++ ++import java.util.List; ++ ++@SmallTest ++@Presubmit ++@RunWith(AndroidJUnit4.class) ++public class AbstractSafeListTest { ++ ++ private static class TestParcelable implements Parcelable { ++ final int mData; ++ ++ TestParcelable(int data) { ++ mData = data; ++ } ++ ++ TestParcelable(Parcel parcel) { ++ mData = parcel.readInt(); ++ } ++ ++ @Override ++ public void writeToParcel(Parcel parcel, int flags) { ++ parcel.writeInt(mData); ++ } ++ ++ @Override ++ public int describeContents() { ++ return 0; ++ } ++ ++ @SuppressWarnings("EffectivelyPrivate") // Parcelable must have CREATOR. ++ public static final Creator CREATOR = new Creator() { ++ @Override ++ public TestParcelable createFromParcel(Parcel parcel) { ++ return new TestParcelable(parcel); ++ } ++ ++ @Override ++ public TestParcelable[] newArray(int size) { ++ return new TestParcelable[size]; ++ } ++ }; ++ } ++ ++ @Test ++ public void testMarshallThenUnmarshall() { ++ List originalArray = List.of(new TestParcelable(1), new TestParcelable(2)); ++ byte[] marshalled = AbstractSafeList.marshall(originalArray); ++ assertNotNull(marshalled); ++ List unmarshalled = ++ AbstractSafeList.unmarshall(marshalled, TestParcelable.CREATOR); ++ assertNotNull(unmarshalled); ++ assertEquals(originalArray.size(), unmarshalled.size()); ++ for (int i = 0; i < originalArray.size(); i++) { ++ assertEquals(originalArray.get(i).mData, unmarshalled.get(i).mData); ++ } ++ } ++ ++ @Test ++ public void testMarshallEmptyArray() { ++ List originalArray = List.of(); ++ byte[] marshalled = AbstractSafeList.marshall(originalArray); ++ assertNotNull(marshalled); ++ List unmarshalled = ++ AbstractSafeList.unmarshall(marshalled, TestParcelable.CREATOR); ++ assertNotNull(unmarshalled); ++ assertEquals(0, unmarshalled.size()); ++ } ++} +diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java +new file mode 100644 +index 000000000000..089ffb80d7a9 +--- /dev/null ++++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java +@@ -0,0 +1,128 @@ ++/* ++ * Copyright 2025 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.internal.inputmethod; ++ ++import static org.junit.Assert.assertEquals; ++import static org.junit.Assert.assertNotNull; ++import static org.junit.Assert.assertNotSame; ++import static org.junit.Assert.assertTrue; ++ ++import android.os.Parcel; ++import android.platform.test.annotations.Presubmit; ++import android.view.inputmethod.InputMethodSubtype; ++ ++import androidx.test.ext.junit.runners.AndroidJUnit4; ++import androidx.test.filters.SmallTest; ++ ++import org.junit.Test; ++import org.junit.runner.RunWith; ++ ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.function.Function; ++ ++@SmallTest ++@Presubmit ++@RunWith(AndroidJUnit4.class) ++public class InputMethodSubtypeSafeListTest { ++ ++ private static InputMethodSubtype createFakeInputMethodSubtype(String locale, String mode) { ++ return new InputMethodSubtype.InputMethodSubtypeBuilder() ++ .setSubtypeLocale(locale) ++ .setSubtypeMode(mode) ++ .build(); ++ } ++ ++ private static List createTestInputMethodSubtypeList() { ++ List list = new ArrayList<>(); ++ list.add(createFakeInputMethodSubtype("en_US", "keyboard")); ++ list.add(createFakeInputMethodSubtype("ja_JP", "keyboard")); ++ list.add(createFakeInputMethodSubtype("en_GB", "voice")); ++ return list; ++ } ++ ++ private static void assertItemsAfterExtract( ++ List originals, ++ Function, InputMethodSubtypeSafeList> factory) { ++ InputMethodSubtypeSafeList list = factory.apply(originals); ++ List extracted = InputMethodSubtypeSafeList.extractFrom(list); ++ assertEquals(originals.size(), extracted.size()); ++ for (int i = 0; i < originals.size(); i++) { ++ assertNotSame( ++ "InputMethodSubtypeSafeList.extractFrom() must clone each instance", ++ originals.get(i), extracted.get(i)); ++ assertEquals( ++ "Verify the cloned instances have the equal locale", ++ originals.get(i).getLocale(), extracted.get(i).getLocale()); ++ assertEquals( ++ "Verify the cloned instances have the equal mode", ++ originals.get(i).getMode(), extracted.get(i).getMode()); ++ } ++ ++ // Subsequent calls of InputMethodSubtypeSafeList.extractFrom() return an empty list. ++ List extracted2 = InputMethodSubtypeSafeList.extractFrom(list); ++ assertTrue(extracted2.isEmpty()); ++ } ++ ++ private static InputMethodSubtypeSafeList cloneViaParcel(InputMethodSubtypeSafeList original) { ++ Parcel parcel = null; ++ try { ++ parcel = Parcel.obtain(); ++ original.writeToParcel(parcel, 0); ++ parcel.setDataPosition(0); ++ InputMethodSubtypeSafeList newInstance = ++ InputMethodSubtypeSafeList.CREATOR.createFromParcel(parcel); ++ assertNotNull(newInstance); ++ return newInstance; ++ } finally { ++ if (parcel != null) { ++ parcel.recycle(); ++ } ++ } ++ } ++ ++ @Test ++ public void testCreate() { ++ assertNotNull(InputMethodSubtypeSafeList.create(createTestInputMethodSubtypeList())); ++ } ++ ++ @Test ++ public void testExtract() { ++ assertItemsAfterExtract( ++ createTestInputMethodSubtypeList(), ++ InputMethodSubtypeSafeList::create); ++ } ++ ++ @Test ++ public void testExtractAfterParceling() { ++ assertItemsAfterExtract( ++ createTestInputMethodSubtypeList(), ++ originals -> cloneViaParcel(InputMethodSubtypeSafeList.create(originals))); ++ } ++ ++ @Test ++ public void testExtractEmptyList() { ++ assertItemsAfterExtract(Collections.emptyList(), InputMethodSubtypeSafeList::create); ++ } ++ ++ @Test ++ public void testExtractAfterParcelingEmptyList() { ++ assertItemsAfterExtract(Collections.emptyList(), ++ originals -> cloneViaParcel(InputMethodSubtypeSafeList.create(originals))); ++ } ++} +diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java +index 15f186b047f2..7845a73111d6 100644 +--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java ++++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java +@@ -48,6 +48,7 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; + import com.android.internal.inputmethod.IRemoteInputConnection; + import com.android.internal.inputmethod.InputBindResult; + import com.android.internal.inputmethod.InputMethodInfoSafeList; ++import com.android.internal.inputmethod.InputMethodSubtypeSafeList; + import com.android.internal.inputmethod.SoftInputShowHideReason; + import com.android.internal.inputmethod.StartInputFlags; + import com.android.internal.inputmethod.StartInputReason; +@@ -108,7 +109,8 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub { + @NonNull + List getEnabledInputMethodListLegacy(@UserIdInt int userId); + +- List getEnabledInputMethodSubtypeList(String imiId, ++ @NonNull ++ InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(String imiId, + boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId); + + InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId); +@@ -278,8 +280,9 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub { + return mCallback.getEnabledInputMethodListLegacy(userId); + } + ++ @NonNull + @Override +- public List getEnabledInputMethodSubtypeList(String imiId, ++ public InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(String imiId, + boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { + return mCallback.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes, + userId); +diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +index 5e3224d1012e..7df43d114906 100644 +--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java ++++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +@@ -172,6 +172,7 @@ import com.android.internal.inputmethod.InputMethodDebug; + import com.android.internal.inputmethod.InputMethodInfoSafeList; + import com.android.internal.inputmethod.InputMethodNavButtonFlags; + import com.android.internal.inputmethod.InputMethodSubtypeHandle; ++import com.android.internal.inputmethod.InputMethodSubtypeSafeList; + import com.android.internal.inputmethod.SoftInputShowHideReason; + import com.android.internal.inputmethod.StartInputFlags; + import com.android.internal.inputmethod.StartInputReason; +@@ -1590,7 +1591,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + } + if (!mUserManagerInternal.exists(userId)) { +- return InputMethodInfoSafeList.empty(); ++ return InputMethodInfoSafeList.create(null); + } + final int callingUid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); +@@ -1611,7 +1612,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + } + if (!mUserManagerInternal.exists(userId)) { +- return InputMethodInfoSafeList.empty(); ++ return InputMethodInfoSafeList.create(null); + } + final int callingUid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); +@@ -1738,8 +1739,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + * subtypes + * @param userId the user ID to be queried about + */ ++ @NonNull + @Override +- public List getEnabledInputMethodSubtypeList(String imiId, ++ public InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(String imiId, + boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingOrSelfPermission( +@@ -1749,8 +1751,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + final int callingUid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { +- return getEnabledInputMethodSubtypeListInternal(imiId, +- allowsImplicitlyEnabledSubtypes, userId, callingUid); ++ return InputMethodSubtypeSafeList.create( ++ getEnabledInputMethodSubtypeListInternal(imiId, ++ allowsImplicitlyEnabledSubtypes, userId, callingUid)); + } finally { + Binder.restoreCallingIdentity(ident); + } +diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java +index 12c1d9cbb2a1..6a83475fb774 100644 +--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java ++++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java +@@ -65,6 +65,7 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; + import com.android.internal.inputmethod.IRemoteInputConnection; + import com.android.internal.inputmethod.InputBindResult; + import com.android.internal.inputmethod.InputMethodInfoSafeList; ++import com.android.internal.inputmethod.InputMethodSubtypeSafeList; + import com.android.internal.inputmethod.SoftInputShowHideReason; + import com.android.internal.inputmethod.StartInputFlags; + import com.android.internal.inputmethod.StartInputReason; +@@ -160,8 +161,9 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback { + return mInner.getEnabledInputMethodListLegacy(userId); + } + ++ @NonNull + @Override +- public List getEnabledInputMethodSubtypeList(String imiId, ++ public InputMethodSubtypeSafeList getEnabledInputMethodSubtypeList(String imiId, + boolean allowsImplicitlyEnabledSubtypes, int userId) { + return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes, + userId); +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch new file mode 100644 index 0000000..adc26f5 --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-38_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch @@ -0,0 +1,149 @@ +From c148b4fae6347652231d1c4a633f5cc9a8f057f8 Mon Sep 17 00:00:00 2001 +From: Annie Lin +Date: Wed, 19 Nov 2025 20:16:52 +0000 +Subject: [PATCH] Prevent launchedFromPackage spoofing via + FLAG_ACTIVITY_FORWARD_RESULT. + +Bug: 457742426 +Test: atest ActivityStarterTests +Test: Verified via test app +Flag: EXEMPT CVE_FIX +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:3bb240273822e41f3c6911c60d15983a600308f7 +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:79fe04dbdf31ab80311069a4d0a7b518d47c31ac +Merged-In: Ic9637c56803b00acc9fca59f8092ed02dd46a4fb +Change-Id: Ic9637c56803b00acc9fca59f8092ed02dd46a4fb +--- + .../android/server/wm/ActivityStarter.java | 20 +++-- + .../server/wm/ActivityStarterTests.java | 82 +++++++++++++++++++ + 2 files changed, 97 insertions(+), 5 deletions(-) + +diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java +index 92f51bed419f..222698ce24db 100644 +--- a/services/core/java/com/android/server/wm/ActivityStarter.java ++++ b/services/core/java/com/android/server/wm/ActivityStarter.java +@@ -1142,14 +1142,24 @@ class ActivityStarter { + // in the flow, and asking to forward its result back to the previous. In this + // case the activity is serving as a trampoline between the two, so we also want + // to update its launchedFromPackage to be the same as the previous activity. +- // Note that this is safe, since we know these two packages come from the same +- // uid; the caller could just as well have supplied that same package name itself +- // . This specifially deals with the case of an intent picker/chooser being ++ // This specifically deals with the case of an intent picker/chooser being + // launched in the app flow to redirect to an activity picked by the user, where + // we want the final activity to consider it to have been launched by the + // previous app activity. +- callingPackage = sourceRecord.launchedFromPackage; +- callingFeatureId = sourceRecord.launchedFromFeatureId; ++ final String launchedFromPackage = sourceRecord.launchedFromPackage; ++ if (launchedFromPackage != null) { ++ final PackageManagerInternal pmInternal = ++ mService.getPackageManagerInternalLocked(); ++ final int packageUid = pmInternal.getPackageUid( ++ launchedFromPackage, 0 /* flags */, ++ UserHandle.getUserId(callingUid)); ++ // Only override callingPackage and callingFeatureId based on package UID check. ++ // This is to prevent spoofing. See b/457742426. ++ if (UserHandle.isSameApp(packageUid, callingUid)) { ++ callingPackage = launchedFromPackage; ++ callingFeatureId = sourceRecord.launchedFromFeatureId; ++ } ++ } + } + } + +diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +index 08b0077c49b3..2948b3da7db8 100644 +--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java ++++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +@@ -1882,6 +1882,88 @@ public class ActivityStarterTests extends WindowTestsBase { + assertEquals(bubbledActivity.getTask(), targetRecord.getTask()); + } + ++ /** ++ * This test simulates the following scenario: ++ * 1. Privileged app (P) starts malicious app's activity (M1). ++ * 2. M1 starts M2 (also in malicious app) using startNextMatchingActivity(). ++ * This causes M2's launchedFromPackage to be P. ++ * 3. M2 starts an activity in P (P2) using startActivity() with ++ * FLAG_ACTIVITY_FORWARD_RESULT. ++ * The test verifies that P2's launchedFromPackage is M, not P. ++ * See b/457742426 for details. ++ */ ++ @Test ++ public void testLaunchedFromPackage_nextMatchingActivity_forwardResult() { ++ final String privilegedPackage = "com.test.privileged"; ++ final int privilegedUid = 10001; ++ final String maliciousPackage = "com.test.malicious"; ++ final int maliciousUid = 10002; ++ ++ // Setup P1 activity ++ final ActivityRecord p1 = new ActivityBuilder(mAtm) ++ .setComponent(new ComponentName(privilegedPackage, "P1Activity")) ++ .setUid(privilegedUid) ++ .setCreateTask(true) ++ .build(); ++ ++ // Setup M1 activity, launched by P1 ++ final ActivityRecord m1 = new ActivityBuilder(mAtm) ++ .setComponent(new ComponentName(maliciousPackage, "M1Activity")) ++ .setUid(maliciousUid) ++ .setCreateTask(true) ++ .setLaunchedFromPackage(privilegedPackage) ++ .setLaunchedFromUid(privilegedUid) ++ .build(); ++ m1.resultTo = p1; ++ ++ // Setup M2 activity, as if launched from M1 via startNextMatchingActivity() ++ final ActivityRecord m2 = new ActivityBuilder(mAtm) ++ .setComponent(new ComponentName(maliciousPackage, "M2Activity")) ++ .setUid(maliciousUid) ++ .setCreateTask(true) ++ .setLaunchedFromPackage(privilegedPackage) // Spoofed package name ++ .setLaunchedFromUid(maliciousUid) ++ .build(); ++ m2.resultTo = p1; // result is forwarded ++ ++ // M2 starts P2 ++ final ActivityStarter starter = prepareStarter(0); ++ doReturn(privilegedUid).when(mMockPackageManager).getPackageUid( ++ eq(privilegedPackage), anyLong(), anyInt()); ++ doReturn(maliciousUid).when(mMockPackageManager).getPackageUid( ++ eq(maliciousPackage), anyLong(), anyInt()); ++ starter.setCallingPackage(maliciousPackage); ++ starter.setCallingUid(maliciousUid); ++ ++ final Intent p2Intent = new Intent(); ++ p2Intent.setComponent(new ComponentName(privilegedPackage, "P2Activity")); ++ p2Intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); ++ ++ final ActivityInfo p2ActivityInfo = new ActivityInfo(); ++ p2ActivityInfo.applicationInfo = new ApplicationInfo(); ++ p2ActivityInfo.applicationInfo.packageName = privilegedPackage; ++ p2ActivityInfo.applicationInfo.uid = privilegedUid; ++ p2ActivityInfo.name = "P2Activity"; ++ ++ final ActivityRecord[] outActivity = new ActivityRecord[1]; ++ ++ // The request simulates M2 starting P2 ++ starter.setIntent(p2Intent) ++ .setActivityInfo(p2ActivityInfo) ++ .setResultTo(m2.token) // sourceRecord is m2 ++ .setRequestCode(-1) // for startActivity() ++ .setOutActivity(outActivity) ++ .execute(); ++ ++ final ActivityRecord p2 = outActivity[0]; ++ ++ assertNotNull(p2); ++ assertEquals("launchedFromPackage should be the immediate caller", ++ maliciousPackage, p2.launchedFromPackage); ++ assertEquals("launchedFromUid should be the immediate caller", ++ maliciousUid, p2.launchedFromUid); ++ } ++ + private ActivityRecord createBubbledActivity() { + final ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setTaskAlwaysOnTop(true); +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-39_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch b/asb/2026-03/frameworks/base/2026-01-07_21-39_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch new file mode 100644 index 0000000..59f23d5 --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-39_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch @@ -0,0 +1,238 @@ +From 014dea279c49d532bc4fbbdebbc024133967b6a8 Mon Sep 17 00:00:00 2001 +From: Julia Reynolds +Date: Wed, 12 Nov 2025 12:09:31 -0500 +Subject: [PATCH] Be more strict about content types for message array + +Now with fewer class cast exceptions + +Test: ConversationNotification +Test: NotificationManagerServiceTest +Bug: 433746973 +Flag: EXEMPT BUGFIX +(cherry picked from commit 71d4afae00c7d6d9238f8ec82303e1e13da50fbb) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:70fe31848f073029cdb38cd6c5fe47f200dd4c78 +Merged-In: I3022e010de95f14dcd0d09d123684ee265101e0a +Change-Id: I3022e010de95f14dcd0d09d123684ee265101e0a +--- + core/java/android/app/Notification.java | 18 ++-- + .../NotificationManagerServiceTest.java | 101 +++++++++++++++++- + 2 files changed, 107 insertions(+), 12 deletions(-) + +diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java +index 2ed95ab7ad82..b921280da563 100644 +--- a/core/java/android/app/Notification.java ++++ b/core/java/android/app/Notification.java +@@ -3152,8 +3152,8 @@ public class Notification implements Parcelable + person.visitUris(visitor); + } + +- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, +- Parcelable.class); ++ final Bundle[] messages = ++ getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class); + if (!ArrayUtils.isEmpty(messages)) { + for (MessagingStyle.Message message : MessagingStyle.Message + .getMessagesFromBundleArray(messages)) { +@@ -3161,8 +3161,8 @@ public class Notification implements Parcelable + } + } + +- final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, +- Parcelable.class); ++ final Parcelable[] historic = ++ getParcelableArrayFromBundle(extras, EXTRA_HISTORIC_MESSAGES, Bundle.class); + if (!ArrayUtils.isEmpty(historic)) { + for (MessagingStyle.Message message : MessagingStyle.Message + .getMessagesFromBundleArray(historic)) { +@@ -8203,8 +8203,8 @@ public class Notification implements Parcelable + */ + public boolean hasImage() { + if (isStyle(MessagingStyle.class) && extras != null) { +- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, +- Parcelable.class); ++ final Bundle[] messages = ++ getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class); + if (!ArrayUtils.isEmpty(messages)) { + for (MessagingStyle.Message m : MessagingStyle.Message + .getMessagesFromBundleArray(messages)) { +@@ -9485,10 +9485,10 @@ public class Notification implements Parcelable + mUser = user; + } + mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); +- Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class); ++ Bundle[] messages = getParcelableArrayFromBundle(extras, EXTRA_MESSAGES, Bundle.class); + mMessages = Message.getMessagesFromBundleArray(messages); +- Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, +- Parcelable.class); ++ Bundle[] histMessages = getParcelableArrayFromBundle( ++ extras, EXTRA_HISTORIC_MESSAGES, Bundle.class); + mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); + mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); + mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); +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 41cd746e7a04..883c6cc64fbe 100644 +--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java ++++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +@@ -27,6 +27,8 @@ import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS; + import static android.app.Flags.FLAG_NM_SUMMARIZATION; + import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME; + import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP; ++import static android.app.Notification.EXTRA_MESSAGES; ++import static android.app.Notification.EXTRA_MESSAGING_PERSON; + import static android.app.Notification.EXTRA_PICTURE; + import static android.app.Notification.EXTRA_PICTURE_ICON; + import static android.app.Notification.EXTRA_TEXT; +@@ -81,6 +83,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BA + import static android.app.PendingIntent.FLAG_IMMUTABLE; + import static android.app.PendingIntent.FLAG_MUTABLE; + import static android.app.PendingIntent.FLAG_ONE_SHOT; ++import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; + import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; + import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; + import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG; +@@ -235,11 +238,13 @@ import android.companion.ICompanionDeviceManager; + import android.compat.testing.PlatformCompatChangeRule; + import android.content.BroadcastReceiver; + import android.content.ComponentName; ++import android.content.ContentProvider; + import android.content.ContentUris; + import android.content.Context; + import android.content.IIntentSender; + import android.content.Intent; + import android.content.IntentFilter; ++import android.content.UriPermission; + import android.content.pm.ActivityInfo; + import android.content.pm.ApplicationInfo; + import android.content.pm.IPackageManager; +@@ -255,6 +260,7 @@ import android.content.pm.VersionedPackage; + import android.content.res.Resources; + import android.graphics.Bitmap; + import android.graphics.Color; ++import android.graphics.Rect; + import android.graphics.drawable.Icon; + import android.media.AudioAttributes; + import android.media.AudioManager; +@@ -1503,6 +1509,95 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + captor.getValue().getIntent().getPackage()); + } + ++ public void testNoUriGrantsForBadMessagesList() throws RemoteException { ++ Uri targetUri = Uri.parse("content://com.android.contacts/display_photo/1"); ++ ++ // create message person ++ Person person = new Person.Builder() ++ .setName("Name") ++ .setIcon(Icon.createWithContentUri(targetUri)) ++ .setKey("user_123") ++ .setBot(false) ++ .build(); ++ ++ // create MessagingStyle ++ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(person) ++ .setConversationTitle("Bug discussion") ++ .setGroupConversation(true) ++ .addMessage("Hi,look my photo", System.currentTimeMillis() - 60000, person) ++ .addMessage("Oho, you used my contacts photo", ++ System.currentTimeMillis() - 30000, "Friend"); ++ ++ // create Notification ++ Notification notification = new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setSmallIcon(R.drawable.sym_def_app_icon) ++ .setContentTitle("") ++ .setContentText("") ++ .setAutoCancel(true) ++ .setStyle(messagingStyle) ++ .setCategory(Notification.CATEGORY_MESSAGE) ++ .setFlag(Notification.FLAG_GROUP_SUMMARY, true) ++ .build(); ++ notification.contentIntent = createPendingIntent("open"); ++ ++ notification.extras.remove(EXTRA_MESSAGING_PERSON); ++ ++ // add BadClipDescription to avoid visitUri check uris in EXTRA_MESSAGES value ++ ArrayList parcelableArray = ++ new ArrayList<>(List.of(notification.extras.getParcelableArray(EXTRA_MESSAGES))); ++ parcelableArray.add(new MyParceledListSlice()); ++ notification.extras.putParcelableArray( ++ EXTRA_MESSAGES, parcelableArray.toArray(new Parcelable[0])); ++ try { ++ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, ++ "testNoUriGrantsForBadMessagesList", ++ 1, notification, mContext.getUserId()); ++ waitForIdle(); ++ fail("should have failed to parse messages"); ++ } catch (java.lang.ArrayStoreException e) { ++ verify(mUgmInternal, never()).checkGrantUriPermission( ++ anyInt(), any(), eq(ContentProvider.getUriWithoutUserId(targetUri)), ++ anyInt(), anyInt()); ++ } ++ } ++ ++ private class MyParceledListSlice extends Intent { ++ @Override ++ public void writeToParcel(Parcel dest, int i) { ++ Parcel test = Parcel.obtain(); ++ test.writeString(this.getClass().getName()); ++ int strLength = test.dataSize(); ++ test.recycle(); ++ dest.setDataPosition(dest.dataPosition() - strLength); ++ dest.writeString("android.content.pm.ParceledListSlice"); ++ ++ dest.writeInt(1); ++ dest.writeString(UriPermission.class.getName()); ++ dest.writeInt(0); // use binder ++ dest.writeStrongBinder(new Binder() { ++ private int callingPid = -1; ++ @Override ++ public boolean onTransact(int code, Parcel data, Parcel reply, int flags) ++ throws RemoteException { ++ if (code == 1) { ++ reply.writeNoException(); ++ reply.writeInt(1); ++ if (getCallingUid() == 1000 && callingPid == -1) { ++ reply.writeParcelable(new Rect(), 0); ++ callingPid = getCallingPid(); ++ } else { ++ reply.writeInt(-1); ++ reply.writeInt(-1); ++ reply.writeLong(0); ++ } ++ return true; ++ } ++ return super.onTransact(code, data, reply, flags); ++ } ++ }); ++ } ++ } ++ + @Test + public void testDefaultAssistant_overrideDefault() { + final int userId = mContext.getUserId(); +@@ -8086,7 +8181,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + Bundle extras = new Bundle(); + extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); + extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString()); +- extras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person1); ++ extras.putParcelable(EXTRA_MESSAGING_PERSON, person1); + extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, + new ArrayList<>(Arrays.asList(person2, person3))); + extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, +@@ -8224,13 +8319,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + Bundle messagingExtras = new Bundle(); +- messagingExtras.putParcelable(Notification.EXTRA_MESSAGING_PERSON, ++ messagingExtras.putParcelable(EXTRA_MESSAGING_PERSON, + personWithIcon("content://user")); + messagingExtras.putParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES, + new Bundle[] { new Notification.MessagingStyle.Message("Heyhey!", + System.currentTimeMillis() - 100, + personWithIcon("content://historicalMessenger")).toBundle()}); +- messagingExtras.putParcelableArray(Notification.EXTRA_MESSAGES, ++ messagingExtras.putParcelableArray(EXTRA_MESSAGES, + new Bundle[] { new Notification.MessagingStyle.Message("Are you there?", + System.currentTimeMillis(), + personWithIcon("content://messenger")).toBundle()}); +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-39_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch b/asb/2026-03/frameworks/base/2026-01-07_21-39_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch new file mode 100644 index 0000000..429acb4 --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-39_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch @@ -0,0 +1,335 @@ +From e363f82104566378b4b9936d6caf27c3ee631d80 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= +Date: Wed, 20 Aug 2025 15:57:28 +0200 +Subject: [PATCH] Limit the number of services (NLSes, etc) that can be + approved per user + +Trying to activate additional packages/components will be silently rejected. + +Bug: 428701593 +Test: atest ManagedServicesTest NotificationManagerServiceTest +Flag: com.android.server.notification.limit_managed_services_count +(cherry picked from commit a132684a093d9e1750100b39d4e4168f2d27d349) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:3b41dfeec7ebfcd313ff26b7f27b7e7971af4497 +Merged-In: Iddd8044997c41f97369b768f4da5e49efc43ad06 +Change-Id: Iddd8044997c41f97369b768f4da5e49efc43ad06 +--- + .../server/notification/ManagedServices.java | 59 ++++++++++++----- + .../NotificationManagerService.java | 34 +++++++--- + .../notification/ManagedServicesTest.java | 63 +++++++++++++++++++ + .../NotificationManagerServiceTest.java | 27 ++++++++ + 4 files changed, 160 insertions(+), 23 deletions(-) + +diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java +index 62e26e189a35..159d33999739 100644 +--- a/services/core/java/com/android/server/notification/ManagedServices.java ++++ b/services/core/java/com/android/server/notification/ManagedServices.java +@@ -133,6 +133,13 @@ abstract public class ManagedServices { + static final int APPROVAL_BY_PACKAGE = 0; + static final int APPROVAL_BY_COMPONENT = 1; + ++ /** ++ * Maximum number of entries allowed in the lists of packages/components contained in ++ * {@link #mApproved} or {@link #mUserSetServices}. For the first, this effectively limits ++ * the number of services (e.g. NLSes) that will be bound per user. ++ */ ++ private static final int MAX_SERVICE_ENTRIES = 100; ++ + protected final Context mContext; + protected final Object mMutex; + private final UserProfiles mUserProfiles; +@@ -915,16 +922,22 @@ abstract public class ManagedServices { + } + } + +- protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, ++ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId, + boolean isPrimary, boolean enabled) { +- setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, true); ++ return setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, true); + } + +- protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, ++ /** ++ * Changes the enabled state of a managed service. ++ * ++ * @return true if the change (enabling or disabling) was applied; false otherwise ++ */ ++ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId, + boolean isPrimary, boolean enabled, boolean userSet) { + Slog.i(TAG, + (enabled ? " Allowing " : "Disallowing ") + mConfig.caption + " " + + pkgOrComponent + " (userSet: " + userSet + ")"); ++ boolean changed = false; + synchronized (mApproved) { + ArrayMap> allowedByType = mApproved.get(userId); + if (allowedByType == null) { +@@ -940,24 +953,42 @@ abstract public class ManagedServices { + + if (approvedItem != null) { + if (enabled) { +- approved.add(approvedItem); ++ if (approved.size() < MAX_SERVICE_ENTRIES) { ++ approved.add(approvedItem); ++ changed = true; ++ } else { ++ Slog.w(TAG, TextUtils.formatSimple( ++ "Failed to allow %s %s because there are too many already", ++ mConfig.caption, pkgOrComponent)); ++ } + } else { + approved.remove(approvedItem); ++ changed = true; + } + } +- ArraySet userSetServices = mUserSetServices.get(userId); +- if (userSetServices == null) { +- userSetServices = new ArraySet<>(); +- mUserSetServices.put(userId, userSetServices); +- } +- if (userSet) { +- userSetServices.add(pkgOrComponent); +- } else { +- userSetServices.remove(pkgOrComponent); ++ ++ if (changed) { ++ ArraySet userSetServices = mUserSetServices.get(userId); ++ if (userSetServices == null) { ++ userSetServices = new ArraySet<>(); ++ mUserSetServices.put(userId, userSetServices); ++ } ++ if (userSet) { ++ if (userSetServices.size() < MAX_SERVICE_ENTRIES) { ++ userSetServices.add(pkgOrComponent); ++ } ++ } else { ++ userSetServices.remove(pkgOrComponent); ++ } ++ + } + } + +- rebindServices(false, userId); ++ if (changed) { ++ rebindServices(false, userId); ++ } ++ ++ return changed; + } + + private String getApprovedValue(String pkgOrComponent) { +diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java +index d7913baf02cd..da76c82e76fc 100644 +--- a/services/core/java/com/android/server/notification/NotificationManagerService.java ++++ b/services/core/java/com/android/server/notification/NotificationManagerService.java +@@ -6692,8 +6692,11 @@ public class NotificationManagerService extends SystemService { + try { + if (mAllowedManagedServicePackages.test( + pkg, userId, mConditionProviders.getRequiredPermission())) { +- mConditionProviders.setPackageOrComponentEnabled( +- pkg, userId, true, granted); ++ boolean changed = mConditionProviders.setPackageOrComponentEnabled(pkg, userId, ++ /* isPrimary= */ true, granted); ++ if (!changed) { ++ return; ++ } + + getContext().sendBroadcastAsUser(new Intent( + ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) +@@ -6963,10 +6966,15 @@ public class NotificationManagerService extends SystemService { + try { + if (mAllowedManagedServicePackages.test( + listener.getPackageName(), userId, mListeners.getRequiredPermission())) { ++ boolean changed = mListeners.setPackageOrComponentEnabled( ++ listener.flattenToString(), userId, /* isPrimary= */ true, granted, ++ userSet); ++ if (!changed) { ++ return; ++ } ++ + mConditionProviders.setPackageOrComponentEnabled(listener.flattenToString(), + userId, false, granted, userSet); +- mListeners.setPackageOrComponentEnabled(listener.flattenToString(), +- userId, true, granted, userSet); + + getContext().sendBroadcastAsUser(new Intent( + ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) +@@ -12698,19 +12706,20 @@ public class NotificationManagerService extends SystemService { + } + + @Override +- protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, ++ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId, + boolean isPrimary, boolean enabled, boolean userSet) { + // Ensures that only one component is enabled at a time + if (enabled) { + List allowedComponents = getAllowedComponents(userId); + if (!allowedComponents.isEmpty()) { + ComponentName currentComponent = CollectionUtils.firstOrNull(allowedComponents); +- if (currentComponent.flattenToString().equals(pkgOrComponent)) return; ++ if (currentComponent.flattenToString().equals(pkgOrComponent)) return false; + setNotificationAssistantAccessGrantedForUserInternal( + currentComponent, userId, false, userSet); + } + } +- super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet); ++ return super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, ++ userSet); + } + + private boolean isVerboseLogEnabled() { +@@ -13057,9 +13066,14 @@ public class NotificationManagerService extends SystemService { + } + + @Override +- protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, ++ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId, + boolean isPrimary, boolean enabled, boolean userSet) { +- super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet); ++ boolean changed = super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, ++ enabled, userSet); ++ if (!changed) { ++ return false; ++ } ++ + String pkgName = getPackageName(pkgOrComponent); + if (redactSensitiveNotificationsFromUntrustedListeners()) { + int uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId); +@@ -13079,6 +13093,8 @@ public class NotificationManagerService extends SystemService { + new Intent(ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), + UserHandle.of(userId), null); ++ ++ return true; + } + + @Override +diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +index 9c85b04fc4ff..cff652c642ee 100644 +--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java ++++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +@@ -33,6 +33,7 @@ import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAG + import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled; + + import static com.google.common.truth.Truth.assertThat; ++import static com.google.common.truth.Truth.assertWithMessage; + + import static junit.framework.Assert.assertEquals; + import static junit.framework.Assert.assertFalse; +@@ -2534,6 +2535,68 @@ public class ManagedServicesTest extends UiServiceTestCase { + assertThat(listener.enabledAndUserMatches(visibleBackgroundUserId)).isFalse(); + } + ++ @Test ++ public void setPackageOrComponentEnabled_tooManyPackages_stopsAdding() { ++ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, ++ mIpm, APPROVAL_BY_PACKAGE); ++ int userId = 0; ++ ++ for (int i = 1; i <= 100; i++) { ++ assertWithMessage("Trying pkg" + i) ++ .that(service.setPackageOrComponentEnabled("pkg" + i, userId, true, true)) ++ .isTrue(); ++ assertThat(service.isPackageAllowed("pkg" + i, userId)).isTrue(); ++ } ++ ++ // And finally, monsieur, a wafer-thin mint. ++ assertThat(service.setPackageOrComponentEnabled("toomany", userId, true, true)).isFalse(); ++ assertThat(service.isPackageAllowed("toomany", userId)).isFalse(); ++ ++ // We can still DISABLE packages though. ++ assertThat(service.isPackageAllowed("pkg33", userId)).isTrue(); ++ assertThat(service.setPackageOrComponentEnabled("pkg33", userId, true, false)).isTrue(); ++ assertThat(service.isPackageAllowed("pkg33", userId)).isFalse(); ++ ++ // And that allows adding new ones. ++ assertThat(service.setPackageOrComponentEnabled("onemore", userId, true, true)).isTrue(); ++ assertThat(service.isPackageAllowed("onemore", userId)).isTrue(); ++ } ++ ++ @Test ++ public void setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet() { ++ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, ++ mIpm, APPROVAL_BY_PACKAGE); ++ int userId = 0; ++ ++ for (int i = 1; i <= 100; i++) { ++ assertWithMessage("Enabling pkg" + i) ++ .that(service.setPackageOrComponentEnabled("pkg" + i, userId, true, true)) ++ .isTrue(); ++ assertWithMessage("Disabling pkg" + i) ++ .that(service.setPackageOrComponentEnabled("pkg" + i, userId, true, false)) ++ .isTrue(); ++ assertThat(service.isPackageAllowed("pkg" + i, userId)).isFalse(); ++ assertThat(service.isPackageOrComponentUserSet("pkg" + i, userId)).isTrue(); ++ } ++ ++ // Too many disabled services. ++ assertThat(service.setPackageOrComponentEnabled("toomany", userId, true, true)).isTrue(); ++ assertThat(service.isPackageAllowed("toomany", userId)).isTrue(); ++ assertThat(service.isPackageOrComponentUserSet("toomany", userId)).isFalse(); ++ assertThat(service.setPackageOrComponentEnabled("toomany", userId, true, false)).isTrue(); ++ assertThat(service.isPackageAllowed("toomany", userId)).isFalse(); ++ assertThat(service.isPackageOrComponentUserSet("toomany", userId)).isFalse(); ++ ++ // We make space only when packages are uninstalled. ++ service.onPackagesChanged(/* removingPackage= */ true, new String[] { "pkg22" }, ++ new int[] { 22 }); ++ ++ // And that allows tracking new ones. ++ assertThat(service.setPackageOrComponentEnabled("onemore", userId, true, true)).isTrue(); ++ assertThat(service.setPackageOrComponentEnabled("onemore", userId, true, false)).isTrue(); ++ assertThat(service.isPackageOrComponentUserSet("onemore", userId)).isTrue(); ++ } ++ + private void mockServiceInfoWithMetaData(List componentNames, + ManagedServices service, ArrayMap metaDatas) + throws RemoteException { +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 883c6cc64fbe..3eb35decad73 100644 +--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java ++++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +@@ -713,6 +713,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + mPolicyFile.finishWrite(fos); + + // Setup managed services ++ when(mListeners.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean())) ++ .thenReturn(true); ++ when(mListeners.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean(), ++ anyBoolean())).thenReturn(true); ++ when(mAssistants.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean())) ++ .thenReturn(true); ++ when(mAssistants.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean(), ++ anyBoolean())).thenReturn(true); ++ when(mConditionProviders.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), ++ anyBoolean())).thenReturn(true); ++ when(mConditionProviders.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), ++ anyBoolean(), anyBoolean())).thenReturn(true); + when(mNlf.isTypeAllowed(anyInt())).thenReturn(true); + when(mNlf.isPackageAllowed(any())).thenReturn(true); + when(mNlf.isPackageAllowed(null)).thenReturn(true); +@@ -6110,6 +6122,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + c.flattenToString(), user.getIdentifier(), true, /* enabled= */ false, true); + } + ++ @Test ++ public void testSetListenerAccessForUser_tooManyListeners_skipsFollowups() throws Exception { ++ UserHandle user = UserHandle.of(mContext.getUserId() + 10); ++ ComponentName c = ComponentName.unflattenFromString("package/Component"); ++ when(mListeners.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean(), ++ anyBoolean())).thenReturn(false); ++ ++ mBinderService.setNotificationListenerAccessGrantedForUser( ++ c, user.getIdentifier(), /* enabled= */ true, true); ++ ++ verify(mConditionProviders, never()).setPackageOrComponentEnabled(any(), anyInt(), ++ anyBoolean(), anyBoolean(), anyBoolean()); ++ verify(mContext, never()).sendBroadcastAsUser(any(), any(), any()); ++ } ++ + @Test + public void testSetAssistantAccessForUser() throws Exception { + UserInfo ui = new UserInfo(); +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-39_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch b/asb/2026-03/frameworks/base/2026-01-07_21-39_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch new file mode 100644 index 0000000..0c7e4e3 --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-07_21-39_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch @@ -0,0 +1,42 @@ +From 924df83d73d9f938fde025c2e793ca12646207e0 Mon Sep 17 00:00:00 2001 +From: Evan Chen +Date: Tue, 18 Nov 2025 22:34:11 +0000 +Subject: [PATCH] Remove any revoked associations after reboot + +Test: manually +Bug: 442392902 +Flag: EXEMPT bugfix +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:13714bcfaff6ef1c16d0aa3d359b1c8bc1859ac3 +Merged-In: I94b96d98608d6702e1d3a9581e135280149bf7e1 +Change-Id: I94b96d98608d6702e1d3a9581e135280149bf7e1 +--- + .../server/companion/CompanionDeviceManagerService.java | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +index 7ff1ddb7dc79..fdefbc412136 100644 +--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java ++++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +@@ -33,6 +33,7 @@ import static com.android.internal.util.CollectionUtils.any; + import static com.android.internal.util.Preconditions.checkState; + import static com.android.server.companion.association.DisassociationProcessor.REASON_API; + import static com.android.server.companion.association.DisassociationProcessor.REASON_PKG_DATA_CLEARED; ++import static com.android.server.companion.association.DisassociationProcessor.REASON_REVOKED; + import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; + import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed; + import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; +@@ -197,6 +198,11 @@ public class CompanionDeviceManagerService extends SystemService { + // Init association stores + mAssociationStore.refreshCache(); + ++ // Remove any revoked associations after reboot. ++ for (AssociationInfo ai : mAssociationStore.getRevokedAssociations()) { ++ mDisassociationProcessor.disassociate(ai.getId(), REASON_REVOKED); ++ } ++ + // Init UUID store + mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId()); + +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/base/2026-01-15_06-49_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch b/asb/2026-03/frameworks/base/2026-01-15_06-49_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch new file mode 100644 index 0000000..66d223b --- /dev/null +++ b/asb/2026-03/frameworks/base/2026-01-15_06-49_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch @@ -0,0 +1,101 @@ +From 51368785bd09cd652ed9851727b16545cb92c4e5 Mon Sep 17 00:00:00 2001 +From: Song Chun Fan +Date: Tue, 11 Nov 2025 00:03:48 +0000 +Subject: [PATCH] [UidMigration] fix update uninstallation with + sharedUserMaxSdkVersion + +When a system app is re-enabled after the update is uninstalled, when +the system app has shared uid, the current code doesn't support +directly reusing the disabled package setting. Instead, the preloaded +version is re-scanned and installed as a new app, which can bring +breaking behavior of changed UIDs when the manifest has +sharedUserMaxSdkVersion. + +This change fixes the bug where registerExistingAppId fails when it +comes to shared uid, therefore directly reuses the disabled package +setting, consistent with the behavior for non-shared-uid system apps. + +FLAG: EXEMPT BUGFIX +Test: manually with system-app-test.sh +BUG: 454062218 + + + +(cherry picked from commit 6b5ea2f7fbf50313d46e54e0d8f8c18c398e4869) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:740256b41ba113708655f82dc5664291bf79edd0 +Merged-In: I417cec27697a210416027e862a5e5d207d268b82 +Change-Id: I417cec27697a210416027e862a5e5d207d268b82 +--- + .../core/java/com/android/server/pm/Settings.java | 14 +++++++++----- + .../src/com/android/server/pm/MockSystem.kt | 2 +- + 2 files changed, 10 insertions(+), 6 deletions(-) + +diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java +index 92257f1ee2dd..ab13d7e766e8 100644 +--- a/services/core/java/com/android/server/pm/Settings.java ++++ b/services/core/java/com/android/server/pm/Settings.java +@@ -916,8 +916,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile + p.getPkgState().setUpdatedSystemApp(false); + final AndroidPackageInternal pkg = p.getPkg(); + PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(), p.getAppId(), +- p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId(), +- pkg == null ? false : pkg.isSdkLibrary()); ++ p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId(), ++ pkg == null ? false : pkg.isSdkLibrary(), p.hasSharedUser()); + if (ret != null) { + ret.setLegacyNativeLibraryPath(p.getLegacyNativeLibraryPath()); + ret.setPrimaryCpuAbi(p.getPrimaryCpuAbiLegacy()); +@@ -937,6 +937,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile + ret.setRestrictUpdateHash(p.getRestrictUpdateHash()); + ret.setScannedAsStoppedSystemApp(p.isScannedAsStoppedSystemApp()); + ret.setInstallSource(p.getInstallSource()); ++ ret.setSharedUserAppId(p.getSharedUserAppId()); + } + mDisabledSysPackages.remove(name); + return ret; +@@ -958,7 +959,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile + } + + PackageSetting addPackageLPw(String name, String realName, File codePath, int uid, +- int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId, boolean isSdkLibrary) { ++ int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId, boolean isSdkLibrary, ++ boolean hasSharedUser) { + PackageSetting p = mPackages.get(name); + if (p != null) { + if (p.getAppId() == uid) { +@@ -971,7 +973,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile + p = new PackageSetting(name, realName, codePath, pkgFlags, pkgPrivateFlags, domainSetId) + .setAppId(uid); + if ((uid == Process.INVALID_UID && isSdkLibrary && Flags.disallowSdkLibsToBeApps()) +- || mAppIds.registerExistingAppId(uid, p, name)) { ++ || mAppIds.registerExistingAppId(uid, p, name) ++ || hasSharedUser) { + mPackages.put(name, p); + return p; + } +@@ -4266,7 +4269,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile + } else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary + && Flags.disallowSdkLibsToBeApps())) { + packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr), +- appId, pkgFlags, pkgPrivateFlags, domainSetId, isSdkLibrary); ++ appId, pkgFlags, pkgPrivateFlags, domainSetId, isSdkLibrary, ++ /* hasSharedUser= */ false); + if (PackageManagerService.DEBUG_SETTINGS) + Log.i(PackageManagerService.TAG, "Reading package " + name + ": appId=" + + appId + " pkg=" + packageSetting); +diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +index 1b2ab2702d49..9a73ba3c155e 100644 +--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt ++++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +@@ -168,7 +168,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { + null + } + whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(), +- nullable(), nullable(), nullable(), nullable())) { ++ nullable(), nullable(), nullable(), nullable(), nullable())) { + val name: String = getArgument(0) + val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name } + ?: return@whenever null +-- +2.53.0 + diff --git a/asb/2026-03/frameworks/native/2026-01-07_21-39_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch b/asb/2026-03/frameworks/native/2026-01-07_21-39_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch new file mode 100644 index 0000000..b22e893 --- /dev/null +++ b/asb/2026-03/frameworks/native/2026-01-07_21-39_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch @@ -0,0 +1,38 @@ +From c81cf361489e3a3cd764c0a0c85c84958e25d63c Mon Sep 17 00:00:00 2001 +From: Alec Mouri +Date: Wed, 5 Nov 2025 21:29:10 +0000 +Subject: [PATCH] Clip to layer bounds when drawing blur regions + +Otherwise blurs can "escape" layer bounds. This would make 1-2 +pixel-large layers fill the entire screen if the blur region is +appropriately crafted, which is not great. + +Bug: 455563813 +Flag: EXEMPT CVE_FIX +Test: PoC app +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:123f8fec995a3103acbc3a1191b9cef71523e013 +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:f3fecb02978030ae4066235cbe638250996b6a9a +Merged-In: If59833f2d5060f5f81395d602e2dcb369a10fdbb +Change-Id: If59833f2d5060f5f81395d602e2dcb369a10fdbb +--- + libs/renderengine/skia/SkiaRenderEngine.cpp | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp +index 5b6edb4e30..46c58b0c27 100644 +--- a/libs/renderengine/skia/SkiaRenderEngine.cpp ++++ b/libs/renderengine/skia/SkiaRenderEngine.cpp +@@ -907,6 +907,10 @@ void SkiaRenderEngine::drawLayersInternal( + SkAutoCanvasRestore acr(canvas, true); + if (!roundRectClip.isEmpty()) { + canvas->clipRRect(roundRectClip, true); ++ } else { ++ // We need to clip bounds here since otherwise a client sending a bigger blur region ++ // enables the blur to "escape" the layer bounds which is very bad for security ++ canvas->clipRRect(bounds, true); + } + + // TODO(b/182216890): Filter out empty layers earlier +-- +2.53.0 + diff --git a/asb/2026-03/hardware/st/nfc/2026-01-07_21-40_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch b/asb/2026-03/hardware/st/nfc/2026-01-07_21-40_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch new file mode 100644 index 0000000..977a93b --- /dev/null +++ b/asb/2026-03/hardware/st/nfc/2026-01-07_21-40_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch @@ -0,0 +1,82 @@ +From c6da9eeb710c6690d189cb2d1b80b44755860b55 Mon Sep 17 00:00:00 2001 +From: Kyle Hsiao +Date: Wed, 1 Oct 2025 11:57:54 +0000 +Subject: [PATCH] [NFC] Fix use-after-free in eventCallback + +Transform access to the static shared pointer 'Nfc::mCallback' to be thread-safe to prevent a use-after-free crash. + +The use-after-free occurred when an asynchronous thread (eventCallback) attempted to call a method on 'mCallback' immediately after the main thread Nfc::open had destroyed the underlying object. The simple null check was insufficient due to the race condition. + +This fix implements the correct shared pointer synchronization pattern: +1. Protects the read/write of 'mCallback' using 'mCallbackLock'. +2. Creates a local 'std::shared_ptr localCallback' inside the critical section. This local copy holds a temporary strong reference, guaranteeing the object's lifetime for the duration of the subsequent sendEvent() call. + +Bug: 392699284 +Test: nfc_service_fuzzer +Test: atest NfcNciUnitTests +Test: atest CtsNfcTestCases +Test: atest VtsAidlHalNfcTargetTest +Test: atest NfcTestCases +Test: atest NfcServiceTest +(cherry picked from commit fc619c3348188ff0020cdeb7d4c4728a0246fe1a) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:33b3c128e0f480f6e421761b85c5f00289b62449 +Merged-In: I8e9e83be7f939bbfc183cb3879808ceacf931450 +Change-Id: I8e9e83be7f939bbfc183cb3879808ceacf931450 +--- + aidl/Nfc.h | 18 ++++++++++++++---- + 1 file changed, 14 insertions(+), 4 deletions(-) + +diff --git a/aidl/Nfc.h b/aidl/Nfc.h +index 707bd65..fd23e24 100644 +--- a/aidl/Nfc.h ++++ b/aidl/Nfc.h +@@ -32,6 +32,8 @@ using ::aidl::android::hardware::nfc::NfcConfig; + using ::aidl::android::hardware::nfc::NfcEvent; + using ::aidl::android::hardware::nfc::NfcStatus; + ++static pthread_mutex_t sCallbackLock = PTHREAD_MUTEX_INITIALIZER; ++ + // Default implementation that reports no support NFC. + struct Nfc : public BnNfc { + public: +@@ -52,7 +54,11 @@ struct Nfc : public BnNfc { + binder_status_t dump(int fd, const char** args, uint32_t numArgs) override; + + static void eventCallback(uint8_t event, uint8_t status) { +- if (mCallback != nullptr) { ++ std::shared_ptr localCallback; ++ pthread_mutex_lock(&sCallbackLock); ++ localCallback = mCallback; ++ pthread_mutex_unlock(&sCallbackLock); ++ if (localCallback != nullptr) { + NfcEvent mEvent; + NfcStatus mStatus; + switch (event) { +@@ -96,7 +102,7 @@ struct Nfc : public BnNfc { + default: + mStatus = NfcStatus::FAILED; + } +- auto ret = mCallback->sendEvent(mEvent, mStatus); ++ auto ret = localCallback->sendEvent(mEvent, mStatus); + if (!ret.isOk()) { + LOG(ERROR) << "Failed to send event!"; + } +@@ -104,9 +110,13 @@ struct Nfc : public BnNfc { + } + + static void dataCallback(uint16_t data_len, uint8_t* p_data) { ++ std::shared_ptr localCallback; ++ pthread_mutex_lock(&sCallbackLock); ++ localCallback = mCallback; ++ pthread_mutex_unlock(&sCallbackLock); + std::vector data(p_data, p_data + data_len); +- if (mCallback != nullptr) { +- auto ret = mCallback->sendData(data); ++ if (localCallback != nullptr) { ++ auto ret = localCallback->sendData(data); + if (!ret.isOk()) { + LOG(ERROR) << "Failed to send data!"; + } +-- +2.53.0 + diff --git a/asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch b/asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch new file mode 100644 index 0000000..ca4c2e0 --- /dev/null +++ b/asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch @@ -0,0 +1,71 @@ +From 48af8a13dd12ecbd0569c328a56d1a7b61a59ca3 Mon Sep 17 00:00:00 2001 +From: Mill Chen +Date: Fri, 26 Sep 2025 09:02:26 +0000 +Subject: [PATCH] Check permission of the calling package in multi-pane devices + +Bug: 430047417 +Test: manual test +Flag: EXEMPT BUGFIX +(cherry picked from commit bd4d57ade07792f2a9160acbe480603b30e79917) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:2860bd01810adb2d0f00fba8f327cdae3f20ab9d +Merged-In: I91dafa77d07970fdf2628b4d9e89ca1c4b74194c +Change-Id: I91dafa77d07970fdf2628b4d9e89ca1c4b74194c +--- + AndroidManifest.xml | 2 +- + .../settings/applications/AppInfoBase.java | 20 +++++++++++++++++++ + 2 files changed, 21 insertions(+), 1 deletion(-) + +diff --git a/AndroidManifest.xml b/AndroidManifest.xml +index ed19890456f..e2d249add13 100644 +--- a/AndroidManifest.xml ++++ b/AndroidManifest.xml +@@ -2488,7 +2488,7 @@ + android:name="Settings$AppUsageAccessSettingsActivity" + android:exported="true" + android:label="@string/usage_access_title"> +- ++ + + + +diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java +index 02237b886d9..14e54eb5b03 100644 +--- a/src/com/android/settings/applications/AppInfoBase.java ++++ b/src/com/android/settings/applications/AppInfoBase.java +@@ -49,6 +49,7 @@ import androidx.fragment.app.Fragment; + + import com.android.settings.SettingsActivity; + import com.android.settings.SettingsPreferenceFragment; ++import com.android.settings.activityembedding.ActivityEmbeddingUtils; + import com.android.settings.applications.manageapplications.ManageApplications; + import com.android.settings.core.SubSettingLauncher; + import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +@@ -178,6 +179,25 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment + if (!(activity instanceof SettingsActivity)) { + return false; + } ++ // Check the permission of the calling package if the device supports multi-pane. ++ if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity)) { ++ final String callingPackageName = ++ ((SettingsActivity) activity).getInitialCallingPackage(); ++ ++ if (TextUtils.isEmpty(callingPackageName)) { ++ Log.w(TAG, "Not able to get calling package name for permission check"); ++ return false; ++ } ++ if (mPm.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, ++ callingPackageName) ++ != PackageManager.PERMISSION_GRANTED) { ++ Log.w(TAG, "Package " + callingPackageName + " does not have required permission " ++ + Manifest.permission.INTERACT_ACROSS_USERS_FULL); ++ return false; ++ } ++ return true; ++ } ++ + try { + int callerUid = ActivityManager.getService().getLaunchedFromUid( + activity.getActivityToken()); +-- +2.53.0 + diff --git a/asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch b/asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch new file mode 100644 index 0000000..2262513 --- /dev/null +++ b/asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch @@ -0,0 +1,600 @@ +From 7d8fbee887fc9577337c2a80513ae4399bf60111 Mon Sep 17 00:00:00 2001 +From: Shawn Lin +Date: Tue, 14 Oct 2025 08:08:58 +0000 +Subject: [PATCH] Fixed "Unlock your phone" unexpectedlly turned ON after OTA + +If the new settings key is not set, we should use the values of the old +keys as default value. + +Bug: 444673089 +Test: atest FaceSettingsAppsPreferenceControllerTest + FaceSettingsKeyguardUnlockPreferenceControllerTest + FingerprintSettingsAppsPreferenceControllerTest + FingerprintSettingsKeyguardUnlockPreferenceControllerTest +Flag: EXEMPT BUGFIX +(cherry picked from commit 05f0884146a093e6311d7a30232d6850a28368ef) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:feb931006ee2d56c146156d5bb1491117841ccf3 +Merged-In: I345defc78500c244e29e8595f5fbc705b95f4ba6 +Change-Id: I345defc78500c244e29e8595f5fbc705b95f4ba6 +--- + .../FaceSettingsAppsPreferenceController.java | 17 ++- + ...ngsKeyguardUnlockPreferenceController.java | 13 ++ + ...printSettingsAppsPreferenceController.java | 17 ++- + ...ngsKeyguardUnlockPreferenceController.java | 13 ++ + ...eSettingsAppsPreferenceControllerTest.java | 116 ++++++++++++++++++ + ...eyguardUnlockPreferenceControllerTest.java | 90 ++++++++++++++ + ...tSettingsAppsPreferenceControllerTest.java | 76 ++++++++++++ + ...eyguardUnlockPreferenceControllerTest.java | 90 ++++++++++++++ + 8 files changed, 428 insertions(+), 4 deletions(-) + create mode 100644 tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java + create mode 100644 tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java + create mode 100644 tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java + create mode 100644 tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java + +diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java +index 9500c25a8ec..78d9ffaf7c4 100644 +--- a/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java ++++ b/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java +@@ -16,6 +16,7 @@ + + package com.android.settings.biometrics.face; + ++import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED; + import static android.provider.Settings.Secure.FACE_APP_ENABLED; + + import android.app.settings.SettingsEnums; +@@ -31,6 +32,7 @@ import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; + + public class FaceSettingsAppsPreferenceController extends + FaceSettingsPreferenceController { ++ private static final int NOT_SET = -1; + private static final int ON = 1; + private static final int OFF = 0; + private static final int DEFAULT = ON; +@@ -40,12 +42,23 @@ public class FaceSettingsAppsPreferenceController extends + public FaceSettingsAppsPreferenceController(@NonNull Context context, @NonNull String key) { + super(context, key); + mFaceManager = Utils.getFaceManagerOrNull(context); ++ ++ // For OTA case: if FACE_APP_ENABLED is not set and BIOMETRIC_APP_ENABLED is set, set the ++ // default value of the former to that of the latter. ++ final int defValue = Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ FACE_APP_ENABLED, NOT_SET, getUserId()); ++ final int oldDefValue = Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_APP_ENABLED, NOT_SET, getUserId()); ++ if (defValue == NOT_SET && oldDefValue != NOT_SET) { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FACE_APP_ENABLED, oldDefValue, getUserId()); ++ } + } + + @Override + public boolean isChecked() { +- return Settings.Secure.getIntForUser(mContext.getContentResolver(), FACE_APP_ENABLED, +- DEFAULT, getUserId()) == ON; ++ return Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ FACE_APP_ENABLED, DEFAULT, getUserId()) == ON; + } + + @Override +diff --git a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java +index 5ff15232a02..46731b94451 100644 +--- a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java ++++ b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java +@@ -16,6 +16,7 @@ + + package com.android.settings.biometrics.face; + ++import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED; + import static android.provider.Settings.Secure.FACE_KEYGUARD_ENABLED; + + import android.app.settings.SettingsEnums; +@@ -31,6 +32,7 @@ import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; + + public class FaceSettingsKeyguardUnlockPreferenceController extends + FaceSettingsPreferenceController { ++ private static final int NOT_SET = -1; + private static final int ON = 1; + private static final int OFF = 0; + private static final int DEFAULT = ON; +@@ -41,6 +43,17 @@ public class FaceSettingsKeyguardUnlockPreferenceController extends + @NonNull Context context, @NonNull String key) { + super(context, key); + mFaceManager = Utils.getFaceManagerOrNull(context); ++ ++ // For OTA case: if FACE_KEYGUARD_ENABLED is not set and BIOMETRIC_KEYGUARD_ENABLED is set, ++ // set the default value of the former to that of the latter. ++ final int defValue = Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ FACE_KEYGUARD_ENABLED, NOT_SET, getUserId()); ++ final int oldDefValue = Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_KEYGUARD_ENABLED, NOT_SET, getUserId()); ++ if (defValue == NOT_SET && oldDefValue != NOT_SET) { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FACE_KEYGUARD_ENABLED, oldDefValue, getUserId()); ++ } + } + + @Override +diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java +index 63fc3dcef23..574d406e382 100644 +--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java ++++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java +@@ -16,6 +16,7 @@ + + package com.android.settings.biometrics.fingerprint; + ++import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED; + import static android.provider.Settings.Secure.FINGERPRINT_APP_ENABLED; + + import android.app.settings.SettingsEnums; +@@ -31,6 +32,7 @@ import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; + + public class FingerprintSettingsAppsPreferenceController + extends FingerprintSettingsPreferenceController { ++ private static final int NOT_SET = -1; + private static final int ON = 1; + private static final int OFF = 0; + private static final int DEFAULT = ON; +@@ -41,12 +43,23 @@ public class FingerprintSettingsAppsPreferenceController + @NonNull Context context, @NonNull String key) { + super(context, key); + mFingerprintManager = Utils.getFingerprintManagerOrNull(context); ++ ++ // For OTA case: if FINGERPRINT_APP_ENABLED is not set and BIOMETRIC_APP_ENABLED is set, ++ // set the default value of the former to that of the latter. ++ final int defValue = Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ FINGERPRINT_APP_ENABLED, NOT_SET, getUserId()); ++ final int oldDefValue = Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_APP_ENABLED, NOT_SET, getUserId()); ++ if (defValue == NOT_SET && oldDefValue != NOT_SET) { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FINGERPRINT_APP_ENABLED, oldDefValue, getUserId()); ++ } + } + + @Override + public boolean isChecked() { +- return Settings.Secure.getIntForUser(mContext.getContentResolver(), FINGERPRINT_APP_ENABLED, +- DEFAULT, getUserId()) == ON; ++ return Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ FINGERPRINT_APP_ENABLED, DEFAULT, getUserId()) == ON; + } + + @Override +diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java +index 56ef2c3db8b..89bea9b5fde 100644 +--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java ++++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java +@@ -16,6 +16,7 @@ + + package com.android.settings.biometrics.fingerprint; + ++import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED; + import static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED; + + import android.app.settings.SettingsEnums; +@@ -32,6 +33,7 @@ import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; + public class FingerprintSettingsKeyguardUnlockPreferenceController + extends FingerprintSettingsPreferenceController { + ++ private static final int NOT_SET = -1; + private static final int ON = 1; + private static final int OFF = 0; + private static final int DEFAULT = ON; +@@ -42,6 +44,17 @@ public class FingerprintSettingsKeyguardUnlockPreferenceController + @NonNull Context context, @NonNull String key) { + super(context, key); + mFingerprintManager = Utils.getFingerprintManagerOrNull(context); ++ ++ // For OTA case: if FINGERPRINT_KEYGUARD_ENABLED is not set and BIOMETRIC_KEYGUARD_ENABLED ++ // is set, set the default value of the former to that of the latter. ++ final int defValue = Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ FINGERPRINT_KEYGUARD_ENABLED, NOT_SET, getUserId()); ++ final int oldDefValue = Settings.Secure.getIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_KEYGUARD_ENABLED, NOT_SET, getUserId()); ++ if (defValue == NOT_SET && oldDefValue != NOT_SET) { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FINGERPRINT_KEYGUARD_ENABLED, oldDefValue, getUserId()); ++ } + } + + @Override +diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java +new file mode 100644 +index 00000000000..676031c4951 +--- /dev/null ++++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java +@@ -0,0 +1,116 @@ ++/* ++ * Copyright (C) 2025 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.settings.biometrics.face; ++ ++import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED; ++import static android.provider.Settings.Secure.FACE_APP_ENABLED; ++ ++import static com.google.common.truth.Truth.assertThat; ++ ++import static org.mockito.Mockito.when; ++ ++import android.content.Context; ++import android.hardware.biometrics.ComponentInfoInternal; ++import android.hardware.biometrics.SensorProperties; ++import android.hardware.face.FaceManager; ++import android.hardware.face.FaceSensorProperties; ++import android.hardware.face.FaceSensorPropertiesInternal; ++import android.provider.Settings; ++ ++import androidx.test.core.app.ApplicationProvider; ++ ++import com.android.settings.testutils.FakeFeatureFactory; ++import com.android.settings.testutils.shadow.ShadowSecureSettings; ++import com.android.settings.testutils.shadow.ShadowUtils; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++import org.mockito.Mock; ++import org.mockito.Spy; ++import org.mockito.junit.MockitoJUnit; ++import org.mockito.junit.MockitoRule; ++import org.robolectric.RobolectricTestRunner; ++import org.robolectric.annotation.Config; ++ ++import java.util.ArrayList; ++ ++@RunWith(RobolectricTestRunner.class) ++@Config(shadows = {ShadowSecureSettings.class}) ++public class FaceSettingsAppsPreferenceControllerTest { ++ @Rule ++ public final MockitoRule mMockitoRule = MockitoJUnit.rule(); ++ @Spy ++ private Context mContext = ApplicationProvider.getApplicationContext(); ++ @Mock ++ private FaceManager mFaceManager; ++ private FaceSettingsAppsPreferenceController mController; ++ ++ private FaceSensorPropertiesInternal mConvenienceSensorProperty = ++ new FaceSensorPropertiesInternal( ++ 0 /* sensorId */, ++ SensorProperties.STRENGTH_CONVENIENCE, ++ 1 /* maxEnrollmentsPerUser */, ++ new ArrayList(), ++ FaceSensorProperties.TYPE_UNKNOWN, ++ true /* supportsFaceDetection */, ++ true /* supportsSelfIllumination */, ++ true /* resetLockoutRequiresChallenge */); ++ private FakeFeatureFactory mFeatureFactory; ++ ++ @Before ++ public void setUp() { ++ when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager); ++ final ArrayList list = new ArrayList<>(); ++ list.add(mConvenienceSensorProperty); ++ when(mFaceManager.getSensorPropertiesInternal()).thenReturn(list); ++ mFeatureFactory = FakeFeatureFactory.setupForTest(); ++ } ++ ++ @After ++ public void tearDown() { ++ ShadowUtils.reset(); ++ } ++ ++ @Test ++ public void isChecked_BiometricAppEnableOff_FaceAppEnabledNotSet_returnFalse() { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_APP_ENABLED, 0, mContext.getUserId()); ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FACE_APP_ENABLED, -1, mContext.getUserId()); ++ ++ mController = new FaceSettingsAppsPreferenceController( ++ mContext, "biometric_settings_face_app"); ++ ++ assertThat(mController.isChecked()).isFalse(); ++ } ++ ++ @Test ++ public void isChecked_BiometricAppEnableOff_FaceAppEnabledOn_returnTrue() { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_APP_ENABLED, 0, mContext.getUserId()); ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FACE_APP_ENABLED, 1, mContext.getUserId()); ++ ++ mController = new FaceSettingsAppsPreferenceController( ++ mContext, "biometric_settings_face_app"); ++ ++ assertThat(mController.isChecked()).isTrue(); ++ } ++} +diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java +new file mode 100644 +index 00000000000..837f31e3bd6 +--- /dev/null ++++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java +@@ -0,0 +1,90 @@ ++/* ++ * Copyright (C) 2025 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.settings.biometrics.face; ++ ++import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED; ++import static android.provider.Settings.Secure.FACE_KEYGUARD_ENABLED; ++ ++import static com.google.common.truth.Truth.assertThat; ++ ++import static org.mockito.Mockito.when; ++ ++import android.content.Context; ++import android.os.UserManager; ++import android.provider.Settings; ++ ++import androidx.test.core.app.ApplicationProvider; ++ ++import com.android.settings.testutils.FakeFeatureFactory; ++import com.android.settings.testutils.shadow.ShadowSecureSettings; ++ ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++import org.mockito.Mock; ++import org.mockito.Spy; ++import org.mockito.junit.MockitoJUnit; ++import org.mockito.junit.MockitoRule; ++import org.robolectric.RobolectricTestRunner; ++import org.robolectric.annotation.Config; ++ ++@RunWith(RobolectricTestRunner.class) ++@Config(shadows = {ShadowSecureSettings.class}) ++public class FaceSettingsKeyguardUnlockPreferenceControllerTest { ++ @Rule ++ public final MockitoRule mMockitoRule = MockitoJUnit.rule(); ++ @Spy ++ Context mContext = ApplicationProvider.getApplicationContext(); ++ private FaceSettingsKeyguardUnlockPreferenceController mController; ++ private FakeFeatureFactory mFeatureFactory; ++ @Mock ++ private UserManager mUserManager; ++ ++ @Before ++ public void setUp() { ++ ++ mFeatureFactory = FakeFeatureFactory.setupForTest(); ++ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); ++ } ++ ++ @Test ++ public void isChecked_BiometricKeyguardEnabledOff_FaceKeyguardEnabledNotSet_returnFalse() { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_KEYGUARD_ENABLED, 0, mContext.getUserId()); ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FACE_KEYGUARD_ENABLED, -1, mContext.getUserId()); ++ ++ mController = new FaceSettingsKeyguardUnlockPreferenceController( ++ mContext, "biometric_settings_face_keyguard"); ++ ++ assertThat(mController.isChecked()).isFalse(); ++ } ++ ++ @Test ++ public void isChecked_BiometricKeyguardEnabledOff_FaceKeyguardEnabledOn_returnTrue() { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_KEYGUARD_ENABLED, 0, mContext.getUserId()); ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FACE_KEYGUARD_ENABLED, 1, mContext.getUserId()); ++ ++ mController = new FaceSettingsKeyguardUnlockPreferenceController( ++ mContext, "biometric_settings_face_keyguard"); ++ ++ assertThat(mController.isChecked()).isTrue(); ++ } ++} +diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java +new file mode 100644 +index 00000000000..841cec2f3de +--- /dev/null ++++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java +@@ -0,0 +1,76 @@ ++/* ++ * Copyright (C) 2025 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.settings.biometrics.fingerprint; ++ ++import static android.provider.Settings.Secure.BIOMETRIC_APP_ENABLED; ++import static android.provider.Settings.Secure.FINGERPRINT_APP_ENABLED; ++ ++import static com.google.common.truth.Truth.assertThat; ++ ++import android.content.Context; ++import android.provider.Settings; ++ ++import androidx.test.core.app.ApplicationProvider; ++ ++import com.android.settings.testutils.FakeFeatureFactory; ++import com.android.settings.testutils.shadow.ShadowSecureSettings; ++ ++import org.junit.Before; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++import org.robolectric.RobolectricTestRunner; ++import org.robolectric.annotation.Config; ++ ++@RunWith(RobolectricTestRunner.class) ++@Config(shadows = {ShadowSecureSettings.class}) ++public class FingerprintSettingsAppsPreferenceControllerTest { ++ private Context mContext; ++ private FingerprintSettingsAppsPreferenceController mController; ++ private FakeFeatureFactory mFeatureFactory; ++ ++ @Before ++ public void setUp() { ++ mContext = ApplicationProvider.getApplicationContext(); ++ mFeatureFactory = FakeFeatureFactory.setupForTest(); ++ } ++ ++ @Test ++ public void isChecked_BiometricAppEnableOff_FingerprintAppEnabledNotSet_returnFalse() { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_APP_ENABLED, 0, mContext.getUserId()); ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FINGERPRINT_APP_ENABLED, -1, mContext.getUserId()); ++ ++ mController = new FingerprintSettingsAppsPreferenceController( ++ mContext, "biometric_settings_fingerprint_app"); ++ ++ assertThat(mController.isChecked()).isFalse(); ++ } ++ ++ @Test ++ public void isChecked_BiometricAppEnableOff_FingerprintAppEnabledOn_returnTrue() { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_APP_ENABLED, 0, mContext.getUserId()); ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FINGERPRINT_APP_ENABLED, 1, mContext.getUserId()); ++ ++ mController = new FingerprintSettingsAppsPreferenceController( ++ mContext, "biometric_settings_fingerprint_app"); ++ ++ assertThat(mController.isChecked()).isTrue(); ++ } ++} +diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java +new file mode 100644 +index 00000000000..119fda8bbf2 +--- /dev/null ++++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java +@@ -0,0 +1,90 @@ ++/* ++ * Copyright (C) 2025 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.settings.biometrics.fingerprint; ++ ++import static android.provider.Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED; ++import static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED; ++ ++import static com.google.common.truth.Truth.assertThat; ++ ++import static org.mockito.Mockito.when; ++ ++import android.content.Context; ++import android.os.UserManager; ++import android.provider.Settings; ++ ++import androidx.test.core.app.ApplicationProvider; ++ ++import com.android.settings.testutils.FakeFeatureFactory; ++import com.android.settings.testutils.shadow.ShadowSecureSettings; ++ ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++import org.mockito.Mock; ++import org.mockito.Spy; ++import org.mockito.junit.MockitoJUnit; ++import org.mockito.junit.MockitoRule; ++import org.robolectric.RobolectricTestRunner; ++import org.robolectric.annotation.Config; ++ ++@RunWith(RobolectricTestRunner.class) ++@Config(shadows = {ShadowSecureSettings.class}) ++public class FingerprintSettingsKeyguardUnlockPreferenceControllerTest { ++ @Rule ++ public final MockitoRule mMockitoRule = MockitoJUnit.rule(); ++ @Spy ++ Context mContext = ApplicationProvider.getApplicationContext(); ++ private FingerprintSettingsKeyguardUnlockPreferenceController mController; ++ private FakeFeatureFactory mFeatureFactory; ++ @Mock ++ private UserManager mUserManager; ++ ++ @Before ++ public void setUp() { ++ mFeatureFactory = FakeFeatureFactory.setupForTest(); ++ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); ++ } ++ ++ @Test ++ public void ++ isChecked_BiometricKeyguardEnabledOff_FingerprintKeyguardEnabledNotSet_returnFalse() { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_KEYGUARD_ENABLED, 0, mContext.getUserId()); ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FINGERPRINT_KEYGUARD_ENABLED, -1, mContext.getUserId()); ++ ++ mController = new FingerprintSettingsKeyguardUnlockPreferenceController( ++ mContext, "biometric_settings_fingerprint_keyguard"); ++ ++ assertThat(mController.isChecked()).isFalse(); ++ } ++ ++ @Test ++ public void isChecked_BiometricKeyguardEnabledOff_FingerprintKeyguardEnabledOn_returnTrue() { ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ BIOMETRIC_KEYGUARD_ENABLED, 0, mContext.getUserId()); ++ Settings.Secure.putIntForUser(mContext.getContentResolver(), ++ FINGERPRINT_KEYGUARD_ENABLED, 1, mContext.getUserId()); ++ ++ mController = new FingerprintSettingsKeyguardUnlockPreferenceController( ++ mContext, "biometric_settings_fingerprint_keyguard"); ++ ++ assertThat(mController.isChecked()).isTrue(); ++ } ++} +-- +2.53.0 + diff --git a/asb/2026-03/packages/modules/Virtualization/2026-01-07_21-42_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch b/asb/2026-03/packages/modules/Virtualization/2026-01-07_21-42_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch new file mode 100644 index 0000000..3d22b05 --- /dev/null +++ b/asb/2026-03/packages/modules/Virtualization/2026-01-07_21-42_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch @@ -0,0 +1,68 @@ +From f0271f36388ec9630d89ff8b3ee4cb22e2ca3eaf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pierre-Cl=C3=A9ment=20Tosi?= +Date: Tue, 28 Oct 2025 09:46:06 +0000 +Subject: [PATCH] vmbase,pvmfw: aarch64: Clean dcache to PoC not PoU + +Some SoCs (with a unified cache before the PoC) might not flush the data +to main memory when performing CMOs to PoU so perform them to PoC. + +Test: b/434562039#comment35 +Bug: 434562039 +Bug: 455777515 +Flag: EXEMPT CVE_FIX +(cherry picked from commit a6f64040dba5a3aae3f67c68e2a754a5c946610d) +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:ab5f74693d42e114af5ac238cca0bcb4b17698ac +Merged-In: Id2398e9bcf8dcf7f7a10d254d8eb411d39e109db +Change-Id: Id2398e9bcf8dcf7f7a10d254d8eb411d39e109db +--- + guest/pvmfw/src/arch/aarch64/payload.rs | 6 +++--- + libs/libvmbase/src/arch.rs | 2 +- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/guest/pvmfw/src/arch/aarch64/payload.rs b/guest/pvmfw/src/arch/aarch64/payload.rs +index 77e9a31..8c2242e 100644 +--- a/guest/pvmfw/src/arch/aarch64/payload.rs ++++ b/guest/pvmfw/src/arch/aarch64/payload.rs +@@ -91,7 +91,7 @@ pub fn jump_to_payload(entrypoint: usize, slices: &MemorySlices) -> ! { + "b.lo 0b", + + // Flush d-cache over .data & .bss (including skipped region). +- "0: dc cvau, {cache_line}", ++ "0: dc cvac, {cache_line}", + "add {cache_line}, {cache_line}, {dcache_line_size}", + "cmp {cache_line}, {scratch_end}", + "b.lo 0b", +@@ -103,7 +103,7 @@ pub fn jump_to_payload(entrypoint: usize, slices: &MemorySlices) -> ! { + "b.lo 0b", + + // Flush d-cache over stack region. +- "0: dc cvau, {cache_line}", ++ "0: dc cvac, {cache_line}", + "add {cache_line}, {cache_line}, {dcache_line_size}", + "cmp {cache_line}, {stack_end}", + "b.lo 0b", +@@ -115,7 +115,7 @@ pub fn jump_to_payload(entrypoint: usize, slices: &MemorySlices) -> ! { + "b.lo 0b", + + // Flush d-cache over EH stack region. +- "0: dc cvau, {cache_line}", ++ "0: dc cvac, {cache_line}", + "add {cache_line}, {cache_line}, {dcache_line_size}", + "cmp {cache_line}, {eh_stack_end}", + "b.lo 0b", +diff --git a/libs/libvmbase/src/arch.rs b/libs/libvmbase/src/arch.rs +index 29d3a32..e25745b 100644 +--- a/libs/libvmbase/src/arch.rs ++++ b/libs/libvmbase/src/arch.rs +@@ -47,7 +47,7 @@ pub(crate) fn flush_region(start: usize, size: usize) { + let end = start + size; + let start = crate::util::unchecked_align_down(start, line_size); + for line in (start..end).step_by(line_size) { +- crate::dc!("cvau", line); ++ crate::dc!("cvac", line); + } + } else { + compile_error!("Unsupported target_arch") +-- +2.53.0 + diff --git a/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch b/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch new file mode 100644 index 0000000..07a859c --- /dev/null +++ b/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch @@ -0,0 +1,65 @@ +From 69a25763cdb46c8f23fe9eb976132acbe2af82d6 Mon Sep 17 00:00:00 2001 +From: Himanshu Arora +Date: Fri, 24 Oct 2025 16:47:37 +0000 +Subject: [PATCH] Fix ACCESS_MEDIA_LOCATION bypass via SAF picker + +When an app uses a picker to access media files, the picker should respect the app's permissions. Currently, it is possible to bypass the ACCESS_MEDIA_LOCATION permission and get unredacted location data. + +This change fixes this by having MediaProvider check the permissions of the app on whose behalf the media is being opened. When a `mediaCapabilitiesUid` is passed, MediaProvider now checks if that UID has the necessary permissions. + +Bug: 326211886 +Test: manual +Flag: EXEMPT BUGFIX +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:e5e47f93838e1e9a3a3a520f7c89229fc041a8c3 +Merged-In: I7ad51535d5ae8a3803162f688c4794edbbcfb167 +Change-Id: I7ad51535d5ae8a3803162f688c4794edbbcfb167 +--- + .../providers/media/MediaProvider.java | 20 +++++++++++++++++-- + 1 file changed, 18 insertions(+), 2 deletions(-) + +diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java +index 0d75628c5..4a09b7500 100644 +--- a/src/com/android/providers/media/MediaProvider.java ++++ b/src/com/android/providers/media/MediaProvider.java +@@ -10253,7 +10253,7 @@ public class MediaProvider extends ContentProvider { + + // Figure out if we need to redact contents + final boolean redactionNeeded = isRedactionNeededForOpenViaContentResolver(redactedUri, +- ownerPackageName, file); ++ ownerPackageName, file, opts); + long[] redactionRanges; + try { + redactionRanges = redactionNeeded ? RedactionUtils.getRedactionRanges(file) +@@ -10372,12 +10372,28 @@ public class MediaProvider extends ContentProvider { + } + + private boolean isRedactionNeededForOpenViaContentResolver(Uri redactedUri, +- String ownerPackageName, File file) { ++ String ownerPackageName, File file, Bundle opts) { + // Redacted Uris should always redact information + if (redactedUri != null) { + return true; + } + ++ // If the caller provides a media capabilities UID, we check if that UID has the ++ // PERMISSION_IS_REDACTION_NEEDED permission. If so, we redact the data. This is ++ // used for cases where an app is acting on behalf of another app, and we need ++ // to respect the capabilities of the app for which the action is being performed. ++ if (opts != null) { ++ final int mediaCapabilitiesUid = opts.getInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID); ++ if (mediaCapabilitiesUid > 0) { ++ final LocalCallingIdentity identity = LocalCallingIdentity.fromExternal( ++ getContext(), ++ mUserCache, mediaCapabilitiesUid, null, null); ++ if (identity.hasPermission(PERMISSION_IS_REDACTION_NEEDED)) { ++ return true; ++ } ++ } ++ } ++ + final boolean callerIsOwner = Objects.equals(getCallingPackageOrSelf(), ownerPackageName); + if (callerIsOwner) { + return false; +-- +2.53.0 + diff --git a/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch b/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch new file mode 100644 index 0000000..c4b3b8d --- /dev/null +++ b/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch @@ -0,0 +1,91 @@ +From 119013a3d7e8f1eab671bce4c6a85748752081ed Mon Sep 17 00:00:00 2001 +From: Garvita Jain +Date: Mon, 1 Dec 2025 11:02:37 +0000 +Subject: [PATCH] Throw exception on MediaStore createRequest for non-existent + uri + +Throw IllegalArgument exception if a caller requests +CREATE_WRITE/DELETE/.._REQUEST for a uri that does not exist in the +Files table at the time of request. + +BUG: 418773439 +Test: atest MediaProviderTests +Flag: EXEMPT bugfix +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:268fc3fb0a438abbc710687a9590cb80b3c0e8bc +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:973f11ad05506303f6bbb3fd6275c3a2b824b2e8 +Merged-In: I00a3b15d8dbcd19afaec3980f7d7a07122bef640 +Change-Id: I00a3b15d8dbcd19afaec3980f7d7a07122bef640 +--- + .../android/providers/media/MediaProvider.java | 16 ++++++++++++++++ + .../providers/media/MediaProviderTest.java | 18 ++++++++++++++++-- + 2 files changed, 32 insertions(+), 2 deletions(-) + +diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java +index 4a09b7500..b9fc95667 100644 +--- a/src/com/android/providers/media/MediaProvider.java ++++ b/src/com/android/providers/media/MediaProvider.java +@@ -8383,6 +8383,22 @@ public class MediaProvider extends ContentProvider { + } + } + ++ // Do not allow to create request if the list contains a uri which does not exist ++ final LocalCallingIdentity token = clearLocalCallingIdentity(); ++ try { ++ for (Uri uri : uris) { ++ try (Cursor c = queryForSingleItem(uri, new String[]{FileColumns._ID}, null, null, ++ null)) { ++ // queryForSingleItem method throws FileNotFoundException if no items were ++ // found, or multiple items were found, or there was trouble reading the data. ++ } catch (FileNotFoundException e) { ++ throw new IllegalArgumentException("Invalid Uri: " + uri, e); ++ } ++ } ++ } finally { ++ restoreLocalCallingIdentity(token); ++ } ++ + final Context context = getContext(); + final Intent intent = new Intent(method, null, context, PermissionActivity.class); + extras.putInt(EXTRA_CALLING_PACKAGE_UID, getCallingUidOrSelf()); +diff --git a/tests/src/com/android/providers/media/MediaProviderTest.java b/tests/src/com/android/providers/media/MediaProviderTest.java +index 21cb56e48..660276ce3 100644 +--- a/tests/src/com/android/providers/media/MediaProviderTest.java ++++ b/tests/src/com/android/providers/media/MediaProviderTest.java +@@ -39,6 +39,7 @@ import static org.junit.Assert.assertNotNull; + import static org.junit.Assert.assertNull; + import static org.junit.Assert.assertTrue; + import static org.junit.Assert.fail; ++import static org.junit.Assume.assumeTrue; + + import android.Manifest; + import android.content.ContentInterface; +@@ -334,11 +335,24 @@ public class MediaProviderTest { + */ + @Test + public void testCreateRequest() throws Exception { +- final Collection uris = Arrays.asList( +- MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY, 42)); ++ final ContentValues values = new ContentValues(); ++ values.put(MediaColumns.DISPLAY_NAME, "test.mp3"); ++ values.put(MediaColumns.MIME_TYPE, "audio/mpeg"); ++ final Uri uri = sIsolatedResolver.insert( ++ MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), values); ++ assumeTrue(uri != null); ++ final Collection uris = List.of(uri); + assertNotNull(MediaStore.createWriteRequest(sIsolatedResolver, uris)); + } + ++ @Test ++ public void testCreateRequest_invalidUri_throwsException() throws Exception { ++ final Collection uris = List.of( ++ MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY, 42)); ++ assertThrows(IllegalArgumentException.class, ++ () -> MediaStore.createWriteRequest(sIsolatedResolver, uris)); ++ } ++ + @Test + public void testRequestThumbnail_noAccess_throwsSecurityException() throws Exception { + final File dir = Environment +-- +2.53.0 + From 6754e87947a889c22c25b94ce32f12d5e77cc599 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 1 Apr 2026 18:17:55 -0400 Subject: [PATCH 2/7] Fix --- apply.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apply.sh b/apply.sh index e0cfd10..1be2509 100755 --- a/apply.sh +++ b/apply.sh @@ -92,6 +92,8 @@ apply_patches() { } reset_one() { + [ "$(basename "$PWD")" == "patches" ] && return + check_baseline() { current=$(git rev-parse HEAD) baseline=$REPO_LREV @@ -160,7 +162,7 @@ reset_patches() { skipped_count=$(grep -c "^SKIP_REPO:" /tmp/reset_output.txt 2>/dev/null || echo 0) - if [ "$skipped_count" -gt 0 ]; then + if [ "$skipped_count" != "0" ]; then echo "" echo "Skipped the following repositories (not reset):" grep "^SKIP_REPO:" /tmp/reset_output.txt | sed 's/SKIP_REPO://' From 1d8f2a5806648ac5b961b26f50926de97be5b7d9 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 1 Apr 2026 18:33:35 -0400 Subject: [PATCH 3/7] Use 3-way merge --- apply.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apply.sh b/apply.sh index 1be2509..c3831ba 100755 --- a/apply.sh +++ b/apply.sh @@ -33,7 +33,7 @@ apply_asb_patches() { cd "$target_dir" - if ! git am "$patch_dir"/*.patch 2>/dev/null; then + if ! git am -3 "$patch_dir"/*.patch 2>/dev/null; then echo "Failed to apply ASB patches to $repo_path" echo "ASB patch application failed, aborting." exit 1 @@ -69,7 +69,7 @@ apply_petergsi_patches() { git tag -d "before-petergsi" 2>/dev/null || true git tag "before-petergsi" 2>/dev/null || true - if ! git am "$patch_dir"/*.patch 2>/dev/null; then + if ! git am -3 "$patch_dir"/*.patch 2>/dev/null; then echo "$repo_path" >> "$failed_file" echo "Failed to apply patches to $repo_path" fi From 8ac4e813d3fcd807661ddcbdc482b7c27d5ce4a4 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 1 Apr 2026 18:37:11 -0400 Subject: [PATCH 4/7] Redownload patches to include exact timestamps... --- ..._21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch} | 0 ..._a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch} | 0 ...sandboxed_UIDs_are_treated_as_untrusted_in_b51a58ecec96.patch} | 0 ...t_untrusted_proxys_from_specifying_proxied_a4523e227733.patch} | 0 ...800_Trim_permission_permission_group_names_e770e9f02341.patch} | 0 ...lock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch} | 0 ...nputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch} | 0 ...-0800_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch} | 0 ...oadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch} | 0 ...launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch} | 0 ...ly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch} | 0 ...strict_about_content_types_for_message_arr_014dea279c49.patch} | 0 ...move_any_revoked_associations_after_reboot_924df83d73d9.patch} | 0 ...e_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch} | 0 ...tion_fix_update_uninstallation_with_shared_51368785bd09.patch} | 0 ..._to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch} | 0 ...00_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch} | 0 ...rmission_of_the_calling_package_in_multi-p_48af8a13dd12.patch} | 0 ...lock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch} | 0 ...epvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch} | 0 ...CCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch} | 0 ...ception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch} | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename asb/2026-03/external/dng_sdk/{2026-01-07_21-35_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch => 2026-01-07_21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch} (100%) rename asb/2026-03/frameworks/base/{2025-10-10_14-16_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch => 2025-10-10_14-16-14_-0700_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-37_Ensure_sandboxed_UIDs_are_treated_as_untrusted_in_b51a58ecec96.patch => 2026-01-07_21-37-16_-0800_Ensure_sandboxed_UIDs_are_treated_as_untrusted_in_b51a58ecec96.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-37_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch => 2026-01-07_21-37-24_-0800_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-37_Trim_permission_permission_group_names_e770e9f02341.patch => 2026-01-07_21-37-33_-0800_Trim_permission_permission_group_names_e770e9f02341.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-37_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch => 2026-01-07_21-37-49_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-37_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch => 2026-01-07_21-37-56_-0800_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-38_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch => 2026-01-07_21-38-03_-0800_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-38_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch => 2026-01-07_21-38-11_-0800_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-38_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch => 2026-01-07_21-38-18_-0800_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-38_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch => 2026-01-07_21-38-25_-0800_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-39_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch => 2026-01-07_21-39-14_-0800_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-39_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch => 2026-01-07_21-39-30_-0800_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-07_21-39_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch => 2026-01-07_21-39-44_-0800_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch} (100%) rename asb/2026-03/frameworks/base/{2026-01-15_06-49_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch => 2026-01-15_06-49-40_-0800_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch} (100%) rename asb/2026-03/frameworks/native/{2026-01-07_21-39_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch => 2026-01-07_21-39-59_-0800_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch} (100%) rename asb/2026-03/hardware/st/nfc/{2026-01-07_21-40_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch => 2026-01-07_21-40-07_-0800_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch} (100%) rename asb/2026-03/packages/apps/Settings/{2026-01-07_21-40_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch => 2026-01-07_21-40-30_-0800_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch} (100%) rename asb/2026-03/packages/apps/Settings/{2026-01-07_21-40_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch => 2026-01-07_21-40-38_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch} (100%) rename asb/2026-03/packages/modules/Virtualization/{2026-01-07_21-42_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch => 2026-01-07_21-42-04_-0800_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch} (100%) rename asb/2026-03/packages/providers/MediaProvider/{2026-01-07_21-42_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch => 2026-01-07_21-42-46_-0800_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch} (100%) rename asb/2026-03/packages/providers/MediaProvider/{2026-01-07_21-42_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch => 2026-01-07_21-42-52_-0800_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch} (100%) diff --git a/asb/2026-03/external/dng_sdk/2026-01-07_21-35_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch b/asb/2026-03/external/dng_sdk/2026-01-07_21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch similarity index 100% rename from asb/2026-03/external/dng_sdk/2026-01-07_21-35_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch rename to asb/2026-03/external/dng_sdk/2026-01-07_21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch diff --git a/asb/2026-03/frameworks/base/2025-10-10_14-16_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch b/asb/2026-03/frameworks/base/2025-10-10_14-16-14_-0700_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch similarity index 100% rename from asb/2026-03/frameworks/base/2025-10-10_14-16_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch rename to asb/2026-03/frameworks/base/2025-10-10_14-16-14_-0700_Enforce_a_hard_limit_for_the_size_of_images_to_be_d6df825fda3a.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37_Ensure_sandboxed_UIDs_are_treated_as_untrusted_in_b51a58ecec96.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37-16_-0800_Ensure_sandboxed_UIDs_are_treated_as_untrusted_in_b51a58ecec96.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-37_Ensure_sandboxed_UIDs_are_treated_as_untrusted_in_b51a58ecec96.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-37-16_-0800_Ensure_sandboxed_UIDs_are_treated_as_untrusted_in_b51a58ecec96.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37-24_-0800_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-37_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-37-24_-0800_Prohibit_untrusted_proxys_from_specifying_proxied_a4523e227733.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37_Trim_permission_permission_group_names_e770e9f02341.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37-33_-0800_Trim_permission_permission_group_names_e770e9f02341.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-37_Trim_permission_permission_group_names_e770e9f02341.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-37-33_-0800_Trim_permission_permission_group_names_e770e9f02341.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37-49_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-37_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-37-49_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_cea235f00865.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37-56_-0800_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-37_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-37-56_-0800_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38-03_-0800_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-38_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-38-03_-0800_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38-11_-0800_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-38_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-38-11_-0800_Handle_loadDescription_OutOfMemoryError_in_DeviceA_cee45869c491.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38-18_-0800_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-38_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-38-18_-0800_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38-25_-0800_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-38_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-38-25_-0800_Explicitly_unset_INSTALL_FROM_MANAGED_USER_OR_PROF_09055276288a.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-39_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch b/asb/2026-03/frameworks/base/2026-01-07_21-39-14_-0800_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-39_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-39-14_-0800_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-39_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch b/asb/2026-03/frameworks/base/2026-01-07_21-39-30_-0800_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-39_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-39-30_-0800_Remove_any_revoked_associations_after_reboot_924df83d73d9.patch diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-39_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch b/asb/2026-03/frameworks/base/2026-01-07_21-39-44_-0800_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-07_21-39_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch rename to asb/2026-03/frameworks/base/2026-01-07_21-39-44_-0800_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch diff --git a/asb/2026-03/frameworks/base/2026-01-15_06-49_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch b/asb/2026-03/frameworks/base/2026-01-15_06-49-40_-0800_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch similarity index 100% rename from asb/2026-03/frameworks/base/2026-01-15_06-49_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch rename to asb/2026-03/frameworks/base/2026-01-15_06-49-40_-0800_UidMigration_fix_update_uninstallation_with_shared_51368785bd09.patch diff --git a/asb/2026-03/frameworks/native/2026-01-07_21-39_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch b/asb/2026-03/frameworks/native/2026-01-07_21-39-59_-0800_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch similarity index 100% rename from asb/2026-03/frameworks/native/2026-01-07_21-39_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch rename to asb/2026-03/frameworks/native/2026-01-07_21-39-59_-0800_Clip_to_layer_bounds_when_drawing_blur_regions_c81cf361489e.patch diff --git a/asb/2026-03/hardware/st/nfc/2026-01-07_21-40_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch b/asb/2026-03/hardware/st/nfc/2026-01-07_21-40-07_-0800_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch similarity index 100% rename from asb/2026-03/hardware/st/nfc/2026-01-07_21-40_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch rename to asb/2026-03/hardware/st/nfc/2026-01-07_21-40-07_-0800_NFC_Fix_use-after-free_in_eventCallback_c6da9eeb710c.patch diff --git a/asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch b/asb/2026-03/packages/apps/Settings/2026-01-07_21-40-30_-0800_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch similarity index 100% rename from asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch rename to asb/2026-03/packages/apps/Settings/2026-01-07_21-40-30_-0800_Check_permission_of_the_calling_package_in_multi-p_48af8a13dd12.patch diff --git a/asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch b/asb/2026-03/packages/apps/Settings/2026-01-07_21-40-38_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch similarity index 100% rename from asb/2026-03/packages/apps/Settings/2026-01-07_21-40_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch rename to asb/2026-03/packages/apps/Settings/2026-01-07_21-40-38_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch diff --git a/asb/2026-03/packages/modules/Virtualization/2026-01-07_21-42_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch b/asb/2026-03/packages/modules/Virtualization/2026-01-07_21-42-04_-0800_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch similarity index 100% rename from asb/2026-03/packages/modules/Virtualization/2026-01-07_21-42_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch rename to asb/2026-03/packages/modules/Virtualization/2026-01-07_21-42-04_-0800_vmbasepvmfw_aarch64_Clean_dcache_to_PoC_not_PoU_f0271f36388e.patch diff --git a/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch b/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42-46_-0800_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch similarity index 100% rename from asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch rename to asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42-46_-0800_Fix_ACCESS_MEDIA_LOCATION_bypass_via_SAF_picker_69a25763cdb4.patch diff --git a/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch b/asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42-52_-0800_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch similarity index 100% rename from asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch rename to asb/2026-03/packages/providers/MediaProvider/2026-01-07_21-42-52_-0800_Throw_exception_on_MediaStore_createRequest_for_no_119013a3d7e8.patch From 0baa2212766900049b95a36ba969b27d2cd1e65d Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 1 Apr 2026 18:37:46 -0400 Subject: [PATCH 5/7] Switch to LineageOS's dng_sdk patch because AOSP's doesn't apply --- ...ate_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch | 182 ++++++++++++------ 1 file changed, 124 insertions(+), 58 deletions(-) diff --git a/asb/2026-03/external/dng_sdk/2026-01-07_21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch b/asb/2026-03/external/dng_sdk/2026-01-07_21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch index ac11b0e..f67859f 100644 --- a/asb/2026-03/external/dng_sdk/2026-01-07_21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch +++ b/asb/2026-03/external/dng_sdk/2026-01-07_21-35-05_-0800_Update_to_DNG_SDK_171_2410_6b5cf2a88ebd.patch @@ -1,4 +1,4 @@ -From 6b5cf2a88ebd2b099e56d0d4717e962772ff9067 Mon Sep 17 00:00:00 2001 +From ce3416390c7d1e60d07e03b66b5c34b9a34e7d08 Mon Sep 17 00:00:00 2001 From: John Reck Date: Tue, 9 Dec 2025 11:40:14 -0500 Subject: [PATCH] Update to DNG SDK 1.7.1 2410 @@ -6,7 +6,6 @@ Subject: [PATCH] Update to DNG SDK 1.7.1 2410 Bug: 465781139 Test: make Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:403d0a45641560631b0d6a9653c94a22163e3008 -Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:2101eead79db44325953c0350db9bf1c868a44f0 Merged-In: Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8 Change-Id: Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8 --- @@ -23,7 +22,7 @@ Change-Id: Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8 source/dng_exceptions.cpp | 6 + source/dng_exceptions.h | 88 +- source/dng_exif.cpp | 4 +- - source/dng_file_stream.cpp | 8 +- + source/dng_file_stream.cpp | 33 +- source/dng_fingerprint.cpp | 86 +- source/dng_fingerprint.h | 128 ++- source/dng_flags.h | 125 ++- @@ -37,7 +36,7 @@ Change-Id: Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8 source/dng_image_writer.h | 22 +- source/dng_info.cpp | 31 +- source/dng_info.h | 2 + - source/dng_jpeg_image.cpp | 19 +- + source/dng_jpeg_image.cpp | 26 +- source/dng_jxl.cpp | 1217 ++++++++++++++++++++------- source/dng_jxl.h | 4 + source/dng_linearization_info.cpp | 89 +- @@ -71,7 +70,7 @@ Change-Id: Ia850e7c3a3ab1b35dd619cc78159663df3b2b2c8 source/dng_xmp.h | 9 + source/dng_xmp_sdk.cpp | 37 +- source/dng_xmp_sdk.h | 5 + - 61 files changed, 2721 insertions(+), 694 deletions(-) + 61 files changed, 2726 insertions(+), 721 deletions(-) diff --git a/source/dng_abort_sniffer.cpp b/source/dng_abort_sniffer.cpp index 8590546..a7f1d32 100644 @@ -138,10 +137,10 @@ index 2a7bc47..497fb1e 100644 /// will be called if one is pending. diff --git a/source/dng_area_task.cpp b/source/dng_area_task.cpp -index 7eac4a4..21a9401 100644 +index f27c48f..65822c4 100644 --- a/source/dng_area_task.cpp +++ b/source/dng_area_task.cpp -@@ -552,6 +552,7 @@ class dng_range_parallel_func_task: public dng_range_parallel_task +@@ -562,6 +562,7 @@ class dng_range_parallel_func_task: public dng_range_parallel_task r.fBegin = startIndex; r.fEnd = stopIndex; r.fSniffer = sniffer; @@ -150,10 +149,10 @@ index 7eac4a4..21a9401 100644 fFunc (r); diff --git a/source/dng_area_task.h b/source/dng_area_task.h -index e6d6db7..0bd24d4 100644 +index 64f789c..f72d457 100644 --- a/source/dng_area_task.h +++ b/source/dng_area_task.h -@@ -369,6 +369,7 @@ class dng_range_parallel_task: public dng_area_task, dng_uncopyable +@@ -375,6 +375,7 @@ class dng_range_parallel_task: public dng_area_task, dng_uncopyable int32 fBegin; int32 fEnd; dng_abort_sniffer *fSniffer; @@ -925,10 +924,20 @@ index cdee762..36ffeed 100644 } diff --git a/source/dng_file_stream.cpp b/source/dng_file_stream.cpp -index 39242a0..8fb984b 100644 +index e364eb5..86b20e2 100644 --- a/source/dng_file_stream.cpp +++ b/source/dng_file_stream.cpp -@@ -120,11 +120,9 @@ dng_file_stream::dng_file_stream (int fd, +@@ -100,8 +100,7 @@ dng_file_stream::dng_file_stream (int fd, + + #if qDNGValidate + +- ReportError ("Unable to open file", +- filename); ++ ReportError ("Unable to open file"); + + ThrowSilentError (); + +@@ -121,36 +120,12 @@ dng_file_stream::dng_file_stream (int fd, bool output, uint32 bufferSize) @@ -943,6 +952,31 @@ index 39242a0..8fb984b 100644 { +- // Note: Use dup here as caller is responsible for separately managing fd. +- +- fFile = fdopen (dup (fd), output ? "wb" : "rb"); +- +- if (!fFile) +- { +- +- #if qDNGValidate +- +- ReportError ("Unable to open file", +- filename); +- +- ThrowSilentError (); +- +- #else +- +- ThrowOpenFile (); +- +- #endif +- +- } +- + } + + /*****************************************************************************/ diff --git a/source/dng_fingerprint.cpp b/source/dng_fingerprint.cpp index 4dcd1f5..6113ca7 100644 --- a/source/dng_fingerprint.cpp @@ -1309,7 +1343,7 @@ index c19fb25..3d8fb75 100644 /*****************************************************************************/ diff --git a/source/dng_flags.h b/source/dng_flags.h -index f625725..bc764e2 100644 +index f16bfa2..ea34e18 100644 --- a/source/dng_flags.h +++ b/source/dng_flags.h @@ -19,6 +19,21 @@ @@ -1906,7 +1940,7 @@ index 60459d4..bdf2fa4 100644 public: diff --git a/source/dng_image_writer.cpp b/source/dng_image_writer.cpp -index bf9b20c..b97ab74 100644 +index 5f926ad..4452d57 100644 --- a/source/dng_image_writer.cpp +++ b/source/dng_image_writer.cpp @@ -167,7 +167,7 @@ static void SpoolAdobeData (dng_stream &stream, @@ -1986,7 +2020,7 @@ index bf9b20c..b97ab74 100644 dPtr += 32; -@@ -5142,7 +5151,7 @@ void dng_write_tiles_task::Process (uint32 /* threadIndex */, +@@ -5259,7 +5268,7 @@ void dng_write_tiles_task::Process (uint32 /* threadIndex */, tileStream.SetReadPosition (0); @@ -1995,7 +2029,7 @@ index bf9b20c..b97ab74 100644 tileStream.CopyToStream (md5stream, tileByteCount); -@@ -5188,8 +5197,7 @@ void dng_write_tiles_task::Process (uint32 /* threadIndex */, +@@ -5305,8 +5314,7 @@ void dng_write_tiles_task::Process (uint32 /* threadIndex */, if (fNeedDigest) { @@ -2005,7 +2039,7 @@ index bf9b20c..b97ab74 100644 } -@@ -5550,7 +5558,7 @@ void dng_image_writer::WriteImage (dng_host &host, +@@ -5667,7 +5675,7 @@ void dng_image_writer::WriteImage (dng_host &host, subTileBlockBuffer.Reset (host.Allocate (uncompressedSize.Get ())); } @@ -2014,7 +2048,7 @@ index bf9b20c..b97ab74 100644 const bool needDigest = (outDigest != nullptr); -@@ -5628,7 +5636,7 @@ void dng_image_writer::WriteImage (dng_host &host, +@@ -5745,7 +5753,7 @@ void dng_image_writer::WriteImage (dng_host &host, tileStream.SetReadPosition (0); @@ -2023,7 +2057,7 @@ index bf9b20c..b97ab74 100644 tileStream.CopyToStream (md5stream, tileByteCount); -@@ -5636,8 +5644,7 @@ void dng_image_writer::WriteImage (dng_host &host, +@@ -5753,8 +5761,7 @@ void dng_image_writer::WriteImage (dng_host &host, // Update the overall digest. @@ -2033,7 +2067,7 @@ index bf9b20c..b97ab74 100644 // Copy the tile data to the main stream. -@@ -6481,7 +6488,6 @@ void dng_image_writer::CleanUpMetadata (dng_host &host, +@@ -6598,7 +6605,6 @@ void dng_image_writer::CleanUpMetadata (dng_host &host, #endif // qDNGUseXMP } @@ -2041,7 +2075,7 @@ index bf9b20c..b97ab74 100644 /*****************************************************************************/ -@@ -6547,7 +6553,10 @@ void dng_image_writer::WriteTIFF (dng_host &host, +@@ -6664,7 +6670,10 @@ void dng_image_writer::WriteTIFF (dng_host &host, bool hasTransparency, bool allowBigTIFF, const dng_image *gainMapImage, @@ -2053,7 +2087,7 @@ index bf9b20c..b97ab74 100644 { const void *profileData = NULL; -@@ -6579,7 +6588,10 @@ void dng_image_writer::WriteTIFF (dng_host &host, +@@ -6696,7 +6705,10 @@ void dng_image_writer::WriteTIFF (dng_host &host, hasTransparency, allowBigTIFF, gainMapImage, @@ -2065,7 +2099,7 @@ index bf9b20c..b97ab74 100644 } -@@ -6711,7 +6723,10 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, +@@ -6828,7 +6840,10 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, bool hasTransparency, bool allowBigTIFF, const dng_image *gainMapImage, @@ -2077,7 +2111,7 @@ index bf9b20c..b97ab74 100644 { // Force writing all TIFF files in BigTIFF format. -@@ -6869,7 +6884,12 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, +@@ -6986,7 +7001,12 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, AutoPtr gainMapImageIFD; @@ -2091,7 +2125,7 @@ index bf9b20c..b97ab74 100644 if (hasGainMap) { -@@ -6889,6 +6909,26 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, +@@ -7006,6 +7026,26 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, gainMapTagSet.Reset (new dng_basic_tag_set (gainMapIFD, *gainMapImageIFD)); @@ -2118,7 +2152,7 @@ index bf9b20c..b97ab74 100644 } // Resolution. -@@ -7055,7 +7095,7 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, +@@ -7172,7 +7212,7 @@ void dng_image_writer::WriteTIFFWithProfile (dng_host &host, // Write the gain map image. @@ -2127,7 +2161,7 @@ index bf9b20c..b97ab74 100644 { WriteImage (host, -@@ -7170,7 +7210,10 @@ void dng_image_writer::WriteDNG (dng_host &host, +@@ -7287,7 +7327,10 @@ void dng_image_writer::WriteDNG (dng_host &host, bool uncompressed, bool allowBigTIFF, const dng_image *gainMapImage, @@ -2139,7 +2173,7 @@ index bf9b20c..b97ab74 100644 { WriteDNGWithMetadata (host, -@@ -7182,7 +7225,10 @@ void dng_image_writer::WriteDNG (dng_host &host, +@@ -7299,7 +7342,10 @@ void dng_image_writer::WriteDNG (dng_host &host, uncompressed, allowBigTIFF, gainMapImage, @@ -2151,7 +2185,7 @@ index bf9b20c..b97ab74 100644 } -@@ -7380,7 +7426,10 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, +@@ -7497,7 +7543,10 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, const bool uncompressed, bool allowBigTIFF, const dng_image *gainMapImage, @@ -2163,7 +2197,7 @@ index bf9b20c..b97ab74 100644 { // Force writing all DNG files in 64-bit format. -@@ -7583,6 +7632,8 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, +@@ -7700,6 +7749,8 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, bool hasProfileWith_1_6_Features = false; bool hasProfileWith_1_7_Features = false; @@ -2172,7 +2206,7 @@ index bf9b20c..b97ab74 100644 // Create the main IFD. -@@ -7606,6 +7657,9 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, +@@ -7723,6 +7774,9 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, profileSet.Reset (new profile_tag_set (host, mainIFD, mainProfile)); @@ -2182,7 +2216,7 @@ index bf9b20c..b97ab74 100644 colorSet.Reset (new color_tag_set (mainIFD, negative)); -@@ -7677,6 +7731,10 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, +@@ -7794,6 +7848,10 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, AutoPtr gainMapImageIFD; @@ -2193,7 +2227,7 @@ index bf9b20c..b97ab74 100644 const bool hasGainMap = (gainMapImage != nullptr); if (hasGainMap) -@@ -7692,6 +7750,31 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, +@@ -7809,6 +7867,31 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, gainMapTagSet.Reset (new dng_basic_tag_set (gainMapIFD, *gainMapImageIFD)); @@ -2225,7 +2259,7 @@ index bf9b20c..b97ab74 100644 } -@@ -8516,7 +8599,7 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, +@@ -8633,7 +8716,7 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, } tag_uint8_ptr tagRawImageDigest (useNewDigest ? tcNewRawImageDigest : tcRawImageDigest, @@ -2234,7 +2268,7 @@ index bf9b20c..b97ab74 100644 16); if (mainImageRawImageDigest.IsValid ()) -@@ -8533,7 +8616,7 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, +@@ -8650,7 +8733,7 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, const auto rawDataUniqueID = negative.RawDataUniqueID (); tag_uint8_ptr tagRawDataUniqueID (tcRawDataUniqueID, @@ -2243,7 +2277,7 @@ index bf9b20c..b97ab74 100644 16); if (rawDataUniqueID.IsValid ()) -@@ -8562,7 +8645,7 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, +@@ -8679,7 +8762,7 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, negative.OriginalRawFileData ()); tag_uint8_ptr tagOriginalRawFileDigest (tcOriginalRawFileDigest, @@ -2252,7 +2286,7 @@ index bf9b20c..b97ab74 100644 16); if (negative.OriginalRawFileData ()) -@@ -8717,12 +8800,10 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, +@@ -8834,12 +8917,10 @@ void dng_image_writer::WriteDNGWithMetadata (dng_host &host, if (negative.HasProfileGainTableMap () && @@ -2393,19 +2427,54 @@ index 068b447..a5890f3 100644 std::vector fIFD; diff --git a/source/dng_jpeg_image.cpp b/source/dng_jpeg_image.cpp -index a9373ea..c8e97cc 100644 +index f0c8cbe..940f824 100644 --- a/source/dng_jpeg_image.cpp +++ b/source/dng_jpeg_image.cpp -@@ -21,7 +21,7 @@ +@@ -21,6 +21,7 @@ #include "dng_uncopyable.h" #include --#include +#include /*****************************************************************************/ -@@ -350,10 +350,10 @@ dng_fingerprint dng_lossy_compressed_image::FindDigest (dng_host &host) const +@@ -182,7 +183,7 @@ void dng_compressed_image_tiles::EncodeTiles (dng_host &host, + + snprintf (message, + sizeof (message), +- "JXL encode %u by %u pixels, distance = %.2f, effort = %d, size = %llu", ++ "JXL encode %u by %u pixels, distance = %.2f, effort = %d, size = %" PRIu64, + image.Width (), + image.Height (), + ifd.fJXLEncodeSettings->Distance (), +@@ -196,7 +197,7 @@ void dng_compressed_image_tiles::EncodeTiles (dng_host &host, + + snprintf (message, + sizeof (message), +- "Lossy JPEG encode %u by %u pixels, quality = %d, size = %llu", ++ "Lossy JPEG encode %u by %u pixels, quality = %d, size = %" PRIu64, + image.Width (), + image.Height (), + ifd.fCompressionQuality, +@@ -209,7 +210,7 @@ void dng_compressed_image_tiles::EncodeTiles (dng_host &host, + + snprintf (message, + sizeof (message), +- "Deflate encode %u by %u pixels, quality = %d, size = %llu", ++ "Deflate encode %u by %u pixels, quality = %d, size = %" PRIu64, + image.Width (), + image.Height (), + ifd.fCompressionQuality, +@@ -222,7 +223,7 @@ void dng_compressed_image_tiles::EncodeTiles (dng_host &host, + + snprintf (message, + sizeof (message), +- "EncodeTiles %u by %u pixels, size = %llu", ++ "EncodeTiles %u by %u pixels, size = %" PRIu64, + image.Width (), + image.Height (), + NonHeaderSize ()); +@@ -349,10 +350,10 @@ dng_fingerprint dng_lossy_compressed_image::FindDigest (dng_host &host) const for (int32 i = ra.fBegin; i < ra.fEnd; i++) { @@ -2419,7 +2488,7 @@ index a9373ea..c8e97cc 100644 digests [i] = printer.Result (); -@@ -369,11 +369,10 @@ dng_fingerprint dng_lossy_compressed_image::FindDigest (dng_host &host) const +@@ -368,11 +369,10 @@ dng_fingerprint dng_lossy_compressed_image::FindDigest (dng_host &host) const { @@ -2433,7 +2502,7 @@ index a9373ea..c8e97cc 100644 return printer.Result (); -@@ -486,10 +485,10 @@ void dng_jpeg_image::DoFindDigest (dng_host & /* host */, +@@ -485,10 +485,10 @@ void dng_jpeg_image::DoFindDigest (dng_host & /* host */, if (fJPEGTables.Get ()) { @@ -2448,7 +2517,7 @@ index a9373ea..c8e97cc 100644 digests.push_back (printer.Result ()); diff --git a/source/dng_jxl.cpp b/source/dng_jxl.cpp -index 17e28c1..eabd114 100644 +index f136e40..efba363 100644 --- a/source/dng_jxl.cpp +++ b/source/dng_jxl.cpp @@ -31,6 +31,7 @@ @@ -3932,15 +4001,15 @@ index 17e28c1..eabd114 100644 fColorSpaceInfo.fICCProfile.Reset (profileBlock.Release ()); -@@ -3511,4 +4126,4 @@ bool SupportsJXL (const dng_image &image) - return false; +@@ -3529,4 +4144,4 @@ bool SupportsJXL (const dng_image &image) + } -/*****************************************************************************/ +/*****************************************************************************/ \ No newline at end of file diff --git a/source/dng_jxl.h b/source/dng_jxl.h -index f5b3fef..7407d3d 100644 +index 7f05d2a..cc17814 100644 --- a/source/dng_jxl.h +++ b/source/dng_jxl.h @@ -37,6 +37,10 @@ @@ -4510,7 +4579,7 @@ index 56e1fd0..38ee103 100644 } diff --git a/source/dng_negative.cpp b/source/dng_negative.cpp -index 4564a8c..2126416 100644 +index 3aaa136..183ac59 100644 --- a/source/dng_negative.cpp +++ b/source/dng_negative.cpp @@ -578,7 +578,7 @@ dng_fingerprint dng_metadata::IPTCDigest (bool includePadding) const @@ -4746,7 +4815,7 @@ index 4564a8c..2126416 100644 } -@@ -7342,6 +7354,16 @@ void dng_negative::ResizeTransparencyToMatchStage3 (dng_host &host, +@@ -7530,6 +7542,16 @@ void dng_negative::ResizeTransparencyToMatchStage3 (dng_host &host, (TransparencyMask ()->PixelType () != ttByte && convertTo8Bit)) { @@ -4763,7 +4832,7 @@ index 4564a8c..2126416 100644 AutoPtr newMask (host.Make_dng_image (fStage3Image->Bounds (), 1, convertTo8Bit ? -@@ -7362,7 +7384,9 @@ void dng_negative::ResizeTransparencyToMatchStage3 (dng_host &host, +@@ -7550,7 +7572,9 @@ void dng_negative::ResizeTransparencyToMatchStage3 (dng_host &host, { fRawTransparencyMaskBitDepth = 8; } @@ -4775,10 +4844,10 @@ index 4564a8c..2126416 100644 } diff --git a/source/dng_negative.h b/source/dng_negative.h -index 786329a..f470f00 100644 +index d5ee5be..28ad828 100644 --- a/source/dng_negative.h +++ b/source/dng_negative.h -@@ -2132,7 +2132,8 @@ class dng_negative +@@ -2128,7 +2128,8 @@ class dng_negative // Returns the camera profile to embed when saving to DNG. bool GetProfileToEmbed (const dng_metadata &metadata, @@ -4788,7 +4857,7 @@ index 786329a..f470f00 100644 // API for AsShotProfileName. -@@ -2702,6 +2703,13 @@ class dng_negative +@@ -2698,6 +2699,13 @@ class dng_negative // Returns the raw image data. const dng_image & RawImage () const; @@ -4802,7 +4871,7 @@ index 786329a..f470f00 100644 // Returns the raw image black level in 16-bit space. -@@ -3143,7 +3151,8 @@ class dng_negative +@@ -3139,7 +3147,8 @@ class dng_negative virtual bool GetProfileToEmbedFromList (const dng_profile_metadata_list &list, const dng_metadata &metadata, @@ -4912,7 +4981,7 @@ index 66c2ea0..990799b 100644 uint32 cols = area.W (); diff --git a/source/dng_preview.cpp b/source/dng_preview.cpp -index 9ea94df..40d1874 100644 +index 940cd3e..bc1cc50 100644 --- a/source/dng_preview.cpp +++ b/source/dng_preview.cpp @@ -76,7 +76,7 @@ dng_preview_tag_set::dng_preview_tag_set (dng_tiff_directory &directory, @@ -4924,7 +4993,7 @@ index 9ea94df..40d1874 100644 16) , fColorSpaceTag (tcPreviewColorSpace, -@@ -723,6 +723,78 @@ dng_basic_tag_set * dng_raw_preview::AddTagSet (dng_host & /* host */, +@@ -764,6 +764,78 @@ dng_basic_tag_set * dng_raw_preview::AddTagSet (dng_host & /* host */, } /*****************************************************************************/ @@ -6104,6 +6173,3 @@ index fc485e0..bbb8f43 100644 extern const char *XMP_NS_CRX; extern const char *XMP_NS_DNG; --- -2.53.0 - From 65699c52f6256de755fe53294d6ad537ec5a92b3 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 1 Apr 2026 18:51:38 -0400 Subject: [PATCH 6/7] Fix apply.sh --- apply.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apply.sh b/apply.sh index c3831ba..2b93464 100755 --- a/apply.sh +++ b/apply.sh @@ -31,18 +31,21 @@ apply_asb_patches() { continue fi - cd "$target_dir" + pushd "$target_dir" if ! git am -3 "$patch_dir"/*.patch 2>/dev/null; then echo "Failed to apply ASB patches to $repo_path" echo "ASB patch application failed, aborting." exit 1 fi + + popd done } apply_petergsi_patches() { local patch_base="$(realpath petergsi)" + echo "petergsi directory: $patch_base" local failed_file="/tmp/apply_failed_repos_$$.txt" if [ ! -d "$patch_base" ]; then From 12ad124755d57cb729ad617e6169356f35637920 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 1 Apr 2026 18:51:45 -0400 Subject: [PATCH 7/7] Switch some March ASB patches to LineageOS versions because AOSP's doesn't apply Is this what AOSP quality is nowadays? --- ...ing_against_large_metad_2bca2265ff3e.patch | 41 +- ...utMethodSubtypeSafeList_a438ce172b44.patch | 71 ++-- ..._spoofing_via_FLAG_ACTI_c148b4fae634.patch | 25 +- ...t_types_for_message_arr_014dea279c49.patch | 46 ++- ...s_NLSes_etc_that_can_be_e363f8210456.patch | 353 ++++-------------- ...xpectedlly_turned_ON_af_7d8fbee887fc.patch | 47 ++- 6 files changed, 175 insertions(+), 408 deletions(-) diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-37-56_-0800_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch b/asb/2026-03/frameworks/base/2026-01-07_21-37-56_-0800_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch index ebaf486..c1d162d 100644 --- a/asb/2026-03/frameworks/base/2026-01-07_21-37-56_-0800_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch +++ b/asb/2026-03/frameworks/base/2026-01-07_21-37-56_-0800_Harden_InputMethodInfo_parsing_against_large_metad_2bca2265ff3e.patch @@ -1,4 +1,4 @@ -From 2bca2265ff3e26b09f9b31c31063147a94e4c5aa Mon Sep 17 00:00:00 2001 +From 304bb7de32dea642932c73e09ec1bb2ef6cbf3d5 Mon Sep 17 00:00:00 2001 From: Hiroki Sato Date: Mon, 20 Oct 2025 19:13:15 +0900 Subject: [PATCH] Harden InputMethodInfo parsing against large metadata @@ -23,19 +23,19 @@ Test: CtsInputMethodTestCases:{InputMethodRegistrationTest,InputMethodInfoTest} Test: InputMethodCoreTests:{InputMethodSubtypeArrayTest,InputMethodInfoTest} Flag: EXEMPT BUGFIX (cherry picked from commit 7afc13faace7cfafd0353482db33504c5e269d69) -Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:311c7f2c2b8b927571884765c7322a21f8115383 +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:75fd0e67bd2945d7314b56c24850bd9f1c2c4dbf Merged-In: I43f7be8eb80abeb39863a3b01d3a606beb90120c Change-Id: I43f7be8eb80abeb39863a3b01d3a606beb90120c --- - .../view/inputmethod/InputMethodInfo.java | 284 ++++++++++++++---- + .../view/inputmethod/InputMethodInfo.java | 280 ++++++++++++++---- .../view/inputmethod/InputMethodInfoTest.java | 98 ++++++ - 2 files changed, 323 insertions(+), 59 deletions(-) + 2 files changed, 321 insertions(+), 57 deletions(-) diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java -index 31c7b7b20ef7..f81e2fab44aa 100644 +index c9485d7d3b0f4..184fea5be0f57 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java -@@ -50,6 +50,8 @@ import android.util.Slog; +@@ -50,6 +50,8 @@ import android.util.Xml; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; @@ -44,7 +44,7 @@ index 31c7b7b20ef7..f81e2fab44aa 100644 import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -@@ -314,70 +316,72 @@ public final class InputMethodInfo implements Parcelable { +@@ -313,68 +315,70 @@ public InputMethodInfo(Context context, ResolveInfo service, "Meta-data does not start with input-method tag"); } @@ -52,10 +52,8 @@ index 31c7b7b20ef7..f81e2fab44aa 100644 - com.android.internal.R.styleable.InputMethod); - settingsActivityComponent = sa.getString( - com.android.internal.R.styleable.InputMethod_settingsActivity); -- if (Flags.imeSwitcherRevampApi()) { -- languageSettingsActivityComponent = sa.getString( -- com.android.internal.R.styleable.InputMethod_languageSettingsActivity); -- } +- languageSettingsActivityComponent = sa.getString( +- com.android.internal.R.styleable.InputMethod_languageSettingsActivity); - if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) - || (settingsActivityComponent != null - && settingsActivityComponent.length() @@ -71,10 +69,8 @@ index 31c7b7b20ef7..f81e2fab44aa 100644 + readTracker)) { + settingsActivityComponent = sa.getString( + com.android.internal.R.styleable.InputMethod_settingsActivity); -+ if (Flags.imeSwitcherRevampApi()) { -+ languageSettingsActivityComponent = sa.getString( -+ com.android.internal.R.styleable.InputMethod_languageSettingsActivity); -+ } ++ languageSettingsActivityComponent = sa.getString( ++ com.android.internal.R.styleable.InputMethod_languageSettingsActivity); + isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, + false); + isVirtualDeviceOnly = sa.getBoolean( @@ -171,7 +167,7 @@ index 31c7b7b20ef7..f81e2fab44aa 100644 .setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable .InputMethod_Subtype_label, 0)) .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable -@@ -402,12 +406,11 @@ public final class InputMethodInfo implements Parcelable { +@@ -399,12 +403,11 @@ public InputMethodInfo(Context context, ResolveInfo service, .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)) .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable .InputMethod_Subtype_isAsciiCapable, false)).build(); @@ -188,7 +184,7 @@ index 31c7b7b20ef7..f81e2fab44aa 100644 } } catch (NameNotFoundException | IndexOutOfBoundsException | NumberFormatException e) { throw new XmlPullParserException( -@@ -471,6 +474,11 @@ public final class InputMethodInfo implements Parcelable { +@@ -468,6 +471,11 @@ private static void validateXmlMetaData(@NonNull ServiceInfo si, @NonNull Resour return; } @@ -200,7 +196,7 @@ index 31c7b7b20ef7..f81e2fab44aa 100644 // Validate file size using InputStream.skip() long totalBytesSkipped = 0; // Loop to ensure we skip the required number of bytes, as a single -@@ -1132,4 +1140,162 @@ public final class InputMethodInfo implements Parcelable { +@@ -1128,4 +1136,162 @@ public InputMethodInfo[] newArray(int size) { public int describeContents() { return 0; } @@ -364,7 +360,7 @@ index 31c7b7b20ef7..f81e2fab44aa 100644 + } } diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java -index dfe7d0306905..a7b2bba045dc 100644 +index 87333dd31b8d3..1ccc9e5483298 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java @@ -16,18 +16,27 @@ @@ -395,7 +391,7 @@ index dfe7d0306905..a7b2bba045dc 100644 import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -@@ -38,6 +47,7 @@ import com.android.frameworks.inputmethodcoretests.R; +@@ -38,6 +47,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -403,7 +399,7 @@ index dfe7d0306905..a7b2bba045dc 100644 @SmallTest @RunWith(AndroidJUnit4.class) -@@ -133,6 +143,94 @@ public class InputMethodInfoTest { +@@ -131,6 +141,94 @@ public void testIsVirtualDeviceOnly() throws Exception { assertThat(clone.isVirtualDeviceOnly(), is(true)); } @@ -498,6 +494,3 @@ index dfe7d0306905..a7b2bba045dc 100644 private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes) throws Exception { final Context context = InstrumentationRegistry.getInstrumentation().getContext(); --- -2.53.0 - diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38-03_-0800_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38-03_-0800_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch index e51fde4..728c798 100644 --- a/asb/2026-03/frameworks/base/2026-01-07_21-38-03_-0800_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch +++ b/asb/2026-03/frameworks/base/2026-01-07_21-38-03_-0800_Introduce_InputMethodSubtypeSafeList_a438ce172b44.patch @@ -1,4 +1,4 @@ -From a438ce172b441c8297eadde8d990ab292f5aa7d1 Mon Sep 17 00:00:00 2001 +From bec80a52aaab578aa0f18527b6e40e165bfb45ab Mon Sep 17 00:00:00 2001 From: Hiroki Sato Date: Tue, 4 Nov 2025 17:29:42 +0900 Subject: [PATCH] Introduce InputMethodSubtypeSafeList @@ -16,6 +16,8 @@ SafeList classes to extend it. [1] I0a7667070fcdf17d34b248a5988c38064588718a +DISABLE_TOPIC_PROTECTOR + Bug: 449416164 Bug: 449181366 Bug: 449393786 @@ -24,7 +26,7 @@ Test: CtsInputMethodTestCases:{InputMethodRegistrationTest,InputMethodInfoTest} Test: InputMethodCoreTests Flag: EXEMPT BUGFIX (cherry picked from commit 1d68a1099be2b99e8410dad01822851287994682) -Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:e99cb1a4f240988380e43592d845c64f78e1a6d7 +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:46388aba14b1698df8c98e96d97b50130d1ce085 Merged-In: Ied64a9f018fd3e79cfc51ccd82d361b43e5f29dc Change-Id: Ied64a9f018fd3e79cfc51ccd82d361b43e5f29dc --- @@ -47,18 +49,18 @@ Change-Id: Ied64a9f018fd3e79cfc51ccd82d361b43e5f29dc create mode 100644 core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java -index 290885593ee6..6fbb76577fb8 100644 +index fe5afe437834d..b679da84c24d1 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java -@@ -43,6 +43,7 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +@@ -44,6 +44,7 @@ + import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; - import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodInfoSafeList; +import com.android.internal.inputmethod.InputMethodSubtypeSafeList; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; -@@ -297,8 +298,9 @@ final class IInputMethodManagerGlobalInvoker { +@@ -250,8 +251,9 @@ static List getEnabledInputMethodSubtypeList(@Nullable Strin return new ArrayList<>(); } try { @@ -72,7 +74,7 @@ index 290885593ee6..6fbb76577fb8 100644 } diff --git a/core/java/com/android/internal/inputmethod/AbstractSafeList.java b/core/java/com/android/internal/inputmethod/AbstractSafeList.java new file mode 100644 -index 000000000000..697b153afecf +index 0000000000000..697b153afecfe --- /dev/null +++ b/core/java/com/android/internal/inputmethod/AbstractSafeList.java @@ -0,0 +1,127 @@ @@ -204,10 +206,10 @@ index 000000000000..697b153afecf + } +} diff --git a/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java b/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java -index 9e720fb6ccee..a2ea5b08f13f 100644 +index 9e720fb6cceea..a2ea5b08f13f3 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java +++ b/core/java/com/android/internal/inputmethod/InputMethodInfoSafeList.java -@@ -19,24 +19,24 @@ package com.android.internal.inputmethod; +@@ -19,24 +19,24 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; @@ -330,7 +332,7 @@ index 9e720fb6ccee..a2ea5b08f13f 100644 } public static final Creator CREATOR = new Creator<>() { -@@ -141,16 +80,4 @@ public final class InputMethodInfoSafeList implements Parcelable { +@@ -141,16 +80,4 @@ public InputMethodInfoSafeList[] newArray(int size) { return new InputMethodInfoSafeList[size]; } }; @@ -349,7 +351,7 @@ index 9e720fb6ccee..a2ea5b08f13f 100644 } diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl new file mode 100644 -index 000000000000..11000632eba5 +index 0000000000000..11000632eba54 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.aidl @@ -0,0 +1,19 @@ @@ -374,7 +376,7 @@ index 000000000000..11000632eba5 +parcelable InputMethodSubtypeSafeList; diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java new file mode 100644 -index 000000000000..cd95088f5cf0 +index 0000000000000..cd95088f5cf0d --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSafeList.java @@ -0,0 +1,87 @@ @@ -466,10 +468,10 @@ index 000000000000..cd95088f5cf0 + }; +} diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl -index 0791612fa0e8..d9b52404724e 100644 +index 29363a533a036..3f6098c270d47 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl -@@ -32,6 +32,7 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +@@ -32,6 +32,7 @@ import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodInfoSafeList; @@ -488,7 +490,7 @@ index 0791612fa0e8..d9b52404724e 100644 @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java new file mode 100644 -index 000000000000..0f72f095dbe3 +index 0000000000000..0f72f095dbe3c --- /dev/null +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/AbstractSafeListTest.java @@ -0,0 +1,98 @@ @@ -592,7 +594,7 @@ index 000000000000..0f72f095dbe3 +} diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java new file mode 100644 -index 000000000000..089ffb80d7a9 +index 0000000000000..089ffb80d7a90 --- /dev/null +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeSafeListTest.java @@ -0,0 +1,128 @@ @@ -725,18 +727,18 @@ index 000000000000..089ffb80d7a9 + } +} diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java -index 15f186b047f2..7845a73111d6 100644 +index baa1d1ca77c0f..419d37fd791a1 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java -@@ -48,6 +48,7 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +@@ -45,6 +45,7 @@ + import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; - import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodInfoSafeList; +import com.android.internal.inputmethod.InputMethodSubtypeSafeList; - import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; -@@ -108,7 +109,8 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub { + import com.android.internal.view.IInputMethodManager; +@@ -104,7 +105,8 @@ List getInputMethodListLegacy(@UserIdInt int userId, @NonNull List getEnabledInputMethodListLegacy(@UserIdInt int userId); @@ -746,7 +748,7 @@ index 15f186b047f2..7845a73111d6 100644 boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId); InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId); -@@ -278,8 +280,9 @@ final class IInputMethodManagerImpl extends IInputMethodManager.Stub { +@@ -255,8 +257,9 @@ public List getEnabledInputMethodListLegacy(@UserIdInt int user return mCallback.getEnabledInputMethodListLegacy(userId); } @@ -758,10 +760,10 @@ index 15f186b047f2..7845a73111d6 100644 return mCallback.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes, userId); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java -index 5e3224d1012e..7df43d114906 100644 +index 355ca4048868a..1ec1c79497ee0 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java -@@ -172,6 +172,7 @@ import com.android.internal.inputmethod.InputMethodDebug; +@@ -172,6 +172,7 @@ import com.android.internal.inputmethod.InputMethodInfoSafeList; import com.android.internal.inputmethod.InputMethodNavButtonFlags; import com.android.internal.inputmethod.InputMethodSubtypeHandle; @@ -769,7 +771,7 @@ index 5e3224d1012e..7df43d114906 100644 import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; -@@ -1590,7 +1591,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. +@@ -1585,7 +1586,7 @@ public InputMethodInfoSafeList getInputMethodList(@UserIdInt int userId, Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } if (!mUserManagerInternal.exists(userId)) { @@ -778,7 +780,7 @@ index 5e3224d1012e..7df43d114906 100644 } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); -@@ -1611,7 +1612,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. +@@ -1606,7 +1607,7 @@ public InputMethodInfoSafeList getEnabledInputMethodList(@UserIdInt int userId) Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } if (!mUserManagerInternal.exists(userId)) { @@ -787,7 +789,7 @@ index 5e3224d1012e..7df43d114906 100644 } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); -@@ -1738,8 +1739,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. +@@ -1730,8 +1731,9 @@ private List getEnabledInputMethodListInternal(@UserIdInt int u * subtypes * @param userId the user ID to be queried about */ @@ -798,7 +800,7 @@ index 5e3224d1012e..7df43d114906 100644 boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingOrSelfPermission( -@@ -1749,8 +1751,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. +@@ -1741,8 +1743,9 @@ public List getEnabledInputMethodSubtypeList(String imiId, final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { @@ -811,18 +813,18 @@ index 5e3224d1012e..7df43d114906 100644 Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java -index 12c1d9cbb2a1..6a83475fb774 100644 +index 910c9a688969b..e8a3da5f0199b 100644 --- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java +++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java -@@ -65,6 +65,7 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +@@ -61,6 +61,7 @@ + import com.android.internal.inputmethod.IRemoteComputerControlInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; - import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodInfoSafeList; +import com.android.internal.inputmethod.InputMethodSubtypeSafeList; - import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; -@@ -160,8 +161,9 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback { + import com.android.internal.util.FunctionalUtils.ThrowingRunnable; +@@ -149,8 +150,9 @@ public List getEnabledInputMethodListLegacy(int userId) { return mInner.getEnabledInputMethodListLegacy(userId); } @@ -833,6 +835,3 @@ index 12c1d9cbb2a1..6a83475fb774 100644 boolean allowsImplicitlyEnabledSubtypes, int userId) { return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes, userId); --- -2.53.0 - diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-38-18_-0800_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch b/asb/2026-03/frameworks/base/2026-01-07_21-38-18_-0800_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch index adc26f5..4a2fba0 100644 --- a/asb/2026-03/frameworks/base/2026-01-07_21-38-18_-0800_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch +++ b/asb/2026-03/frameworks/base/2026-01-07_21-38-18_-0800_Prevent_launchedFromPackage_spoofing_via_FLAG_ACTI_c148b4fae634.patch @@ -1,6 +1,6 @@ -From c148b4fae6347652231d1c4a633f5cc9a8f057f8 Mon Sep 17 00:00:00 2001 +From 4264c2f606cdd538fc5825827ede4d1669015f66 Mon Sep 17 00:00:00 2001 From: Annie Lin -Date: Wed, 19 Nov 2025 20:16:52 +0000 +Date: Wed, 3 Dec 2025 17:18:51 -0800 Subject: [PATCH] Prevent launchedFromPackage spoofing via FLAG_ACTIVITY_FORWARD_RESULT. @@ -9,7 +9,7 @@ Test: atest ActivityStarterTests Test: Verified via test app Flag: EXEMPT CVE_FIX Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:3bb240273822e41f3c6911c60d15983a600308f7 -Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:79fe04dbdf31ab80311069a4d0a7b518d47c31ac +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:8e6622267809c37fa7989e4f07af3834f39f09e2 Merged-In: Ic9637c56803b00acc9fca59f8092ed02dd46a4fb Change-Id: Ic9637c56803b00acc9fca59f8092ed02dd46a4fb --- @@ -18,10 +18,10 @@ Change-Id: Ic9637c56803b00acc9fca59f8092ed02dd46a4fb 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java -index 92f51bed419f..222698ce24db 100644 +index 21638a31a629b..b0a80079682af 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java -@@ -1142,14 +1142,24 @@ class ActivityStarter { +@@ -1131,14 +1131,24 @@ private int executeRequest(Request request) { // in the flow, and asking to forward its result back to the previous. In this // case the activity is serving as a trampoline between the two, so we also want // to update its launchedFromPackage to be the same as the previous activity. @@ -52,11 +52,11 @@ index 92f51bed419f..222698ce24db 100644 } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java -index 08b0077c49b3..2948b3da7db8 100644 +index e4ad38689daa4..6d59fe5b21bda 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java -@@ -1882,6 +1882,88 @@ public class ActivityStarterTests extends WindowTestsBase { - assertEquals(bubbledActivity.getTask(), targetRecord.getTask()); +@@ -1997,6 +1997,88 @@ private ActivityRecord createBubbledActivity() { + .build(); } + /** @@ -141,9 +141,6 @@ index 08b0077c49b3..2948b3da7db8 100644 + maliciousUid, p2.launchedFromUid); + } + - private ActivityRecord createBubbledActivity() { - final ActivityOptions opts = ActivityOptions.makeBasic(); - opts.setTaskAlwaysOnTop(true); --- -2.53.0 - + private static void startActivityInner(ActivityStarter starter, ActivityRecord target, + ActivityRecord source, ActivityOptions options, Task inTask, + TaskFragment inTaskFragment) { diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-39-14_-0800_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch b/asb/2026-03/frameworks/base/2026-01-07_21-39-14_-0800_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch index 59f23d5..454d1a3 100644 --- a/asb/2026-03/frameworks/base/2026-01-07_21-39-14_-0800_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch +++ b/asb/2026-03/frameworks/base/2026-01-07_21-39-14_-0800_Be_more_strict_about_content_types_for_message_arr_014dea279c49.patch @@ -1,4 +1,4 @@ -From 014dea279c49d532bc4fbbdebbc024133967b6a8 Mon Sep 17 00:00:00 2001 +From 3aaff9fb5b1b5428d57297b168efa01072463321 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Wed, 12 Nov 2025 12:09:31 -0500 Subject: [PATCH] Be more strict about content types for message array @@ -10,19 +10,19 @@ Test: NotificationManagerServiceTest Bug: 433746973 Flag: EXEMPT BUGFIX (cherry picked from commit 71d4afae00c7d6d9238f8ec82303e1e13da50fbb) -Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:70fe31848f073029cdb38cd6c5fe47f200dd4c78 +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:f64c1e377842d9a8df814bcbad831bd4ce01583d Merged-In: I3022e010de95f14dcd0d09d123684ee265101e0a Change-Id: I3022e010de95f14dcd0d09d123684ee265101e0a --- core/java/android/app/Notification.java | 18 ++-- - .../NotificationManagerServiceTest.java | 101 +++++++++++++++++- - 2 files changed, 107 insertions(+), 12 deletions(-) + .../NotificationManagerServiceTest.java | 102 +++++++++++++++++- + 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java -index 2ed95ab7ad82..b921280da563 100644 +index 354e594cf7303..85921a77c6b43 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java -@@ -3152,8 +3152,8 @@ public class Notification implements Parcelable +@@ -3236,8 +3236,8 @@ public void visitUris(@NonNull Consumer visitor) { person.visitUris(visitor); } @@ -33,7 +33,7 @@ index 2ed95ab7ad82..b921280da563 100644 if (!ArrayUtils.isEmpty(messages)) { for (MessagingStyle.Message message : MessagingStyle.Message .getMessagesFromBundleArray(messages)) { -@@ -3161,8 +3161,8 @@ public class Notification implements Parcelable +@@ -3245,8 +3245,8 @@ public void visitUris(@NonNull Consumer visitor) { } } @@ -44,7 +44,7 @@ index 2ed95ab7ad82..b921280da563 100644 if (!ArrayUtils.isEmpty(historic)) { for (MessagingStyle.Message message : MessagingStyle.Message .getMessagesFromBundleArray(historic)) { -@@ -8203,8 +8203,8 @@ public class Notification implements Parcelable +@@ -8501,8 +8501,8 @@ public boolean showsChronometer() { */ public boolean hasImage() { if (isStyle(MessagingStyle.class) && extras != null) { @@ -55,7 +55,7 @@ index 2ed95ab7ad82..b921280da563 100644 if (!ArrayUtils.isEmpty(messages)) { for (MessagingStyle.Message m : MessagingStyle.Message .getMessagesFromBundleArray(messages)) { -@@ -9485,10 +9485,10 @@ public class Notification implements Parcelable +@@ -9794,10 +9794,10 @@ protected void restoreFromExtras(Bundle extras) { mUser = user; } mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); @@ -70,19 +70,19 @@ index 2ed95ab7ad82..b921280da563 100644 mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); 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 41cd746e7a04..883c6cc64fbe 100644 +index 7dc0921db4104..7be8cba2d23bf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java -@@ -27,6 +27,8 @@ import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS; - import static android.app.Flags.FLAG_NM_SUMMARIZATION; - import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME; +@@ -30,6 +30,8 @@ + import static android.app.Flags.FLAG_NM_SUMMARIZATION_UI; + import static android.app.Flags.FLAG_UI_RICH_ONGOING; import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP; +import static android.app.Notification.EXTRA_MESSAGES; +import static android.app.Notification.EXTRA_MESSAGING_PERSON; import static android.app.Notification.EXTRA_PICTURE; import static android.app.Notification.EXTRA_PICTURE_ICON; - import static android.app.Notification.EXTRA_TEXT; -@@ -81,6 +83,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BA + import static android.app.Notification.EXTRA_PREFER_SMALL_ICON; +@@ -87,6 +89,7 @@ import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; @@ -90,7 +90,7 @@ index 41cd746e7a04..883c6cc64fbe 100644 import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG; -@@ -235,11 +238,13 @@ import android.companion.ICompanionDeviceManager; +@@ -246,11 +249,13 @@ import android.compat.testing.PlatformCompatChangeRule; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -104,7 +104,7 @@ index 41cd746e7a04..883c6cc64fbe 100644 import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; -@@ -255,6 +260,7 @@ import android.content.pm.VersionedPackage; +@@ -267,6 +272,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Color; @@ -112,10 +112,11 @@ index 41cd746e7a04..883c6cc64fbe 100644 import android.graphics.drawable.Icon; import android.media.AudioAttributes; import android.media.AudioManager; -@@ -1503,6 +1509,95 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { - captor.getValue().getIntent().getPackage()); +@@ -1535,6 +1541,96 @@ private void verifyToastShownForTestPackage(String text, int displayId) { + eq(TOAST_DURATION), any(), eq(displayId)); } ++ @Test + public void testNoUriGrantsForBadMessagesList() throws RemoteException { + Uri targetUri = Uri.parse("content://com.android.contacts/display_photo/1"); + @@ -208,7 +209,7 @@ index 41cd746e7a04..883c6cc64fbe 100644 @Test public void testDefaultAssistant_overrideDefault() { final int userId = mContext.getUserId(); -@@ -8086,7 +8181,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { +@@ -8589,7 +8685,7 @@ public void testVisitUris() throws Exception { Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents); extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, backgroundImage.toString()); @@ -217,7 +218,7 @@ index 41cd746e7a04..883c6cc64fbe 100644 extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, new ArrayList<>(Arrays.asList(person2, person3))); extras.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, -@@ -8224,13 +8319,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { +@@ -8727,13 +8823,13 @@ public void testVisitUris_styleExtrasWithoutStyle() { .setSmallIcon(android.R.drawable.sym_def_app_icon); Bundle messagingExtras = new Bundle(); @@ -233,6 +234,3 @@ index 41cd746e7a04..883c6cc64fbe 100644 new Bundle[] { new Notification.MessagingStyle.Message("Are you there?", System.currentTimeMillis(), personWithIcon("content://messenger")).toBundle()}); --- -2.53.0 - diff --git a/asb/2026-03/frameworks/base/2026-01-07_21-39-44_-0800_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch b/asb/2026-03/frameworks/base/2026-01-07_21-39-44_-0800_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch index 429acb4..04919cf 100644 --- a/asb/2026-03/frameworks/base/2026-01-07_21-39-44_-0800_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch +++ b/asb/2026-03/frameworks/base/2026-01-07_21-39-44_-0800_Limit_the_number_of_services_NLSes_etc_that_can_be_e363f8210456.patch @@ -1,4 +1,4 @@ -From e363f82104566378b4b9936d6caf27c3ee631d80 Mon Sep 17 00:00:00 2001 +From dc8121842868bd90e04176ed42feab3f7e47956b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Wed, 20 Aug 2025 15:57:28 +0200 Subject: [PATCH] Limit the number of services (NLSes, etc) that can be @@ -10,326 +10,109 @@ Bug: 428701593 Test: atest ManagedServicesTest NotificationManagerServiceTest Flag: com.android.server.notification.limit_managed_services_count (cherry picked from commit a132684a093d9e1750100b39d4e4168f2d27d349) -Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:3b41dfeec7ebfcd313ff26b7f27b7e7971af4497 +Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:182548fd95b0f245385e5dc45efd2cbd4cd35b57 Merged-In: Iddd8044997c41f97369b768f4da5e49efc43ad06 Change-Id: Iddd8044997c41f97369b768f4da5e49efc43ad06 --- - .../server/notification/ManagedServices.java | 59 ++++++++++++----- - .../NotificationManagerService.java | 34 +++++++--- - .../notification/ManagedServicesTest.java | 63 +++++++++++++++++++ - .../NotificationManagerServiceTest.java | 27 ++++++++ - 4 files changed, 160 insertions(+), 23 deletions(-) + .../com/android/server/notification/ManagedServices.java | 8 +++----- + .../server/notification/NotificationManagerService.java | 6 +++--- + .../android/server/notification/ManagedServicesTest.java | 2 -- + .../notification/NotificationManagerServiceTest.java | 1 - + 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java -index 62e26e189a35..159d33999739 100644 +index 54cf810fc0397..1dd298cb67b33 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java -@@ -133,6 +133,13 @@ abstract public class ManagedServices { - static final int APPROVAL_BY_PACKAGE = 0; - static final int APPROVAL_BY_COMPONENT = 1; - -+ /** -+ * Maximum number of entries allowed in the lists of packages/components contained in -+ * {@link #mApproved} or {@link #mUserSetServices}. For the first, this effectively limits -+ * the number of services (e.g. NLSes) that will be bound per user. -+ */ -+ private static final int MAX_SERVICE_ENTRIES = 100; -+ - protected final Context mContext; - protected final Object mMutex; - private final UserProfiles mUserProfiles; -@@ -915,16 +922,22 @@ abstract public class ManagedServices { - } - } - -- protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, -+ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId, - boolean isPrimary, boolean enabled) { -- setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, true); -+ return setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, true); - } - -- protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, -+ /** -+ * Changes the enabled state of a managed service. -+ * -+ * @return true if the change (enabling or disabling) was applied; false otherwise -+ */ -+ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId, - boolean isPrimary, boolean enabled, boolean userSet) { - Slog.i(TAG, - (enabled ? " Allowing " : "Disallowing ") + mConfig.caption + " " - + pkgOrComponent + " (userSet: " + userSet + ")"); -+ boolean changed = false; - synchronized (mApproved) { - ArrayMap> allowedByType = mApproved.get(userId); - if (allowedByType == null) { -@@ -940,24 +953,42 @@ abstract public class ManagedServices { - +@@ -978,8 +978,7 @@ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId if (approvedItem != null) { + int uid = getUidForPackageOrComponent(pkgOrComponent, userId); if (enabled) { -- approved.add(approvedItem); +- if (!Flags.limitManagedServicesCount() +- || approved.size() < MAX_SERVICE_ENTRIES) { + if (approved.size() < MAX_SERVICE_ENTRIES) { -+ approved.add(approvedItem); -+ changed = true; -+ } else { -+ Slog.w(TAG, TextUtils.formatSimple( -+ "Failed to allow %s %s because there are too many already", -+ mConfig.caption, pkgOrComponent)); -+ } - } else { - approved.remove(approvedItem); -+ changed = true; + approved.add(approvedItem); + if (uid != Process.INVALID_UID) { + approvedUids.add(uid); +@@ -1006,8 +1005,7 @@ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId + mUserSetServices.put(userId, userSetServices); } - } -- ArraySet userSetServices = mUserSetServices.get(userId); -- if (userSetServices == null) { -- userSetServices = new ArraySet<>(); -- mUserSetServices.put(userId, userSetServices); -- } -- if (userSet) { -- userSetServices.add(pkgOrComponent); -- } else { -- userSetServices.remove(pkgOrComponent); -+ -+ if (changed) { -+ ArraySet userSetServices = mUserSetServices.get(userId); -+ if (userSetServices == null) { -+ userSetServices = new ArraySet<>(); -+ mUserSetServices.put(userId, userSetServices); -+ } -+ if (userSet) { + if (userSet) { +- if (!Flags.limitManagedServicesCount() +- || userSetServices.size() < MAX_SERVICE_ENTRIES) { + if (userSetServices.size() < MAX_SERVICE_ENTRIES) { -+ userSetServices.add(pkgOrComponent); -+ } -+ } else { -+ userSetServices.remove(pkgOrComponent); -+ } -+ + userSetServices.add(pkgOrComponent); + } + } else { +@@ -1016,7 +1014,7 @@ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId } } -- rebindServices(false, userId); +- if (!Flags.limitManagedServicesCount() || changed) { + if (changed) { -+ rebindServices(false, userId); -+ } -+ -+ return changed; - } + rebindServices(false, userId); + } - private String getApprovedValue(String pkgOrComponent) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java -index d7913baf02cd..da76c82e76fc 100644 +index 1b2042fc8532c..11ea74dae665b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java -@@ -6692,8 +6692,11 @@ public class NotificationManagerService extends SystemService { - try { - if (mAllowedManagedServicePackages.test( +@@ -7006,7 +7006,7 @@ public void setNotificationPolicyAccessGrantedForUser( pkg, userId, mConditionProviders.getRequiredPermission())) { -- mConditionProviders.setPackageOrComponentEnabled( -- pkg, userId, true, granted); -+ boolean changed = mConditionProviders.setPackageOrComponentEnabled(pkg, userId, -+ /* isPrimary= */ true, granted); + boolean changed = mConditionProviders.setPackageOrComponentEnabled(pkg, userId, + /* isPrimary= */ true, granted); +- if (Flags.limitManagedServicesCount() && !changed) { + if (!changed) { -+ return; -+ } + return; + } - getContext().sendBroadcastAsUser(new Intent( - ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) -@@ -6963,10 +6966,15 @@ public class NotificationManagerService extends SystemService { - try { - if (mAllowedManagedServicePackages.test( - listener.getPackageName(), userId, mListeners.getRequiredPermission())) { -+ boolean changed = mListeners.setPackageOrComponentEnabled( -+ listener.flattenToString(), userId, /* isPrimary= */ true, granted, -+ userSet); +@@ -7276,7 +7276,7 @@ public void setNotificationListenerAccessGrantedForUser(ComponentName listener, + boolean changed = mListeners.setPackageOrComponentEnabled( + listener.flattenToString(), userId, /* isPrimary= */ true, granted, + userSet); +- if (Flags.limitManagedServicesCount() && !changed) { + if (!changed) { -+ return; -+ } -+ - mConditionProviders.setPackageOrComponentEnabled(listener.flattenToString(), - userId, false, granted, userSet); -- mListeners.setPackageOrComponentEnabled(listener.flattenToString(), -- userId, true, granted, userSet); + return; + } - getContext().sendBroadcastAsUser(new Intent( - ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED) -@@ -12698,19 +12706,20 @@ public class NotificationManagerService extends SystemService { - } - - @Override -- protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, -+ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId, +@@ -13809,7 +13809,7 @@ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId boolean isPrimary, boolean enabled, boolean userSet) { - // Ensures that only one component is enabled at a time - if (enabled) { - List allowedComponents = getAllowedComponents(userId); - if (!allowedComponents.isEmpty()) { - ComponentName currentComponent = CollectionUtils.firstOrNull(allowedComponents); -- if (currentComponent.flattenToString().equals(pkgOrComponent)) return; -+ if (currentComponent.flattenToString().equals(pkgOrComponent)) return false; - setNotificationAssistantAccessGrantedForUserInternal( - currentComponent, userId, false, userSet); - } - } -- super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet); -+ return super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, -+ userSet); - } - - private boolean isVerboseLogEnabled() { -@@ -13057,9 +13066,14 @@ public class NotificationManagerService extends SystemService { - } - - @Override -- protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, -+ protected boolean setPackageOrComponentEnabled(String pkgOrComponent, int userId, - boolean isPrimary, boolean enabled, boolean userSet) { -- super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet); -+ boolean changed = super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, -+ enabled, userSet); + boolean changed = super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, + enabled, userSet); +- if (Flags.limitManagedServicesCount() && !changed) { + if (!changed) { -+ return false; -+ } -+ - String pkgName = getPackageName(pkgOrComponent); - if (redactSensitiveNotificationsFromUntrustedListeners()) { - int uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId); -@@ -13079,6 +13093,8 @@ public class NotificationManagerService extends SystemService { - new Intent(ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED) - .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), - UserHandle.of(userId), null); -+ -+ return true; - } + return false; + } - @Override diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java -index 9c85b04fc4ff..cff652c642ee 100644 +index 97f9f9ceb4aff..0de1d377bd09a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java -@@ -33,6 +33,7 @@ import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAG - import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled; - - import static com.google.common.truth.Truth.assertThat; -+import static com.google.common.truth.Truth.assertWithMessage; - - import static junit.framework.Assert.assertEquals; - import static junit.framework.Assert.assertFalse; -@@ -2534,6 +2535,68 @@ public class ManagedServicesTest extends UiServiceTestCase { - assertThat(listener.enabledAndUserMatches(visibleBackgroundUserId)).isFalse(); +@@ -2586,7 +2586,6 @@ public void isUidAllowed_multipleApprovedUids_returnsTrueForBoth() { } -+ @Test -+ public void setPackageOrComponentEnabled_tooManyPackages_stopsAdding() { -+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, -+ mIpm, APPROVAL_BY_PACKAGE); -+ int userId = 0; -+ -+ for (int i = 1; i <= 100; i++) { -+ assertWithMessage("Trying pkg" + i) -+ .that(service.setPackageOrComponentEnabled("pkg" + i, userId, true, true)) -+ .isTrue(); -+ assertThat(service.isPackageAllowed("pkg" + i, userId)).isTrue(); -+ } -+ -+ // And finally, monsieur, a wafer-thin mint. -+ assertThat(service.setPackageOrComponentEnabled("toomany", userId, true, true)).isFalse(); -+ assertThat(service.isPackageAllowed("toomany", userId)).isFalse(); -+ -+ // We can still DISABLE packages though. -+ assertThat(service.isPackageAllowed("pkg33", userId)).isTrue(); -+ assertThat(service.setPackageOrComponentEnabled("pkg33", userId, true, false)).isTrue(); -+ assertThat(service.isPackageAllowed("pkg33", userId)).isFalse(); -+ -+ // And that allows adding new ones. -+ assertThat(service.setPackageOrComponentEnabled("onemore", userId, true, true)).isTrue(); -+ assertThat(service.isPackageAllowed("onemore", userId)).isTrue(); -+ } -+ -+ @Test -+ public void setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet() { -+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, -+ mIpm, APPROVAL_BY_PACKAGE); -+ int userId = 0; -+ -+ for (int i = 1; i <= 100; i++) { -+ assertWithMessage("Enabling pkg" + i) -+ .that(service.setPackageOrComponentEnabled("pkg" + i, userId, true, true)) -+ .isTrue(); -+ assertWithMessage("Disabling pkg" + i) -+ .that(service.setPackageOrComponentEnabled("pkg" + i, userId, true, false)) -+ .isTrue(); -+ assertThat(service.isPackageAllowed("pkg" + i, userId)).isFalse(); -+ assertThat(service.isPackageOrComponentUserSet("pkg" + i, userId)).isTrue(); -+ } -+ -+ // Too many disabled services. -+ assertThat(service.setPackageOrComponentEnabled("toomany", userId, true, true)).isTrue(); -+ assertThat(service.isPackageAllowed("toomany", userId)).isTrue(); -+ assertThat(service.isPackageOrComponentUserSet("toomany", userId)).isFalse(); -+ assertThat(service.setPackageOrComponentEnabled("toomany", userId, true, false)).isTrue(); -+ assertThat(service.isPackageAllowed("toomany", userId)).isFalse(); -+ assertThat(service.isPackageOrComponentUserSet("toomany", userId)).isFalse(); -+ -+ // We make space only when packages are uninstalled. -+ service.onPackagesChanged(/* removingPackage= */ true, new String[] { "pkg22" }, -+ new int[] { 22 }); -+ -+ // And that allows tracking new ones. -+ assertThat(service.setPackageOrComponentEnabled("onemore", userId, true, true)).isTrue(); -+ assertThat(service.setPackageOrComponentEnabled("onemore", userId, true, false)).isTrue(); -+ assertThat(service.isPackageOrComponentUserSet("onemore", userId)).isTrue(); -+ } -+ - private void mockServiceInfoWithMetaData(List componentNames, - ManagedServices service, ArrayMap metaDatas) - throws RemoteException { + @Test +- @EnableFlags(Flags.FLAG_LIMIT_MANAGED_SERVICES_COUNT) + public void setPackageOrComponentEnabled_tooManyPackages_stopsAdding() { + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, APPROVAL_BY_PACKAGE); +@@ -2614,7 +2613,6 @@ public void setPackageOrComponentEnabled_tooManyPackages_stopsAdding() { + } + + @Test +- @EnableFlags(Flags.FLAG_LIMIT_MANAGED_SERVICES_COUNT) + public void setPackageOrComponentEnabled_tooManyChanges_stopsAddingToUserSet() { + ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, APPROVAL_BY_PACKAGE); 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 883c6cc64fbe..3eb35decad73 100644 +index 7be8cba2d23bf..f96fd66caca6d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java -@@ -713,6 +713,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { - mPolicyFile.finishWrite(fos); - - // Setup managed services -+ when(mListeners.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean())) -+ .thenReturn(true); -+ when(mListeners.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean(), -+ anyBoolean())).thenReturn(true); -+ when(mAssistants.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean())) -+ .thenReturn(true); -+ when(mAssistants.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean(), -+ anyBoolean())).thenReturn(true); -+ when(mConditionProviders.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), -+ anyBoolean())).thenReturn(true); -+ when(mConditionProviders.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), -+ anyBoolean(), anyBoolean())).thenReturn(true); - when(mNlf.isTypeAllowed(anyInt())).thenReturn(true); - when(mNlf.isPackageAllowed(any())).thenReturn(true); - when(mNlf.isPackageAllowed(null)).thenReturn(true); -@@ -6110,6 +6122,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { - c.flattenToString(), user.getIdentifier(), true, /* enabled= */ false, true); +@@ -6478,7 +6478,6 @@ public void testSetListenerAccessForUser_revokeWithNameTooLong_okay() throws Exc } -+ @Test -+ public void testSetListenerAccessForUser_tooManyListeners_skipsFollowups() throws Exception { -+ UserHandle user = UserHandle.of(mContext.getUserId() + 10); -+ ComponentName c = ComponentName.unflattenFromString("package/Component"); -+ when(mListeners.setPackageOrComponentEnabled(any(), anyInt(), anyBoolean(), anyBoolean(), -+ anyBoolean())).thenReturn(false); -+ -+ mBinderService.setNotificationListenerAccessGrantedForUser( -+ c, user.getIdentifier(), /* enabled= */ true, true); -+ -+ verify(mConditionProviders, never()).setPackageOrComponentEnabled(any(), anyInt(), -+ anyBoolean(), anyBoolean(), anyBoolean()); -+ verify(mContext, never()).sendBroadcastAsUser(any(), any(), any()); -+ } -+ @Test - public void testSetAssistantAccessForUser() throws Exception { - UserInfo ui = new UserInfo(); --- -2.53.0 - +- @EnableFlags(Flags.FLAG_LIMIT_MANAGED_SERVICES_COUNT) + public void testSetListenerAccessForUser_tooManyListeners_skipsFollowups() throws Exception { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = ComponentName.unflattenFromString("package/Component"); diff --git a/asb/2026-03/packages/apps/Settings/2026-01-07_21-40-38_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch b/asb/2026-03/packages/apps/Settings/2026-01-07_21-40-38_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch index 2262513..c3109b3 100644 --- a/asb/2026-03/packages/apps/Settings/2026-01-07_21-40-38_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch +++ b/asb/2026-03/packages/apps/Settings/2026-01-07_21-40-38_-0800_Fixed_Unlock_your_phone_unexpectedlly_turned_ON_af_7d8fbee887fc.patch @@ -1,4 +1,4 @@ -From 7d8fbee887fc9577337c2a80513ae4399bf60111 Mon Sep 17 00:00:00 2001 +From c56a925678e3d0030a5a8b0417451a684374abfa Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Tue, 14 Oct 2025 08:08:58 +0000 Subject: [PATCH] Fixed "Unlock your phone" unexpectedlly turned ON after OTA @@ -32,7 +32,7 @@ Change-Id: I345defc78500c244e29e8595f5fbc705b95f4ba6 create mode 100644 tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java -index 9500c25a8ec..78d9ffaf7c4 100644 +index 24b0127d2689..c46b0f02cdc4 100644 --- a/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceController.java @@ -16,6 +16,7 @@ @@ -43,7 +43,7 @@ index 9500c25a8ec..78d9ffaf7c4 100644 import static android.provider.Settings.Secure.FACE_APP_ENABLED; import android.app.settings.SettingsEnums; -@@ -31,6 +32,7 @@ import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; +@@ -33,6 +34,7 @@ public class FaceSettingsAppsPreferenceController extends FaceSettingsPreferenceController { @@ -51,10 +51,10 @@ index 9500c25a8ec..78d9ffaf7c4 100644 private static final int ON = 1; private static final int OFF = 0; private static final int DEFAULT = ON; -@@ -40,12 +42,23 @@ public class FaceSettingsAppsPreferenceController extends - public FaceSettingsAppsPreferenceController(@NonNull Context context, @NonNull String key) { - super(context, key); - mFaceManager = Utils.getFaceManagerOrNull(context); +@@ -53,12 +55,23 @@ public FaceSettingsAppsPreferenceController(@NonNull Context context, @NonNull S + } + } + } + + // For OTA case: if FACE_APP_ENABLED is not set and BIOMETRIC_APP_ENABLED is set, set the + // default value of the former to that of the latter. @@ -78,7 +78,7 @@ index 9500c25a8ec..78d9ffaf7c4 100644 @Override diff --git a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java -index 5ff15232a02..46731b94451 100644 +index 64b036b034cb..d8ad4f827ac5 100644 --- a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceController.java @@ -16,6 +16,7 @@ @@ -89,7 +89,7 @@ index 5ff15232a02..46731b94451 100644 import static android.provider.Settings.Secure.FACE_KEYGUARD_ENABLED; import android.app.settings.SettingsEnums; -@@ -31,6 +32,7 @@ import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; +@@ -32,6 +33,7 @@ public class FaceSettingsKeyguardUnlockPreferenceController extends FaceSettingsPreferenceController { @@ -97,10 +97,10 @@ index 5ff15232a02..46731b94451 100644 private static final int ON = 1; private static final int OFF = 0; private static final int DEFAULT = ON; -@@ -41,6 +43,17 @@ public class FaceSettingsKeyguardUnlockPreferenceController extends - @NonNull Context context, @NonNull String key) { +@@ -44,6 +46,17 @@ public FaceSettingsKeyguardUnlockPreferenceController( super(context, key); mFaceManager = Utils.getFaceManagerOrNull(context); + mUserManager = context.getSystemService(UserManager.class); + + // For OTA case: if FACE_KEYGUARD_ENABLED is not set and BIOMETRIC_KEYGUARD_ENABLED is set, + // set the default value of the former to that of the latter. @@ -116,7 +116,7 @@ index 5ff15232a02..46731b94451 100644 @Override diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java -index 63fc3dcef23..574d406e382 100644 +index 63fc3dcef230..574d406e3821 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java @@ -16,6 +16,7 @@ @@ -127,7 +127,7 @@ index 63fc3dcef23..574d406e382 100644 import static android.provider.Settings.Secure.FINGERPRINT_APP_ENABLED; import android.app.settings.SettingsEnums; -@@ -31,6 +32,7 @@ import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; +@@ -31,6 +32,7 @@ public class FingerprintSettingsAppsPreferenceController extends FingerprintSettingsPreferenceController { @@ -135,7 +135,7 @@ index 63fc3dcef23..574d406e382 100644 private static final int ON = 1; private static final int OFF = 0; private static final int DEFAULT = ON; -@@ -41,12 +43,23 @@ public class FingerprintSettingsAppsPreferenceController +@@ -41,12 +43,23 @@ public FingerprintSettingsAppsPreferenceController( @NonNull Context context, @NonNull String key) { super(context, key); mFingerprintManager = Utils.getFingerprintManagerOrNull(context); @@ -162,7 +162,7 @@ index 63fc3dcef23..574d406e382 100644 @Override diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java -index 56ef2c3db8b..89bea9b5fde 100644 +index c572f2d85843..88f0a125d0aa 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java @@ -16,6 +16,7 @@ @@ -173,7 +173,7 @@ index 56ef2c3db8b..89bea9b5fde 100644 import static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED; import android.app.settings.SettingsEnums; -@@ -32,6 +33,7 @@ import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; +@@ -33,6 +34,7 @@ public class FingerprintSettingsKeyguardUnlockPreferenceController extends FingerprintSettingsPreferenceController { @@ -181,10 +181,10 @@ index 56ef2c3db8b..89bea9b5fde 100644 private static final int ON = 1; private static final int OFF = 0; private static final int DEFAULT = ON; -@@ -42,6 +44,17 @@ public class FingerprintSettingsKeyguardUnlockPreferenceController - @NonNull Context context, @NonNull String key) { +@@ -45,6 +47,17 @@ public FingerprintSettingsKeyguardUnlockPreferenceController( super(context, key); mFingerprintManager = Utils.getFingerprintManagerOrNull(context); + mUserManager = context.getSystemService(UserManager.class); + + // For OTA case: if FINGERPRINT_KEYGUARD_ENABLED is not set and BIOMETRIC_KEYGUARD_ENABLED + // is set, set the default value of the former to that of the latter. @@ -201,7 +201,7 @@ index 56ef2c3db8b..89bea9b5fde 100644 @Override diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java new file mode 100644 -index 00000000000..676031c4951 +index 000000000000..676031c4951e --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsAppsPreferenceControllerTest.java @@ -0,0 +1,116 @@ @@ -323,7 +323,7 @@ index 00000000000..676031c4951 +} diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java new file mode 100644 -index 00000000000..837f31e3bd6 +index 000000000000..837f31e3bd6f --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsKeyguardUnlockPreferenceControllerTest.java @@ -0,0 +1,90 @@ @@ -419,7 +419,7 @@ index 00000000000..837f31e3bd6 +} diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java new file mode 100644 -index 00000000000..841cec2f3de +index 000000000000..841cec2f3de6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceControllerTest.java @@ -0,0 +1,76 @@ @@ -501,7 +501,7 @@ index 00000000000..841cec2f3de +} diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java new file mode 100644 -index 00000000000..119fda8bbf2 +index 000000000000..119fda8bbf28 --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceControllerTest.java @@ -0,0 +1,90 @@ @@ -595,6 +595,3 @@ index 00000000000..119fda8bbf2 + assertThat(mController.isChecked()).isTrue(); + } +} --- -2.53.0 -